diff --git a/Cargo.toml b/Cargo.toml index 3ce6d79..ea1660e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "macroconf" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "macro for creating configurations using miniconf" license = "MIT OR Apache-2.0" @@ -30,5 +30,5 @@ quote = "1.0" convert_case = "0.6.0" [dev-dependencies] -miniconf = "0.9" +miniconf = { version = "0.13", features = ["json-core"] } serde = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 41060ec..08b6435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ fn generate_helper_struct( .any(|key| attr.path().is_ident(key)) }); - field.attrs.push(parse_quote!(#[tree(depth(1))])); + field.attrs.push(parse_quote!(#[tree(depth=1)])); let vis = if matches!(field.vis, Visibility::Public(_)) || matches!(field.vis, Visibility::Inherited) { @@ -159,6 +159,7 @@ fn generate_helper_struct( let tree_key = generate_tree_key(&new_type_ident, new_type_miniconf_names.iter()); let tree_serialize = generate_tree_serialize(&new_type_ident, &new_type_miniconf_consts[..]); let tree_deserialize = generate_tree_deserialize(&new_type_ident, miniconf_fields); + let tree_any = generate_tree_any(&new_type_ident); Some(quote! { #[allow(clippy::derive_partial_eq_without_eq)] @@ -173,6 +174,7 @@ fn generate_helper_struct( #tree_key #tree_serialize #tree_deserialize + #tree_any }) } @@ -424,32 +426,16 @@ fn generate_tree_key<'a>( .expect("safe because both iterators (once and original keys) are exact"); let max_length = keys.clone().map(|v| v.len()).max(); quote! { - impl #ident { - const __MINICONF_NAMES: [&'static str; #num_keys] = [#(#keys,)*]; + impl ::miniconf::KeyLookup for #ident { + const LEN: usize = #num_keys; + const NAMES: &'static [&'static str] = &[#(#keys,)*]; + + fn name_to_index(value: &str) -> Option { + Self::NAMES.iter().position(|name| *name == value) + } } impl ::miniconf::TreeKey<1> for #ident { - fn name_to_index(name: &str) -> ::core::option::Option { - Self::__MINICONF_NAMES.iter().position(|&n| n == name) - } - - fn traverse_by_key(mut keys: K, mut func: F) -> ::core::result::Result> - where - K: ::core::iter::Iterator, - K::Item: ::miniconf::Key, - // Writing this to return an iterator instead of using a callback - // would have worse performance (O(n^2) instead of O(n) for matching) - F: FnMut(usize, &str) -> ::core::result::Result<(), E>, - { - let ::core::option::Option::Some(key) = keys.next() else { return ::core::result::Result::Ok(0) }; - let index = ::miniconf::Key::find::<1, Self>(&key).ok_or(::miniconf::Error::NotFound(1))?; - let name = Self::__MINICONF_NAMES - .get(index) - .ok_or(::miniconf::Error::NotFound(1))?; - func(index, name)?; - ::miniconf::Increment::increment(::core::result::Result::Ok(0)) - } - fn metadata() -> miniconf::Metadata { let mut metadata = miniconf::Metadata::default(); metadata.max_depth = 1; @@ -457,6 +443,22 @@ fn generate_tree_key<'a>( metadata.max_length = #max_length; metadata } + + fn traverse_by_key(mut keys: K, mut func: F) -> ::core::result::Result> + where + K: ::miniconf::Keys, + // Writing this to return an iterator instead of using a callback + // would have worse performance (O(n^2) instead of O(n) for matching) + F: FnMut(usize, Option<&'static str>, usize) -> ::core::result::Result<(), E>, + { + let ::core::result::Result::Ok(key) = keys.next::() else { return ::core::result::Result::Ok(0) }; + let index = ::miniconf::Key::find::(&key).ok_or(::miniconf::Traversal::NotFound(1))?; + let name = ::NAMES + .get(index) + .ok_or(::miniconf::Traversal::NotFound(1))?; + func(index, Some(name), #num_keys).map_err(|err| ::miniconf::Error::Inner(1, err))?; + ::miniconf::Error::increment_result(::core::result::Result::Ok(0)) + } } } } @@ -465,7 +467,7 @@ fn generate_tree_serialize(ident: &Ident, consts: &[Ident]) -> TokenStream2 { let matches = consts.iter().enumerate().map(|(i, ident)| { let index = i + 1; quote! { - #index => ::miniconf::Serialize::serialize(&Self::#ident, ser)?, + #index => ::miniconf::Serialize::serialize(&Self::#ident, ser).map_err(|err| ::miniconf::Error::Inner(0, err))?, } }); quote! { @@ -476,23 +478,22 @@ fn generate_tree_serialize(ident: &Ident, consts: &[Ident]) -> TokenStream2 { ser: S, ) -> ::core::result::Result> where - K: ::core::iter::Iterator, - K::Item: ::miniconf::Key, + K: ::miniconf::Keys, S: ::serde::Serializer, { - let ::core::option::Option::Some(key) = keys.next() else { - return ::miniconf::Increment::increment({ - ::miniconf::Serialize::serialize(&self.0, ser)?; + let ::core::result::Result::Ok(key) = keys.next::() else { + return ::miniconf::Error::increment_result({ + ::miniconf::Serialize::serialize(&self.0, ser).map_err(|err| ::miniconf::Error::Inner(0, err))?; ::core::result::Result::Ok(0) }); }; - let index = ::miniconf::Key::find::<1, Self>(&key).ok_or(miniconf::Error::NotFound(1))?; - if keys.next().is_some() { - return ::core::result::Result::Err(::miniconf::Error::TooLong(1)); + let index = ::miniconf::Key::find::(&key).ok_or(miniconf::Traversal::NotFound(1))?; + if !keys.finalize() { + ::core::result::Result::Err(::miniconf::Traversal::TooLong(1))?; } - ::miniconf::Increment::increment({ + ::miniconf::Error::increment_result({ match index { - 0 => ::miniconf::Serialize::serialize(&self.0, ser)?, + 0 => ::miniconf::Serialize::serialize(&self.0, ser).map_err(|err| ::miniconf::Error::Inner(0, err))?, #(#matches)* _ => unreachable!(), }; @@ -512,27 +513,46 @@ fn generate_tree_deserialize(ident: &Ident, num_keys: usize) -> TokenStream2 { de: D, ) -> ::core::result::Result> where - K: ::core::iter::Iterator, - K::Item: ::miniconf::Key, + K: ::miniconf::Keys, D: ::serde::Deserializer<'de>, { - let ::core::option::Option::Some(key) = keys.next() else { - self.0 = ::miniconf::Deserialize::deserialize(de)?; + let ::core::result::Result::Ok(key) = keys.next::() else { + self.0 = ::miniconf::Deserialize::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?; return ::core::result::Result::Ok(0); }; - let index = ::miniconf::Key::find::<1, Self>(&key).ok_or(::miniconf::Error::NotFound(1))?; - if keys.next().is_some() { - return ::core::result::Result::Err(miniconf::Error::TooLong(1)); + let index = ::miniconf::Key::find::(&key).ok_or(::miniconf::Traversal::NotFound(1))?; + if !keys.finalize() { + ::core::result::Result::Err(miniconf::Traversal::TooLong(1))?; } match index { - 0 => ::miniconf::Increment::increment({ - self.0 = ::miniconf::Deserialize::deserialize(de)?; + 0 => ::miniconf::Error::increment_result((||{ + self.0 = ::miniconf::Deserialize::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?; Ok(0) - }), - 1..=#num_keys => ::core::result::Result::Err(::miniconf::Error::Absent(0)), + })()), + 1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Absent(0))?, _ => unreachable!(), } } } } } + +fn generate_tree_any(ident: &Ident) -> TokenStream2 { + quote! { + impl ::miniconf::TreeAny<1> for #ident { + fn ref_any_by_key(&self, keys: K) -> Result<&dyn ::core::any::Any, ::miniconf::Traversal> + where + K: ::miniconf::Keys, + { + unimplemented!(); + } + + fn mut_any_by_key(&mut self, keys: K) -> Result<&mut dyn ::core::any::Any, ::miniconf::Traversal> + where + K: ::miniconf::Keys, + { + unimplemented!(); + } + } + } +} diff --git a/tests/simple.rs b/tests/simple.rs index 2bb34a9..951ee4e 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -1,7 +1,12 @@ use std::str::from_utf8; use macroconf::config; -use miniconf::{Error::Absent, JsonCoreSlash, Tree, TreeKey}; +use miniconf::{ + Error::Traversal, + IntoKeys, JsonCoreSlash, Path, + Traversal::{Absent, TooLong}, + Tree, TreeKey, +}; use serde::{Deserialize, Serialize}; #[config] @@ -21,7 +26,7 @@ struct SubConfig { #[config] #[derive(Debug, Clone, Copy, Tree)] struct Config { - #[tree(depth(2))] + #[tree(depth = 2)] sub_config: SubConfig, } @@ -32,8 +37,8 @@ fn keys() { .enumerate() { assert_eq!( - SubConfig::traverse_by_key(std::iter::once(field), |index, name| { - assert_eq!((id, field), (index, name)); + SubConfig::traverse_by_key([field].into_keys(), |index, name, _len| { + assert_eq!((id, Some(field)), (index, name)); Ok::<_, ()>(()) }), Ok(1) @@ -44,16 +49,20 @@ fn keys() { #[test] fn sub_keys() { assert_eq!( - SubConfig::traverse_by_key(["skipped", "value"].into_iter(), |_, _| Ok::<_, ()>(())), + SubConfig::traverse_by_key(["skipped", "value"].into_keys(), |_, _, _| Ok::<_, ()>(())), + Err(Traversal(TooLong(1))) + ); + assert_eq!( + SubConfig::traverse_by_key(["skipped"].into_keys(), |_, _, _| Ok::<_, ()>(())), Ok(1) ); for field in ["min", "max", "default", "description"] { assert_eq!( - SubConfig::traverse_by_key([field, "value"].into_iter(), |_, _| Ok::<_, ()>(())), + SubConfig::traverse_by_key([field, "value"].into_keys(), |_, _, _| Ok::<_, ()>(())), Ok(2) ); assert_eq!( - SubConfig::traverse_by_key([field, field].into_iter(), |_, _| Ok::<_, ()>(())), + SubConfig::traverse_by_key([field, field].into_keys(), |_, _, _| Ok::<_, ()>(())), Ok(2) ); } @@ -123,7 +132,7 @@ fn deserialize() { "/description/description", ] { let res = config.set_json(input, b"10"); - assert_eq!(res, Err(Absent(1))); + assert_eq!(res, Err(Traversal(Absent(1)))); } } @@ -140,8 +149,8 @@ fn subconfig() { "/sub_config/description/value".to_owned(), "/sub_config/description/description".to_owned(), ]; - let paths: Vec = Config::iter_paths::("/") - .filter_map(|path| path.ok()) + let paths: Vec = Config::nodes::>() + .filter_map(|path| path.ok().map(|(n, _)| n.0)) .collect(); assert_eq!(paths, control); }