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    pub target: ItemTarget,
11
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub relation: Option<&'static str>,
14
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub scale: Option<u32>,
17
18    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
19    pub validators: &'static [TypeValidator],
20
21    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
22    pub sanitizers: &'static [TypeSanitizer],
23
24    #[serde(default, skip_serializing_if = "Not::not")]
25    pub indirect: bool,
26}
27
28impl Item {
29    #[must_use]
30    pub const fn is_relation(&self) -> bool {
31        self.relation.is_some()
32    }
33}
34
35impl ValidateNode for Item {
36    fn validate(&self) -> Result<(), ErrorTree> {
37        let mut errs = ErrorTree::new();
38        let schema = schema_read();
39
40        // Phase 1: validate target shape.
41        match &self.target {
42            ItemTarget::Is(path) => {
43                // cannot be an entity
44                if schema.check_node_as::<Entity>(path).is_ok() {
45                    err!(errs, "a non-relation Item cannot reference an Entity");
46                }
47            }
48
49            ItemTarget::Primitive(_) => {}
50        }
51
52        // Phase 2: validate relation target compatibility.
53        if let Some(relation) = &self.relation {
54            // Step 1: Ensure the relation path exists and is an Entity
55            match schema.cast_node::<Entity>(relation) {
56                Ok(entity) => {
57                    // Step 2: Get target of the relation entity (usually from its primary key field)
58                    if let Some(primary_field) = entity.get_pk_field() {
59                        let relation_target = &primary_field.value.item.target;
60
61                        // Step 3: Compare declared item target and decimal-scale metadata.
62                        let relation_scale = primary_field.value.item.scale;
63                        if &self.target != relation_target || self.scale != relation_scale {
64                            err!(
65                                errs,
66                                "relation target type mismatch: expected ({:?}, scale={:?}), found ({:?}, scale={:?})",
67                                relation_target,
68                                relation_scale,
69                                self.target,
70                                self.scale
71                            );
72                        }
73                    } else {
74                        err!(
75                            errs,
76                            "relation entity '{relation}' missing primary key field '{0}'",
77                            entity.primary_key.field
78                        );
79                    }
80                }
81                Err(_) => {
82                    err!(errs, "relation entity '{relation}' not found");
83                }
84            }
85        }
86
87        errs.result()
88    }
89}
90
91impl VisitableNode for Item {
92    fn drive<V: Visitor>(&self, v: &mut V) {
93        for node in self.validators {
94            node.accept(v);
95        }
96    }
97}
98
99///
100/// ItemTarget
101///
102
103#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
104pub enum ItemTarget {
105    Is(&'static str),
106    Primitive(Primitive),
107}