Compare commits
No commits in common. "main" and "v0.3.1" have entirely different histories.
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "macroconf"
|
name = "macroconf"
|
||||||
version = "0.3.6"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "macro for creating configurations using miniconf"
|
description = "macro for creating configurations using miniconf"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
29
src/lib.rs
29
src/lib.rs
@ -2,41 +2,12 @@
|
|||||||
//! extra information about the field.
|
//! extra information about the field.
|
||||||
|
|
||||||
use darling::{util::PathList, FromMeta};
|
use darling::{util::PathList, FromMeta};
|
||||||
use parser::Enum;
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, parse_quote, DataStruct, DeriveInput};
|
use syn::{parse_macro_input, parse_quote, DataStruct, DeriveInput};
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
/// Implements `miniconf::Tree` for the given enum.
|
|
||||||
///
|
|
||||||
/// This implementation of `miniconf::Tree` adds a value and a variants path.
|
|
||||||
/// The value path serializes the enum.
|
|
||||||
/// The variants path serializes all possible variants as an array.
|
|
||||||
/// Optionally default and description paths can be generated.
|
|
||||||
/// The description is generated from the docstring describing the enum.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use macroconf::ConfigEnum;
|
|
||||||
/// use serde::{Serialize, Deserialize};
|
|
||||||
///
|
|
||||||
/// /// Description
|
|
||||||
/// #[derive(Default, ConfigEnum, Serialize, Deserialize)]
|
|
||||||
/// enum Test {
|
|
||||||
/// #[default]
|
|
||||||
/// Variant1,
|
|
||||||
/// Variant2,
|
|
||||||
/// Variant3,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[proc_macro_derive(ConfigEnum, attributes(default))]
|
|
||||||
pub fn config_enum(item: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(item as Enum);
|
|
||||||
input.generate_tree().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates structs for the values to extend them with extra metadata.
|
/// Creates structs for the values to extend them with extra metadata.
|
||||||
///
|
///
|
||||||
/// supported metadata is `min`, `max` and `default`. Doc comments are parsed as `description`
|
/// supported metadata is `min`, `max` and `default`. Doc comments are parsed as `description`
|
||||||
|
296
src/parser.rs
296
src/parser.rs
@ -5,12 +5,12 @@ use std::convert::identity;
|
|||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use darling::{
|
use darling::{
|
||||||
ast::{self, Data},
|
ast::{self, Data},
|
||||||
util::{Flag, Override, PathList},
|
util::{Override, PathList},
|
||||||
Error, FromDeriveInput, FromField, FromMeta,
|
Error, FromDeriveInput, FromField, FromMeta,
|
||||||
};
|
};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{braced, parse::Parse, parse_quote, spanned::Spanned, Token};
|
use syn::{parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
#[derive(Debug, FromField)]
|
#[derive(Debug, FromField)]
|
||||||
#[darling(attributes(config))]
|
#[darling(attributes(config))]
|
||||||
@ -26,7 +26,6 @@ pub struct ConfigField {
|
|||||||
typ: Option<syn::Type>,
|
typ: Option<syn::Type>,
|
||||||
get: Option<syn::Path>,
|
get: Option<syn::Path>,
|
||||||
set: Option<syn::Path>,
|
set: Option<syn::Path>,
|
||||||
no_serde: Flag,
|
|
||||||
#[darling(skip)]
|
#[darling(skip)]
|
||||||
description: Option<syn::Expr>,
|
description: Option<syn::Expr>,
|
||||||
#[darling(skip)]
|
#[darling(skip)]
|
||||||
@ -35,12 +34,11 @@ pub struct ConfigField {
|
|||||||
|
|
||||||
impl ConfigField {
|
impl ConfigField {
|
||||||
pub(crate) fn needs_newtype(&self) -> bool {
|
pub(crate) fn needs_newtype(&self) -> bool {
|
||||||
self.helper_keys().len() > 1 || self.typ.is_some()
|
self.helper_keys().len() > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn helper(&self, attrs: &[syn::Attribute]) -> (Option<TokenStream>, syn::Type) {
|
pub(crate) fn helper(&self, attrs: &[syn::Attribute]) -> (Option<TokenStream>, syn::Type) {
|
||||||
if self.needs_newtype() {
|
if self.needs_newtype() {
|
||||||
let default = self.helper_default();
|
|
||||||
let derives = attrs
|
let derives = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|attr| attr.path().is_ident("derive"))
|
.find(|attr| attr.path().is_ident("derive"))
|
||||||
@ -52,7 +50,6 @@ impl ConfigField {
|
|||||||
derive.contains("Tree")
|
derive.contains("Tree")
|
||||||
|| derive.contains("Serialize")
|
|| derive.contains("Serialize")
|
||||||
|| derive.contains("Deserialize")
|
|| derive.contains("Deserialize")
|
||||||
|| (default.is_some() && derive.contains("Default"))
|
|
||||||
}) == Some(false)
|
}) == Some(false)
|
||||||
});
|
});
|
||||||
quote! {#[derive(#(#derives,)*)]}
|
quote! {#[derive(#(#derives,)*)]}
|
||||||
@ -62,6 +59,7 @@ impl ConfigField {
|
|||||||
let ty = &self.ty;
|
let ty = &self.ty;
|
||||||
|
|
||||||
let new = self.helper_new();
|
let new = self.helper_new();
|
||||||
|
let default = self.helper_default();
|
||||||
let serde = self.helper_serde();
|
let serde = self.helper_serde();
|
||||||
let tree = self.helper_tree();
|
let tree = self.helper_tree();
|
||||||
|
|
||||||
@ -154,9 +152,6 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn helper_default(&self) -> Option<TokenStream> {
|
pub(crate) fn helper_default(&self) -> Option<TokenStream> {
|
||||||
if self.typ.is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
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!(::core::default::Default::default());
|
||||||
@ -171,10 +166,7 @@ impl ConfigField {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn helper_serde(&self) -> Option<TokenStream> {
|
pub(crate) fn helper_serde(&self) -> TokenStream {
|
||||||
if self.no_serde.is_present() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let ident = self.helper_ident();
|
let ident = self.helper_ident();
|
||||||
let conversion = if self.has_custom_limits() {
|
let conversion = if self.has_custom_limits() {
|
||||||
quote! {
|
quote! {
|
||||||
@ -188,7 +180,7 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ty = &self.ty;
|
let ty = &self.ty;
|
||||||
Some(quote! {
|
quote! {
|
||||||
impl ::serde::Serialize for #ident {
|
impl ::serde::Serialize for #ident {
|
||||||
fn serialize<S>(&self, serializer: S) -> ::core::result::Result::<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> ::core::result::Result::<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@ -208,7 +200,7 @@ impl ConfigField {
|
|||||||
#conversion
|
#conversion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn helper_tree(&self) -> TokenStream {
|
pub(crate) fn helper_tree(&self) -> TokenStream {
|
||||||
@ -354,13 +346,6 @@ impl ConfigField {
|
|||||||
.typ
|
.typ
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(|| quote!(Self), |typ| quote!(#typ));
|
.map_or_else(|| quote!(Self), |typ| quote!(#typ));
|
||||||
let match_range = if num_keys > 1 {
|
|
||||||
Some(
|
|
||||||
quote!(1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
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>(
|
||||||
@ -385,7 +370,8 @@ impl ConfigField {
|
|||||||
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
#match_range
|
,
|
||||||
|
1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,13 +387,6 @@ impl ConfigField {
|
|||||||
} else {
|
} else {
|
||||||
quote!(::core::result::Result::Ok(&mut *self))
|
quote!(::core::result::Result::Ok(&mut *self))
|
||||||
};
|
};
|
||||||
let match_range = if num_keys > 1 {
|
|
||||||
Some(
|
|
||||||
quote!(1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::miniconf::TreeAny<1> for #ident {
|
impl ::miniconf::TreeAny<1> for #ident {
|
||||||
fn ref_any_by_key<K>(&self, mut keys: K) -> ::core::result::Result<&dyn ::core::any::Any, ::miniconf::Traversal>
|
fn ref_any_by_key<K>(&self, mut keys: K) -> ::core::result::Result<&dyn ::core::any::Any, ::miniconf::Traversal>
|
||||||
@ -423,7 +402,7 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
match index {
|
match index {
|
||||||
0 => ::core::result::Result::Ok(&*self),
|
0 => ::core::result::Result::Ok(&*self),
|
||||||
#match_range
|
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,7 +420,7 @@ impl ConfigField {
|
|||||||
}
|
}
|
||||||
match index {
|
match index {
|
||||||
0 => #ref_mut,
|
0 => #ref_mut,
|
||||||
#match_range
|
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -515,256 +494,3 @@ impl Config {
|
|||||||
&mut fields.fields
|
&mut fields.fields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Enum {
|
|
||||||
doc: Option<String>,
|
|
||||||
ident: syn::Ident,
|
|
||||||
variants: Vec<syn::Ident>,
|
|
||||||
default: Option<syn::Ident>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Enum {
|
|
||||||
pub fn generate_tree(&self) -> TokenStream {
|
|
||||||
let mut tokens = self.generate_tree_key();
|
|
||||||
tokens.extend(self.generate_tree_serialize());
|
|
||||||
tokens.extend(self.generate_tree_deserialize());
|
|
||||||
tokens.extend(self.generate_tree_any());
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keys(&self) -> Vec<(&'static str, syn::Expr)> {
|
|
||||||
let variants = &self.variants;
|
|
||||||
let mut keys = vec![
|
|
||||||
("value", parse_quote!(self)),
|
|
||||||
("variants", parse_quote!([#(Self::#variants,)*])),
|
|
||||||
];
|
|
||||||
if let Some(ref doc) = self.doc {
|
|
||||||
keys.push(("description", parse_quote!(#doc)));
|
|
||||||
}
|
|
||||||
if let Some(ref default) = self.default {
|
|
||||||
keys.push(("default", parse_quote!(Self::#default)));
|
|
||||||
}
|
|
||||||
keys
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_tree_key(&self) -> TokenStream {
|
|
||||||
let ident = &self.ident;
|
|
||||||
let keys = self.keys();
|
|
||||||
let num_keys = keys.len();
|
|
||||||
let max_length = keys.iter().map(|(path, _)| path.len()).max();
|
|
||||||
let keys = keys.iter().map(|(path, _)| path);
|
|
||||||
quote! {
|
|
||||||
impl ::miniconf::KeyLookup for #ident {
|
|
||||||
const LEN: usize = #num_keys;
|
|
||||||
const NAMES: &'static [&'static str] = &[#(#keys,)*];
|
|
||||||
|
|
||||||
fn name_to_index(value: &str) -> Option<usize> {
|
|
||||||
Self::NAMES.iter().position(|name| *name == value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::miniconf::TreeKey<1> for #ident {
|
|
||||||
fn metadata() -> ::miniconf::Metadata {
|
|
||||||
let mut metadata = ::miniconf::Metadata::default();
|
|
||||||
metadata.max_depth = 1;
|
|
||||||
metadata.count = #num_keys;
|
|
||||||
metadata.max_length = #max_length;
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> ::core::result::Result<usize, ::miniconf::Error<E>>
|
|
||||||
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::<Self>() else { return ::core::result::Result::Ok(0) };
|
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
||||||
let name = <Self as ::miniconf::KeyLookup>::NAMES
|
|
||||||
.get(index)
|
|
||||||
.ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
||||||
func(index, Some(name), #num_keys).map_err(|err| ::miniconf::Error::Inner(1, err))?;
|
|
||||||
::core::result::Result::Ok(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_tree_serialize(&self) -> TokenStream {
|
|
||||||
let ident = &self.ident;
|
|
||||||
let matches = self
|
|
||||||
.keys()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (_, expr))| {
|
|
||||||
quote! {
|
|
||||||
#i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
quote! {
|
|
||||||
impl ::miniconf::TreeSerialize<1> for #ident {
|
|
||||||
fn serialize_by_key<K, S>(
|
|
||||||
&self,
|
|
||||||
mut keys: K,
|
|
||||||
ser: S,
|
|
||||||
) -> ::core::result::Result<usize, ::miniconf::Error<S::Error>>
|
|
||||||
where
|
|
||||||
K: ::miniconf::Keys,
|
|
||||||
S: ::serde::Serializer,
|
|
||||||
{
|
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
||||||
return ::serde::Serialize::serialize(self, ser).map_err(|err| ::miniconf::Error::Inner(0, err)).map(|_| 0);
|
|
||||||
};
|
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(0))?;
|
|
||||||
if !keys.finalize() {
|
|
||||||
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(0).into());
|
|
||||||
}
|
|
||||||
match index {
|
|
||||||
#(#matches)*
|
|
||||||
_ => unreachable!(),
|
|
||||||
}?;
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_tree_deserialize(&self) -> TokenStream {
|
|
||||||
let ident = &self.ident;
|
|
||||||
let num_keys = self.keys().len();
|
|
||||||
quote! {
|
|
||||||
impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
|
|
||||||
fn deserialize_by_key<K, D>(
|
|
||||||
&mut self,
|
|
||||||
mut keys: K,
|
|
||||||
de: D,
|
|
||||||
) -> ::core::result::Result<usize, ::miniconf::Error<D::Error>>
|
|
||||||
where
|
|
||||||
K: ::miniconf::Keys,
|
|
||||||
D: ::serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
||||||
*self = <Self as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?;
|
|
||||||
return ::core::result::Result::Ok(0);
|
|
||||||
};
|
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
||||||
if !keys.finalize() {
|
|
||||||
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1).into());
|
|
||||||
}
|
|
||||||
match index {
|
|
||||||
0 => {
|
|
||||||
*self = <Self as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?;
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_tree_any(&self) -> TokenStream {
|
|
||||||
let ident = &self.ident;
|
|
||||||
let num_keys = self.keys().len();
|
|
||||||
quote! {
|
|
||||||
impl ::miniconf::TreeAny<1> for #ident {
|
|
||||||
fn ref_any_by_key<K>(&self, mut keys: K) -> ::core::result::Result<&dyn ::core::any::Any, ::miniconf::Traversal>
|
|
||||||
where
|
|
||||||
K: ::miniconf::Keys,
|
|
||||||
{
|
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
||||||
return ::core::result::Result::Ok(self);
|
|
||||||
};
|
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
||||||
if !keys.finalize() {
|
|
||||||
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
|
|
||||||
}
|
|
||||||
match index {
|
|
||||||
0 => ::core::result::Result::Ok(self),
|
|
||||||
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mut_any_by_key<K>(&mut self, mut keys: K) -> ::core::result::Result<&mut dyn ::core::any::Any, ::miniconf::Traversal>
|
|
||||||
where
|
|
||||||
K: ::miniconf::Keys,
|
|
||||||
{
|
|
||||||
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
||||||
return Ok(self);
|
|
||||||
};
|
|
||||||
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
||||||
if !keys.finalize() {
|
|
||||||
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
|
|
||||||
}
|
|
||||||
match index {
|
|
||||||
0 => Ok(self),
|
|
||||||
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Enum {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let attrs = input.call(syn::Attribute::parse_outer)?;
|
|
||||||
let doc = attrs
|
|
||||||
.iter()
|
|
||||||
.find(|attr| attr.path().is_ident("doc"))
|
|
||||||
.map(|attr| {
|
|
||||||
let meta = attr.meta.require_name_value()?;
|
|
||||||
if let syn::Expr::Lit(syn::ExprLit {
|
|
||||||
attrs: _,
|
|
||||||
lit: syn::Lit::Str(ref lit),
|
|
||||||
}) = meta.value
|
|
||||||
{
|
|
||||||
Ok(lit.value().trim().to_owned())
|
|
||||||
} else {
|
|
||||||
Err(syn::Error::new_spanned(
|
|
||||||
&meta.value,
|
|
||||||
"Expected string literal for doc comment",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let _vis = input.parse::<syn::Visibility>()?;
|
|
||||||
let _enum_token = input.parse::<Token![enum]>()?;
|
|
||||||
|
|
||||||
let ident = input.parse::<syn::Ident>()?;
|
|
||||||
|
|
||||||
let content;
|
|
||||||
let _brace = braced!(content in input);
|
|
||||||
let variants = content.parse_terminated(syn::Variant::parse, Token![,])?;
|
|
||||||
if let Some(variant) = variants.iter().find(|variant| !variant.fields.is_empty()) {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
&variant.fields,
|
|
||||||
"only unit variants are supported for now",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let default = variants
|
|
||||||
.iter()
|
|
||||||
.find(|variant| {
|
|
||||||
variant
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.any(|attr| attr.path().is_ident("default"))
|
|
||||||
})
|
|
||||||
.map(|variant| variant.ident.clone());
|
|
||||||
let variants = variants.into_iter().map(|variant| variant.ident).collect();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
doc,
|
|
||||||
ident,
|
|
||||||
variants,
|
|
||||||
default,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
use macroconf::ConfigEnum;
|
|
||||||
use miniconf::{Error::Traversal, JsonCoreSlash, JsonPath, Traversal::Access, TreeKey};
|
|
||||||
use rstest::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::str::from_utf8;
|
|
||||||
|
|
||||||
/// Description
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, ConfigEnum, Serialize, Deserialize)]
|
|
||||||
enum Test {
|
|
||||||
#[default]
|
|
||||||
Variant1,
|
|
||||||
Variant2,
|
|
||||||
Variant3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nodes() {
|
|
||||||
let nodes = Test::nodes::<JsonPath<String>>()
|
|
||||||
.map(|p| p.unwrap().0.into_inner())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
|
||||||
nodes,
|
|
||||||
vec![".value", ".variants", ".description", ".default"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[fixture]
|
|
||||||
#[once]
|
|
||||||
fn test() -> Test {
|
|
||||||
Test::Variant2
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("/", "\"Variant2\"")]
|
|
||||||
#[case("/value", "\"Variant2\"")]
|
|
||||||
#[case("/description", "\"Description\"")]
|
|
||||||
#[case("/default", "\"Variant1\"")]
|
|
||||||
#[case("/variants", "[\"Variant1\",\"Variant2\",\"Variant3\"]")]
|
|
||||||
fn serialize(test: &Test, #[case] path: &str, #[case] expected: &str) {
|
|
||||||
let mut buffer = [0u8; 64];
|
|
||||||
let len = test.get_json(path, &mut buffer).unwrap();
|
|
||||||
assert_eq!(from_utf8(&buffer[..len]), Ok(expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("/", Ok(10))]
|
|
||||||
#[case("/value", Ok(10))]
|
|
||||||
#[case("/variants", Err(Traversal(Access(0, "Cannot write limits"))))]
|
|
||||||
#[case("/default", Err(Traversal(Access(0, "Cannot write limits"))))]
|
|
||||||
#[case("/description", Err(Traversal(Access(0, "Cannot write limits"))))]
|
|
||||||
fn deserialize(
|
|
||||||
#[case] path: &str,
|
|
||||||
#[case] expected: Result<usize, miniconf::Error<serde_json_core::de::Error>>,
|
|
||||||
) {
|
|
||||||
let mut config = Test::Variant2;
|
|
||||||
|
|
||||||
let res = config.set_json(path, b"\"Variant3\"");
|
|
||||||
assert_eq!(res, expected);
|
|
||||||
if res.is_ok() {
|
|
||||||
assert_eq!(config, Test::Variant3);
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,17 +28,6 @@ struct Config {
|
|||||||
field: Cell<i32>,
|
field: Cell<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[config]
|
|
||||||
struct WithSetterAndDefault {
|
|
||||||
#[config(
|
|
||||||
default = "42",
|
|
||||||
typ = "i32",
|
|
||||||
get = "Cell::get",
|
|
||||||
set = "set_cell::<{i32::MIN}, {i32::MAX}>"
|
|
||||||
)]
|
|
||||||
field: Cell<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get() {
|
fn get() {
|
||||||
let mut buffer = [0u8; 32];
|
let mut buffer = [0u8; 32];
|
||||||
|
@ -31,14 +31,6 @@ struct Config {
|
|||||||
sub_config: SubConfig,
|
sub_config: SubConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config with default derive and default field
|
|
||||||
#[config]
|
|
||||||
#[derive(Default)]
|
|
||||||
struct _DefaultConfig {
|
|
||||||
#[config(default)]
|
|
||||||
field: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ["skipped"])]
|
#[case(0, ["skipped"])]
|
||||||
#[case(1, ["min"])]
|
#[case(1, ["min"])]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user