Skip to main content

comnum_spec/blocks/schema/
macros.rs

1//! The macros that declare parameters: `block_params!` (a block or a group of
2//! parameters) and `json_param!` (a parameter of several JSON shapes).
3
4/// A parameter whose value has several JSON shapes (a name or a schedule, a number or a
5/// list), kept as given, with its JSON Schema: `json_param! { /// DOC NAME, SCHEMA }`.
6macro_rules! json_param {
7    ($(#[$m:meta])* $name:ident, $schema:expr $(,)?) => {
8        $(#[$m])*
9        #[derive(Clone, Debug, serde::Deserialize)]
10        #[serde(transparent)]
11        pub(crate) struct $name(pub(crate) serde_json::Value);
12
13        impl $crate::blocks::schema::ParamType for $name {
14            fn schema() -> serde_json::Value {
15                $schema
16            }
17        }
18    };
19}
20
21/// Declare the parameters of a block: the structure, its [`Params`](crate::blocks::schema::Params) parser and its
22/// documentation (see the [module documentation](crate::blocks::schema)).
23///
24/// ```text
25/// block_params! {
26///     [pub] struct NAME {
27///         FIELD: TYPE [DEFAULT] ["UNIT"] [one_of [VALUE, …] | one_of (CONST)] [> B] [>= B] [<= B] [< B], "MEANING";
28///         FIELD: GROUP [flatten], "MEANING";
29///         …
30///     }
31/// }
32/// ```
33///
34/// `DEFAULT` is `required`, `absent`, `absent "TEXT"`, a value or `VALUE => "TEXT"`; the
35/// type of a field implements [`ParamType`](crate::blocks::schema::ParamType) (its JSON Schema) and `Deserialize`. The
36/// accepted values are a list of literals or, in parentheses, a constant array of
37/// strings kept with the type they name (`one_of (DetectorKind::NAMES)`). A `[flatten]`
38/// field is a group of parameters declared once with `block_params!` and shared by
39/// several blocks: its parameters are those of the block, at the place of the field,
40/// and a name that neither the block nor the group declares is still rejected.
41///
42/// ```
43/// use comnum_spec::blocks::Params;
44/// comnum_spec::block_params! {
45///     struct Decoder {
46///         iterations: usize [50] >= 1, "decoding iterations at most";
47///     }
48/// }
49/// comnum_spec::block_params! {
50///     struct Code {
51///         n: usize [required], "length";
52///         decoder: Decoder [flatten], "the decoder";
53///     }
54/// }
55/// let c = Code::parse("code", serde_json::json!({"n": 8, "iterations": 5})).unwrap();
56/// assert_eq!((c.n, c.decoder.iterations), (8, 5));
57/// assert_eq!(Code::params().iter().map(|p| p.name).collect::<Vec<_>>(), ["n", "iterations"]);
58/// assert!(Code::parse("code", serde_json::json!({"n": 8, "rule": "min_sum"})).is_err());
59/// ```
60#[macro_export]
61macro_rules! block_params {
62    (@default required) => { $crate::blocks::schema::ParamDefault::Required };
63    (@default absent) => { $crate::blocks::schema::ParamDefault::Absent("none") };
64    (@default absent $text:literal) => { $crate::blocks::schema::ParamDefault::Absent($text) };
65    (@default $v:expr => $text:literal) => {
66        $crate::blocks::schema::ParamDefault::Value($crate::__macro::json!($v), $text.to_string())
67    };
68    (@default $v:expr) => {
69        $crate::blocks::schema::ParamDefault::Value(
70            $crate::__macro::json!($v),
71            $crate::blocks::schema::shown(stringify!($v)),
72        )
73    };
74    (@or $d:literal) => { $d };
75    (@or $d:literal $v:literal) => { $v };
76    (@bound) => { None };
77    (@bound $b:literal) => { Some(f64::from($b)) };
78    (@choices) => { &[] };
79    (@choices [$($c:literal),+ $(,)?]) => { &[$($c),+] };
80    (@choices ($c:expr)) => { &$c };
81    (@info $v:ident, $field:ident, $ty:ty, [flatten], $($rest:tt)*) => {
82        $v.extend(<$ty as $crate::blocks::schema::Params>::params());
83    };
84    (
85        @info $v:ident, $field:ident, $ty:ty, [$($def:tt)+], [$($unit:literal)?], [$($choices:tt)?],
86        [$($gt:literal)?], [$($ge:literal)?], [$($le:literal)?], [$($lt:literal)?], $doc:expr
87    ) => {
88        $v.push($crate::blocks::schema::ParamInfo {
89            name: stringify!($field),
90            default: $crate::block_params!(@default $($def)+),
91            unit: $crate::block_params!(@or "" $($unit)?),
92            choices: $crate::block_params!(@choices $($choices)?),
93            gt: $crate::block_params!(@bound $($gt)?),
94            ge: $crate::block_params!(@bound $($ge)?),
95            le: $crate::block_params!(@bound $($le)?),
96            lt: $crate::block_params!(@bound $($lt)?),
97            doc: $doc,
98            schema: <$ty as $crate::blocks::schema::ParamType>::schema(),
99            variants: <$ty as $crate::blocks::schema::ParamType>::variants(),
100            fields: <$ty as $crate::blocks::schema::ParamType>::fields(),
101        });
102    };
103    (@take $kind:ident, $m:ident, $params:ident, $field:ident, $ty:ty, [flatten]) => {
104        <$ty as $crate::blocks::schema::Params>::take_from($kind, $m, $params)?
105    };
106    (@take $kind:ident, $m:ident, $params:ident, $field:ident, $ty:ty, [$($def:tt)+]) => {
107        $crate::blocks::schema::take($kind, $m, $params, stringify!($field))?
108    };
109    (
110        $(#[$sattr:meta])*
111        $vis:vis struct $name:ident {
112            $(
113                $(#[$fattr:meta])*
114                $field:ident : $ty:ty [ $($def:tt)+ ]
115                $($unit:literal)?
116                $(one_of $choices:tt)?
117                $(> $gt:literal)?
118                $(>= $ge:literal)?
119                $(<= $le:literal)?
120                $(< $lt:literal)?
121                , $doc:expr ;
122            )*
123        }
124    ) => {
125        $(#[$sattr])*
126        $vis struct $name {
127            $( $(#[$fattr])* $field: $ty, )*
128        }
129
130        impl $crate::blocks::schema::Params for $name {
131            #[allow(unused_mut, reason = "a block without parameters")]
132            fn params() -> Vec<$crate::blocks::schema::ParamInfo> {
133                let mut v = Vec::new();
134                $(
135                    $crate::block_params!(
136                        @info v, $field, $ty, [$($def)+], [$($unit)?], [$($choices)?],
137                        [$($gt)?], [$($ge)?], [$($le)?], [$($lt)?], $doc
138                    );
139                )*
140                v
141            }
142
143            #[allow(unused_variables, reason = "a block without parameters")]
144            fn take_from(
145                kind: &str,
146                m: &mut $crate::__macro::Map<String, $crate::__macro::Json>,
147                params: &[$crate::blocks::schema::ParamInfo],
148            ) -> $crate::__macro::Result<Self> {
149                Ok($name {
150                    $( $field: $crate::block_params!(@take kind, m, params, $field, $ty, [$($def)+]), )*
151                })
152            }
153        }
154    };
155}