Compare commits
No commits in common. "ea6c3a34b8d2b9d5e6aea668cb929a5629a74e1a" and "eeed85aaa9940f057b4a991663fe242dfa1b5dfd" have entirely different histories.
ea6c3a34b8
...
eeed85aaa9
@ -33,5 +33,3 @@ darling = "0.20"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
miniconf = { version = "0.13", features = ["json-core"] }
|
miniconf = { version = "0.13", features = ["json-core"] }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
rstest = "0.22"
|
|
||||||
serde-json-core = "0.5.1"
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#![allow(clippy::option_if_let_else)]
|
#![allow(clippy::option_if_let_else)]
|
||||||
|
|
||||||
use std::convert::identity;
|
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use darling::{
|
use darling::{
|
||||||
ast::{self, Data},
|
ast::{self, Data},
|
||||||
@ -10,7 +8,7 @@ use darling::{
|
|||||||
};
|
};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{parse_quote, spanned::Spanned};
|
use syn::parse_quote;
|
||||||
|
|
||||||
#[derive(Debug, FromField)]
|
#[derive(Debug, FromField)]
|
||||||
#[darling(attributes(config))]
|
#[darling(attributes(config))]
|
||||||
@ -23,9 +21,6 @@ pub struct ConfigField {
|
|||||||
min: Option<Override<syn::Expr>>,
|
min: Option<Override<syn::Expr>>,
|
||||||
max: Option<Override<syn::Expr>>,
|
max: Option<Override<syn::Expr>>,
|
||||||
default: Option<Override<syn::Expr>>,
|
default: Option<Override<syn::Expr>>,
|
||||||
typ: Option<syn::Type>,
|
|
||||||
get: Option<syn::Path>,
|
|
||||||
set: Option<syn::Path>,
|
|
||||||
#[darling(skip)]
|
#[darling(skip)]
|
||||||
description: Option<syn::Expr>,
|
description: Option<syn::Expr>,
|
||||||
#[darling(skip)]
|
#[darling(skip)]
|
||||||
@ -93,8 +88,7 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_custom_limits(&self) -> bool {
|
pub(crate) fn has_custom_limits(&self) -> bool {
|
||||||
self.typ.is_none()
|
!((self.min.is_none() || self.min == Some(Override::Inherit))
|
||||||
&& !((self.min.is_none() || self.min == Some(Override::Inherit))
|
|
||||||
&& (self.max.is_none() || self.max == Some(Override::Inherit)))
|
&& (self.max.is_none() || self.max == Some(Override::Inherit)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +148,7 @@ impl ConfigField {
|
|||||||
pub(crate) fn helper_default(&self) -> Option<TokenStream> {
|
pub(crate) fn helper_default(&self) -> Option<TokenStream> {
|
||||||
self.default.as_ref().map(|default| {
|
self.default.as_ref().map(|default| {
|
||||||
let ident = self.helper_ident();
|
let ident = self.helper_ident();
|
||||||
let default_default = parse_quote!(::core::default::Default::default());
|
let default_default = parse_quote!(default::Default());
|
||||||
let default = default.as_ref().unwrap_or(&default_default);
|
let default = default.as_ref().unwrap_or(&default_default);
|
||||||
quote! {
|
quote! {
|
||||||
impl ::core::default::Default for #ident {
|
impl ::core::default::Default for #ident {
|
||||||
@ -215,7 +209,7 @@ impl ConfigField {
|
|||||||
macro_rules! field_to_key {
|
macro_rules! field_to_key {
|
||||||
($field: ident, $default: expr) => {
|
($field: ident, $default: expr) => {
|
||||||
self.$field.as_ref().map(|value| {
|
self.$field.as_ref().map(|value| {
|
||||||
let ty = self.typ.as_ref().unwrap_or(&self.ty);
|
let ty = &self.ty;
|
||||||
(stringify!($field),
|
(stringify!($field),
|
||||||
value.clone().explicit().map(|val| {
|
value.clone().explicit().map(|val| {
|
||||||
parse_quote!({
|
parse_quote!({
|
||||||
@ -294,7 +288,6 @@ impl ConfigField {
|
|||||||
.helper_keys()
|
.helper_keys()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(1)
|
|
||||||
.map(|(i, (_, expr))| {
|
.map(|(i, (_, expr))| {
|
||||||
quote! {
|
quote! {
|
||||||
#i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
#i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
||||||
@ -302,10 +295,6 @@ impl ConfigField {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let ident = self.helper_ident();
|
let ident = self.helper_ident();
|
||||||
let get = self
|
|
||||||
.get
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| quote! {self}, |getter| quote! {#getter(&self.0)});
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::miniconf::TreeSerialize<1> for #ident {
|
impl ::miniconf::TreeSerialize<1> for #ident {
|
||||||
fn serialize_by_key<K, S>(
|
fn serialize_by_key<K, S>(
|
||||||
@ -318,14 +307,13 @@ impl ConfigField {
|
|||||||
S: ::serde::Serializer,
|
S: ::serde::Serializer,
|
||||||
{
|
{
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
||||||
return ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)).map(|_| 0);
|
return ::serde::Serialize::serialize(&self, ser).map_err(|err| ::miniconf::Error::Inner(1, err)).map(|_| 1);
|
||||||
};
|
};
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(0))?;
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
||||||
if !keys.finalize() {
|
if !keys.finalize() {
|
||||||
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(0).into());
|
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1).into());
|
||||||
}
|
}
|
||||||
match index {
|
match index {
|
||||||
0 => ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
|
||||||
#(#matches)*
|
#(#matches)*
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}?;
|
}?;
|
||||||
@ -338,14 +326,6 @@ impl ConfigField {
|
|||||||
pub(crate) fn helper_tree_deserialize(&self) -> TokenStream {
|
pub(crate) fn helper_tree_deserialize(&self) -> TokenStream {
|
||||||
let ident = self.helper_ident();
|
let ident = self.helper_ident();
|
||||||
let num_keys = self.helper_keys().len();
|
let num_keys = self.helper_keys().len();
|
||||||
let set = self.set.as_ref().map_or_else(
|
|
||||||
|| quote!((|val| {*self = val; core::result::Result::<_, ::miniconf::Traversal>::Ok(())})),
|
|
||||||
|set| quote!((|val| #set(&mut self.0, val).map_err(|e| ::miniconf::Traversal::Invalid(0, e)))),
|
|
||||||
);
|
|
||||||
let typ = self
|
|
||||||
.typ
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| quote!(Self), |typ| quote!(#typ));
|
|
||||||
quote! {
|
quote! {
|
||||||
impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
|
impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
|
||||||
fn deserialize_by_key<K, D>(
|
fn deserialize_by_key<K, D>(
|
||||||
@ -358,7 +338,7 @@ impl ConfigField {
|
|||||||
D: ::serde::Deserializer<'de>,
|
D: ::serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
||||||
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
*self = <Self as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?;
|
||||||
return ::core::result::Result::Ok(0);
|
return ::core::result::Result::Ok(0);
|
||||||
};
|
};
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
||||||
@ -367,7 +347,7 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
match index {
|
match index {
|
||||||
0 => {
|
0 => {
|
||||||
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
*self = <Self as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?;
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
@ -456,26 +436,6 @@ impl Config {
|
|||||||
parse_quote!(#description)
|
parse_quote!(#description)
|
||||||
});
|
});
|
||||||
field.parent_ident = Some(ident.clone());
|
field.parent_ident = Some(ident.clone());
|
||||||
let get_set_typ = [
|
|
||||||
field.typ.is_some(),
|
|
||||||
field.get.is_some(),
|
|
||||||
field.set.is_some(),
|
|
||||||
];
|
|
||||||
if get_set_typ.iter().copied().any(identity) && !get_set_typ.into_iter().all(identity) {
|
|
||||||
return Err(
|
|
||||||
Error::custom("need all or none of 'typ', 'get', 'set'").with_span(
|
|
||||||
&field.typ.as_ref().map_or_else(
|
|
||||||
|| {
|
|
||||||
field.get.as_ref().map_or_else(
|
|
||||||
|| field.set.as_ref().unwrap().span(),
|
|
||||||
Spanned::span,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Spanned::span,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
use std::{cell::Cell, str::from_utf8};
|
|
||||||
|
|
||||||
use macroconf::config;
|
|
||||||
use miniconf::JsonCoreSlash;
|
|
||||||
|
|
||||||
fn set_cell<const MIN: i32, const MAX: i32>(
|
|
||||||
cell: &Cell<i32>,
|
|
||||||
val: i32,
|
|
||||||
) -> Result<(), &'static str> {
|
|
||||||
if (MIN..=MAX).contains(&val) {
|
|
||||||
cell.set(val);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("value out of bounds")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[config]
|
|
||||||
struct Config {
|
|
||||||
#[config(
|
|
||||||
min = "-128",
|
|
||||||
max = "128",
|
|
||||||
default,
|
|
||||||
typ = "i32",
|
|
||||||
get = "Cell::get",
|
|
||||||
set = "set_cell::<-128,128>"
|
|
||||||
)]
|
|
||||||
field: Cell<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get() {
|
|
||||||
let mut buffer = [0u8; 32];
|
|
||||||
let config = Config {
|
|
||||||
field: __ConfigField::new(Cell::new(42)),
|
|
||||||
};
|
|
||||||
let len = config.get_json("/field", &mut buffer).unwrap();
|
|
||||||
assert_eq!(from_utf8(&buffer[..len]), Ok("42"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set() {
|
|
||||||
let mut config = Config {
|
|
||||||
field: __ConfigField::new(Cell::new(42)),
|
|
||||||
};
|
|
||||||
config.set_json("/field", b"-32").unwrap();
|
|
||||||
assert_eq!(config.field.get(), -32);
|
|
||||||
config
|
|
||||||
.set_json("/field", b"256")
|
|
||||||
.expect_err("result not checked");
|
|
||||||
config
|
|
||||||
.set_json("/field", b"-256")
|
|
||||||
.expect_err("result not checked");
|
|
||||||
}
|
|
137
tests/simple.rs
137
tests/simple.rs
@ -7,7 +7,6 @@ use miniconf::{
|
|||||||
Traversal::{Access, TooLong},
|
Traversal::{Access, TooLong},
|
||||||
Tree, TreeKey,
|
Tree, TreeKey,
|
||||||
};
|
};
|
||||||
use rstest::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[config]
|
#[config]
|
||||||
@ -31,39 +30,27 @@ struct Config {
|
|||||||
sub_config: SubConfig,
|
sub_config: SubConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[test]
|
||||||
#[case(0, ["skipped"])]
|
fn keys() {
|
||||||
#[case(1, ["min"])]
|
for (id, field) in ["skipped", "min", "max", "default", "description"]
|
||||||
#[case(2, ["max"])]
|
.into_iter()
|
||||||
#[case(3, ["default"])]
|
.enumerate()
|
||||||
#[case(4, ["description"])]
|
{
|
||||||
fn key<const N: usize>(#[case] id: usize, #[case] field: [&str; N]) {
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SubConfig::traverse_by_key(field.into_keys(), |index, name, _len| {
|
SubConfig::traverse_by_key([field].into_keys(), |index, name, _len| {
|
||||||
assert_eq!((id, Some(field[field.len() - 1])), (index, name));
|
assert_eq!((id, Some(field)), (index, name));
|
||||||
Ok::<_, ()>(())
|
Ok::<_, ()>(())
|
||||||
}),
|
}),
|
||||||
Ok(1)
|
Ok(1)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[test]
|
||||||
#[case(["skipped", "value"], Err(Traversal(TooLong(1))))]
|
fn sub_keys() {
|
||||||
#[case(["min", "value"], Ok(2))]
|
|
||||||
#[case(["max", "value"], Ok(2))]
|
|
||||||
#[case(["default", "value"], Ok(2))]
|
|
||||||
#[case(["description", "value"], Ok(2))]
|
|
||||||
#[case(["min", "min"], Ok(2))]
|
|
||||||
#[case(["max", "max"], Ok(2))]
|
|
||||||
#[case(["default", "default"], Ok(2))]
|
|
||||||
#[case(["description", "description"], Ok(2))]
|
|
||||||
fn sub_keys<const N: usize>(
|
|
||||||
#[case] fields: [&str; N],
|
|
||||||
#[case] expected: Result<usize, miniconf::Error<()>>,
|
|
||||||
) {
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SubConfig::traverse_by_key(fields.into_keys(), |_, _, _| Ok::<_, ()>(())),
|
SubConfig::traverse_by_key(["skipped", "value"].into_keys(), |_, _, _| Ok::<_, ()>(())),
|
||||||
expected
|
Err(Traversal(TooLong(1)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SubConfig::traverse_by_key(["skipped"].into_keys(), |_, _, _| Ok::<_, ()>(())),
|
SubConfig::traverse_by_key(["skipped"].into_keys(), |_, _, _| Ok::<_, ()>(())),
|
||||||
@ -81,59 +68,39 @@ fn sub_keys<const N: usize>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[fixture]
|
#[test]
|
||||||
#[once]
|
fn serialize() {
|
||||||
fn sub_config() -> SubConfig {
|
let mut buffer = [0u8; 32];
|
||||||
SubConfig {
|
let config = SubConfig {
|
||||||
skipped: 1,
|
skipped: 1,
|
||||||
min: __SubConfigMin::new(2),
|
min: __SubConfigMin::new(2),
|
||||||
max: __SubConfigMax::new(3),
|
max: __SubConfigMax::new(3),
|
||||||
default: __SubConfigDefault::new(4),
|
default: __SubConfigDefault::new(4),
|
||||||
description: __SubConfigDescription::new(5),
|
description: __SubConfigDescription::new(5),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (input, output) in [
|
||||||
|
("/skipped", "1"),
|
||||||
|
("/min", "2"),
|
||||||
|
("/min/value", "2"),
|
||||||
|
("/min/min", "-2147483648"),
|
||||||
|
("/max", "3"),
|
||||||
|
("/max/value", "3"),
|
||||||
|
("/max/max", "2147483647"),
|
||||||
|
("/default", "4"),
|
||||||
|
("/default/value", "4"),
|
||||||
|
("/default/default", "0"),
|
||||||
|
("/description", "5"),
|
||||||
|
("/description/value", "5"),
|
||||||
|
("/description/description", "\"This is a description\""),
|
||||||
|
] {
|
||||||
|
let len = config.get_json(input, &mut buffer).unwrap();
|
||||||
|
assert_eq!(from_utf8(&buffer[..len]), Ok(output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[test]
|
||||||
#[case("/skipped", "1")]
|
fn deserialize() {
|
||||||
#[case("/min", "2")]
|
|
||||||
#[case("/min/value", "2")]
|
|
||||||
#[case("/min/min", "-2147483648")]
|
|
||||||
#[case("/max", "3")]
|
|
||||||
#[case("/max/value", "3")]
|
|
||||||
#[case("/max/max", "2147483647")]
|
|
||||||
#[case("/default", "4")]
|
|
||||||
#[case("/default/value", "4")]
|
|
||||||
#[case("/default/default", "0")]
|
|
||||||
#[case("/description", "5")]
|
|
||||||
#[case("/description/value", "5")]
|
|
||||||
#[case("/description/description", "\"This is a description\"")]
|
|
||||||
fn serialize(sub_config: &SubConfig, #[case] path: &str, #[case] expected: &str) {
|
|
||||||
let mut buffer = [0u8; 32];
|
|
||||||
let len = sub_config.get_json(path, &mut buffer).unwrap();
|
|
||||||
assert_eq!(from_utf8(&buffer[..len]), Ok(expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("/skipped", Ok(2))]
|
|
||||||
#[case("/min", Ok(2))]
|
|
||||||
#[case("/min/value", Ok(2))]
|
|
||||||
#[case("/max", Ok(2))]
|
|
||||||
#[case("/max/value", Ok(2))]
|
|
||||||
#[case("/default", Ok(2))]
|
|
||||||
#[case("/default/value", Ok(2))]
|
|
||||||
#[case("/description", Ok(2))]
|
|
||||||
#[case("/description/value", Ok(2))]
|
|
||||||
#[case("/min/min", Err(Traversal(Access(1, "Cannot write limits"))))]
|
|
||||||
#[case("/max/max", Err(Traversal(Access(1, "Cannot write limits"))))]
|
|
||||||
#[case("/default/default", Err(Traversal(Access(1, "Cannot write limits"))))]
|
|
||||||
#[case(
|
|
||||||
"/description/description",
|
|
||||||
Err(Traversal(Access(1, "Cannot write limits")))
|
|
||||||
)]
|
|
||||||
fn deserialize(
|
|
||||||
#[case] path: &str,
|
|
||||||
#[case] expected: Result<usize, miniconf::Error<serde_json_core::de::Error>>,
|
|
||||||
) {
|
|
||||||
let mut config = SubConfig {
|
let mut config = SubConfig {
|
||||||
skipped: 0,
|
skipped: 0,
|
||||||
min: __SubConfigMin::new(0),
|
min: __SubConfigMin::new(0),
|
||||||
@ -142,12 +109,34 @@ fn deserialize(
|
|||||||
description: __SubConfigDescription::new(0),
|
description: __SubConfigDescription::new(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = config.set_json(path, b"10");
|
for input in [
|
||||||
assert_eq!(res, expected);
|
"/skipped",
|
||||||
|
"/min",
|
||||||
|
"/min/value",
|
||||||
|
"/max",
|
||||||
|
"/max/value",
|
||||||
|
"/default",
|
||||||
|
"/default/value",
|
||||||
|
"/description",
|
||||||
|
"/description/value",
|
||||||
|
] {
|
||||||
|
let res = config.set_json(input, b"10");
|
||||||
|
assert_eq!(res, Ok(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
for input in [
|
||||||
|
"/min/min",
|
||||||
|
"/max/max",
|
||||||
|
"/default/default",
|
||||||
|
"/description/description",
|
||||||
|
] {
|
||||||
|
let res = config.set_json(input, b"10");
|
||||||
|
assert_eq!(res, Err(Traversal(Access(1, "Cannot write limits"))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_paths() {
|
fn subconfig() {
|
||||||
let control = vec![
|
let control = vec![
|
||||||
"/sub_config/skipped".to_owned(),
|
"/sub_config/skipped".to_owned(),
|
||||||
"/sub_config/min/value".to_owned(),
|
"/sub_config/min/value".to_owned(),
|
||||||
|
Loading…
Reference in New Issue
Block a user