Skip to content

Commit d1a62e4

Browse files
Add map default values (e.g. default = { "foo": 33 })
This commit also adds a bunch of tests testing maps in several situations.
1 parent 093957b commit d1a62e4

21 files changed

+381
-47
lines changed

macro/src/gen/meta.rs

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream};
22
use quote::quote;
33
use syn::Ident;
44

5-
use crate::ir::{self, Expr, FieldKind, LeafKind};
5+
use crate::ir::{self, Expr, FieldKind, LeafKind, MapKey};
66

77

88

@@ -21,7 +21,7 @@ pub(super) fn gen(input: &ir::Input) -> TokenStream {
2121
let name = f.name.to_string();
2222
let doc = &f.doc;
2323
let kind = match &f.kind {
24-
FieldKind::Nested { ty }=> {
24+
FieldKind::Nested { ty } => {
2525
quote! {
2626
confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META }
2727
}
@@ -73,27 +73,50 @@ pub(super) fn gen(input: &ir::Input) -> TokenStream {
7373
}
7474
}
7575

76+
/// Helper macro to deduplicate logic for literals. Only used in the function
77+
/// below.
78+
macro_rules! match_literals {
79+
($v:expr, $ty:expr, $ns:ident, { $($other_arms:tt)* }) => {
80+
match $v {
81+
$ns::Bool(v) => quote! { confique::meta::$ns::Bool(#v) },
82+
$ns::Str(s) => quote! { confique::meta::$ns::Str(#s) },
83+
$ns::Int(i) => {
84+
let variant = infer_type(i.suffix(), $ty, "I32", int_type_to_variant);
85+
quote! { confique::meta::$ns::Integer(confique::meta::Integer::#variant(#i)) }
86+
}
87+
$ns::Float(f) => {
88+
let variant = infer_type(f.suffix(), $ty, "F64", float_type_to_variant);
89+
quote! { confique::meta::$ns::Float(confique::meta::Float::#variant(#f)) }
90+
}
91+
$($other_arms)*
92+
}
93+
};
94+
}
95+
7696
/// Generates the meta expression of type `meta::Expr` to be used for the
7797
/// `default` field. `ty` is the type of the field that is used to better infer
7898
/// the exact type of the default value.
7999
fn default_value_to_meta_expr(default: &Expr, ty: Option<&syn::Type>) -> TokenStream {
80-
match default {
81-
Expr::Bool(v) => quote! { confique::meta::Expr::Bool(#v) },
82-
Expr::Str(s) => quote! { confique::meta::Expr::Str(#s) },
83-
Expr::Int(i) => {
84-
let variant = infer_type(i.suffix(), ty, "I32", int_type_to_variant);
85-
quote! { confique::meta::Expr::Integer(confique::meta::Integer::#variant(#i)) }
86-
}
87-
Expr::Float(f) => {
88-
let variant = infer_type(f.suffix(), ty, "F64", float_type_to_variant);
89-
quote! { confique::meta::Expr::Float(confique::meta::Float::#variant(#f)) }
90-
}
100+
match_literals!(default, ty, Expr, {
91101
Expr::Array(items) => {
92-
let item_type = ty.and_then(get_item_ty);
102+
let item_type = ty.and_then(get_array_item_type);
93103
let items = items.iter().map(|item| default_value_to_meta_expr(item, item_type));
94104
quote! { confique::meta::Expr::Array(&[#( #items ),*]) }
95105
}
96-
}
106+
Expr::Map(entries) => {
107+
// TODO: use `Option::unzip` once stable
108+
let types = ty.and_then(get_map_entry_types);
109+
let key_type = types.map(|(t, _)| t);
110+
let value_type = types.map(|(_, v)| v);
111+
112+
let pairs = entries.iter().map(|e| {
113+
let key = match_literals!(&e.key, key_type, MapKey, {});
114+
let value = default_value_to_meta_expr(&e.value, value_type);
115+
quote! { confique::meta::MapEntry { key: #key, value: #value } }
116+
});
117+
quote! { confique::meta::Expr::Map(&[#( #pairs ),*]) }
118+
}
119+
})
97120
}
98121

99122
/// Maps an integer type to the `meta::Expr` variant (e.g. `u32` -> `U32`).
@@ -149,10 +172,9 @@ fn infer_type(
149172
Ident::new(variant, Span::call_site())
150173
}
151174

152-
153175
/// Tries to extract the type of the item of a field with an array default
154176
/// value. Examples: `&[u32]` -> `u32`, `Vec<String>` -> `String`.
155-
fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
177+
fn get_array_item_type(ty: &syn::Type) -> Option<&syn::Type> {
156178
match ty {
157179
// The easy types.
158180
syn::Type::Slice(slice) => Some(&slice.elem),
@@ -184,9 +206,40 @@ fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
184206
},
185207

186208
// Just recurse on inner type.
187-
syn::Type::Reference(r) => get_item_ty(&r.elem),
188-
syn::Type::Group(g) => get_item_ty(&g.elem),
189-
syn::Type::Paren(p) => get_item_ty(&p.elem),
209+
syn::Type::Reference(r) => get_array_item_type(&r.elem),
210+
syn::Type::Group(g) => get_array_item_type(&g.elem),
211+
syn::Type::Paren(p) => get_array_item_type(&p.elem),
212+
213+
_ => None,
214+
}
215+
}
216+
217+
/// Tries to extract the key and value types from a map value. Examples:
218+
/// `HashMap<String, u32>` -> `(String, u32)`.
219+
fn get_map_entry_types(ty: &syn::Type) -> Option<(&syn::Type, &syn::Type)> {
220+
match ty {
221+
// We simply check if the last element in the path has exactly two
222+
// generic type arguments, in which case we use those. Otherwise we
223+
// can't really know.
224+
syn::Type::Path(p) => {
225+
let args = match &p.path.segments.last().expect("empty type path").arguments {
226+
syn::PathArguments::AngleBracketed(args) => &args.args,
227+
_ => return None,
228+
};
229+
230+
if args.len() != 2 {
231+
return None;
232+
}
233+
234+
match (&args[0], &args[1]) {
235+
(syn::GenericArgument::Type(k), syn::GenericArgument::Type(v)) => Some((k, v)),
236+
_ => None,
237+
}
238+
},
239+
240+
// Just recurse on inner type.
241+
syn::Type::Group(g) => get_map_entry_types(&g.elem),
242+
syn::Type::Paren(p) => get_map_entry_types(&p.elem),
190243

191244
_ => None,
192245
}

macro/src/gen/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,23 @@ fn default_value_to_deserializable_expr(expr: &ir::Expr) -> TokenStream {
304304
};
305305
quote! { confique::internal::ArrayIntoDeserializer([ #(#items),* ] #type_annotation) }
306306
},
307+
ir::Expr::Map(entries) => {
308+
let items = entries.iter().map(|e| {
309+
let key = default_value_to_deserializable_expr(&e.key.clone().into());
310+
let value = default_value_to_deserializable_expr(&e.value);
311+
quote! { (#key, #value) }
312+
});
313+
314+
// Empty arrays cause "cannot infer type" errors here. However, it
315+
// really doesn't matter what type the array has as there are 0
316+
// elements anyway. So we just pick `()`.
317+
let type_annotation = if entries.is_empty() {
318+
quote! { as Vec<((), ())> }
319+
} else {
320+
quote! {}
321+
};
322+
quote! { confique::internal::MapIntoDeserializer(vec![ #(#items),* ] #type_annotation) }
323+
},
307324
}
308325
}
309326

macro/src/ir.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,18 @@ pub(crate) enum Expr {
6767
Float(syn::LitFloat),
6868
Bool(syn::LitBool),
6969
Array(Vec<Expr>),
70+
Map(Vec<MapEntry>),
71+
}
72+
73+
pub(crate) struct MapEntry {
74+
pub(crate) key: MapKey,
75+
pub(crate) value: Expr,
76+
}
77+
78+
#[derive(Clone)]
79+
pub(crate) enum MapKey {
80+
Str(syn::LitStr),
81+
Int(syn::LitInt),
82+
Float(syn::LitFloat),
83+
Bool(syn::LitBool),
7084
}

macro/src/parse.rs

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
22

33
use crate::{
4-
ir::{Input, Field, FieldKind, LeafKind, Expr},
4+
ir::{Input, Field, FieldKind, LeafKind, Expr, MapEntry, MapKey},
55
util::{unwrap_option, is_option},
66
};
77

@@ -112,21 +112,54 @@ impl Parse for Expr {
112112
(string, integer, float, bool), and arrays";
113113

114114
if input.peek(syn::token::Bracket) {
115+
// ----- Array -----
115116
let content;
116117
syn::bracketed!(content in input);
117118

118119
let items = <Punctuated<Expr, Token![,]>>::parse_terminated(&content)?;
119120
Ok(Self::Array(items.into_iter().collect()))
121+
} else if input.peek(syn::token::Brace) {
122+
// ----- Map -----
123+
let content;
124+
syn::braced!(content in input);
125+
126+
let items = <Punctuated<MapEntry, Token![,]>>::parse_terminated(&content)?;
127+
Ok(Self::Map(items.into_iter().collect()))
120128
} else {
121-
let lit: syn::Lit = input.parse()
122-
.map_err(|_| Error::new(input.span(), msg))?;
123-
match lit {
124-
syn::Lit::Str(l) => Ok(Self::Str(l)),
125-
syn::Lit::Int(l) => Ok(Self::Int(l)),
126-
syn::Lit::Float(l) => Ok(Self::Float(l)),
127-
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
128-
_ => Err(Error::new(lit.span(), msg)),
129-
}
129+
// ----- Literal -----
130+
131+
// We just use `MapKey` here as it's exactly what we want, despite
132+
// this not having anything to do with maps.
133+
input.parse::<MapKey>()
134+
.map_err(|_| Error::new(input.span(), msg))
135+
.map(Into::into)
136+
}
137+
}
138+
}
139+
140+
141+
142+
impl Parse for MapEntry {
143+
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
144+
let key: MapKey = input.parse()?;
145+
let _: syn::Token![:] = input.parse()?;
146+
let value: Expr = input.parse()?;
147+
Ok(Self { key, value })
148+
}
149+
}
150+
151+
impl Parse for MapKey {
152+
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
153+
let lit: syn::Lit = input.parse()?;
154+
match lit {
155+
syn::Lit::Str(l) => Ok(Self::Str(l)),
156+
syn::Lit::Int(l) => Ok(Self::Int(l)),
157+
syn::Lit::Float(l) => Ok(Self::Float(l)),
158+
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
159+
_ => Err(Error::new(
160+
lit.span(),
161+
"only string, integer, float, and Boolean literals allowed as map key",
162+
)),
130163
}
131164
}
132165
}
@@ -297,3 +330,14 @@ fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
297330
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
298331
}
299332
}
333+
334+
impl From<MapKey> for Expr {
335+
fn from(src: MapKey) -> Self {
336+
match src {
337+
MapKey::Str(v) => Self::Str(v),
338+
MapKey::Int(v) => Self::Int(v),
339+
MapKey::Float(v) => Self::Float(v),
340+
MapKey::Bool(v) => Self::Bool(v),
341+
}
342+
}
343+
}

src/internal.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,21 @@ where
8282
serde::de::value::SeqDeserializer::new(self.0.into_iter())
8383
}
8484
}
85+
86+
/// `serde` does implement `IntoDeserializer` for `HashMap` and `BTreeMap` but
87+
/// we want to keep the exact source code order of entries, so we need our own
88+
/// type.
89+
pub struct MapIntoDeserializer<K, V>(pub Vec<(K, V)>);
90+
91+
impl<'de, K, V, E> serde::de::IntoDeserializer<'de, E> for MapIntoDeserializer<K, V>
92+
where
93+
K: serde::de::IntoDeserializer<'de, E>,
94+
V: serde::de::IntoDeserializer<'de, E>,
95+
E: serde::de::Error,
96+
{
97+
type Deserializer = serde::de::value::MapDeserializer<'de, std::vec::IntoIter<(K, V)>, E>;
98+
99+
fn into_deserializer(self) -> Self::Deserializer {
100+
serde::de::value::MapDeserializer::new(self.0.into_iter())
101+
}
102+
}

src/lib.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,19 @@ pub use crate::{
284284
/// - **`#[config(default = ...)]`**: sets a default value for this field. This
285285
/// is returned by [`Partial::default_values`] and, in most circumstances,
286286
/// used as a last "layer" to pull values from that have not been set in a
287-
/// layer of higher-priority. Currently, Boolean, float, integer, string, and
288-
/// array values are allowed. The same set of types is allowed as type for
289-
/// array items.
287+
/// layer of higher-priority. Currently, the following expressions are
288+
/// allowed:
289+
///
290+
/// - Booleans, e.g. `default = true`
291+
/// - Integers, e.g. `default = 900`
292+
/// - Floats, e.g. `default = 3.14`
293+
/// - Strings, e.g. `default = "fox"`
294+
/// - Arrays, e.g. `default = ["foo", "bar"]`
295+
/// - Key value maps, e.g. `default = { "cat": 3.14, "bear": 9.0 }`
296+
///
297+
/// Map keys can be Booleans, integers, floats, and strings. For array and map
298+
/// values, you can use any of the expressions in the list above (i.e. you
299+
/// can nest arrays/maps).
290300
///
291301
/// The field value is deserialized from the specified default value
292302
/// (via `serde::de::IntoDeserializer`). So the expression after `default =`

src/meta.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ pub enum Expr {
5656
Integer(Integer),
5757
Bool(bool),
5858
Array(&'static [Expr]),
59+
60+
/// A key value map, stored as slice in source code order.
61+
#[serde(serialize_with = "serialize_map")]
62+
Map(&'static [MapEntry])
63+
}
64+
65+
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
66+
#[serde(untagged)]
67+
#[non_exhaustive]
68+
pub enum MapKey {
69+
Str(&'static str),
70+
Float(Float),
71+
Integer(Integer),
72+
Bool(bool),
73+
}
74+
75+
#[derive(Debug, Clone, Copy, PartialEq)]
76+
pub struct MapEntry {
77+
pub key: MapKey,
78+
pub value: Expr,
5979
}
6080

6181
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
@@ -110,3 +130,27 @@ impl fmt::Display for Integer {
110130
}
111131
}
112132
}
133+
134+
impl From<MapKey> for Expr {
135+
fn from(src: MapKey) -> Self {
136+
match src {
137+
MapKey::Str(v) => Self::Str(v),
138+
MapKey::Integer(v) => Self::Integer(v),
139+
MapKey::Float(v) => Self::Float(v),
140+
MapKey::Bool(v) => Self::Bool(v),
141+
}
142+
}
143+
}
144+
145+
fn serialize_map<S>(map: &&'static [MapEntry], serializer: S) -> Result<S::Ok, S::Error>
146+
where
147+
S: serde::Serializer,
148+
{
149+
use serde::ser::SerializeMap;
150+
151+
let mut s = serializer.serialize_map(Some(map.len()))?;
152+
for entry in *map {
153+
s.serialize_entry(&entry.key, &entry.value)?;
154+
}
155+
s.end()
156+
}

src/test_utils/example1.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::{
23
net::IpAddr,
34
path::PathBuf,
@@ -50,6 +51,10 @@ pub struct Headers {
5051
/// Headers that are allowed.
5152
#[config(default = ["content-type", "content-encoding"])]
5253
pub allowed: Vec<String>,
54+
55+
/// Assigns a score to some headers.
56+
#[config(default = { "cookie": 1.5, "server": 12.7 })]
57+
pub score: HashMap<String, f32>,
5358
}
5459

5560

0 commit comments

Comments
 (0)