mirror of
https://github.com/OMGeeky/confique.git
synced 2025-12-26 16:07:44 +01:00
Add map default values (e.g. default = { "foo": 33 })
This commit also adds a bunch of tests testing maps in several situations.
This commit is contained in:
@@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::ir::{self, Expr, FieldKind, LeafKind};
|
||||
use crate::ir::{self, Expr, FieldKind, LeafKind, MapKey};
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(super) fn gen(input: &ir::Input) -> TokenStream {
|
||||
let name = f.name.to_string();
|
||||
let doc = &f.doc;
|
||||
let kind = match &f.kind {
|
||||
FieldKind::Nested { ty }=> {
|
||||
FieldKind::Nested { ty } => {
|
||||
quote! {
|
||||
confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META }
|
||||
}
|
||||
@@ -73,27 +73,50 @@ pub(super) fn gen(input: &ir::Input) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper macro to deduplicate logic for literals. Only used in the function
|
||||
/// below.
|
||||
macro_rules! match_literals {
|
||||
($v:expr, $ty:expr, $ns:ident, { $($other_arms:tt)* }) => {
|
||||
match $v {
|
||||
$ns::Bool(v) => quote! { confique::meta::$ns::Bool(#v) },
|
||||
$ns::Str(s) => quote! { confique::meta::$ns::Str(#s) },
|
||||
$ns::Int(i) => {
|
||||
let variant = infer_type(i.suffix(), $ty, "I32", int_type_to_variant);
|
||||
quote! { confique::meta::$ns::Integer(confique::meta::Integer::#variant(#i)) }
|
||||
}
|
||||
$ns::Float(f) => {
|
||||
let variant = infer_type(f.suffix(), $ty, "F64", float_type_to_variant);
|
||||
quote! { confique::meta::$ns::Float(confique::meta::Float::#variant(#f)) }
|
||||
}
|
||||
$($other_arms)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates the meta expression of type `meta::Expr` to be used for the
|
||||
/// `default` field. `ty` is the type of the field that is used to better infer
|
||||
/// the exact type of the default value.
|
||||
fn default_value_to_meta_expr(default: &Expr, ty: Option<&syn::Type>) -> TokenStream {
|
||||
match default {
|
||||
Expr::Bool(v) => quote! { confique::meta::Expr::Bool(#v) },
|
||||
Expr::Str(s) => quote! { confique::meta::Expr::Str(#s) },
|
||||
Expr::Int(i) => {
|
||||
let variant = infer_type(i.suffix(), ty, "I32", int_type_to_variant);
|
||||
quote! { confique::meta::Expr::Integer(confique::meta::Integer::#variant(#i)) }
|
||||
}
|
||||
Expr::Float(f) => {
|
||||
let variant = infer_type(f.suffix(), ty, "F64", float_type_to_variant);
|
||||
quote! { confique::meta::Expr::Float(confique::meta::Float::#variant(#f)) }
|
||||
}
|
||||
match_literals!(default, ty, Expr, {
|
||||
Expr::Array(items) => {
|
||||
let item_type = ty.and_then(get_item_ty);
|
||||
let item_type = ty.and_then(get_array_item_type);
|
||||
let items = items.iter().map(|item| default_value_to_meta_expr(item, item_type));
|
||||
quote! { confique::meta::Expr::Array(&[#( #items ),*]) }
|
||||
}
|
||||
}
|
||||
Expr::Map(entries) => {
|
||||
// TODO: use `Option::unzip` once stable
|
||||
let types = ty.and_then(get_map_entry_types);
|
||||
let key_type = types.map(|(t, _)| t);
|
||||
let value_type = types.map(|(_, v)| v);
|
||||
|
||||
let pairs = entries.iter().map(|e| {
|
||||
let key = match_literals!(&e.key, key_type, MapKey, {});
|
||||
let value = default_value_to_meta_expr(&e.value, value_type);
|
||||
quote! { confique::meta::MapEntry { key: #key, value: #value } }
|
||||
});
|
||||
quote! { confique::meta::Expr::Map(&[#( #pairs ),*]) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Maps an integer type to the `meta::Expr` variant (e.g. `u32` -> `U32`).
|
||||
@@ -149,10 +172,9 @@ fn infer_type(
|
||||
Ident::new(variant, Span::call_site())
|
||||
}
|
||||
|
||||
|
||||
/// Tries to extract the type of the item of a field with an array default
|
||||
/// value. Examples: `&[u32]` -> `u32`, `Vec<String>` -> `String`.
|
||||
fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
fn get_array_item_type(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
match ty {
|
||||
// The easy types.
|
||||
syn::Type::Slice(slice) => Some(&slice.elem),
|
||||
@@ -184,9 +206,40 @@ fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
},
|
||||
|
||||
// Just recurse on inner type.
|
||||
syn::Type::Reference(r) => get_item_ty(&r.elem),
|
||||
syn::Type::Group(g) => get_item_ty(&g.elem),
|
||||
syn::Type::Paren(p) => get_item_ty(&p.elem),
|
||||
syn::Type::Reference(r) => get_array_item_type(&r.elem),
|
||||
syn::Type::Group(g) => get_array_item_type(&g.elem),
|
||||
syn::Type::Paren(p) => get_array_item_type(&p.elem),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract the key and value types from a map value. Examples:
|
||||
/// `HashMap<String, u32>` -> `(String, u32)`.
|
||||
fn get_map_entry_types(ty: &syn::Type) -> Option<(&syn::Type, &syn::Type)> {
|
||||
match ty {
|
||||
// We simply check if the last element in the path has exactly two
|
||||
// generic type arguments, in which case we use those. Otherwise we
|
||||
// can't really know.
|
||||
syn::Type::Path(p) => {
|
||||
let args = match &p.path.segments.last().expect("empty type path").arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => &args.args,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match (&args[0], &args[1]) {
|
||||
(syn::GenericArgument::Type(k), syn::GenericArgument::Type(v)) => Some((k, v)),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
|
||||
// Just recurse on inner type.
|
||||
syn::Type::Group(g) => get_map_entry_types(&g.elem),
|
||||
syn::Type::Paren(p) => get_map_entry_types(&p.elem),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -304,6 +304,23 @@ fn default_value_to_deserializable_expr(expr: &ir::Expr) -> TokenStream {
|
||||
};
|
||||
quote! { confique::internal::ArrayIntoDeserializer([ #(#items),* ] #type_annotation) }
|
||||
},
|
||||
ir::Expr::Map(entries) => {
|
||||
let items = entries.iter().map(|e| {
|
||||
let key = default_value_to_deserializable_expr(&e.key.clone().into());
|
||||
let value = default_value_to_deserializable_expr(&e.value);
|
||||
quote! { (#key, #value) }
|
||||
});
|
||||
|
||||
// Empty arrays cause "cannot infer type" errors here. However, it
|
||||
// really doesn't matter what type the array has as there are 0
|
||||
// elements anyway. So we just pick `()`.
|
||||
let type_annotation = if entries.is_empty() {
|
||||
quote! { as Vec<((), ())> }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
quote! { confique::internal::MapIntoDeserializer(vec![ #(#items),* ] #type_annotation) }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,4 +67,18 @@ pub(crate) enum Expr {
|
||||
Float(syn::LitFloat),
|
||||
Bool(syn::LitBool),
|
||||
Array(Vec<Expr>),
|
||||
Map(Vec<MapEntry>),
|
||||
}
|
||||
|
||||
pub(crate) struct MapEntry {
|
||||
pub(crate) key: MapKey,
|
||||
pub(crate) value: Expr,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum MapKey {
|
||||
Str(syn::LitStr),
|
||||
Int(syn::LitInt),
|
||||
Float(syn::LitFloat),
|
||||
Bool(syn::LitBool),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
|
||||
|
||||
use crate::{
|
||||
ir::{Input, Field, FieldKind, LeafKind, Expr},
|
||||
ir::{Input, Field, FieldKind, LeafKind, Expr, MapEntry, MapKey},
|
||||
util::{unwrap_option, is_option},
|
||||
};
|
||||
|
||||
@@ -112,21 +112,54 @@ impl Parse for Expr {
|
||||
(string, integer, float, bool), and arrays";
|
||||
|
||||
if input.peek(syn::token::Bracket) {
|
||||
// ----- Array -----
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
|
||||
let items = <Punctuated<Expr, Token![,]>>::parse_terminated(&content)?;
|
||||
Ok(Self::Array(items.into_iter().collect()))
|
||||
} else if input.peek(syn::token::Brace) {
|
||||
// ----- Map -----
|
||||
let content;
|
||||
syn::braced!(content in input);
|
||||
|
||||
let items = <Punctuated<MapEntry, Token![,]>>::parse_terminated(&content)?;
|
||||
Ok(Self::Map(items.into_iter().collect()))
|
||||
} else {
|
||||
let lit: syn::Lit = input.parse()
|
||||
.map_err(|_| Error::new(input.span(), msg))?;
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l)),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l)),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l)),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
|
||||
_ => Err(Error::new(lit.span(), msg)),
|
||||
}
|
||||
// ----- Literal -----
|
||||
|
||||
// We just use `MapKey` here as it's exactly what we want, despite
|
||||
// this not having anything to do with maps.
|
||||
input.parse::<MapKey>()
|
||||
.map_err(|_| Error::new(input.span(), msg))
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl Parse for MapEntry {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let key: MapKey = input.parse()?;
|
||||
let _: syn::Token![:] = input.parse()?;
|
||||
let value: Expr = input.parse()?;
|
||||
Ok(Self { key, value })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MapKey {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let lit: syn::Lit = input.parse()?;
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l)),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l)),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l)),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
|
||||
_ => Err(Error::new(
|
||||
lit.span(),
|
||||
"only string, integer, float, and Boolean literals allowed as map key",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,3 +330,14 @@ fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
|
||||
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapKey> for Expr {
|
||||
fn from(src: MapKey) -> Self {
|
||||
match src {
|
||||
MapKey::Str(v) => Self::Str(v),
|
||||
MapKey::Int(v) => Self::Int(v),
|
||||
MapKey::Float(v) => Self::Float(v),
|
||||
MapKey::Bool(v) => Self::Bool(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,3 +82,21 @@ where
|
||||
serde::de::value::SeqDeserializer::new(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// `serde` does implement `IntoDeserializer` for `HashMap` and `BTreeMap` but
|
||||
/// we want to keep the exact source code order of entries, so we need our own
|
||||
/// type.
|
||||
pub struct MapIntoDeserializer<K, V>(pub Vec<(K, V)>);
|
||||
|
||||
impl<'de, K, V, E> serde::de::IntoDeserializer<'de, E> for MapIntoDeserializer<K, V>
|
||||
where
|
||||
K: serde::de::IntoDeserializer<'de, E>,
|
||||
V: serde::de::IntoDeserializer<'de, E>,
|
||||
E: serde::de::Error,
|
||||
{
|
||||
type Deserializer = serde::de::value::MapDeserializer<'de, std::vec::IntoIter<(K, V)>, E>;
|
||||
|
||||
fn into_deserializer(self) -> Self::Deserializer {
|
||||
serde::de::value::MapDeserializer::new(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -284,9 +284,19 @@ pub use crate::{
|
||||
/// - **`#[config(default = ...)]`**: sets a default value for this field. This
|
||||
/// is returned by [`Partial::default_values`] and, in most circumstances,
|
||||
/// used as a last "layer" to pull values from that have not been set in a
|
||||
/// layer of higher-priority. Currently, Boolean, float, integer, string, and
|
||||
/// array values are allowed. The same set of types is allowed as type for
|
||||
/// array items.
|
||||
/// layer of higher-priority. Currently, the following expressions are
|
||||
/// allowed:
|
||||
///
|
||||
/// - Booleans, e.g. `default = true`
|
||||
/// - Integers, e.g. `default = 900`
|
||||
/// - Floats, e.g. `default = 3.14`
|
||||
/// - Strings, e.g. `default = "fox"`
|
||||
/// - Arrays, e.g. `default = ["foo", "bar"]`
|
||||
/// - Key value maps, e.g. `default = { "cat": 3.14, "bear": 9.0 }`
|
||||
///
|
||||
/// Map keys can be Booleans, integers, floats, and strings. For array and map
|
||||
/// values, you can use any of the expressions in the list above (i.e. you
|
||||
/// can nest arrays/maps).
|
||||
///
|
||||
/// The field value is deserialized from the specified default value
|
||||
/// (via `serde::de::IntoDeserializer`). So the expression after `default =`
|
||||
|
||||
44
src/meta.rs
44
src/meta.rs
@@ -56,6 +56,26 @@ pub enum Expr {
|
||||
Integer(Integer),
|
||||
Bool(bool),
|
||||
Array(&'static [Expr]),
|
||||
|
||||
/// A key value map, stored as slice in source code order.
|
||||
#[serde(serialize_with = "serialize_map")]
|
||||
Map(&'static [MapEntry])
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[non_exhaustive]
|
||||
pub enum MapKey {
|
||||
Str(&'static str),
|
||||
Float(Float),
|
||||
Integer(Integer),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MapEntry {
|
||||
pub key: MapKey,
|
||||
pub value: Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
|
||||
@@ -110,3 +130,27 @@ impl fmt::Display for Integer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapKey> for Expr {
|
||||
fn from(src: MapKey) -> Self {
|
||||
match src {
|
||||
MapKey::Str(v) => Self::Str(v),
|
||||
MapKey::Integer(v) => Self::Integer(v),
|
||||
MapKey::Float(v) => Self::Float(v),
|
||||
MapKey::Bool(v) => Self::Bool(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_map<S>(map: &&'static [MapEntry], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut s = serializer.serialize_map(Some(map.len()))?;
|
||||
for entry in *map {
|
||||
s.serialize_entry(&entry.key, &entry.value)?;
|
||||
}
|
||||
s.end()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
path::PathBuf,
|
||||
@@ -50,6 +51,10 @@ pub struct Headers {
|
||||
/// Headers that are allowed.
|
||||
#[config(default = ["content-type", "content-encoding"])]
|
||||
pub allowed: Vec<String>,
|
||||
|
||||
/// Assigns a score to some headers.
|
||||
#[config(default = { "cookie": 1.5, "server": 12.7 })]
|
||||
pub score: HashMap<String, f32>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
44
src/toml.rs
44
src/toml.rs
@@ -6,7 +6,7 @@ use std::fmt::{self, Write};
|
||||
use crate::{
|
||||
Config,
|
||||
template::{self, Formatter},
|
||||
meta::Expr,
|
||||
meta::{Expr, MapKey},
|
||||
};
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ impl TomlFormatter {
|
||||
}
|
||||
|
||||
impl Formatter for TomlFormatter {
|
||||
type ExprPrinter = PrintExpr;
|
||||
type ExprPrinter = PrintExpr<'static>;
|
||||
|
||||
fn buffer(&mut self) -> &mut String {
|
||||
&mut self.buffer
|
||||
@@ -159,22 +159,50 @@ impl Formatter for TomlFormatter {
|
||||
}
|
||||
|
||||
/// Helper to emit `meta::Expr` into TOML.
|
||||
struct PrintExpr(&'static Expr);
|
||||
struct PrintExpr<'a>(&'a Expr);
|
||||
|
||||
impl From<&'static Expr> for PrintExpr {
|
||||
impl From<&'static Expr> for PrintExpr<'static> {
|
||||
fn from(expr: &'static Expr) -> Self {
|
||||
Self(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrintExpr {
|
||||
impl fmt::Display for PrintExpr<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
toml::to_string(&self.0)
|
||||
.expect("string serialization to TOML failed")
|
||||
.fmt(f)
|
||||
match self.0 {
|
||||
Expr::Map(entries) => {
|
||||
// TODO: pretty printing of long arrays onto multiple lines?
|
||||
f.write_str("{ ")?;
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
|
||||
match entry.key {
|
||||
MapKey::Str(s) if is_valid_bare_key(s) => f.write_str(s)?,
|
||||
_ => PrintExpr(&entry.key.clone().into()).fmt(f)?,
|
||||
}
|
||||
f.write_str(" = ")?;
|
||||
PrintExpr(&entry.value).fmt(f)?;
|
||||
}
|
||||
f.write_str(" }")?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// All these other types can simply be serialized as is.
|
||||
Expr::Str(_) | Expr::Float(_) | Expr::Integer(_) | Expr::Bool(_) | Expr::Array(_) => {
|
||||
toml::to_string(&self.0)
|
||||
.expect("string serialization to TOML failed")
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_bare_key(s: &str) -> bool {
|
||||
s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test_utils::{self, include_format_output};
|
||||
|
||||
23
src/yaml.rs
23
src/yaml.rs
@@ -121,7 +121,7 @@ impl YamlFormatter {
|
||||
}
|
||||
|
||||
impl Formatter for YamlFormatter {
|
||||
type ExprPrinter = PrintExpr;
|
||||
type ExprPrinter = PrintExpr<'static>;
|
||||
|
||||
fn buffer(&mut self) -> &mut String {
|
||||
&mut self.buffer
|
||||
@@ -161,15 +161,15 @@ impl Formatter for YamlFormatter {
|
||||
}
|
||||
|
||||
/// Helper to emit `meta::Expr` into YAML.
|
||||
struct PrintExpr(&'static Expr);
|
||||
struct PrintExpr<'a>(&'a Expr);
|
||||
|
||||
impl From<&'static Expr> for PrintExpr {
|
||||
impl From<&'static Expr> for PrintExpr<'static> {
|
||||
fn from(expr: &'static Expr) -> Self {
|
||||
Self(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrintExpr {
|
||||
impl fmt::Display for PrintExpr<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self.0 {
|
||||
// We have to special case arrays as the normal formatter only emits
|
||||
@@ -187,6 +187,21 @@ impl fmt::Display for PrintExpr {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Expr::Map(entries) => {
|
||||
// TODO: pretty printing of long arrays onto multiple lines?
|
||||
f.write_str("{ ")?;
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
PrintExpr(&entry.key.clone().into()).fmt(f)?;
|
||||
f.write_str(": ")?;
|
||||
PrintExpr(&entry.value).fmt(f)?;
|
||||
}
|
||||
f.write_str(" }")?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// All these other types can simply be serialized as is.
|
||||
Expr::Str(_) | Expr::Float(_) | Expr::Integer(_) | Expr::Bool(_) => {
|
||||
let out = serde_yaml::to_string(&self.0).expect("string serialization to YAML failed");
|
||||
|
||||
@@ -92,4 +92,3 @@ fn inferred_type() {
|
||||
assert_eq!(def.parens, vec![1.0, 2.0]);
|
||||
assert_eq!(def.fallback, std::time::Duration::new(13, 27));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
//
|
||||
// Default value: ["content-type","content-encoding"]
|
||||
//allowed: ["content-type","content-encoding"],
|
||||
|
||||
// Assigns a score to some headers.
|
||||
//
|
||||
// Default value: {"cookie":1.5,"server":12.7}
|
||||
//score: {"cookie":1.5,"server":12.7},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
# Default value: ["content-type", "content-encoding"]
|
||||
#allowed = ["content-type", "content-encoding"]
|
||||
|
||||
# Assigns a score to some headers.
|
||||
#
|
||||
# Default value: { cookie = 1.5, server = 12.7 }
|
||||
#score = { cookie = 1.5, server = 12.7 }
|
||||
|
||||
# Configuring the logging.
|
||||
[log]
|
||||
# If set to `true`, the app will log to stdout.
|
||||
|
||||
@@ -36,6 +36,11 @@ http:
|
||||
# Default value: [content-type, content-encoding]
|
||||
#allowed: [content-type, content-encoding]
|
||||
|
||||
# Assigns a score to some headers.
|
||||
#
|
||||
# Default value: { cookie: 1.5, server: 12.7 }
|
||||
#score: { cookie: 1.5, server: 12.7 }
|
||||
|
||||
# Configuring the logging.
|
||||
log:
|
||||
# If set to `true`, the app will log to stdout.
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
# Default value: ["content-type", "content-encoding"]
|
||||
#allowed = ["content-type", "content-encoding"]
|
||||
|
||||
# Assigns a score to some headers.
|
||||
#
|
||||
# Default value: { cookie = 1.5, server = 12.7 }
|
||||
#score = { cookie = 1.5, server = 12.7 }
|
||||
|
||||
# Configuring the logging.
|
||||
[log]
|
||||
# If set to `true`, the app will log to stdout.
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
# Default value: ["content-type", "content-encoding"]
|
||||
#allowed = ["content-type", "content-encoding"]
|
||||
|
||||
# Assigns a score to some headers.
|
||||
#
|
||||
# Default value: { cookie = 1.5, server = 12.7 }
|
||||
#score = { cookie = 1.5, server = 12.7 }
|
||||
|
||||
|
||||
# Configuring the logging.
|
||||
[log]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//username: "x-username",
|
||||
//display_name: "x-display-name",
|
||||
//allowed: ["content-type","content-encoding"],
|
||||
//score: {"cookie":1.5,"server":12.7},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#username = "x-username"
|
||||
#display_name = "x-display-name"
|
||||
#allowed = ["content-type", "content-encoding"]
|
||||
#score = { cookie = 1.5, server = 12.7 }
|
||||
|
||||
[log]
|
||||
#stdout = true
|
||||
|
||||
@@ -8,6 +8,7 @@ http:
|
||||
#username: x-username
|
||||
#display_name: x-display-name
|
||||
#allowed: [content-type, content-encoding]
|
||||
#score: { cookie: 1.5, server: 12.7 }
|
||||
|
||||
log:
|
||||
#stdout: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{path::PathBuf, net::IpAddr};
|
||||
use std::{path::PathBuf, net::IpAddr, collections::HashMap};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use confique::{Config, meta, Partial};
|
||||
@@ -279,3 +279,18 @@ pub(crate) fn deserialize_dummy<'de, D>(deserializer: D) -> Result<Dummy, D::Err
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(Dummy(format!("dummy {s}")))
|
||||
}
|
||||
|
||||
// This only makes sure this compiles and doesn't result in any "cannot infer
|
||||
// type" problems.
|
||||
#[test]
|
||||
fn empty_array_and_map() {
|
||||
#[derive(Config)]
|
||||
#[allow(dead_code)]
|
||||
struct Animals {
|
||||
#[config(default = [])]
|
||||
cat: Vec<String>,
|
||||
|
||||
#[config(default = {})]
|
||||
dog: HashMap<u32, f32>,
|
||||
}
|
||||
}
|
||||
|
||||
44
tests/map_default.rs
Normal file
44
tests/map_default.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use confique::{Config, meta};
|
||||
|
||||
|
||||
#[test]
|
||||
fn string_to_u32() {
|
||||
#[derive(Config)]
|
||||
struct Foo {
|
||||
/// A nice doc comment.
|
||||
#[config(default = { "peter": 3, "anna": 27 })]
|
||||
bar: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
assert_eq!(Foo::META, meta::Meta {
|
||||
name: "Foo",
|
||||
doc: &[],
|
||||
fields: &[
|
||||
meta::Field {
|
||||
name: "bar",
|
||||
doc: &[" A nice doc comment."],
|
||||
kind: meta::FieldKind::Leaf {
|
||||
env: None,
|
||||
kind: meta::LeafKind::Required {
|
||||
default: Some(meta::Expr::Map(&[
|
||||
meta::MapEntry {
|
||||
key: meta::MapKey::Str("peter"),
|
||||
value: meta::Expr::Integer(meta::Integer::U32(3)),
|
||||
},
|
||||
meta::MapEntry {
|
||||
key: meta::MapKey::Str("anna"),
|
||||
value: meta::Expr::Integer(meta::Integer::U32(27)),
|
||||
},
|
||||
])),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let def = Foo::builder().load().unwrap();
|
||||
assert_eq!(def.bar, HashMap::from([("peter".into(), 3), ("anna".into(), 27)]));
|
||||
}
|
||||
Reference in New Issue
Block a user