Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Pfaff 5ce0f242f6
Cleanup 2022-05-30 07:40:22 -04:00
Michael Pfaff df882cb64c
Bump version 2022-05-28 10:55:30 -04:00
Michael Pfaff 694ce4d66f
Overhaul 2022-05-28 10:55:14 -04:00
3 changed files with 421 additions and 353 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ql" name = "ql"
version = "0.2.2" version = "0.3.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -1,9 +1,105 @@
/// 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<String>),
//! }
//! }
//! #
//! # 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 { pub trait DynamicQuery {
/// A type defining the fields to include in the filtered structure.
type Definition; type Definition;
/// Filters the fields of this structure in place according to the `def`.
fn filter_ref(&mut self, def: &Self::Definition); fn filter_ref(&mut self, def: &Self::Definition);
/// Filters the fields of this structure in place according to the `def`.
/// This method takes ownership of the structure, making it more suitable
/// for pipelines.
#[inline(always)] #[inline(always)]
fn filter(mut self, def: &Self::Definition) -> Self fn filter(mut self, def: &Self::Definition) -> Self
where where
@ -18,20 +114,20 @@ pub trait DynamicQuery {
#[doc(hidden)] #[doc(hidden)]
pub use paste; pub use paste;
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use serde;
mod macros; mod macros;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde::{Deserialize, Serialize};
use super::DynamicQuery; use super::DynamicQuery;
// just tests that the macro compiles // just tests that the macro compiles
crate::query_def_struct! { crate::query_def! {
/// Foo doc.
#[derive(PartialEq)] #[derive(PartialEq)]
Foo { struct Foo {
/// Field doc.
#[serde(flatten)] #[serde(flatten)]
bar: (Bar), bar: (Bar),
baz: (bool), baz: (bool),
@ -39,15 +135,12 @@ mod tests {
bars_by_bazs: 'map (bool, Bar), bars_by_bazs: 'map (bool, Bar),
} }
/// Bar doc.
#[derive(PartialEq)] #[derive(PartialEq)]
Bar { struct Bar {
baz_inner: (bool), baz_inner: (bool),
} }
}
crate::query_def_enum! { enum FooOrBar {
FooOrBar {
Foo { Foo {
value: 'dyn (Foo), value: 'dyn (Foo),
}, },
@ -57,22 +150,20 @@ mod tests {
} }
} }
OptionalFooOrBarDyn { enum OptionalFooOrBarDyn {
Some { Some {
value: 'dyn (FooOrBar), value: 'dyn (FooOrBar),
}, },
None { None,
}
} }
OptionalFooOrBar { enum OptionalFooOrBar {
Some { Some {
value: (FooOrBar), value: (FooOrBar),
}, },
None { None
}
} }
} }
@ -87,18 +178,24 @@ mod tests {
bars: Some(vec![]), bars: Some(vec![]),
bars_by_bazs: Some(std::collections::HashMap::new()), bars_by_bazs: Some(std::collections::HashMap::new()),
}; };
assert_eq!(serde_json::to_string(&x).ok(), Some(r#"{"baz_inner":true,"baz":false,"bars":[],"bars_by_bazs":{}}"#.to_owned())); assert_eq!(
serde_json::to_string(&x).ok(),
Some(r#"{"baz_inner":true,"baz":false,"bars":[],"bars_by_bazs":{}}"#.to_owned())
);
assert_eq!(x.clone().filter(&FooDef { assert_eq!(
bar: false, x.clone().filter(&FooDef {
baz: true, bar: false,
bars: Some(BarDef::default()), baz: true,
bars_by_bazs: None, bars: Some(BarDef::default()),
}), Foo { bars_by_bazs: None,
bar: None, }),
baz: Some(false), Foo {
bars: Some(vec![]), bar: None,
bars_by_bazs: None, baz: Some(false),
}) bars: Some(vec![]),
bars_by_bazs: None,
}
)
} }
} }

View File

@ -1,16 +1,16 @@
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! query_def_type { macro_rules! query_def_type {
(@ ($type:ty)) => { (($type:ty)) => {
$type $type
}; };
(@ 'dyn ($type:ty)) => { ('dyn ($type:ty)) => {
$type $type
}; };
(@ 'vec ($type:ty)) => { ('vec ($type:ty)) => {
::std::vec::Vec<$type> ::std::vec::Vec<$type>
}; };
(@ 'map ($key:ty, $value:ty)) => { ('map ($key:ty, $value:ty)) => {
::std::collections::HashMap<$key, $value> ::std::collections::HashMap<$key, $value>
}; };
} }
@ -21,16 +21,16 @@ macro_rules! query_def_field {
(@dyn $type:ty) => { (@dyn $type:ty) => {
Option<<$type as $crate::DynamicQuery>::Definition> Option<<$type as $crate::DynamicQuery>::Definition>
}; };
(@ ($type:ty)) => { (($type:ty)) => {
bool bool
}; };
(@ 'dyn ($type:ty)) => { ('dyn ($type:ty)) => {
$crate::query_def_field!(@dyn $type) $crate::query_def_field!(@dyn $type)
}; };
(@ 'vec ($type:ty)) => { ('vec ($type:ty)) => {
$crate::query_def_field!(@dyn $type) $crate::query_def_field!(@dyn $type)
}; };
(@ 'map ($key:ty, $value:ty)) => { ('map ($key:ty, $value:ty)) => {
$crate::query_def_field!(@dyn $value) $crate::query_def_field!(@dyn $value)
}; };
} }
@ -39,8 +39,9 @@ macro_rules! query_def_field {
#[macro_export] #[macro_export]
macro_rules! query_def_filter { macro_rules! query_def_filter {
// note about this: 'dyn is implied for 'vec and 'map because if the type is not 'dyn, then the // 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. // special handling is not necessary in the first place (i.e. you can just do `(Vec<Type>)`
(@ $query:expr, $value:ident, 'vec ($_type:path)) => { // instead of `'vec (Type)`.
($query:expr, $value:ident, 'vec ($_type:path)) => {
match $query { match $query {
Some(query) => { Some(query) => {
if let Some(ref mut value) = $value { 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 { match $query {
Some(query) => { Some(query) => {
if let Some(ref mut value) = $value { 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 mut value) = $value {
if let Some(ref query) = $query { if let Some(ref query) = $query {
value.filter_ref(&query); value.filter_ref(&query);
} }
} }
}; };
(@ $query:expr, $value:ident, ($($_type:tt)+)) => { ($query:expr, $value:ident, ($($_type:tt)+)) => {
if !*$query { if !*$query {
*$value = None; *$value = None;
} }
@ -85,368 +86,338 @@ macro_rules! query_def_filter {
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! query_def_internal_struct { macro_rules! query_def_internal_struct {
(@ (
$( $( #[$meta:meta] )*
$(#[$meta:meta]) $vis:vis $data:ident$((
* $( #[$def_meta:meta] )+
$vis:vis $data:ident$(( ))? {
$(#[$def_meta:meta]) $(
+ $( #[$field_meta:meta] )*
))? { $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+)
$( ),+
$(#[$field_meta:meta]) }
*
$field:ident: $($qualifier:lifetime)? ($($type:tt)+),
)+
}
)
*
) => { ) => {
$( #[derive(Debug, Clone)]
#[derive(Debug, Clone)] $( #[$meta] )*
$(#[$meta]) $vis struct $data {
* $(
$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]) let query = &def.$field;
* let value = &mut self.$field;
pub $field: Option<$crate::query_def_type!(@ $($qualifier)? ($($type)+))>, $crate::query_def_filter!(query, value, $( $qualifier )? ($( $type )+));
)+ )+
} }
}
};
}
$crate::paste::paste! { #[doc(hidden)]
#[derive(Debug, Clone, Copy, Default)] #[macro_export]
$( $( #[$def_meta] )+ )? macro_rules! query_def_internal_enum_variant_def {
$vis struct [<$data Def>] { (
$( $( #[$variant_def_meta:meta] )*
pub $field: $crate::query_def_field!(@ $($qualifier)? ($($type)+)), $vis:vis $name:ident
)+ ) => {
} #[derive(Debug, Clone, Copy, Default)]
} $( #[$variant_def_meta] )*
$vis struct $name;
impl $crate::DynamicQuery for $data { };
type Definition = $crate::paste::paste! { [<$data Def>] }; (
$( #[$variant_def_meta:meta] )*
fn filter_ref(&mut self, def: &Self::Definition) { $vis:vis $name:ident {
$( $( $field:ident$((
let query = &def.$field; $( #[$variant_def_field_meta:meta] )+
let value = &mut self.$field; ))?: $( $qualifier:lifetime )? ($( $type:tt )+) ),+
$crate::query_def_filter!(@ query, value, $($qualifier)? ($($type)+)); }
) ) => {
+ #[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)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! query_def_internal_enum { macro_rules! query_def_internal_enum {
(@ (
$( $( #[$meta:meta] )*
$(#[$meta:meta]) $vis:vis $data:ident$((
* $( #[$def_meta:meta] )+
$vis:vis $data:ident$(( ))? {
$(#[$def_meta:meta]) $(
+ $( #[$variant_meta:meta] )*
))? { $variant:ident$((
$( $( #[$variant_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>] {
$( $(
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"))] #[cfg(not(feature = "serde"))]
#[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! query_def_struct { macro_rules! query_def_0 {
( (
$( $( #[$meta:meta] )*
$(#[$meta:meta]) $vis:vis struct $data:ident$((
* $( #[$def_meta:meta] )+
$vis:vis $data:ident$(( ))? {
$(#[$def_meta:meta]) $(
+ $( #[$field_meta:meta] )*
))? { $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+)
$( ),+
$(#[$field_meta:meta])
* $(,)?
$field:ident: $($qualifier:lifetime)? ($($type:tt)+), }
)+
}
)
*
) => { ) => {
$crate::query_def_internal_struct!(@ $crate::query_def_internal_struct! {
$( $( #[$meta] )*
$(#[$meta])
*
$vis $data$(( $vis $data$((
$(#[$def_meta]) $( #[$def_meta] )+
+
))? { ))? {
$( $(
$(#[$field_meta]) $( #[$field_meta] )*
* $field: $( $qualifier )? ($( $type )+)
$field: $($qualifier)? ($($type)+),
)+
}
)
*
);
};
}
/// 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)+),
)*
}
),+ ),+
} }
) }
* };
(
$( #[$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!(@ $crate::query_def_internal_enum! {
$( $( #[$meta] )*
$(#[$meta])
*
$vis $data$(( $vis $data$((
$(#[$def_meta]) $( #[$def_meta] )+
+
))? { ))? {
$( $(
$(#[$variant_meta]) $( #[$variant_meta] )*
*
$variant$(( $variant$((
$(#[$variant_def_meta]) $( #[$variant_def_meta] )?
? ))? $({
))? {
$( $(
$(#[$field_meta]) $( #[$field_meta] )*
* $field: $( $qualifier )? ($( $type )+)
$field: $($qualifier)? ($($type)+), ),+
)* })?
}
),+ ),+
} }
) }
*
);
}; };
} }
/// Defines a dynamic query structure.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! query_def_struct { macro_rules! query_def_0 {
( (
$( $( #[$meta:meta] )*
$(#[$meta:meta]) $vis:vis struct $data:ident$((
* $( #[$def_meta:meta] )+
$vis:vis $data:ident$(( ))? {
$(#[$def_meta:meta]) $(
+ $( #[$field_meta:meta] )*
))? { $field:ident: $( $qualifier:lifetime )? ($( $type:tt )+)
$( ),+
$(#[$field_meta:meta])
* $(,)?
$field:ident: $($qualifier:lifetime)? ($($type:tt)+), }
)+
}
)
*
) => { ) => {
$crate::query_def_internal_struct!(@ $crate::query_def_internal_struct! {
$( #[derive($crate::serde::Serialize, $crate::serde::Deserialize)]
#[derive(Serialize, Deserialize)] $( #[$meta] )*
$(#[$meta])
*
$vis $data( $vis $data(
#[derive(Serialize, Deserialize)] #[derive($crate::serde::Serialize, $crate::serde::Deserialize)]
#[serde(default)] #[serde(default)]
$( $( $( #[$def_meta] )+ )?
$(#[$def_meta])
+
)?
) { ) {
$( $(
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
$(#[$field_meta]) $( #[$field_meta] )*
* $field: $( $qualifier )? ($( $type )+)
$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. /// Defines a dynamic query structure.
#[cfg(feature = "serde")] ///
/// See the crate documentation for details.
#[macro_export] #[macro_export]
macro_rules! query_def_enum { macro_rules! query_def {
( ($(
$( $( #[$meta:meta] )*
$(#[$meta:meta]) $vis:vis $type:ident $data:ident$((
* $( #[$def_meta:meta] )+
$vis:vis $data:ident$(( ))? {
$(#[$def_meta:meta]) $($body:tt)+
+ }
)*) => {
$($crate::query_def_0! {
$( #[$meta] )*
$vis $type $data$((
$( #[$def_meta] )+
))? { ))? {
$( $($body)+
$(#[$variant_meta:meta])
*
$variant:ident$((
$(#[$variant_def_meta:meta])
+
))? {
$(
$(#[$field_meta:meta])
*
$field:ident: $($qualifier:lifetime)? ($($type:tt)+),
)*
}
),+
} }
) })*
*
) => {
$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)+),
)*
}
),+
}
)
*
);
}; };
} }