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 = "<[_]>::is_empty")]
16    pub validators: &'static [TypeValidator],
17
18    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
19    pub sanitizers: &'static [TypeSanitizer],
20
21    #[serde(default, skip_serializing_if = "Not::not")]
22    pub indirect: bool,
23}
24
25impl Item {
26    #[must_use]
27    pub const fn is_relation(&self) -> bool {
28        self.relation.is_some()
29    }
30}
31
32impl ValidateNode for Item {
33    fn validate(&self) -> Result<(), ErrorTree> {
34        let mut errs = ErrorTree::new();
35        let schema = schema_read();
36
37        match &self.target {
38            ItemTarget::Is(path) => {
39                // cannot be an entity
40                if schema.check_node_as::<Entity>(path).is_ok() {
41                    err!(errs, "a non-relation Item cannot reference an Entity");
42                }
43            }
44
45            ItemTarget::Primitive(_) => {}
46        }
47
48        // relation
49        if let Some(relation) = &self.relation {
50            if self.indirect {
51                err!(errs, "relations cannot be set to indirect");
52            }
53
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 to self.target()
62                        if &self.target != relation_target {
63                            err!(
64                                errs,
65                                "relation target type mismatch: expected {:?}, found {:?}",
66                                relation_target,
67                                self.target
68                            );
69                        }
70                    } else {
71                        err!(
72                            errs,
73                            "relation entity '{relation}' missing primary key field '{0}'",
74                            entity.primary_key
75                        );
76                    }
77                }
78                Err(_) => {
79                    err!(errs, "relation entity '{relation}' not found");
80                }
81            }
82        }
83
84        errs.result()
85    }
86}
87
88impl VisitableNode for Item {
89    fn drive<V: Visitor>(&self, v: &mut V) {
90        for node in self.validators {
91            node.accept(v);
92        }
93    }
94}
95
96///
97/// ItemTarget
98///
99
100#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
101pub enum ItemTarget {
102    Is(&'static str),
103    Primitive(Primitive),
104}