Skip to main content

icydb_schema/node/
item.rs

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