comnum_spec/blocks/schema/
param.rs1use super::object_schema;
6use comnum_core::{Error, Result};
7use serde::de::DeserializeOwned;
8use serde_json::{Map, Value as Json, json};
9use std::fmt::Write;
10
11#[derive(Clone, Debug)]
13pub enum ParamDefault {
14 Required,
16 Absent(&'static str),
18 Value(Json, String),
20}
21
22#[derive(Clone, Debug)]
24pub struct ParamInfo {
25 pub name: &'static str,
27 pub default: ParamDefault,
29 pub unit: &'static str,
31 pub choices: &'static [&'static str],
33 pub gt: Option<f64>,
35 pub ge: Option<f64>,
37 pub le: Option<f64>,
39 pub lt: Option<f64>,
41 pub doc: &'static str,
43 pub schema: Json,
45 pub variants: Vec<(&'static str, Vec<ParamInfo>)>,
48 pub fields: Vec<ParamInfo>,
51}
52
53impl ParamInfo {
54 fn bounds(&self) -> Vec<String> {
56 [(">", self.gt), (">=", self.ge), ("<=", self.le), ("<", self.lt)]
57 .iter()
58 .filter_map(|(op, b)| b.map(|b| format!("{op} {b}")))
59 .collect()
60 }
61
62 pub fn describe(&self, indent: usize) -> String {
66 let mut doc = self.doc.to_string();
67 if !self.unit.is_empty() {
68 let _ = write!(doc, " [{}]", self.unit);
69 }
70 if !self.choices.is_empty() {
71 let _ = write!(doc, " ({})", self.choices.join(", "));
72 }
73 let bounds = self.bounds();
74 if !bounds.is_empty() {
75 let _ = write!(doc, " ({})", bounds.join(", "));
76 }
77 let pad = " ".repeat(indent);
78 let mut s = format!("{pad}{:<20} {:<14} {doc}\n", self.name, self.default_text());
79 for p in &self.fields {
80 s.push_str(&p.describe(indent + 4));
81 }
82 for (kind, params) in &self.variants {
83 let _ = writeln!(s, "{pad} {{type: {kind}}}");
84 for p in params {
85 s.push_str(&p.describe(indent + 4));
86 }
87 }
88 s
89 }
90
91 pub fn default_text(&self) -> String {
93 match &self.default {
94 ParamDefault::Required => "required".into(),
95 ParamDefault::Absent(t) => (*t).into(),
96 ParamDefault::Value(_, t) => t.clone(),
97 }
98 }
99
100 pub fn json_schema(&self) -> Json {
103 let mut s = match &self.schema {
104 Json::Object(o) => o.clone(),
105 _ => Map::new(),
106 };
107 let mut doc = self.doc.to_string();
108 if !self.unit.is_empty() {
109 s.insert("x-unit".into(), json!(self.unit));
110 let _ = write!(doc, " [{}]", self.unit);
111 }
112 s.insert("description".into(), json!(doc));
113 if !self.choices.is_empty() {
114 match s.get_mut("items") {
116 Some(Json::Object(items)) => {
117 items.insert("enum".into(), json!(self.choices));
118 }
119 _ => {
120 s.insert("enum".into(), json!(self.choices));
121 }
122 }
123 }
124 let bound = |b: f64| Json::from(b);
125 if let Some(b) = self.gt {
126 s.insert("exclusiveMinimum".into(), bound(b));
127 }
128 if let Some(b) = self.ge {
129 s.insert("minimum".into(), bound(b));
130 }
131 if let Some(b) = self.le {
132 s.insert("maximum".into(), bound(b));
133 }
134 if let Some(b) = self.lt {
135 s.insert("exclusiveMaximum".into(), bound(b));
136 }
137 match &self.default {
138 ParamDefault::Value(v, _) => {
139 s.insert("default".into(), v.clone());
140 }
141 ParamDefault::Absent(t) => {
142 s.insert("x-default".into(), json!(t));
143 }
144 ParamDefault::Required => {}
145 }
146 json!({"anyOf": [Json::Object(s), {"type": "string", "pattern": "^\\$", "description": "a grid variable"}]})
147 }
148
149 #[cfg(test)]
151 pub(crate) fn check(&self, kind: &str, v: &Json) -> Result<()> {
152 self.accept(kind, v.clone()).map(drop)
153 }
154
155 pub(crate) fn accept(&self, kind: &str, mut v: Json) -> Result<Json> {
160 if !self.choices.is_empty() {
161 let mut all = true;
162 let mut canonical = |x: &mut Json| {
163 if let Json::String(s) = x {
164 match choice(self.choices, s) {
165 Some(c) => *s = c.to_string(),
166 None => all = false,
167 }
168 }
169 };
170 match &mut v {
171 Json::Array(a) => a.iter_mut().for_each(&mut canonical),
172 x => canonical(x),
173 }
174 if !all {
175 return Err(Error::Config(format!(
176 "block {kind:?}: {} must be one of {:?}, got {v}",
177 self.name, self.choices
178 )));
179 }
180 }
181 let numbers: Vec<f64> = match &v {
182 Json::Number(n) => n.as_f64().into_iter().collect(),
183 Json::Array(a) => a.iter().filter_map(Json::as_f64).collect(),
184 _ => Vec::new(),
185 };
186 for x in numbers {
187 let fails = self.gt.is_some_and(|b| x <= b)
188 || self.ge.is_some_and(|b| x < b)
189 || self.le.is_some_and(|b| x > b)
190 || self.lt.is_some_and(|b| x >= b);
191 if fails {
192 return Err(Error::Config(format!(
193 "block {kind:?}: {} must be {}, got {x}",
194 self.name,
195 self.bounds().join(" and ")
196 )));
197 }
198 }
199 Ok(v)
200 }
201}
202
203pub fn choice(choices: &[&'static str], s: &str) -> Option<&'static str> {
213 choices.iter().copied().find(|c| c.eq_ignore_ascii_case(s))
214}
215
216pub trait ParamType {
218 fn schema() -> Json;
220 fn variants() -> Vec<(&'static str, Vec<ParamInfo>)> {
223 Vec::new()
224 }
225 fn fields() -> Vec<ParamInfo> {
228 Vec::new()
229 }
230}
231
232#[derive(Clone, Debug, Default, PartialEq)]
237pub struct Mapping<T> {
238 pub value: Json,
240 _params: std::marker::PhantomData<fn() -> T>,
241}
242
243impl<'de, T> serde::Deserialize<'de> for Mapping<T> {
244 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
245 Ok(Mapping { value: Json::deserialize(d)?, _params: std::marker::PhantomData })
246 }
247}
248
249impl<T: Params> Mapping<T> {
250 pub fn parse(&self, kind: &str) -> Result<T> {
255 T::parse(kind, self.value.clone())
256 }
257}
258
259impl<T: Params> ParamType for Mapping<T> {
260 fn schema() -> Json {
261 Json::Object(object_schema(&T::params(), &[]))
262 }
263
264 fn fields() -> Vec<ParamInfo> {
265 T::params()
266 }
267}
268
269#[derive(Clone, Debug, Default, PartialEq)]
273pub struct NameOr<T>(pub Mapping<T>);
274
275#[derive(Clone, Debug, PartialEq)]
277pub enum Named<'a, T> {
278 Name(&'a str),
280 Params(T),
282}
283
284impl<'de, T> serde::Deserialize<'de> for NameOr<T> {
285 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
286 Mapping::deserialize(d).map(NameOr)
287 }
288}
289
290impl<T: Params> NameOr<T> {
291 pub fn get(&self, kind: &str) -> Result<Named<'_, T>> {
296 match &self.0.value {
297 Json::String(s) => Ok(Named::Name(s)),
298 Json::Object(_) => self.0.parse(kind).map(Named::Params),
299 other => Err(Error::Config(format!("{kind}: a name or a mapping, got {other}"))),
300 }
301 }
302
303 pub fn json(&self) -> &Json {
305 &self.0.value
306 }
307}
308
309impl<T: Params> ParamType for NameOr<T> {
310 fn schema() -> Json {
311 json!({"anyOf": [{"type": "string"}, Mapping::<T>::schema()]})
312 }
313
314 fn fields() -> Vec<ParamInfo> {
315 T::params()
316 }
317}
318
319impl ParamType for f64 {
320 fn schema() -> Json {
321 json!({"type": "number"})
322 }
323}
324
325macro_rules! unsigned {
326 ($($t:ty),*) => {$(
327 impl ParamType for $t {
328 fn schema() -> Json {
329 json!({"type": "integer", "minimum": 0})
330 }
331 }
332 )*};
333}
334unsigned!(usize, u64, u32, u8);
335
336impl ParamType for i64 {
337 fn schema() -> Json {
338 json!({"type": "integer"})
339 }
340}
341
342impl ParamType for bool {
343 fn schema() -> Json {
344 json!({"type": "boolean"})
345 }
346}
347
348impl ParamType for String {
349 fn schema() -> Json {
350 json!({"type": "string"})
351 }
352}
353
354impl ParamType for Json {
356 fn schema() -> Json {
357 json!({})
358 }
359}
360
361impl<T: ParamType> ParamType for Option<T> {
362 fn schema() -> Json {
363 T::schema()
364 }
365
366 fn variants() -> Vec<(&'static str, Vec<ParamInfo>)> {
367 T::variants()
368 }
369
370 fn fields() -> Vec<ParamInfo> {
371 T::fields()
372 }
373}
374
375impl<T: ParamType, const N: usize> ParamType for [T; N] {
376 fn schema() -> Json {
377 json!({"type": "array", "items": T::schema(), "minItems": N, "maxItems": N})
378 }
379}
380
381impl<T: ParamType> ParamType for Vec<T> {
382 fn schema() -> Json {
383 json!({"type": "array", "items": T::schema()})
384 }
385
386 fn fields() -> Vec<ParamInfo> {
387 T::fields()
388 }
389}
390
391json_param! {
392 NumberOrList,
394 json!({"anyOf": [{"type": "number"}, {"type": "array", "items": {"type": "number"}}]})
395}
396
397pub trait Params: Sized {
399 fn params() -> Vec<ParamInfo>;
401 fn parse(kind: &str, v: Json) -> Result<Self> {
407 let params = Self::params();
408 let mut m = mapping(kind, v, ¶ms)?;
409 Self::take_from(kind, &mut m, ¶ms)
410 }
411 fn take_from(kind: &str, m: &mut Map<String, Json>, params: &[ParamInfo]) -> Result<Self>;
419}
420
421pub fn mapping(kind: &str, v: Json, params: &[ParamInfo]) -> Result<Map<String, Json>> {
427 let m = match v {
428 Json::Object(m) => m,
429 Json::Null => Map::new(),
430 other => return Err(Error::Config(format!("block {kind:?}: the parameters must be a mapping, got {other}"))),
431 };
432 if let Some(unknown) = m.keys().find(|k| !params.iter().any(|p| p.name == k.as_str())) {
433 let names: Vec<String> = params.iter().map(|p| format!("`{}`", p.name)).collect();
434 let expected = match names.len() {
435 0 => "there are no fields".to_string(),
436 1 => format!("expected {}", names[0]),
437 _ => format!("expected one of {}", names.join(", ")),
438 };
439 return Err(Error::Config(format!("block {kind:?}: unknown field `{unknown}`, {expected}")));
440 }
441 Ok(m)
442}
443
444pub fn take<T: DeserializeOwned>(kind: &str, m: &mut Map<String, Json>, params: &[ParamInfo], name: &str) -> Result<T> {
450 let Some(p) = params.iter().find(|p| p.name == name) else {
451 return Err(Error::Config(format!("block {kind:?}: undeclared parameter {name}")));
452 };
453 let v = match m.remove(name) {
454 Some(v) => p.accept(kind, v)?,
455 None => match &p.default {
456 ParamDefault::Required => return Err(Error::Config(format!("block {kind:?}: missing field `{name}`"))),
457 ParamDefault::Absent(_) => Json::Null,
458 ParamDefault::Value(v, _) => v.clone(),
459 },
460 };
461 serde_json::from_value(v).map_err(|e| Error::Config(format!("block {kind:?}: {name}: {e}")))
462}
463
464pub fn shown(tokens: &str) -> String {
467 tokens.strip_prefix('"').and_then(|t| t.strip_suffix('"')).unwrap_or(tokens).to_string()
468}