ql.rs/src/lib.rs

202 lines
4.5 KiB
Rust

//! 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 {
/// A type defining the fields to include in the filtered structure.
type Definition;
/// Filters the fields of this structure in place according to the `def`.
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)]
fn filter(mut self, def: &Self::Definition) -> Self
where
Self: Sized,
{
self.filter_ref(def);
self
}
}
// needed for macros
#[doc(hidden)]
pub use paste;
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use serde;
mod macros;
#[cfg(test)]
mod tests {
use super::DynamicQuery;
// just tests that the macro compiles
crate::query_def! {
#[derive(PartialEq)]
struct Foo {
#[serde(flatten)]
bar: (Bar),
baz: (bool),
bars: 'vec (Bar),
bars_by_bazs: 'map (bool, Bar),
}
#[derive(PartialEq)]
struct Bar {
baz_inner: (bool),
}
enum FooOrBar {
Foo {
value: 'dyn (Foo),
},
Bar {
value: 'dyn (Bar),
}
}
enum OptionalFooOrBarDyn {
Some {
value: 'dyn (FooOrBar),
},
None,
}
enum OptionalFooOrBar {
Some {
value: (FooOrBar),
},
None
}
}
// make sure A. serde is working and B. we get expected structure
#[test]
fn test_json() {
let x = Foo {
bar: Some(Bar {
baz_inner: Some(true),
}),
baz: Some(false),
bars: Some(vec![]),
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!(
x.clone().filter(&FooDef {
bar: false,
baz: true,
bars: Some(BarDef::default()),
bars_by_bazs: None,
}),
Foo {
bar: None,
baz: Some(false),
bars: Some(vec![]),
bars_by_bazs: None,
}
)
}
}