Skip to main content

icydb_schema/node/
item.rs

1use crate::prelude::*;
2use std::ops::Not;
3
4///
5/// Item
6///
7/// Canonical schema item descriptor for one scalar, relation, or primitive
8/// field target plus its attached sanitizers and validators.
9///
10
11#[derive(Clone, Debug, Serialize)]
12pub struct Item {
13    target: ItemTarget,
14
15    #[serde(skip_serializing_if = "Option::is_none")]
16    relation: Option<&'static str>,
17
18    #[serde(skip_serializing_if = "Option::is_none")]
19    scale: Option<u32>,
20
21    #[serde(skip_serializing_if = "<[_]>::is_empty")]
22    validators: &'static [TypeValidator],
23
24    #[serde(skip_serializing_if = "<[_]>::is_empty")]
25    sanitizers: &'static [TypeSanitizer],
26
27    #[serde(skip_serializing_if = "Not::not")]
28    indirect: bool,
29}
30
31impl Item {
32    #[must_use]
33    pub const fn new(
34        target: ItemTarget,
35        relation: Option<&'static str>,
36        scale: Option<u32>,
37        validators: &'static [TypeValidator],
38        sanitizers: &'static [TypeSanitizer],
39        indirect: bool,
40    ) -> Self {
41        Self {
42            target,
43            relation,
44            scale,
45            validators,
46            sanitizers,
47            indirect,
48        }
49    }
50
51    #[must_use]
52    pub const fn target(&self) -> &ItemTarget {
53        &self.target
54    }
55
56    #[must_use]
57    pub const fn relation(&self) -> Option<&'static str> {
58        self.relation
59    }
60
61    #[must_use]
62    pub const fn scale(&self) -> Option<u32> {
63        self.scale
64    }
65
66    #[must_use]
67    pub const fn validators(&self) -> &'static [TypeValidator] {
68        self.validators
69    }
70
71    #[must_use]
72    pub const fn sanitizers(&self) -> &'static [TypeSanitizer] {
73        self.sanitizers
74    }
75
76    #[must_use]
77    pub const fn indirect(&self) -> bool {
78        self.indirect
79    }
80
81    #[must_use]
82    pub const fn is_relation(&self) -> bool {
83        self.relation().is_some()
84    }
85}
86
87impl ValidateNode for Item {
88    fn validate(&self) -> Result<(), ErrorTree> {
89        let mut errs = ErrorTree::new();
90        let schema = schema_read();
91
92        // Phase 1: validate target shape.
93        match self.target() {
94            ItemTarget::Is(path) => {
95                // cannot be an entity
96                if schema.check_node_as::<Entity>(path).is_ok() {
97                    err!(errs, "a non-relation Item cannot reference an Entity");
98                }
99            }
100
101            ItemTarget::Primitive(_) => {}
102        }
103
104        // Phase 2: validate relation target compatibility.
105        if let Some(relation) = self.relation() {
106            // Step 1: Ensure the relation path exists and is an Entity
107            match schema.cast_node::<Entity>(relation) {
108                Ok(entity) => {
109                    // Step 2: Get target of the relation entity (usually from its primary key field)
110                    if let Some(primary_field) = entity.get_pk_field() {
111                        let relation_target = primary_field.value().item().target();
112
113                        // Step 3: Compare declared item target and decimal-scale metadata.
114                        let relation_scale = primary_field.value().item().scale();
115                        if self.target() != relation_target || self.scale() != relation_scale {
116                            err!(
117                                errs,
118                                "relation target type mismatch: expected ({:?}, scale={:?}), found ({:?}, scale={:?})",
119                                relation_target,
120                                relation_scale,
121                                self.target(),
122                                self.scale()
123                            );
124                        }
125                    } else {
126                        err!(
127                            errs,
128                            "relation entity '{relation}' missing primary key field '{0}'",
129                            entity.primary_key().field()
130                        );
131                    }
132                }
133                Err(_) => {
134                    err!(errs, "relation entity '{relation}' not found");
135                }
136            }
137        }
138
139        errs.result()
140    }
141}
142
143impl VisitableNode for Item {
144    fn drive<V: Visitor>(&self, v: &mut V) {
145        for node in self.validators() {
146            node.accept(v);
147        }
148    }
149}
150
151///
152/// ItemTarget
153///
154/// Local item target declaration, either by schema path or primitive runtime
155/// kind.
156///
157
158#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
159pub enum ItemTarget {
160    Is(&'static str),
161    Primitive(Primitive),
162}