icydb_schema/node/
field.rs

1use crate::prelude::*;
2use canic_utils::case::{Case, Casing};
3
4///
5/// FieldList
6///
7
8#[derive(Clone, Debug, Serialize)]
9pub struct FieldList {
10    pub fields: &'static [Field],
11}
12
13impl FieldList {
14    // get
15    #[must_use]
16    pub fn get(&self, ident: &str) -> Option<&Field> {
17        self.fields.iter().find(|f| f.ident == ident)
18    }
19}
20
21impl ValidateNode for FieldList {}
22
23impl VisitableNode for FieldList {
24    fn drive<V: Visitor>(&self, v: &mut V) {
25        for node in self.fields {
26            node.accept(v);
27        }
28    }
29}
30
31///
32/// Field
33///
34
35#[derive(Clone, Debug, Serialize)]
36pub struct Field {
37    pub ident: &'static str,
38    pub value: Value,
39
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub default: Option<Arg>,
42}
43
44impl ValidateNode for Field {
45    fn validate(&self) -> Result<(), ErrorTree> {
46        let mut errs = ErrorTree::new();
47
48        // idents
49        errs.add_result(validate_ident(self.ident));
50        if let Err(msg) = crate::build::validate::validate_field_name_len(self.ident) {
51            err!(errs, "{msg}");
52        }
53
54        // snake case
55        if !self.ident.is_case(Case::Snake) {
56            err!(errs, "field ident '{}' must be snake_case", self.ident);
57        }
58
59        // relation naming
60        if self.value.item.is_relation() {
61            let ident = self.ident;
62            match self.value.cardinality {
63                Cardinality::One | Cardinality::Opt if !ident.ends_with("id") => {
64                    err!(
65                        errs,
66                        "one or optional relationship '{ident}' should end with 'id'"
67                    );
68                }
69                Cardinality::Many if !ident.ends_with("ids") => {
70                    err!(errs, "many relationship '{ident}' should end with 'ids'");
71                }
72
73                _ => {}
74            }
75        }
76
77        errs.result()
78    }
79}
80
81impl VisitableNode for Field {
82    fn route_key(&self) -> String {
83        self.ident.to_string()
84    }
85
86    fn drive<V: Visitor>(&self, v: &mut V) {
87        self.value.accept(v);
88        if let Some(node) = &self.default {
89            node.accept(v);
90        }
91    }
92}