icydb_schema/node/
item.rs1use crate::prelude::*;
2use std::ops::Not;
3
4#[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 match self.target() {
91 ItemTarget::Is(path) => {
92 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 if let Some(relation) = self.relation() {
103 match schema.cast_node::<Entity>(relation) {
105 Ok(entity) => {
106 if let Some(primary_field) = entity.get_pk_field() {
108 let relation_target = primary_field.value().item().target();
109
110 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#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
153pub enum ItemTarget {
154 Is(&'static str),
155 Primitive(Primitive),
156}