Skip to main content

polyspec_crudui_generator/
text.rs

1//! Input text of the generator operations over JSON text
2//! (docs/spec/input-text.md).
3//!
4//! A Rust `String` is always a sequence of Unicode scalar values, so the value
5//! operations need no check. A caller that decodes JSON text with
6//! [`JsonText`] passes its parts here, in the
7//! order each operation checks them, before decoding them into values.
8
9use polyspec_crudui_validator::text::{check_inputs, check_specification};
10use polyspec_crudui_validator::validate::ValidateError;
11
12pub use polyspec_crudui_validator::text::{JsonString, JsonText, JsonTextError};
13
14use crate::error::{FormError, FormResult};
15
16/// Options of a form binding or instance, in code point order of their names.
17const BIND_OPTIONS: [&str; 4] = ["idPrefix", "keyPrefix", "language", "unsupported"];
18
19/// Options of a list or detail model, in code point order of their names.
20const DISPLAY_OPTIONS: [&str; 4] = ["basepath", "data", "language", "layout"];
21
22fn inputs(values: &[(&str, Option<&JsonText>)]) -> FormResult<()> {
23    check_inputs(values).map_err(|error| FormError::input(error.message))
24}
25
26/// The specification and file checks: a value over the limits is an input failure, invalid
27/// text a load failure.
28fn specification(spec: Option<&JsonText>, files: Option<&JsonText>) -> FormResult<()> {
29    check_specification(spec, files).map_err(|error| match error {
30        ValidateError::Input(error) => FormError::input(error.message),
31        ValidateError::Load(error) => error.into(),
32    })
33}
34
35fn options<'a>(
36    options: Option<&'a JsonText>,
37    names: &[&str],
38) -> Vec<(String, Option<&'a JsonText>)> {
39    names
40        .iter()
41        .map(|name| (format!("options.{name}"), options.and_then(|o| o.get(name))))
42        .collect()
43}
44
45fn check(
46    arguments: &[(&str, Option<&JsonText>)],
47    named: Vec<(String, Option<&JsonText>)>,
48) -> FormResult<()> {
49    inputs(arguments)?;
50    let named: Vec<(&str, Option<&JsonText>)> = named
51        .iter()
52        .map(|(name, value)| (name.as_str(), *value))
53        .collect();
54    inputs(&named)
55}
56
57/// The checks of `compile_form`: the specification, the files, then the
58/// `basepath` and `keyPrefix` options.
59pub fn check_compile_form(
60    spec: Option<&JsonText>,
61    options_text: Option<&JsonText>,
62) -> FormResult<()> {
63    specification(spec, options_text.and_then(|o| o.get("files")))?;
64    check(&[], options(options_text, &["basepath", "keyPrefix"]))
65}
66
67/// The checks of `bind_form`, `bind_buttons` and `Form::new`: the template,
68/// the data, then the binding options.
69pub fn check_bind(
70    template: Option<&JsonText>,
71    data: Option<&JsonText>,
72    options_text: Option<&JsonText>,
73) -> FormResult<()> {
74    check(
75        &[("template", template), ("data", data)],
76        options(options_text, &BIND_OPTIONS),
77    )
78}
79
80/// The checks of a list or detail model: the specification, the files, the
81/// rows or record named `input`, then the display options.
82pub fn check_display(
83    spec: Option<&JsonText>,
84    input: &str,
85    value: Option<&JsonText>,
86    options_text: Option<&JsonText>,
87) -> FormResult<()> {
88    specification(spec, options_text.and_then(|o| o.get("files")))?;
89    check(&[(input, value)], options(options_text, &DISPLAY_OPTIONS))
90}
91
92/// The checks of a `Form` method named as in the JavaScript instance API, over
93/// its JSON arguments in order.
94pub fn check_form_method(method: &str, args: &[JsonText]) -> FormResult<()> {
95    let arg = |index: usize| args.get(index);
96    match method {
97        "setData" => check(&[("data", arg(0))], Vec::new()),
98        "getValue" => check(&[("path", arg(0))], Vec::new()),
99        "setValue" => check(&[("path", arg(0)), ("value", arg(1))], Vec::new()),
100        "addRow" => check(
101            &[("path", arg(0))],
102            options(arg(1), &["afterKey", "key", "value"]),
103        ),
104        "copyRow" => check(
105            &[("path", arg(0)), ("key", arg(1))],
106            options(arg(2), &["afterKey", "key"]),
107        ),
108        "removeRow" | "moveRow" => check(&[("path", arg(0)), ("key", arg(1))], Vec::new()),
109        "rekeyRow" => check(
110            &[("path", arg(0)), ("oldKey", arg(1)), ("newKey", arg(2))],
111            Vec::new(),
112        ),
113        _ => Ok(()),
114    }
115}