From 694ce4d66f2ba5e2a0435ea636bde7c5c5ba2f27 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sat, 28 May 2022 10:55:14 -0400 Subject: [PATCH] Overhaul --- src/lib.rs | 126 +++++++-- src/macros.rs | 702 +++++++++++++++++++++++++++----------------------- 2 files changed, 483 insertions(+), 345 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 978ab6b..d7ad0a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,104 @@ -/// A dynamic query structure. +//! A crate providing a (mostly) straighforward syntax for defining dynamic structures for +//! efficient querying. +//! +//! # Features +//! +//! - Filter a struct or enum according to a query definition. +//! - Inspect definitions manually to avoid unnecessary computations before filtering. +//! +//! # Attributes +//! +//! Attributes are fully supported on structs, struct fields, enums, enum variants, and enum +//! variant fields. Additionally, attributes can be applied to some aspects of definitions, though +//! this may be supported for all aspects in the future. +//! +//! # Structs +//! +//! Only struct structs (yes, that is what they're [called](https://doc.rust-lang.org/reference/items/structs.html)), +//! are supported. Struct structs with no fields are also not supported. +//! +//! This fails: +//! ```compile_fail +//! # #[macro_use] extern crate ql; +//! # +//! query_def! { +//! struct Foo { +//! } +//! } +//! # +//! # fn main() { +//! # } +//! ``` +//! +//! This works: +//! ``` +//! # #[macro_use] extern crate ql; +//! # +//! query_def! { +//! struct Foo { +//! a: (bool), +//! b: (usize), +//! c: (String), +//! d: (Vec), +//! } +//! } +//! # +//! # fn main() { +//! # } +//! ``` +//! +//! # Enums +//! +//! Fieldless and struct enums are supported, but struct enums with no fields are not. +//! +//! This fails: +//! ```compile_fail +//! # #[macro_use] extern crate ql; +//! # +//! query_def! { +//! enum Foo { +//! A { } +//! } +//! } +//! # +//! # fn main() { +//! # } +//! ``` +//! +//! This works: +//! ``` +//! # #[macro_use] extern crate ql; +//! # +//! query_def! { +//! enum Foo { +//! A +//! } +//! } +//! # +//! # fn main() { +//! # } +//! ``` +//! +//! Incorrect syntax in the macro invocation can lead to very confusing errors. +//! +//! For example, the incorrect enum syntax shown earlier will fail with this message: +//! +//! ```text +//! error: no rules expected the token `struct` +//! ``` + + +/// A dynamic struct whose fields can be recursively filtered according to a +/// [definition](`Self::Definition`). pub trait DynamicQuery { + /// A type defining the fields to include in the filtered structure. type Definition; + /// Filters the fields of this structure in place. fn filter_ref(&mut self, def: &Self::Definition); + /// Filters the fields of this structure in place. This method takes ownership of the + /// structure, making it more suitable for pipelines. #[inline(always)] fn filter(mut self, def: &Self::Definition) -> Self where @@ -18,20 +113,20 @@ pub trait DynamicQuery { #[doc(hidden)] pub use paste; +#[cfg(feature = "serde")] +#[doc(hidden)] +pub use serde; + mod macros; #[cfg(test)] mod tests { - use serde::{Deserialize, Serialize}; - use super::DynamicQuery; // just tests that the macro compiles - crate::query_def_struct! { - /// Foo doc. + crate::query_def! { #[derive(PartialEq)] - Foo { - /// Field doc. + struct Foo { #[serde(flatten)] bar: (Bar), baz: (bool), @@ -39,15 +134,12 @@ mod tests { bars_by_bazs: 'map (bool, Bar), } - /// Bar doc. #[derive(PartialEq)] - Bar { + struct Bar { baz_inner: (bool), } - } - crate::query_def_enum! { - FooOrBar { + enum FooOrBar { Foo { value: 'dyn (Foo), }, @@ -57,22 +149,20 @@ mod tests { } } - OptionalFooOrBarDyn { + enum OptionalFooOrBarDyn { Some { value: 'dyn (FooOrBar), }, - None { - } + None, } - OptionalFooOrBar { + enum OptionalFooOrBar { Some { value: (FooOrBar), }, - None { - } + None } } diff --git a/src/macros.rs b/src/macros.rs index 54261e8..375f994 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,16 +1,16 @@ #[doc(hidden)] #[macro_export] macro_rules! query_def_type { - (@ ($type:ty)) => { + (($type:ty)) => { $type }; - (@ 'dyn ($type:ty)) => { + ('dyn ($type:ty)) => { $type }; - (@ 'vec ($type:ty)) => { + ('vec ($type:ty)) => { ::std::vec::Vec<$type> }; - (@ 'map ($key:ty, $value:ty)) => { + ('map ($key:ty, $value:ty)) => { ::std::collections::HashMap<$key, $value> }; } @@ -21,16 +21,16 @@ macro_rules! query_def_field { (@dyn $type:ty) => { Option<<$type as $crate::DynamicQuery>::Definition> }; - (@ ($type:ty)) => { + (($type:ty)) => { bool }; - (@ 'dyn ($type:ty)) => { + ('dyn ($type:ty)) => { $crate::query_def_field!(@dyn $type) }; - (@ 'vec ($type:ty)) => { + ('vec ($type:ty)) => { $crate::query_def_field!(@dyn $type) }; - (@ 'map ($key:ty, $value:ty)) => { + ('map ($key:ty, $value:ty)) => { $crate::query_def_field!(@dyn $value) }; } @@ -39,8 +39,9 @@ macro_rules! query_def_field { #[macro_export] macro_rules! query_def_filter { // note about this: 'dyn is implied for 'vec and 'map because if the type is not 'dyn, then the - // special handling is not necessary in the first place. - (@ $query:expr, $value:ident, 'vec ($_type:path)) => { + // special handling is not necessary in the first place (i.e. you can just do `(Vec)` + // instead of `'vec (Type)`. + ($query:expr, $value:ident, 'vec ($_type:path)) => { match $query { Some(query) => { if let Some(ref mut value) = $value { @@ -54,7 +55,7 @@ macro_rules! query_def_filter { } } }; - (@ $query:expr, $value:ident, 'map ($_key:path, $_value:path)) => { + ($query:expr, $value:ident, 'map ($_key:path, $_value:path)) => { match $query { Some(query) => { if let Some(ref mut value) = $value { @@ -68,14 +69,14 @@ macro_rules! query_def_filter { } } }; - (@ $query:expr, $value:ident, 'dyn ($($_type:tt)+)) => { + ($query:expr, $value:ident, 'dyn ($($_type:tt)+)) => { if let Some(ref mut value) = $value { if let Some(ref query) = $query { value.filter_ref(&query); } } }; - (@ $query:expr, $value:ident, ($($_type:tt)+)) => { + ($query:expr, $value:ident, ($($_type:tt)+)) => { if !*$query { *$value = None; } @@ -85,368 +86,415 @@ macro_rules! query_def_filter { #[doc(hidden)] #[macro_export] macro_rules! query_def_internal_struct { - (@ - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )+ - } - ) - * + ( + $( #[$meta:meta] )* + $vis:vis $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$field_meta:meta] )* + $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ + + $(,)? + } ) => { - $( - #[derive(Debug, Clone)] - $(#[$meta]) - * - $vis struct $data { + #[derive(Debug, Clone)] + $( #[$meta] )* + $vis struct $data { + $( + $( #[$field_meta] )* + pub $field: Option<$crate::query_def_type!($( $qualifier )? ($( $type )+))> + ),+ + } + + $crate::paste::paste! { + #[derive(Debug, Clone, Copy, Default)] + $( $( #[$def_meta] )+ )? + $vis struct [<$data Def>] { + $( pub $field: $crate::query_def_field!($( $qualifier )? ($( $type )+)) ),+ + } + } + + impl $crate::DynamicQuery for $data { + type Definition = $crate::paste::paste! { [<$data Def>] }; + + fn filter_ref(&mut self, def: &Self::Definition) { $( - $(#[$field_meta]) - * - pub $field: Option<$crate::query_def_type!(@ $($qualifier)? ($($type)+))>, + let query = &def.$field; + let value = &mut self.$field; + $crate::query_def_filter!(query, value, $( $qualifier )? ($( $type )+)); )+ } + } + }; +} - $crate::paste::paste! { - #[derive(Debug, Clone, Copy, Default)] - $( $( #[$def_meta] )+ )? - $vis struct [<$data Def>] { - $( - pub $field: $crate::query_def_field!(@ $($qualifier)? ($($type)+)), - )+ - } - } - - impl $crate::DynamicQuery for $data { - type Definition = $crate::paste::paste! { [<$data Def>] }; - - fn filter_ref(&mut self, def: &Self::Definition) { - $( - let query = &def.$field; - let value = &mut self.$field; - $crate::query_def_filter!(@ query, value, $($qualifier)? ($($type)+)); - ) - + - } - } - ) - * +#[doc(hidden)] +#[macro_export] +macro_rules! query_def_internal_enum_variant_def { + ( + $( #[$variant_def_meta:meta] )* + $vis:vis $name:ident + ) => { + #[derive(Debug, Clone, Copy, Default)] + $( #[$variant_def_meta] )* + $vis struct $name; + }; + ( + $( #[$variant_def_meta:meta] )* + $vis:vis $name:ident { + $( $field:ident$(( + $( #[$variant_def_field_meta:meta] )+ + ))?: $($qualifier:lifetime)? ($($type:tt)+) ),+ + } + ) => { + #[derive(Debug, Clone, Copy, Default)] + $( #[$variant_def_meta] )* + $vis struct $name { + $( + $( $( #[$variant_def_field_meta] )+ )? + pub $field: $crate::query_def_field!($( $qualifier )? ($( $type )+)) + ),+ + } }; } #[doc(hidden)] #[macro_export] macro_rules! query_def_internal_enum { - (@ - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + - ))? { - $( - $( #[$variant_meta:meta] )* - $variant:ident$(( - $(#[$variant_def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )* - } - ),+ - } - ) - * - ) => { - $( - #[derive(Debug, Clone)] - $(#[$meta]) - * - $vis enum $data { - $( - $( #[$variant_meta] )* - $variant { - $( - $(#[$field_meta]) - * - $field: Option<$crate::query_def_type!(@ $($qualifier)? ($($type)+))>, - )* - } - ),+ - } - - $crate::paste::paste! { - #[derive(Debug, Clone, Copy, Default)] - $( $( #[$def_meta] )+ )? - $vis struct [<$data Def>] { + ( + $( #[$meta:meta] )* + $vis:vis $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$variant_meta:meta] )* + $variant:ident$(( + $( #[$variant_def_meta:meta] )+ + ))? $({ $( - pub [<$variant:snake:lower>]: [<$data Def Variant $variant>], + $( #[$field_meta:meta] )* + $field:ident$(( + $( #[$variant_def_field_meta:meta] )+ + ))?: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ + $(,)? + })? + ),+ + + $(,)? + } + ) => { + #[derive(Debug, Clone)] + $( #[$meta] )* + $vis enum $data { + $( + $( #[$variant_meta] )* + $variant $({ + $( + $( #[$field_meta] )* + $field: Option<$crate::query_def_type!($( $qualifier )? ($( $type )+))> + ),* + })? + ),+ + } + + $crate::paste::paste! { + #[derive(Debug, Clone, Copy, Default)] + $( $( #[$def_meta] )+ )? + $vis struct [<$data Def>] { + $( pub [<$variant:snake:lower>]: [<$data Def Variant $variant>] ),+ + } + } + +// $crate::paste::paste! { +// impl [<$data Def>] { +// $( +// pub type $variant = [<$data Def Variant $variant>]; +// )+ +// } +// } + + $crate::paste::paste! { + $( + $crate::query_def_internal_enum_variant_def! { + $( $( #[$variant_def_meta] )+ )? + $vis [<$data Def Variant $variant>] $({ + $( $field$(( + $( #[$variant_def_field_meta] )+ + ))?: $( $qualifier )? ($( $type )+) ),+ + })? + } + )+ + } + + impl $crate::DynamicQuery for $data { + type Definition = $crate::paste::paste! { [<$data Def>] }; + + fn filter_ref(&mut self, def: &Self::Definition) { + match self { + $( + Self::$variant $({ $( ref mut $field ),* })? => { + $($( + let query = &$crate::paste::paste! { &def.[<$variant:snake:lower>] }.$field; + $crate::query_def_filter!(query, $field, $( $qualifier )? ($( $type )+)); + )*)? + } )+ } } - - $crate::paste::paste! { -// impl [<$data Def>] { -// $( -// pub type $variant = [<$data Def Variant $variant>]; -// )+ -// } - - $( - #[derive(Debug, Clone, Copy, Default)] - $( $( #[$variant_def_meta] )+ )? - $vis struct [<$data Def Variant $variant>] { - $( - pub $field: $crate::query_def_field!(@ $($qualifier)? ($($type)+)), - )* - } - )+ - } - - impl $crate::DynamicQuery for $data { - type Definition = $crate::paste::paste! { [<$data Def>] }; - - fn filter_ref(&mut self, def: &Self::Definition) { - match self { - $( - Self::$variant { $( ref mut $field ),* } => { - $( - let query = &$crate::paste::paste! { &def.[<$variant:snake:lower>] }.$field; - $crate::query_def_filter!(@ query, $field, $($qualifier)? ($($type)+)); - )* - } - )+ - } - } - } - ) - * + } }; } -/// Defines a dynamic query structure. #[cfg(not(feature = "serde"))] +#[doc(hidden)] #[macro_export] -macro_rules! query_def_struct { +macro_rules! query_def_0 { ( - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )+ - } - ) - * - ) => { - $crate::query_def_internal_struct!(@ - $( - $(#[$meta]) - * - $vis $data$(( - $(#[$def_meta]) - + - ))? { - $( - $(#[$field_meta]) - * - $field: $($qualifier)? ($($type)+), - )+ - } - ) - * - ); - }; -} + $( #[$meta:meta] )* + $vis:vis struct $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$field_meta:meta] )* + $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ -/// Defines a dynamic enumerated query structure. -#[cfg(not(feature = "serde"))] -#[macro_export] -macro_rules! query_def_enum { - ( - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + - ))? { - $( - $(#[$variant_meta:meta]) - * - $variant:ident$(( - $(#[$variant_def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )* - } - ),+ - } - ) - * + $(,)? + } ) => { - $crate::query_def_internal_enum!(@ - $( - $(#[$meta]) - * + $crate::query_def_internal_struct! { + $( #[$meta] )* $vis $data$(( - $(#[$def_meta]) - + + $( #[$def_meta] )+ ))? { $( - $(#[$variant_meta]) - * + $( #[$field_meta] )* + $field: $( $qualifier )? ($( $type )+), + )+ + } + } + }; + ( + $( #[$meta:meta] )* + $vis:vis enum $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$variant_meta:meta] )* + $variant:ident$(( + $( #[$variant_def_meta:meta] )+ + ))? $({ + $( + $( #[$field_meta:meta] )* + $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ + + $(,)? + })? + ),+ + + $(,)? + } + ) => { + $crate::query_def_internal_enum! { + $( #[$meta] )* + $vis $data$(( + $( #[$def_meta] )+ + ))? { + $( + $( #[$variant_meta] )* $variant$(( - $(#[$variant_def_meta]) - ? - ))? { + $( #[$variant_def_meta] )? + ))? $({ $( - $(#[$field_meta]) - * - $field: $($qualifier)? ($($type)+), - )* - } + $( #[$field_meta] )* + $field: $( $qualifier )? ($( $type )+) + ),+ + })? ),+ } - ) - * - ); + } }; } -/// Defines a dynamic query structure. #[cfg(feature = "serde")] +#[doc(hidden)] #[macro_export] -macro_rules! query_def_struct { +macro_rules! query_def_0 { ( - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )+ - } - ) - * + $( #[$meta:meta] )* + $vis:vis struct $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$field_meta:meta] )* + $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ + + $(,)? + } ) => { - $crate::query_def_internal_struct!(@ - $( - #[derive(Serialize, Deserialize)] - $(#[$meta]) - * + $crate::query_def_internal_struct! { + #[derive($crate::serde::Serialize, $crate::serde::Deserialize)] + $( #[$meta] )* $vis $data( - #[derive(Serialize, Deserialize)] + #[derive($crate::serde::Serialize, $crate::serde::Deserialize)] #[serde(default)] - $( - $(#[$def_meta]) - + - )? + $( $( #[$def_meta] )+ )? ) { $( #[serde(skip_serializing_if = "Option::is_none")] - $(#[$field_meta]) - * - $field: $($qualifier)? ($($type)+), - )+ + $( #[$field_meta] )* + $field: $( $qualifier )? ($( $type )+) + ),+ } - ) - * - ); + } + }; + ( + $( #[$meta:meta] )* + $vis:vis enum $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $( + $( #[$variant_meta:meta] )* + $variant:ident$(( + $( #[$variant_def_meta:meta] )+ + ))? $({ + $( + $( #[$field_meta:meta] )* + $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+) + ),+ + + $(,)? + })? + ),+ + + $(,)? + } + ) => { + $crate::query_def_internal_enum! { + #[derive($crate::serde::Serialize, $crate::serde::Deserialize)] + $( #[$meta] )* + $vis $data( + #[derive($crate::serde::Serialize, $crate::serde::Deserialize)] + #[serde(default)] + $( $( #[$def_meta] )+ )? + ) { + $( + $( #[$variant_meta] )* + $variant( + #[derive($crate::serde::Serialize, $crate::serde::Deserialize)] + $( $( #[$variant_def_meta] )+ )? + ) $({ + $( + #[serde(skip_serializing_if = "Option::is_none")] + $( #[$field_meta] )* + $field( + #[serde(default)] + ): $( $qualifier )? ($( $type )+), + )+ + })? + ),+ + } + } }; } -/// Defines a dynamic enumerated query structure. -#[cfg(feature = "serde")] +/// Defines a dynamic query structure. +/// +/// # Structs +/// +/// Only struct structs (yes, that is what they're [called](https://doc.rust-lang.org/reference/items/structs.html)), are supported. Struct structs with no fields are also not supported. +/// +/// This fails: +/// ```compile_fail +/// # #[macro_use] extern crate ql; +/// # +/// query_def! { +/// struct Foo { +/// } +/// } +/// # +/// # fn main() { +/// # } +/// ``` +/// +/// This works: +/// ``` +/// # #[macro_use] extern crate ql; +/// # +/// query_def! { +/// struct Foo { +/// a: (bool), +/// b: (usize), +/// c: (String), +/// d: (Vec), +/// } +/// } +/// # +/// # fn main() { +/// # } +/// ``` +/// +/// # Enums +/// +/// Fieldless and struct enums are supported, but struct enums with no fields are not. +/// +/// This fails: +/// ```compile_fail +/// # #[macro_use] extern crate ql; +/// # +/// query_def! { +/// enum Foo { +/// A { } +/// } +/// } +/// # +/// # fn main() { +/// # } +/// ``` +/// +/// This works: +/// ``` +/// # #[macro_use] extern crate ql; +/// # +/// query_def! { +/// enum Foo { +/// A +/// } +/// } +/// # +/// # fn main() { +/// # } +/// ``` +/// +/// Incorrect syntax in the macro invocation can lead to very confusing errors. +/// +/// For example, the incorrect enum syntax shown earlier will fail with this message: +/// +/// ```text +/// error: no rules expected the token `struct` +/// ``` #[macro_export] -macro_rules! query_def_enum { - ( - $( - $(#[$meta:meta]) - * - $vis:vis $data:ident$(( - $(#[$def_meta:meta]) - + +macro_rules! query_def { + ($( + $( #[$meta:meta] )* + $vis:vis $type:ident $data:ident$(( + $( #[$def_meta:meta] )+ + ))? { + $($body:tt)+ + } + )*) => { + $($crate::query_def_0! { + $( #[$meta] )* + $vis $type $data$(( + $( #[$def_meta] )+ ))? { - $( - $(#[$variant_meta:meta]) - * - $variant:ident$(( - $(#[$variant_def_meta:meta]) - + - ))? { - $( - $(#[$field_meta:meta]) - * - $field:ident: $($qualifier:lifetime)? ($($type:tt)+), - )* - } - ),+ + $($body)+ } - ) - * - ) => { - $crate::query_def_internal_enum!(@ - $( - #[derive(Serialize, Deserialize)] - $(#[$meta]) - * - $vis $data( - #[derive(Serialize, Deserialize)] - #[serde(default)] - $( - $(#[$def_meta]) - + - )? - ) { - $( - $(#[$variant_meta]) - * - $variant( - #[derive(Serialize, Deserialize)] - #[serde(default)] - $( - $(#[$variant_def_meta]) - + - )? - ) { - $( - #[serde(skip_serializing_if = "Option::is_none")] - $(#[$field_meta]) - * - $field: $($qualifier)? ($($type)+), - )* - } - ),+ - } - ) - * - ); + })* }; }