Skip to main content

kynos_macros/
lib.rs

1//! Procedural macros for the Kynos REST API framework.
2//!
3//! Nothing here is meant to be used directly: every macro is re-exported from
4//! `kynos`, and the documentation lives next to the trait each one implements.
5//!
6//! # Why the route attributes exist
7//!
8//! Kynos deliberately has no attribute DSL restating a handler's signature.
9//! utoipa's `#[utoipa::path(responses(...))]` is written by hand beside the
10//! code it describes, and nothing keeps the two in step — which is the single
11//! most common way a generated OpenAPI document ends up wrong.
12//!
13//! The route attributes therefore carry only what the types cannot: the method,
14//! the path, and prose. Parameters come from the handler's arguments, responses
15//! from its return type, and neither is restated anywhere.
16//!
17//! What the attribute *does* add is compile-time checking the builder form
18//! cannot do — chiefly that a path template's variables match the handler's
19//! path parameters.
20//!
21//! # Why the examples here are `ignore`d
22//!
23//! Every expansion names `::kynos::…`, which this crate cannot depend on, so a
24//! doctest here would not compile whatever the derive emitted. The compiled
25//! demonstrations live in `crates/kynos/tests/derives.rs` and the framework's
26//! examples; `AGENTS.md` records the carve-out.
27
28#[cfg(feature = "assets")]
29mod assets;
30mod derive;
31mod route;
32
33use proc_macro::TokenStream;
34
35/// Declares a `GET` operation.
36///
37/// ```ignore
38/// /// Fetch a single user.
39/// ///
40/// /// The first line becomes the operation's summary, the rest its description.
41/// #[kynos::get("/users/{id}", catch_panics)]
42/// async fn get_user(Path(id): Path<UserId>) -> Result<Json<User>, ApiError> {
43///     todo!()
44/// }
45/// ```
46///
47/// `catch_panics` installs a compile-time-selected recovery boundary for this
48/// operation and contributes its 500 response. It is a compile-time error to
49/// use it when the final binary is built with `panic = "abort"`.
50///
51/// Accepts `operation_id = "..."` and `tag = SomeTag` after the path. The tag
52/// becomes `EndpointMeta::TAGS`, which is what puts it in the description; it
53/// may be named once, since `Router::tag`, `Group::tag` and
54/// `EndpointBuilder::tag` are how an operation acquires the rest.
55#[proc_macro_attribute]
56pub fn get(attribute: TokenStream, item: TokenStream) -> TokenStream {
57    route::expand("GET", attribute, item)
58}
59
60/// Declares a `POST` operation. See [`macro@get`] for the syntax.
61#[proc_macro_attribute]
62pub fn post(attribute: TokenStream, item: TokenStream) -> TokenStream {
63    route::expand("POST", attribute, item)
64}
65
66/// Declares a `PUT` operation. See [`macro@get`] for the syntax.
67#[proc_macro_attribute]
68pub fn put(attribute: TokenStream, item: TokenStream) -> TokenStream {
69    route::expand("PUT", attribute, item)
70}
71
72/// Declares a `PATCH` operation. See [`macro@get`] for the syntax.
73#[proc_macro_attribute]
74pub fn patch(attribute: TokenStream, item: TokenStream) -> TokenStream {
75    route::expand("PATCH", attribute, item)
76}
77
78/// Declares a `DELETE` operation. See [`macro@get`] for the syntax.
79#[proc_macro_attribute]
80pub fn delete(attribute: TokenStream, item: TokenStream) -> TokenStream {
81    route::expand("DELETE", attribute, item)
82}
83
84/// Declares a `HEAD` operation. See [`macro@get`] for the syntax.
85///
86/// Rarely needed: a `HEAD` is answered from the corresponding `GET` unless one
87/// is declared, per RFC 9110. Declare it only when the description should say
88/// so explicitly.
89#[proc_macro_attribute]
90pub fn head(attribute: TokenStream, item: TokenStream) -> TokenStream {
91    route::expand("HEAD", attribute, item)
92}
93
94/// Declares an `OPTIONS` operation. See [`macro@get`] for the syntax.
95///
96/// CORS preflight is handled without one: where a `Cors` interceptor covers a
97/// path, the router registers a preflight answer on it while the service is
98/// built. Declare this only for an `OPTIONS` that is part of the API's own
99/// contract.
100///
101/// Declaring one *suppresses* the synthesized preflight on that path — a
102/// hand-written operation wins, and it then owns answering preflights there too.
103#[proc_macro_attribute]
104pub fn options(attribute: TokenStream, item: TokenStream) -> TokenStream {
105    route::expand("OPTIONS", attribute, item)
106}
107
108/// Declares a `TRACE` operation. See [`macro@get`] for the syntax.
109#[proc_macro_attribute]
110pub fn trace(attribute: TokenStream, item: TokenStream) -> TokenStream {
111    route::expand("TRACE", attribute, item)
112}
113
114/// Declares a `QUERY` operation.
115///
116/// Requires the `openapi32` feature: `QUERY` has no Path Item field before
117/// OpenAPI 3.2.
118#[cfg(feature = "openapi32")]
119#[proc_macro_attribute]
120pub fn query(attribute: TokenStream, item: TokenStream) -> TokenStream {
121    route::expand("QUERY", attribute, item)
122}
123
124/// Declares an operation for a method with no dedicated attribute.
125///
126/// ```ignore
127/// #[kynos::operation(method = "PROPFIND", path = "/files/{id}")]
128/// async fn propfind(Path(id): Path<FileId>) -> Result<Json<Properties>, ApiError> {
129///     todo!()
130/// }
131/// ```
132///
133/// A method outside the eight OpenAPI 3.1 names emits into
134/// `additionalOperations`, which requires the `openapi32` feature.
135#[proc_macro_attribute]
136pub fn operation(attribute: TokenStream, item: TokenStream) -> TokenStream {
137    route::expand_generic(attribute, item)
138}
139
140/// Collects operations for mounting.
141///
142/// Operations sharing a path are merged into one Path Item, so `routes![list,
143/// create]` on `/users` produces a single entry with `get` and `post`.
144///
145/// ```ignore
146/// Router::new().mount(routes![users::list, users::create, users::get]);
147/// ```
148#[proc_macro]
149pub fn routes(input: TokenStream) -> TokenStream {
150    route::routes::expand_routes(input)
151}
152
153/// A path template validated at compile time.
154///
155/// ```ignore
156/// let template = path!("/users/{id}");
157/// ```
158///
159/// Rejects a template that does not start with `/`, has unbalanced braces,
160/// repeats a variable, or carries a query string — none of which are legal as
161/// a Paths key.
162#[proc_macro]
163pub fn path(input: TokenStream) -> TokenStream {
164    route::path::expand_path(input)
165}
166
167/// Compiles a directory into the binary as a described asset set.
168///
169/// ```ignore
170/// kynos::assets! {
171///     /// The built single-page app.
172///     pub struct Site;
173///     dir = "dist",
174///     exclude = [".map"],
175///     warn_over = "4MiB",
176/// }
177/// ```
178///
179/// `dir` resolves against `CARGO_MANIFEST_DIR`. Every file becomes an
180/// `include_bytes!`, so changing one rebuilds the crate; *adding* or *removing*
181/// one does not, which a `cargo::rerun-if-changed` line in `build.rs` closes.
182///
183/// A file whose name no path template can express is a compile error naming it,
184/// because a static asset Kynos cannot describe is one it will not serve.
185/// Dotfiles and symlinks are skipped: the first keeps `.git` out of a binary,
186/// the second keeps a set inside its own directory.
187///
188/// An embedded set past 2 MiB emits a compiler warning at the `dir` literal.
189/// Raise the threshold with `warn_over = "8MiB"`, or turn it off with
190/// `warn_over = "none"`. Under `-D warnings` it becomes an error, which is
191/// arguably right and is exactly why the override exists.
192#[cfg(feature = "assets")]
193#[proc_macro]
194pub fn assets(item: TokenStream) -> TokenStream {
195    assets::expand(item)
196}
197
198/// Describes a type as JSON Schema.
199///
200/// Reads the serde attributes already on the type — `rename_all`, `skip`,
201/// `flatten`, `alias`, `tag`, `content`, `transparent`, `deny_unknown_fields` —
202/// so the schema and the wire form come from one declaration. A named field
203/// serde reads under an `alias` is a property under each name it reads, present
204/// under exactly one where it is required and under at most one otherwise,
205/// since serde refuses a document naming two. A variant serde reads under an
206/// `alias` is named under each name it reads: in the `enum` of an all-unit
207/// enum, in the `enum` a tag or an externally tagged unit variant then is in
208/// place of a `const`, and as a property of an externally tagged object branch,
209/// present under exactly one. A name two variants claim is named under the
210/// first alone, the one serde reads it as. Under
211/// `deny_unknown_fields`, every object serde then refuses unknown keys in is
212/// closed: a struct, each struct variant's fields, and an adjacently tagged
213/// branch. The derive uses `additionalProperties: false`, or
214/// `unevaluatedProperties: false` where the object carries an `allOf`, which a
215/// flattened field composes members through and an aliased field bounds its
216/// names in. An externally tagged branch that is an object admits
217/// only its variant key, with or without the attribute, since serde reads it as
218/// exactly one entry. A shape that closes does not implement `Flatten`, and
219/// neither does any externally tagged enum. A flattened field of an object the
220/// attribute closes is also bounded by the narrower `ClosedFlatten`, below. A
221/// field is left out of `required` when it is an `Option`, carries
222/// `#[serde(default)]`, or belongs to a struct carrying
223/// `#[serde(default)]`, because the wire form then allows it to be absent both
224/// ways. A named field serde reads and never writes, `skip_serializing` alone,
225/// is described under that same rule, and one serde writes and never reads,
226/// `skip_deserializing` alone, is left out. In an object serde writes,
227/// `skip_serializing_if` on a field serde reads is accepted only alongside an
228/// `Option` or a `#[serde(default)]`, or on a flattened `#[schema(open)]` map,
229/// which serde reads absent as empty and `required` never lists. A field of a
230/// variant serde never writes, `skip_serializing` on the variant, is only read,
231/// so it may carry `skip_serializing_if` without any of them. A `transparent`
232/// struct is described by the one field serde writes and reads through, or the
233/// single field of the one direction serde can derive for it,
234/// keeps its own component name, as a newtype does, and carries that field's
235/// constraints. A named struct's `tag` is one more required property, whose
236/// `const` is the struct's serde name: its container `rename`, the serialize
237/// side where the rename is split, otherwise its identifier, which `rename_all`
238/// does not reach. It carries no `discriminator`, a struct having no branches
239/// to tell apart, and a `transparent` struct writes no tag. A tuple is the array of the members serde does not skip both
240/// ways, and a newtype variant whose member serde skips is the unit variant
241/// serde writes, provided serde also reads it back. A newtype, and each
242/// described member of a tuple, tuple variant or newtype variant, carries its
243/// constraints, prose and `#[deprecated]` as a named field does. A variant
244/// serde skips both ways is described nowhere, and one serde reads and never
245/// writes is described as serde reads it, under its prose and `#[deprecated]`.
246///
247/// Constraints go on fields, named or unnamed, and the grammar is exactly the
248/// keys of
249/// [`Constraints`](https://docs.rs/kynos/latest/kynos/schema/constraints/struct.Constraints.html)
250/// so that
251/// the attribute and the type it fills cannot drift: `minimum`, `maximum`,
252/// `exclusive_minimum`, `exclusive_maximum`, `multiple_of`, `min_length`,
253/// `max_length`, `pattern`, `min_items`, `max_items` and the `unique_items`
254/// flag. They become JSON Schema assertions *and* the parser's checks, which is
255/// what keeps the description honest without a JSON Schema interpreter on the
256/// hot path.
257///
258/// `format` is **not** among them. It states what a value *is*, which follows
259/// from the type or from nothing, so a `String` annotated as a UUID is a
260/// compile error naming the remedy — `uuid::Uuid`, one of the date, time or
261/// decimal types behind their features, or a newtype with its own `Schema`.
262/// A constraint on one field is `pattern`; a claim about a type is the type's.
263///
264/// `open` is the one member of `#[schema(...)]` that is not a constraint. It
265/// goes on a `#[serde(flatten)]` field to say that the object really does admit
266/// members nothing names, which is the only thing a flattened map can mean. The
267/// field's type must implement
268/// [`OpenMap`](https://docs.rs/kynos/latest/kynos/schema/flatten/trait.OpenMap.html)
269/// — a `HashMap`, a `BTreeMap` or an `Unchecked` over a map, not a type that
270/// refers to one — and a key type's `propertyNames` does not survive it.
271///
272/// # Rejected, because serde and the schema would disagree
273///
274/// - `#[serde(with = ...)]`, `serialize_with`, `deserialize_with` on a field or
275///   a variant the schema describes. The wire form no longer follows from the
276///   Rust type, so a schema derived from the Rust type would be a lie. Give the
277///   value a newtype whose own `Serialize`, `Deserialize` and `Schema` agree on
278///   that form instead. Exempt, being in no schema, are a named field serde
279///   never reads, a variant serde skips both ways and its fields, and a member
280///   of a tuple struct, tuple variant or newtype variant serde skips both ways;
281///   a newtype struct's member never is, since serde writes it through the
282///   function whatever it skips. A flattened `PhantomData` serde reads is in no
283///   schema and still refused, since serde hands the function the parent object
284///   to write members into and read members from. Where serde never writes (a
285///   variant carrying `skip_serializing`, its fields, and a named field carrying
286///   `skip_serializing` alone) only `with` and `deserialize_with` are refused. A
287///   `transparent` struct is checked only on the field serde picks in each
288///   direction: `with` and `serialize_with` on the one field it writes through,
289///   `with` and `deserialize_with` on the one field it reads through, and
290///   nothing for a direction with no single candidate, since serde then refuses
291///   that direction's derive.
292/// - `#[serde(untagged)]` enums. `anyOf` with no discriminator is ambiguous to
293///   decode, and the tie-break is inexpressible. Use an internally or
294///   adjacently tagged enum, which becomes a `discriminator`. The same holds for
295///   one untagged variant serde reads or writes: it goes on the wire as its bare
296///   payload, tried only after every tagged variant fails. On a variant serde
297///   skips both ways it is in no schema, and is accepted.
298/// - `#[serde(flatten)]` onto a field whose schema names none of its members,
299///   which a map's does not. Its `additionalProperties` is defined against the
300///   `properties` of its own schema object, and composing it into the parent's
301///   `allOf` leaves it none — so the map's *value* schema would apply to the
302///   properties the parent declared itself. Refused by a
303///   [`Flatten`](https://docs.rs/kynos/latest/kynos/schema/flatten/trait.Flatten.html)
304///   bound the expansion asserts per flattened field. `#[schema(open)]` on that
305///   field is the opt-in that keeps the map: it says the object really is open,
306///   and the map's values become the parent's `unevaluatedProperties`, the one
307///   keyword that sees annotations across an `allOf`. The same bound holds the
308///   payload of an internally tagged enum's newtype variant, which is composed
309///   beside the tag in an `allOf` the same way.
310/// - A `#[serde(other)]` catch-all on a variant serde reads, which only 3.2's
311///   `defaultMapping` could describe and the derive does not emit. On a variant
312///   serde skips both ways it catches nothing, and is accepted.
313/// - `skip_serializing_if` on a non-`Option` field with no `#[serde(default)]`
314///   on the field or its struct, and `#[serde(skip_serializing)]` alone on such
315///   a field. serde may leave the field out of what it writes but still
316///   requires it on read, so no `required` list is true in both directions.
317///   Add `#[serde(default)]` beside it or on the struct. Exempt are a field
318///   serde never reads and a flattened `PhantomData`, both in no schema, any
319///   field of a variant serde never writes, which serde only reads, and any
320///   field of a `transparent` struct, which has no `required` list. Any other
321///   flattened field is decided by `#[schema(open)]` alone, since serde
322///   ignores any default on it: an open map is exempt, and anything else is
323///   refused.
324/// - `#[serde(into = ...)]`, `from` or `try_from` on the type itself, struct or
325///   enum. serde then writes or reads the type they name, which the declared
326///   fields or variants no longer predict. Implement `Schema` by hand, describing
327///   the type serde converts through. `#[serde(remote = ...)]` is accepted,
328///   since its fields mirror the type it names.
329/// - `#[serde(transparent)]` on a struct serde writes through one field and
330///   reads through another. serde writes through the field without `skip` or
331///   `skip_serializing` and reads through the field without `skip`,
332///   `skip_deserializing` or a field-level `default`; where each direction picks
333///   a single field, the two must be the same. A struct where only one direction
334///   picks a single field is described by it, since serde refuses the other
335///   derive itself. Mark every other field `#[serde(skip)]`.
336/// - `#[serde(skip_serializing)]` or `skip_deserializing` alone on a member of a
337///   tuple struct, a tuple variant or a newtype variant, or `skip_serializing_if`
338///   on a tuple member other than the last one under a `#[serde(default)]` on it
339///   or its struct. A position, or a variant's payload, is there or not as a
340///   whole, so serde would write one shape and read another. `#[serde(skip)]`
341///   leaves the member out both ways; a newtype struct is exempt, since serde
342///   ignores all three there, and so are a newtype variant's
343///   `skip_serializing_if` and a `transparent` struct, which is not an array.
344///   Inside a variant serde never writes, `skip_serializing` on the variant,
345///   serde only reads the members, so there only a lone `skip_deserializing`
346///   is refused; a variant serde skips both ways is in no schema.
347/// - `#[serde(skip)]` on the only member of an adjacently tagged newtype variant,
348///   unless that member is an `Option`. serde writes the variant as its tag
349///   alone but reads it only with its content, so no one schema is true of
350///   both. Make the member an `Option`, or skip the whole variant.
351/// - `#[serde(skip_deserializing)]` alone on a variant. serde writes the variant
352///   and refuses to read it back, so a closed `oneOf` or `enum` listing it
353///   describes a request serde refuses, and one leaving it out describes a
354///   response serde writes. `#[serde(skip)]` leaves the variant out both ways.
355///   `skip_serializing` alone on a variant is accepted, since every variant serde
356///   writes is one it reads.
357/// - `#[serde(skip_deserializing)]` without `skip_serializing` on a named field
358///   of an object that `deny_unknown_fields` closes, or beside a
359///   `#[schema(open)]` flattened field whose type does not implement
360///   [`AdmitsAny`](https://docs.rs/kynos/latest/kynos/schema/flatten/trait.AdmitsAny.html),
361///   which a map, whose value schema is hoisted, does not unless its values are
362///   `Unchecked`, and an `Unchecked` map does. serde writes the field and never
363///   reads it, so the schema leaves it out, and the object then refuses what
364///   serde writes of it: through being closed, or through the value schema a
365///   map hoists as `unevaluatedProperties`. `#[serde(skip)]` leaves the field
366///   out both ways. For the same reason a
367///   struct, or an internally tagged enum whose struct variant serde writes,
368///   holding such a field does not implement `Flatten`.
369/// - A flattened `#[schema(open)]` field beside `#[serde(deny_unknown_fields)]`.
370///   serde refuses every key the object's fields do not name before the map
371///   sees it, so it reads the map empty and writes members it would refuse to
372///   read back. Drop one of the two.
373/// - A flattened internally tagged enum, or a flattened struct holding a
374///   flattened field serde reads or carrying a container `#[serde(tag)]`, in
375///   an object `deny_unknown_fields` closes. serde takes a flattened key only
376///   for a type it reads by name, through `deserialize_struct`. It lends an
377///   internally tagged enum, and a struct holding a flattened field, every key
378///   without taking any, and never takes a struct's own tag, so the object
379///   refuses every document the type writes. Refused by a
380///   [`ClosedFlatten`](https://docs.rs/kynos/latest/kynos/schema/flatten/trait.ClosedFlatten.html)
381///   bound the expansion asserts per flattened field of a closed object beside
382///   `Flatten`, which the derive implements for a struct with no flattened
383///   field serde reads and no container tag, and for an adjacently tagged
384///   enum. Flatten one of those instead, or drop `deny_unknown_fields`.
385/// - A variant whose own name, after `rename` and `rename_all`, an earlier
386///   variant also reads, by its own name or an `alias`. serde reads a name as
387///   the first variant claiming it, so the later variant goes on the wire under
388///   a name that reads back as the earlier one. An `alias` an earlier variant
389///   already claims is accepted and named under that variant alone, since serde
390///   never reads it as the later one.
391/// - A container `tag` on a struct carrying `deny_unknown_fields`. serde writes
392///   the tag beside the fields and never reads it back as one of them, so the
393///   closed struct refuses every document it writes. Drop
394///   `deny_unknown_fields`, or drop the tag and declare it as a field. A
395///   `transparent` struct writes no tag, and is accepted.
396/// - A named field serde writes or reads under the name of its struct's own
397///   container `tag`, as its wire name or an `alias`. serde writes the key
398///   twice and reads the tag's value back as the field. Rename the field or the
399///   tag, or skip the field both ways; one serde skips in the direction it
400///   would collide in is accepted. A flattened field's own name is never
401///   written, so it is accepted too, but the keys its type writes are not
402///   checked: the derive cannot see them, as serde's own check of an enum's
403///   internal tag cannot, and one named as the tag gets a schema serde's
404///   document does not meet.
405#[proc_macro_derive(Schema, attributes(schema))]
406pub fn derive_schema(item: TokenStream) -> TokenStream {
407    derive::schema::expand(item)
408}
409
410/// Maps an error type to RFC 9457 problem details.
411///
412/// ```ignore
413/// #[derive(Debug, thiserror::Error, ApiError)]
414/// #[problem(base = "https://errors.example.com/")]
415/// enum StoreError {
416///     #[error("no user with id {id}")]
417///     #[problem(status = 404, title = "User not found")]
418///     NotFound {
419///         #[problem(extension)]
420///         id: UserId,
421///         trace: String,
422///     },
423///
424///     #[error("that email is already registered")]
425///     #[problem(status = 409)]
426///     EmailTaken,
427/// }
428/// ```
429///
430/// `status` is required on every variant and must be between 400 and 599. A
431/// struct declares its one status on the type instead; `base` always belongs on
432/// the type, since it is the prefix every variant's type URI shares.
433///
434/// The error's `Display` supplies each problem's `detail`, which is why
435/// `thiserror` is the expected companion — the `#[error("...")]` a Rust reader
436/// sees is the sentence an API consumer receives. A type without a `Display`
437/// is rejected at the derive rather than at the handler returning it.
438///
439/// A field is published as an extension member only when it says
440/// `#[problem(extension)]`, because a variant carries whatever the error site
441/// had to hand and the default must not be to put that on the wire.
442///
443/// Also emits the `IntoResponse` and `Responses` implementations, so the
444/// statuses the error can produce and the statuses the description advertises
445/// cannot diverge. It is the only supported way to implement
446/// `IntoProblem`.
447#[proc_macro_derive(ApiError, attributes(problem))]
448pub fn derive_api_error(item: TokenStream) -> TokenStream {
449    derive::api_error::expand(item)
450}
451
452/// Declares a closed set of responses, one variant per status.
453///
454/// ```ignore
455/// #[derive(Reply)]
456/// enum CreateReply {
457///     #[reply(status = 201, description = "the user as stored")]
458///     Created(User),
459///
460///     #[reply(status = 200, description = "an identical user already existed")]
461///     AlreadyExists(User),
462/// }
463/// ```
464///
465/// For an operation with more than one success shape. Modelled on
466/// poem-openapi's `ApiResponse`, which is the best existing treatment of this.
467///
468/// `status` is required on every variant and must be between 200 and 599: a 1xx
469/// is an interim response, and a handler returns the final one. No two variants
470/// may declare the same status, since the description keys a reply's variants
471/// by status alone — that is what "one variant per status" means, and it is the
472/// one place this derive is stricter than [`ApiError`](macro@ApiError), whose
473/// variants carry a `detail` that tells two occurrences of a status apart.
474///
475/// A variant's fields are its response body, so a variant holds either nothing,
476/// for the empty body, or exactly one type describing the body. An anonymous
477/// record has no name to register a component under.
478#[proc_macro_derive(Reply, attributes(reply))]
479pub fn derive_reply(item: TokenStream) -> TokenStream {
480    derive::reply::expand(item)
481}
482
483/// Declares a group of path parameters.
484///
485/// Wire names must match the route template's variables in declaration order;
486/// the route attribute emits a const assertion comparing the two lists. A
487/// field's wire name is its `#[param(rename)]`, else serde's `rename`, else
488/// its identifier under the struct's `rename_all`, cased as the
489/// [`Schema`](macro@Schema) derive cases a property, so a field no Kynos
490/// attribute renames carries the name that derive would give its property.
491/// Each field's type, or an `Option`'s inner type, is a
492/// `kynos::schema::ParamValue`.
493///
494/// # Rejected, because a parameter has one name
495///
496/// - `#[serde(alias = "...")]` on any field: serde would read the field under
497///   a second name, and a Parameter Object carries one.
498/// - serde's split `rename(serialize = ..., deserialize = ...)` on a field the
499///   Kynos attribute does not name, and `rename_all(serialize = ...,
500///   deserialize = ...)` on the struct.
501#[proc_macro_derive(PathParams, attributes(param))]
502pub fn derive_path_params(item: TokenStream) -> TokenStream {
503    derive::path_params::expand(item)
504}
505
506/// Declares a group of query parameters.
507///
508/// Each field is one parameter, decoded from its value through `FromStr`, so
509/// its type, or an `Option`'s inner type, must be a
510/// `kynos::schema::ParamValue`: a field whose schema is an object is refused,
511/// since the default `form` style with `explode` would describe it as `x=1&y=2`
512/// while the decoder reads one `name=` pair. For a structured query,
513/// `QueryString<T, M>` describes the whole query string under `openapi32`.
514///
515/// A field's wire name is its `#[param(rename)]`, else serde's `rename`, else
516/// its identifier under the struct's `rename_all`, cased as the
517/// [`Schema`](macro@Schema) derive cases a property, so a field no Kynos
518/// attribute renames carries the name the `Schema` derive gives its property.
519/// The `Schema` derive never reads `#[param(rename)]`, so a field that
520/// attribute renames is a parameter under one name and a property under
521/// another.
522///
523/// # Rejected, because a parameter has one name
524///
525/// - `#[serde(alias = "...")]` on any field: serde would read the field under
526///   a second name, and a Parameter Object carries one.
527/// - serde's split `rename(serialize = ..., deserialize = ...)` on a field the
528///   Kynos attribute does not name, and `rename_all(serialize = ...,
529///   deserialize = ...)` on the struct.
530#[proc_macro_derive(QueryParams, attributes(param))]
531pub fn derive_query_params(item: TokenStream) -> TokenStream {
532    derive::query_params::expand(item)
533}
534
535/// Declares a group of request or response headers.
536///
537/// Rejects `Accept`, `Content-Type` and `Authorization`. The specification says
538/// a parameter definition for those is ignored; `Content-Type` is likewise
539/// derived from a response's content map. Repeated fields such as `Set-Cookie`
540/// remain separate header values rather than being comma joined. The
541/// diagnostic names the right tool for each reserved field. Each field's type,
542/// or an `Option`'s inner type, is a `kynos::schema::ParamValue`, in either
543/// direction.
544///
545/// A field's wire name is its `#[header(rename)]`, else serde's `rename`, else
546/// its identifier under the struct's `rename_all`, cased as the
547/// [`Schema`](macro@Schema) derive cases a property, so `rename_all =
548/// "kebab-case"` names `x_request_id` as `x-request-id`. The reserved names
549/// are checked against that final name.
550///
551/// # Rejected, because a parameter has one name
552///
553/// - `#[serde(alias = "...")]` on any field: serde would read the field under
554///   a second name, and a Parameter Object carries one.
555/// - serde's split `rename(serialize = ..., deserialize = ...)` on a field the
556///   Kynos attribute does not name, and `rename_all(serialize = ...,
557///   deserialize = ...)` on the struct.
558#[proc_macro_derive(HeaderParams, attributes(header))]
559pub fn derive_headers(item: TokenStream) -> TokenStream {
560    derive::headers::expand(item)
561}
562
563/// Declares a group of request cookies.
564///
565/// Each field's type, or an `Option`'s inner type, is a
566/// `kynos::schema::ParamValue`. A field's wire name is its
567/// `#[cookie(rename)]`, else serde's `rename`, else its identifier under the
568/// struct's `rename_all`, cased as the [`Schema`](macro@Schema) derive cases a
569/// property.
570///
571/// # Rejected, because a parameter has one name
572///
573/// - `#[serde(alias = "...")]` on any field: serde would read the field under
574///   a second name, and a Parameter Object carries one.
575/// - serde's split `rename(serialize = ..., deserialize = ...)` on a field the
576///   Kynos attribute does not name, and `rename_all(serialize = ...,
577///   deserialize = ...)` on the struct.
578#[proc_macro_derive(CookieParams, attributes(cookie))]
579pub fn derive_cookies(item: TokenStream) -> TokenStream {
580    derive::cookies::expand(item)
581}
582
583/// Declares the fields of a `multipart/form-data` body, in both directions.
584///
585/// ```ignore
586/// #[derive(Schema, MultipartForm)]
587/// struct Upload {
588///     name: String,
589///     caption: Option<String>,
590///     images: Vec<FilePart>,
591/// }
592/// ```
593///
594/// One declaration, two implementations: `FromMultipart` reads each field from
595/// the part carrying its name, and `IntoMultipart` writes it back under the
596/// same one — so a body `MultipartForm<T>` accepts is a body it can produce.
597///
598/// A field's type says how many parts carry it: `Vec<T>` is one part per
599/// element, `Option<T>` is a part that need not have been sent, and anything
600/// else is a part that must have been, whose second and later occurrences are
601/// ignored. The element type converts through `FromPart` and `IntoPart`, which
602/// Kynos implements for `FilePart`, `String` and `Bytes`.
603///
604/// A part naming no declared field is ignored, since a form may carry what the
605/// agent rendering it added.
606///
607/// There is no attribute of its own. Derive [`Schema`](macro@Schema) alongside:
608/// this derive says how the parts travel and `Schema` is what puts them in the
609/// description, and both read the part names from the same place — the field's
610/// identifier, or serde's `rename` and `rename_all` when the type carries them.
611#[proc_macro_derive(MultipartForm)]
612pub fn derive_multipart_form(item: TokenStream) -> TokenStream {
613    derive::multipart::expand(item)
614}
615
616/// Declares a tag.
617///
618/// ```ignore
619/// #[derive(Tag)]
620/// #[tag(name = "users", description = "Managing user accounts")]
621/// struct Users;
622/// ```
623///
624/// Tags are types rather than strings, so a typo is a compile error and
625/// uniqueness follows from the module system. `#[tag(parent = Admin)]` nests
626/// one tag under another, which requires `openapi32`.
627#[proc_macro_derive(Tag, attributes(tag))]
628pub fn derive_tag(item: TokenStream) -> TokenStream {
629    derive::tag::expand(item)
630}
631
632/// Declares a security scheme.
633///
634/// ```ignore
635/// #[derive(SecurityScheme)]
636/// #[security(http, scheme = "bearer", bearer_format = "JWT")]
637/// struct Bearer;
638/// ```
639#[proc_macro_derive(SecurityScheme, attributes(security))]
640pub fn derive_security_scheme(item: TokenStream) -> TokenStream {
641    derive::security_scheme::expand(item)
642}
643
644/// Declares an application context, emitting one `Provides` implementation per
645/// field.
646///
647/// ```ignore
648/// #[derive(Provider)]
649/// struct App {
650///     pool: Pool,
651///     cache: Cache,
652///     #[provide(skip)]
653///     started_at: Instant,
654/// }
655/// ```
656///
657/// Each provided field's type must be `Clone`, since a value is handed out per
658/// request; a handle is the intended shape. A handler asking for something no
659/// field supplies fails to typecheck, rather than panicking at runtime the way
660/// an erased state map does.
661///
662/// Two provided fields of the same type are rejected here, naming both, rather
663/// than being left to produce a coherence error about the derive's own output.
664#[proc_macro_derive(Provider, attributes(provide))]
665pub fn derive_provider(item: TokenStream) -> TokenStream {
666    derive::provider::expand(item)
667}