deepseek_recipe_macros/lib.rs
1//! Procedural macros for deepseek-recipe: [`prompt!`](macro@prompt), which
2//! builds prompts for prompt rendering, and the
3//! [`UntaggedDeserialize`](derive@UntaggedDeserialize) derive, which
4//! deserializes untagged enums by inspecting the JSON value.
5
6use proc_macro::TokenStream;
7use syn::{DeriveInput, parse_macro_input};
8
9mod prompt;
10mod untagged;
11
12/// Build a `Prompt` from a format string. Template text and string arguments
13/// are plain text; a `Special` argument inserts an explicit special token.
14///
15/// Format placeholders follow `format!`:
16///
17/// - `{}` takes the next positional argument.
18/// - `{name}` takes the named argument `name = ...`, or captures the variable
19/// `name` from the enclosing scope when no such argument exists.
20/// - `{{` and `}}` write literal braces.
21///
22/// Format specifiers are not supported. Every argument must be used. The
23/// expansion evaluates each argument exactly once, from left to right, before
24/// it appends any text. Argument types dispatch through
25/// `deepseek_recipe_core::prompt::PromptFragment`:
26/// `str` and `String` are plain text, `Special<T>` is an explicit special
27/// token, and `Prompt` splices in with its own explicit special tokens. A
28/// special-token spelling in plain text is not an explicit special token.
29///
30/// Re-exported as `deepseek_recipe_core::prompt`.
31#[proc_macro]
32pub fn prompt(input: TokenStream) -> TokenStream {
33 let input = parse_macro_input!(input as prompt::PromptInput);
34 match prompt::expand(input) {
35 Ok(expanded) => TokenStream::from(expanded),
36 Err(err) => err.to_compile_error().into(),
37 }
38}
39
40/// Derive `Deserialize` for an untagged enum by inspecting the JSON value.
41///
42/// The enum attribute `#[untagged_deserialize(error = "...")]` sets the error
43/// for a value that matches no variant. Each variant selects values with
44/// `#[untagged_deserialize(...)]`:
45///
46/// - `field = "name"`: the object has the field;
47/// - `pattern = Variant(_)`: the value, or the selected field, matches a
48/// `serde_json::Value` pattern;
49/// - `value = "text"`: the value, or the selected field, equals a JSON value.
50///
51/// The generated `deserialize` checks the variant conditions in declaration
52/// order and deserializes the first variant whose condition matches. It
53/// returns that variant's result, including a deserialization error, without
54/// trying later variants. A unit variant is constructed directly. A tuple
55/// variant deserializes the whole value. A struct variant deserializes each
56/// field from the field of the same name, reporting a failure as
57/// ``field `name`: {error}``; a missing field deserializes from `null`.
58///
59/// The field name follows a field's `#[serde(rename = "...")]` and a variant's
60/// `#[serde(rename_all = "...")]` with the `snake_case`, `lowercase`, or
61/// `camelCase` rule; other `rename_all` rules are not supported. The derive
62/// ignores other `serde` attributes. The expansion uses the `serde` and
63/// `serde_json` crates.
64#[proc_macro_derive(UntaggedDeserialize, attributes(untagged_deserialize, serde))]
65pub fn untagged_deserialize(input: TokenStream) -> TokenStream {
66 let input = parse_macro_input!(input as DeriveInput);
67 match untagged::expand(input) {
68 Ok(expanded) => TokenStream::from(expanded),
69 Err(err) => err.to_compile_error().into(),
70 }
71}