diff --git a/CHANGELOG.md b/CHANGELOG.md index 82552218..f19ec9a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased](https://github.com/quartiq/miniconf/compare/v0.17.1...HEAD) - DATE + +### Added + +* `deny` proc macro field attribute for fine grained rejection of struct access + (and removed corresponding trait impls). + ## [0.17.1](https://github.com/quartiq/miniconf/compare/v0.17.0...v0.17.1) - 2024-11-12 ### Added diff --git a/miniconf/README.md b/miniconf/README.md index 28d840dc..f472fb55 100644 --- a/miniconf/README.md +++ b/miniconf/README.md @@ -170,7 +170,7 @@ Fields/variants that form internal nodes (non-leaf) need to implement the respec Leaf fields/items need to support the respective [`serde`] (and the desired `serde::Serializer`/`serde::Deserializer` backend) or [`core::any`] trait. -Structs, enums, arrays, and Options can then be cascaded to construct more complex trees. +Structs, enums, arrays, Options, and many other containers can then be cascaded to construct more complex trees. See also the [`TreeKey`] trait documentation for details. @@ -187,8 +187,7 @@ It implements [`Keys`]. ## Limitations -* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one field. Only unit, newtype and skipped variants are supported. Without the derive macros, these `enums` are still however usable in their atomic `serde` form as leaf nodes. Inline tuple variants are supported. -* The derive macros don't handle `std`/`alloc` smart pointers ( `Box`, `Rc`, `Arc`) in any special way. They however still be handled with accessors (`get`, `get_mut`, `validate`). +* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one (non-skip) field. Only unit, newtype and skipped variants are supported. Without the derive macros, any `enum` is still however usable as a `Leaf` node. Note also that netwype variants with a single inline tuple are supported. * The derive macros only support flattening in non-ambiguous situations (single field structs and single variant enums, both modulo skipped fields/variants and unit variants). ## Features diff --git a/miniconf/src/impls.rs b/miniconf/src/impls.rs index b266810c..f669b157 100644 --- a/miniconf/src/impls.rs +++ b/miniconf/src/impls.rs @@ -831,24 +831,6 @@ impl TreeAny for RefCell { } } -impl TreeAny for &RefCell { - #[inline] - fn ref_any_by_key(&self, _keys: K) -> Result<&dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of RefCell")) - } - - #[inline] - fn mut_any_by_key(&mut self, _keys: K) -> Result<&mut dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of RefCell")) - } -} - ///////////////////////////////////////////////////////////////////////////////////////// #[cfg(feature = "alloc")] @@ -1077,24 +1059,6 @@ mod _alloc { } } - impl TreeAny for rc::Weak { - #[inline] - fn ref_any_by_key(&self, _keys: K) -> Result<&dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Weak")) - } - - #[inline] - fn mut_any_by_key(&mut self, _keys: K) -> Result<&mut dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Weak")) - } - } - ///////////////////////////////////////////////////////////////////////////////////////// impl TreeKey for Arc { @@ -1200,24 +1164,6 @@ mod _alloc { .deserialize_by_key(keys, de) } } - - impl TreeAny for sync::Weak { - #[inline] - fn ref_any_by_key(&self, _keys: K) -> Result<&dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Weak")) - } - - #[inline] - fn mut_any_by_key(&mut self, _keys: K) -> Result<&mut dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Weak")) - } - } } ///////////////////////////////////////////////////////////////////////////////////////// @@ -1303,24 +1249,6 @@ mod _std { } } - impl TreeAny for &Mutex { - #[inline] - fn ref_any_by_key(&self, _keys: K) -> Result<&dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Mutex")) - } - - #[inline] - fn mut_any_by_key(&mut self, _keys: K) -> Result<&mut dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of Mutex")) - } - } - ///////////////////////////////////////////////////////////////////////////////////////// impl TreeKey for RwLock { @@ -1347,7 +1275,7 @@ mod _std { S: Serializer, { self.read() - .or(Err(Traversal::Access(0, "Locked")))? + .or(Err(Traversal::Access(0, "Poisoned")))? .serialize_by_key(keys, ser) } } @@ -1360,7 +1288,7 @@ mod _std { D: Deserializer<'de>, { self.write() - .or(Err(Traversal::Access(0, "Locked")))? + .or(Err(Traversal::Access(0, "Poisoned")))? .deserialize_by_key(keys, de) } } @@ -1397,22 +1325,4 @@ mod _std { .mut_any_by_key(keys) } } - - impl TreeAny for &RwLock { - #[inline] - fn ref_any_by_key(&self, _keys: K) -> Result<&dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of RwLock")) - } - - #[inline] - fn mut_any_by_key(&mut self, _keys: K) -> Result<&mut dyn Any, Traversal> - where - K: Keys, - { - Err(Traversal::Access(0, "Can't leak out of RwLock")) - } - } } diff --git a/miniconf/src/leaf.rs b/miniconf/src/leaf.rs index a58fd3b3..55057d5c 100644 --- a/miniconf/src/leaf.rs +++ b/miniconf/src/leaf.rs @@ -237,7 +237,7 @@ impl TreeAny for StrLeaf { K: Keys, { keys.finalize()?; - Err(Traversal::Access(1, "No Any access for StrLeaf")) + Err(Traversal::Access(0, "No Any access for StrLeaf")) } #[inline] @@ -246,6 +246,105 @@ impl TreeAny for StrLeaf { K: Keys, { keys.finalize()?; - Err(Traversal::Access(1, "No Any access for StrLeaf")) + Err(Traversal::Access(0, "No Any access for StrLeaf")) + } +} + +/// Deny any value access +#[derive( + Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, +)] +#[serde(transparent)] +#[repr(transparent)] +pub struct Deny(pub T); + +impl Deref for Deny { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Deny { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deny { + /// Extract just the inner + #[inline] + pub fn into_inner(self) -> T { + self.0 + } +} + +impl From for Deny { + #[inline] + fn from(value: T) -> Self { + Self(value) + } +} + +impl TreeKey for Deny { + #[inline] + fn traverse_all() -> Result { + Ok(W::leaf()) + } + + #[inline] + fn traverse_by_key(mut keys: K, _func: F) -> Result> + where + K: Keys, + F: FnMut(usize, Option<&'static str>, NonZero) -> Result<(), E>, + { + keys.finalize()?; + Ok(0) + } +} + +impl TreeSerialize for Deny { + #[inline] + fn serialize_by_key(&self, mut keys: K, _ser: S) -> Result> + where + K: Keys, + S: Serializer, + { + keys.finalize()?; + Err(Traversal::Access(0, "Denied").into()) + } +} + +impl<'de, T: ?Sized> TreeDeserialize<'de> for Deny { + #[inline] + fn deserialize_by_key(&mut self, mut keys: K, _de: D) -> Result> + where + K: Keys, + D: Deserializer<'de>, + { + keys.finalize()?; + Err(Traversal::Access(0, "Denied").into()) + } +} + +impl TreeAny for Deny { + #[inline] + fn ref_any_by_key(&self, mut keys: K) -> Result<&dyn Any, Traversal> + where + K: Keys, + { + keys.finalize()?; + Err(Traversal::Access(0, "Denied")) + } + + #[inline] + fn mut_any_by_key(&mut self, mut keys: K) -> Result<&mut dyn Any, Traversal> + where + K: Keys, + { + keys.finalize()?; + Err(Traversal::Access(0, "Denied")) } } diff --git a/miniconf/src/tree.rs b/miniconf/src/tree.rs index 9aa50398..65464749 100644 --- a/miniconf/src/tree.rs +++ b/miniconf/src/tree.rs @@ -225,7 +225,7 @@ pub trait TreeKey { /// bar: [Leaf; 5], /// }; /// - /// let idx = [1usize, 1]; + /// let idx = [1, 1]; /// /// let (path, node) = S::transcode::, _>(idx).unwrap(); /// assert_eq!(path.as_str(), "/bar/1"); @@ -377,14 +377,12 @@ pub trait TreeAny { /// Serialize a leaf node by its keys. /// -/// See also [`crate::json`] or `crate::postcard` for convenient functions using these traits. +/// See also [`crate::json`] or `crate::postcard` for convenient wrappers using this trait. /// /// # Derive macro /// -/// [`macro@crate::TreeSerialize`] derives `TreeSerialize` for structs with named fields and tuple structs -/// and for enums with newtype and unit variants. -/// -/// The field attributes are described in the [`TreeKey`] trait. +/// See [`macro@crate::TreeSerialize`]. +/// The derive macro attributes are described in the [`TreeKey`] trait. pub trait TreeSerialize { /// Serialize a node by keys. /// @@ -423,14 +421,12 @@ pub trait TreeSerialize { /// Deserialize a leaf node by its keys. /// -/// See also [`crate::json`] for a convenient helper functions using this trait. +/// See also [`crate::json`] or `crate::postcard` for convenient wrappers using this trait. /// /// # Derive macro /// -/// [`macro@crate::TreeDeserialize`] derives `TreeSerialize` for structs with named fields and tuple structs -/// and for enums with newtype and unit variants. -/// -/// The field attributes are described in the [`TreeKey`] trait. +/// See [`macro@crate::TreeDeserialize`]. +/// The derive macro attributes are described in the [`TreeKey`] trait. pub trait TreeDeserialize<'de> { /// Deserialize a leaf node by its keys. /// diff --git a/miniconf/tests/structs.rs b/miniconf/tests/structs.rs index 699a2775..40eae70c 100644 --- a/miniconf/tests/structs.rs +++ b/miniconf/tests/structs.rs @@ -1,5 +1,6 @@ use miniconf::{ - json, Deserialize, Leaf, Metadata, Serialize, Tree, TreeDeserialize, TreeKey, TreeSerialize, + json, Deserialize, IntoKeys, Leaf, Metadata, Serialize, Traversal, Tree, TreeAny, + TreeDeserialize, TreeKey, TreeSerialize, }; mod common; @@ -74,3 +75,26 @@ fn tuple_struct() { assert_eq!(paths::(), ["/0", "/1"]); } + +#[test] +fn deny_access() { + use core::cell::RefCell; + #[derive(Tree)] + struct S<'a> { + #[tree(deny(deserialize = "no de", mut_any = "no any"))] + field: Leaf, + #[tree(deny(ref_any = "no any", mut_any = "no any"))] + cell: &'a RefCell>, + } + let cell = RefCell::new(2.into()); + let mut s = S { + field: 1.into(), + cell: &cell, + }; + common::set_get(&mut s, "/cell", b"3"); + s.ref_any_by_key([0].into_keys()).unwrap(); + assert!(matches!( + s.mut_any_by_key([0].into_keys()), + Err(Traversal::Access(1, "no any")) + )); +} diff --git a/miniconf_derive/src/field.rs b/miniconf_derive/src/field.rs index 744011f1..df6b6835 100644 --- a/miniconf_derive/src/field.rs +++ b/miniconf_derive/src/field.rs @@ -1,20 +1,43 @@ -use darling::{uses_lifetimes, uses_type_params, util::Flag, FromField}; +use darling::{ + usage::{IdentSet, Purpose, UsesTypeParams}, + uses_lifetimes, uses_type_params, + util::Flag, + FromField, FromMeta, +}; use proc_macro2::{Span, TokenStream}; use quote::quote_spanned; -use syn::spanned::Spanned; +use syn::{parse_quote, spanned::Spanned}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TreeTrait { + Key, + Serialize, + Deserialize, + Any, +} + +#[derive(Debug, FromMeta, PartialEq, Clone, Default)] +struct Deny { + serialize: Option, + deserialize: Option, + ref_any: Option, + mut_any: Option, +} #[derive(Debug, FromField, Clone)] #[darling(attributes(tree))] pub struct TreeField { pub ident: Option, - pub ty: syn::Type, + ty: syn::Type, pub skip: Flag, - pub typ: Option, - pub validate: Option, - pub get: Option, - pub get_mut: Option, - pub rename: Option, - pub defer: Option, + typ: Option, + validate: Option, + get: Option, + get_mut: Option, + rename: Option, + defer: Option, + #[darling(default)] + deny: Deny, } uses_type_params!(TreeField, ty, typ); @@ -32,6 +55,35 @@ impl TreeField { self.typ.as_ref().unwrap_or(&self.ty) } + pub fn bound(&self, traite: TreeTrait, type_set: &IdentSet) -> Option { + if self + .uses_type_params(&Purpose::BoundImpl.into(), type_set) + .is_empty() + { + None + } else { + let bound: Option = match traite { + TreeTrait::Key => Some(parse_quote!(::miniconf::TreeKey)), + TreeTrait::Serialize => self + .deny + .serialize + .is_none() + .then_some(parse_quote!(::miniconf::TreeSerialize)), + TreeTrait::Deserialize => self + .deny + .deserialize + .is_none() + .then_some(parse_quote!(::miniconf::TreeDeserialize<'de>)), + TreeTrait::Any => (self.deny.ref_any.is_none() || self.deny.mut_any.is_none()) + .then_some(parse_quote!(::miniconf::TreeAny)), + }; + bound.map(|bound| { + let ty = self.typ(); + quote_spanned!(self.span()=> #ty: #bound,) + }) + } + } + pub fn name(&self) -> Option<&syn::Ident> { self.rename.as_ref().or(self.ident.as_ref()) } @@ -99,43 +151,67 @@ impl TreeField { pub fn serialize_by_key(&self, i: Option) -> TokenStream { // Quote context is a match of the field index with `serialize_by_key()` args available. - let getter = self.getter(i); - quote_spanned! { self.span()=> - #getter - .and_then(|value| - ::miniconf::TreeSerialize::serialize_by_key(value, keys, ser) - ) + if let Some(s) = &self.deny.serialize { + quote_spanned! { self.span()=> ::core::result::Result::Err( + ::miniconf::Traversal::Access(0, #s).into()) + } + } else { + let getter = self.getter(i); + quote_spanned! { self.span()=> + #getter + .and_then(|value| + ::miniconf::TreeSerialize::serialize_by_key(value, keys, ser) + ) + } } } pub fn deserialize_by_key(&self, i: Option) -> TokenStream { // Quote context is a match of the field index with `deserialize_by_key()` args available. - let getter_mut = self.getter_mut(i); - let validator = self.validator(); - quote_spanned! { self.span()=> - #getter_mut - .and_then(|item| - ::miniconf::TreeDeserialize::<'de>::deserialize_by_key(item, keys, de) - ) - #validator + if let Some(s) = &self.deny.deserialize { + quote_spanned! { self.span()=> ::core::result::Result::Err( + ::miniconf::Traversal::Access(0, #s).into()) + } + } else { + let getter_mut = self.getter_mut(i); + let validator = self.validator(); + quote_spanned! { self.span()=> + #getter_mut + .and_then(|item| + ::miniconf::TreeDeserialize::<'de>::deserialize_by_key(item, keys, de) + ) + #validator + } } } pub fn ref_any_by_key(&self, i: Option) -> TokenStream { // Quote context is a match of the field index with `get_mut_by_key()` args available. - let getter = self.getter(i); - quote_spanned! { self.span()=> - #getter - .and_then(|item| ::miniconf::TreeAny::ref_any_by_key(item, keys)) + if let Some(s) = &self.deny.ref_any { + quote_spanned! { self.span()=> ::core::result::Result::Err( + ::miniconf::Traversal::Access(0, #s).into()) + } + } else { + let getter = self.getter(i); + quote_spanned! { self.span()=> + #getter + .and_then(|item| ::miniconf::TreeAny::ref_any_by_key(item, keys)) + } } } pub fn mut_any_by_key(&self, i: Option) -> TokenStream { // Quote context is a match of the field index with `get_mut_by_key()` args available. - let getter_mut = self.getter_mut(i); - quote_spanned! { self.span()=> - #getter_mut - .and_then(|item| ::miniconf::TreeAny::mut_any_by_key(item, keys)) + if let Some(s) = &self.deny.mut_any { + quote_spanned! { self.span()=> ::core::result::Result::Err( + ::miniconf::Traversal::Access(0, #s).into()) + } + } else { + let getter_mut = self.getter_mut(i); + quote_spanned! { self.span()=> + #getter_mut + .and_then(|item| ::miniconf::TreeAny::mut_any_by_key(item, keys)) + } } } } diff --git a/miniconf_derive/src/tree.rs b/miniconf_derive/src/tree.rs index 38c6182e..096c6424 100644 --- a/miniconf_derive/src/tree.rs +++ b/miniconf_derive/src/tree.rs @@ -1,6 +1,6 @@ use darling::{ ast::{self, Data}, - usage::{GenericsExt, LifetimeRefSet, Purpose, UsesLifetimes, UsesTypeParams}, + usage::{GenericsExt, LifetimeRefSet, Purpose, UsesLifetimes}, util::Flag, Error, FromDeriveInput, FromVariant, }; @@ -8,7 +8,7 @@ use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{parse_quote, WhereClause}; -use crate::field::TreeField; +use crate::field::{TreeField, TreeTrait}; #[derive(Debug, FromVariant, Clone)] #[darling(attributes(tree), supports(newtype, tuple, unit), and_then=Self::parse)] @@ -154,21 +154,14 @@ impl Tree { fn bound_generics( &self, - bound: syn::TraitBound, + traite: TreeTrait, where_clause: Option<&WhereClause>, ) -> Option { let type_set = self.generics.declared_type_params(); let bounds: TokenStream = self .fields() .iter() - .filter_map(|f| { - let ty = f.typ(); - (!f.uses_type_params(&Purpose::BoundImpl.into(), &type_set) - .is_empty()) - .then_some({ - quote! { #ty: #bound, } - }) - }) + .filter_map(|f| f.bound(traite, &type_set)) .collect(); if bounds.is_empty() { where_clause.cloned() @@ -192,8 +185,7 @@ impl Tree { pub fn tree_key(&self) -> TokenStream { let ident = &self.ident; let (impl_generics, ty_generics, orig_where_clause) = self.generics.split_for_impl(); - let where_clause = - self.bound_generics(parse_quote!(::miniconf::TreeKey), orig_where_clause); + let where_clause = self.bound_generics(TreeTrait::Key, orig_where_clause); let fields = self.fields(); let fields_len = fields.len(); let traverse_all_arms = fields.iter().enumerate().map(|(i, f)| { @@ -287,8 +279,7 @@ impl Tree { pub fn tree_serialize(&self) -> TokenStream { let ident = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let where_clause = - self.bound_generics(parse_quote!(::miniconf::TreeSerialize), where_clause); + let where_clause = self.bound_generics(TreeTrait::Serialize, where_clause); let index = self.index(); let (mat, arms, default) = self.arms(|f, i| f.serialize_by_key(i)); let increment = @@ -329,8 +320,7 @@ impl Tree { let mut generics = self.generics.clone(); generics.params.push(syn::GenericParam::Lifetime(de)); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let where_clause = - self.bound_generics(parse_quote!(::miniconf::TreeDeserialize<'de>), where_clause); + let where_clause = self.bound_generics(TreeTrait::Deserialize, where_clause); let index = self.index(); let ident = &self.ident; let (mat, arms, default) = self.arms(|f, i| f.deserialize_by_key(i)); @@ -359,7 +349,7 @@ impl Tree { pub fn tree_any(&self) -> TokenStream { let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let where_clause = self.bound_generics(parse_quote!(::miniconf::TreeAny), where_clause); + let where_clause = self.bound_generics(TreeTrait::Any, where_clause); let index = self.index(); let ident = &self.ident; let (mat, ref_arms, default) = self.arms(|f, i| f.ref_any_by_key(i));