icydb_schema/node/
item.rs1use crate::prelude::*;
2use std::ops::Not;
3
4#[derive(Clone, Debug, Serialize)]
12pub struct Item {
13 target: ItemTarget,
14
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 relation: Option<&'static str>,
17
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 scale: Option<u32>,
20
21 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
22 validators: &'static [TypeValidator],
23
24 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
25 sanitizers: &'static [TypeSanitizer],
26
27 #[serde(default, 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 match self.target() {
94 ItemTarget::Is(path) => {
95 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 if let Some(relation) = self.relation() {
106 match schema.cast_node::<Entity>(relation) {
108 Ok(entity) => {
109 if let Some(primary_field) = entity.get_pk_field() {
111 let relation_target = primary_field.value().item().target();
112
113 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#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
159pub enum ItemTarget {
160 Is(&'static str),
161 Primitive(Primitive),
162}