add custom getter and setter functions
This commit is contained in:
		@@ -1,5 +1,7 @@
 | 
			
		||||
#![allow(clippy::option_if_let_else)]
 | 
			
		||||
 | 
			
		||||
use std::convert::identity;
 | 
			
		||||
 | 
			
		||||
use convert_case::{Case, Casing};
 | 
			
		||||
use darling::{
 | 
			
		||||
    ast::{self, Data},
 | 
			
		||||
@@ -8,7 +10,7 @@ use darling::{
 | 
			
		||||
};
 | 
			
		||||
use proc_macro2::{Span, TokenStream};
 | 
			
		||||
use quote::{format_ident, quote};
 | 
			
		||||
use syn::parse_quote;
 | 
			
		||||
use syn::{parse_quote, spanned::Spanned};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, FromField)]
 | 
			
		||||
#[darling(attributes(config))]
 | 
			
		||||
@@ -21,6 +23,9 @@ pub struct ConfigField {
 | 
			
		||||
    min: Option<Override<syn::Expr>>,
 | 
			
		||||
    max: Option<Override<syn::Expr>>,
 | 
			
		||||
    default: Option<Override<syn::Expr>>,
 | 
			
		||||
    typ: Option<syn::Type>,
 | 
			
		||||
    get: Option<syn::Path>,
 | 
			
		||||
    set: Option<syn::Path>,
 | 
			
		||||
    #[darling(skip)]
 | 
			
		||||
    description: Option<syn::Expr>,
 | 
			
		||||
    #[darling(skip)]
 | 
			
		||||
@@ -88,7 +93,8 @@ impl ConfigField {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn has_custom_limits(&self) -> bool {
 | 
			
		||||
        !((self.min.is_none() || self.min == Some(Override::Inherit))
 | 
			
		||||
        self.typ.is_none()
 | 
			
		||||
            && !((self.min.is_none() || self.min == Some(Override::Inherit))
 | 
			
		||||
                && (self.max.is_none() || self.max == Some(Override::Inherit)))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -148,7 +154,7 @@ impl ConfigField {
 | 
			
		||||
    pub(crate) fn helper_default(&self) -> Option<TokenStream> {
 | 
			
		||||
        self.default.as_ref().map(|default| {
 | 
			
		||||
            let ident = self.helper_ident();
 | 
			
		||||
            let default_default = parse_quote!(default::Default());
 | 
			
		||||
            let default_default = parse_quote!(::core::default::Default::default());
 | 
			
		||||
            let default = default.as_ref().unwrap_or(&default_default);
 | 
			
		||||
            quote! {
 | 
			
		||||
                impl ::core::default::Default for #ident {
 | 
			
		||||
@@ -209,7 +215,7 @@ impl ConfigField {
 | 
			
		||||
        macro_rules! field_to_key {
 | 
			
		||||
            ($field: ident, $default: expr) => {
 | 
			
		||||
                self.$field.as_ref().map(|value| {
 | 
			
		||||
                    let ty = &self.ty;
 | 
			
		||||
                    let ty = self.typ.as_ref().unwrap_or(&self.ty);
 | 
			
		||||
                    (stringify!($field),
 | 
			
		||||
                    value.clone().explicit().map(|val| {
 | 
			
		||||
                        parse_quote!({
 | 
			
		||||
@@ -288,6 +294,7 @@ impl ConfigField {
 | 
			
		||||
            .helper_keys()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .enumerate()
 | 
			
		||||
            .skip(1)
 | 
			
		||||
            .map(|(i, (_, expr))| {
 | 
			
		||||
                quote! {
 | 
			
		||||
                    #i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
 | 
			
		||||
@@ -295,6 +302,10 @@ impl ConfigField {
 | 
			
		||||
            })
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
        let ident = self.helper_ident();
 | 
			
		||||
        let get = self
 | 
			
		||||
            .get
 | 
			
		||||
            .as_ref()
 | 
			
		||||
            .map_or_else(|| quote! {self}, |getter| quote! {#getter(&self.0)});
 | 
			
		||||
        quote! {
 | 
			
		||||
            impl ::miniconf::TreeSerialize<1> for #ident {
 | 
			
		||||
                fn serialize_by_key<K, S>(
 | 
			
		||||
@@ -307,13 +318,14 @@ impl ConfigField {
 | 
			
		||||
                    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(1, err)).map(|_| 1);
 | 
			
		||||
                        return ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)).map(|_| 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(0))?;
 | 
			
		||||
                    if !keys.finalize() {
 | 
			
		||||
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1).into());
 | 
			
		||||
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(0).into());
 | 
			
		||||
                    }
 | 
			
		||||
                    match index {
 | 
			
		||||
                        0 => ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
 | 
			
		||||
                        #(#matches)*
 | 
			
		||||
                        _ => unreachable!(),
 | 
			
		||||
                    }?;
 | 
			
		||||
@@ -326,6 +338,14 @@ impl ConfigField {
 | 
			
		||||
    pub(crate) fn helper_tree_deserialize(&self) -> TokenStream {
 | 
			
		||||
        let ident = self.helper_ident();
 | 
			
		||||
        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! {
 | 
			
		||||
            impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
 | 
			
		||||
                fn deserialize_by_key<K, D>(
 | 
			
		||||
@@ -338,7 +358,7 @@ impl ConfigField {
 | 
			
		||||
                    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))?;
 | 
			
		||||
                        #set(<#typ 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))?;
 | 
			
		||||
@@ -347,7 +367,7 @@ impl ConfigField {
 | 
			
		||||
                    }
 | 
			
		||||
                    match index {
 | 
			
		||||
                        0 => {
 | 
			
		||||
                            *self = <Self 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)
 | 
			
		||||
                        }
 | 
			
		||||
                        ,
 | 
			
		||||
@@ -436,6 +456,26 @@ impl Config {
 | 
			
		||||
                    parse_quote!(#description)
 | 
			
		||||
                });
 | 
			
		||||
            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)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								tests/get_set.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/get_set.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
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");
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user