1#[cfg(test)]
8mod tests;
9
10use crate::{node::relation::RelationComponentContract, prelude::*};
11use std::ops::Not;
12
13#[derive(Clone, Debug, Serialize)]
21pub struct Item {
22 target: ItemTarget,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
25 relation: Option<&'static str>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
28 scale: Option<u32>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
31 max_len: Option<u32>,
32
33 #[serde(skip_serializing_if = "<[_]>::is_empty")]
34 validators: &'static [TypeValidator],
35
36 #[serde(skip_serializing_if = "<[_]>::is_empty")]
37 normalizers: &'static [TypeNormalizer],
38
39 #[serde(skip_serializing_if = "Not::not")]
40 indirect: bool,
41}
42
43impl Item {
44 #[must_use]
45 pub const fn new(
46 target: ItemTarget,
47 relation: Option<&'static str>,
48 scale: Option<u32>,
49 max_len: Option<u32>,
50 validators: &'static [TypeValidator],
51 normalizers: &'static [TypeNormalizer],
52 indirect: bool,
53 ) -> Self {
54 Self {
55 target,
56 relation,
57 scale,
58 max_len,
59 validators,
60 normalizers,
61 indirect,
62 }
63 }
64
65 #[must_use]
66 pub const fn target(&self) -> &ItemTarget {
67 &self.target
68 }
69
70 #[must_use]
71 pub const fn relation(&self) -> Option<&'static str> {
72 self.relation
73 }
74
75 #[must_use]
76 pub const fn scale(&self) -> Option<u32> {
77 self.scale
78 }
79
80 #[must_use]
81 pub const fn max_len(&self) -> Option<u32> {
82 self.max_len
83 }
84
85 #[must_use]
86 pub const fn validators(&self) -> &'static [TypeValidator] {
87 self.validators
88 }
89
90 #[must_use]
91 pub const fn normalizers(&self) -> &'static [TypeNormalizer] {
92 self.normalizers
93 }
94
95 #[must_use]
96 pub const fn indirect(&self) -> bool {
97 self.indirect
98 }
99
100 #[must_use]
101 pub const fn is_relation(&self) -> bool {
102 self.relation().is_some()
103 }
104}
105
106impl ValidateNode for Item {
107 fn validate(&self) -> Result<(), ErrorTree> {
108 let mut errs = ErrorTree::new();
109 let schema = schema_read();
110
111 match self.target() {
113 ItemTarget::Is(path) => {
114 if schema.check_node_as::<Entity>(path).is_ok() {
116 err!(errs, "a non-relation Item cannot reference an Entity");
117 }
118 }
119
120 ItemTarget::Primitive(_) => {}
121 }
122
123 if let Some(relation) = self.relation() {
125 match schema.cast_node::<Entity>(relation) {
126 Ok(entity) => {
127 if entity.primary_key().fields().len() != 1 {
128 err!(
129 errs,
130 "relation entity '{relation}' uses composite primary key fields {:?}; single-field relation targets require a scalar primary key; use ordered relation tuple metadata for composite targets",
131 entity.primary_key().fields()
132 );
133 } else if let Some(primary_field) = entity.scalar_primary_key_field() {
134 let expected = RelationComponentContract::from_field(primary_field);
135 let actual = RelationComponentContract::from_item(self);
136 if expected.mismatches(actual) {
137 err!(
138 errs,
139 "relation target type mismatch: expected ({:?}, scale={:?}, max_len={:?}), found ({:?}, scale={:?}, max_len={:?})",
140 expected.target(),
141 expected.scale(),
142 expected.max_len(),
143 actual.target(),
144 actual.scale(),
145 actual.max_len(),
146 );
147 }
148 } else {
149 let primary_key_field =
150 entity.primary_key().scalar_field().unwrap_or("<composite>");
151 err!(
152 errs,
153 "relation entity '{relation}' missing primary key field '{0}'",
154 primary_key_field
155 );
156 }
157 }
158 Err(_) => {
159 err!(errs, "relation entity '{relation}' not found");
160 }
161 }
162 }
163
164 errs.result()
165 }
166}
167
168impl VisitableNode for Item {
169 fn drive<V: Visitor>(&self, v: &mut V) {
170 for node in self.validators() {
171 node.accept(v);
172 }
173 }
174}
175
176#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
184pub enum ItemTarget {
185 Is(&'static str),
186 Primitive(Primitive),
187}