queryweave_codegen/lib.rs
1//! The code generation behind the `queryweave` derives, as an ordinary
2//! library.
3//!
4//! Each derive is a function from the derive's input to the generated code,
5//! generic over the [`conventions::Conventions`] it names things by.
6//! `queryweave-derive` exports every derive with
7//! [`conventions::DefaultConventions`]; a crate wanting other rules writes its
8//! own small proc-macro crate calling these functions with its own
9//! conventions:
10//!
11//! ```rust,ignore
12//! #[proc_macro_derive(Filters, attributes(filter, sortable, filters, equivalent_field))]
13//! pub fn derive_filters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
14//! queryweave_codegen::filters::<MyConventions>(input.into()).into()
15//! }
16//! ```
17//!
18//! Every derive is built as a `steps::Derive` pipeline, private to the crate
19//! (see `docs/macros.md`).
20
21// The public API: what a proc-macro crate with its own conventions needs,
22// besides the derive functions below.
23
24pub mod conventions;
25
26/// Errors spanned to the user's code, for a `Conventions` impl to report a
27/// name that does not fit.
28pub mod errors;
29
30// Documented by its own `//!` comment, whose links resolve inside the module.
31pub mod integrations;
32
33/// Checks on the names of the structs a derive is placed on or links to.
34///
35/// The building blocks of `DefaultConventions`, for a `Conventions` impl to
36/// reuse.
37pub mod names;
38
39// The plumbing of the derives' pipelines, private so it can change freely.
40
41/// Struct and field attributes shared by several derives (e.g.
42/// `#[equivalent_field(name)]`).
43mod attributes;
44
45/// Compile-time assertions (e.g. that a mapped field exists) for generated code.
46mod checks;
47
48/// Mod containing utils.
49mod utils;
50
51// Documented by its own `//!` comment, whose links resolve inside the module.
52mod steps;
53
54mod analyze;
55mod code_gen;
56mod lower;
57mod mapping;
58mod parse;
59mod parse_top_level_meta;
60mod pipeline;
61
62use conventions::Conventions;
63use proc_macro2::TokenStream;
64
65/// `#[derive(Filters)]`: the filtering, sorting and paging types of a struct,
66/// from its `#[filter(lt|eq|gt|sw|c|ew|bool)]` and `#[sortable]` fields.
67///
68/// The struct is either:
69///
70/// - a **root** model (no `#[filters(mirrors = ...)]`): it gets the query,
71/// filter-options, sort-field and sort-details models handing filters and
72/// sorting to the persistence layer as data, and publishes them through
73/// `queryweave::Filterable`;
74/// - a **mirror** of a model (`#[filters(mirrors = path)]`, e.g. an API DTO):
75/// it gets its own sort types and query parameters, converting into the
76/// mirrored model's types. A plain mirror names only the core runtime; the
77/// flags `#[filters(mirrors = path, serde, utoipa, validator)]` opt into
78/// deserializable query parameters (and a `PageDto` alias), API
79/// documentation schemas and validation (which needs `serde`), and
80/// `schemars` and `garde` into their alternatives (JSON schemas, validation
81/// needing no serializer), each needing only its own crate. A field
82/// named differently on the mirrored model maps onto it with
83/// `#[equivalent_field(name)]`; the query parameters keep the mirror's names.
84/// The mirrored model is a root model or another mirror (e.g. a gRPC DTO
85/// mirroring an API DTO): a mirror publishes its own types through
86/// `queryweave::Filterable` too, every chain ending at a root model, and
87/// `queryweave::IntoRootQuery` converts a query straight into the root's.
88///
89/// Attributes: `filter`, `sortable`, `filters`, `equivalent_field`.
90#[must_use]
91pub fn filters<C: Conventions>(input: TokenStream) -> TokenStream {
92 steps::expand::<pipeline::filters::Filters<C>>(input)
93}
94
95/// `#[derive(MergePatch)]`: the functions merge patching a model with its
96/// merge patch model counterpart.
97///
98/// Every field not marked `#[merge_patch(skip)]` must exist on the merge patch
99/// model as an `Option<P>` where `P: Into<FieldType>`; `Some` replaces the
100/// field, `None` leaves it untouched.
101///
102/// Attributes: `merge_patch` (`patch = path` on the struct, `skip` on a
103/// field).
104#[must_use]
105#[cfg(feature = "merge-patch")]
106pub fn merge_patch<C: Conventions>(input: TokenStream) -> TokenStream {
107 steps::expand::<pipeline::merge_patch::MergePatch<C>>(input)
108}
109
110/// `#[derive(MergePatchNullableFields)]`: a function listing the nullable
111/// fields of a struct used for merge-patching, i.e. its `Option<Option<T>>`
112/// fields.
113#[must_use]
114#[cfg(feature = "merge-patch")]
115pub fn merge_patch_nullable_fields<C: Conventions>(input: TokenStream) -> TokenStream {
116 steps::expand::<pipeline::merge_patch_nullable_fields::MergePatchNullableFields<C>>(input)
117}
118
119/// `#[derive(RawValue)]`: `queryweave::RawValue` for a value object (a
120/// newtype such as `struct Username<T = String>(T);`), whose raw value is the
121/// wrapped type's.
122#[must_use]
123pub fn raw_value<C: Conventions>(input: TokenStream) -> TokenStream {
124 steps::expand::<pipeline::raw_value::RawValue<C>>(input)
125}
126
127/// `#[derive(ValidateNonNullable)]`: a validation function for a merge patch
128/// DTO, mirroring a domain merge patch model.
129///
130/// A field named differently on the domain merge patch model maps onto it with
131/// `#[equivalent_field(name)]`; validation errors keep the DTO's field names.
132/// The mirrored struct is a merge patch model or another mirror (e.g. a gRPC
133/// message mirroring the DTO): every one implements `queryweave::NullableFields`
134/// under its own names, every chain ending at a merge patch model.
135///
136/// Attributes: `validate_non_nullable`, `equivalent_field`.
137#[must_use]
138#[cfg(feature = "merge-patch")]
139pub fn validate_non_nullable<C: Conventions>(input: TokenStream) -> TokenStream {
140 steps::expand::<pipeline::validate_non_nullable::ValidateNonNullable<C>>(input)
141}
142
143/// `#[derive(AppendToQuery)]`: the append to query trait on a wrapper of a
144/// domain query model.
145///
146/// `#[append_to_query(columns = Entity, filters = Wrapper)]` names the entity
147/// (deriving `Columns`) whose columns store the query's domain fields, and
148/// the wrapper building its `WHERE` clause.
149///
150/// Attributes: `append_to_query` (`columns`, `filters`).
151#[must_use]
152pub fn append_to_query<C: Conventions>(input: TokenStream) -> TokenStream {
153 steps::expand::<pipeline::append_to_query::AppendToQuery<C>>(input)
154}
155
156/// `#[derive(AppendConditions)]`: `queryweave_sql::AppendConditions` for a
157/// filter-options wrapper, appending its set filters to a query as conditions.
158///
159/// `#[append_conditions(columns = Entity)]` names the entity (deriving `Columns`) whose columns
160/// store the filtered domain fields.
161///
162/// Attributes: `append_conditions` (`columns`).
163#[must_use]
164pub fn append_conditions<C: Conventions>(input: TokenStream) -> TokenStream {
165 steps::expand::<pipeline::append_conditions::AppendConditions<C>>(input)
166}
167
168/// `#[derive(Columns)]`: `queryweave_sql::Columns` for a `sqlx` entity,
169/// recording which column stores each field of the domain model named by
170/// `#[columns(model = Model)]`.
171///
172/// Each entity field is a column (named after the field, or its
173/// `#[sqlx(rename = "...")]`) storing the domain field of the same name, or
174/// the one named by `#[domain_field(name)]`, which must exist on the domain
175/// model. `#[sqlx(skip)]` and `#[sqlx(flatten)]` fields are not columns.
176///
177/// Attributes: `columns` (`model = path`), `domain_field`.
178#[must_use]
179pub fn columns<C: Conventions>(input: TokenStream) -> TokenStream {
180 steps::expand::<pipeline::columns::Columns<C>>(input)
181}