icydb_schema/node/
entity.rs1use crate::prelude::*;
2use std::any::Any;
3
4#[derive(Clone, Debug, Serialize)]
9pub struct Entity {
10 def: Def,
11 store: &'static str,
12 schema_version: u32,
13 primary_key: PrimaryKey,
14
15 #[serde(skip_serializing_if = "Option::is_none")]
16 name: Option<&'static str>,
17
18 #[serde(skip_serializing_if = "<[_]>::is_empty")]
19 indexes: &'static [Index],
20
21 #[serde(skip_serializing_if = "<[_]>::is_empty")]
22 relations: &'static [RelationEdge],
23
24 fields: FieldList,
25 ty: Type,
26}
27
28impl Entity {
29 #[must_use]
30 #[expect(
31 clippy::too_many_arguments,
32 reason = "schema entity construction keeps store, key, index, relation, field, and type metadata explicit"
33 )]
34 pub const fn new(
35 def: Def,
36 store: &'static str,
37 schema_version: u32,
38 primary_key: PrimaryKey,
39 name: Option<&'static str>,
40 indexes: &'static [Index],
41 relations: &'static [RelationEdge],
42 fields: FieldList,
43 ty: Type,
44 ) -> Self {
45 Self {
46 def,
47 store,
48 schema_version,
49 primary_key,
50 name,
51 indexes,
52 relations,
53 fields,
54 ty,
55 }
56 }
57
58 #[must_use]
59 pub const fn def(&self) -> &Def {
60 &self.def
61 }
62
63 #[must_use]
64 pub const fn store(&self) -> &'static str {
65 self.store
66 }
67
68 #[must_use]
69 pub const fn schema_version(&self) -> u32 {
70 self.schema_version
71 }
72
73 #[must_use]
74 pub const fn primary_key(&self) -> &PrimaryKey {
75 &self.primary_key
76 }
77
78 #[must_use]
79 pub const fn name(&self) -> Option<&'static str> {
80 self.name
81 }
82
83 #[must_use]
84 pub const fn indexes(&self) -> &'static [Index] {
85 self.indexes
86 }
87
88 #[must_use]
89 pub const fn relations(&self) -> &'static [RelationEdge] {
90 self.relations
91 }
92
93 #[must_use]
94 pub const fn fields(&self) -> &FieldList {
95 &self.fields
96 }
97
98 #[must_use]
99 pub const fn ty(&self) -> &Type {
100 &self.ty
101 }
102
103 #[must_use]
106 pub fn scalar_primary_key_field(&self) -> Option<&Field> {
107 self.fields().get(self.primary_key().scalar_field()?)
108 }
109
110 #[must_use]
112 pub fn resolved_name(&self) -> &'static str {
113 self.name().unwrap_or_else(|| self.def().ident())
114 }
115
116 fn validate_relation_storage_policy(&self, errs: &mut ErrorTree) {
117 for field in self.fields().fields() {
118 if let Some(target) = field.value().item().relation() {
119 self.validate_relation_target_storage_policy(errs, field.ident(), target);
120 }
121 }
122
123 for relation in self.relations() {
124 self.validate_relation_target_storage_policy(errs, relation.ident(), relation.target());
125 }
126 }
127
128 fn validate_relation_target_storage_policy(
129 &self,
130 errs: &mut ErrorTree,
131 relation_name: &str,
132 target_path: &str,
133 ) {
134 let schema = schema_read();
135 let Ok(source_store) = schema.cast_node::<Store>(self.store()) else {
136 return;
137 };
138 let Ok(target) = schema.cast_node::<Self>(target_path) else {
139 return;
140 };
141 let Ok(target_store) = schema.cast_node::<Store>(target.store()) else {
142 return;
143 };
144
145 let source_capabilities = source_store.storage_capabilities();
146 let target_capabilities = target_store.storage_capabilities();
147 if matches!(
148 source_capabilities.relation_source(),
149 RelationSourceCapability::DurableSource
150 ) && matches!(
151 target_capabilities.relation_target(),
152 RelationTargetCapability::VolatileTarget
153 ) {
154 err!(
155 errs,
156 "relation '{}' from durable store '{}' to volatile target store '{}' is not supported; durable stores cannot own referential integrity against volatile heap targets",
157 relation_name,
158 self.store(),
159 target.store(),
160 );
161 }
162 }
163}
164
165impl MacroNode for Entity {
166 fn as_any(&self) -> &dyn Any {
167 self
168 }
169}
170
171impl ValidateNode for Entity {
172 fn validate(&self) -> Result<(), ErrorTree> {
173 let mut errs = ErrorTree::new();
174 let schema = schema_read();
175
176 if self.schema_version() == 0 {
177 err!(errs, "entity schema_version must be a positive integer");
178 }
179
180 match schema.cast_node::<Store>(self.store()) {
182 Ok(_) => {}
183 Err(e) => errs.add(e),
184 }
185
186 for relation in self.relations() {
187 if let Err(e) = relation.validate_for_source(self) {
188 errs.merge_for(relation.ident(), e);
189 }
190 }
191 self.validate_relation_storage_policy(&mut errs);
192
193 errs.result()
194 }
195}
196
197impl VisitableNode for Entity {
198 fn route_key(&self) -> String {
199 self.def().path()
200 }
201
202 fn drive<V: Visitor>(&self, v: &mut V) {
203 self.def().accept(v);
204 self.fields().accept(v);
205 self.ty().accept(v);
206 }
207}
208
209#[cfg(test)]
210mod tests;