1use crate::model::{field::FieldModel, index::IndexModel};
7
8#[derive(Debug)]
17pub struct PrimaryKeyModel {
18 fields: PrimaryKeyModelFields,
19}
20
21impl PrimaryKeyModel {
22 #[must_use]
24 pub const fn scalar(field: &'static FieldModel) -> Self {
25 Self {
26 fields: PrimaryKeyModelFields::Scalar(field),
27 }
28 }
29
30 #[must_use]
32 pub const fn ordered(fields: &'static [&'static FieldModel]) -> Self {
33 assert!(!fields.is_empty(), "primary key model requires fields");
34 Self {
35 fields: PrimaryKeyModelFields::Ordered(fields),
36 }
37 }
38
39 #[must_use]
41 pub const fn len(&self) -> usize {
42 match self.fields {
43 PrimaryKeyModelFields::Scalar(_) => 1,
44 PrimaryKeyModelFields::Ordered(fields) => fields.len(),
45 }
46 }
47
48 #[must_use]
50 pub const fn is_empty(&self) -> bool {
51 self.len() == 0
52 }
53
54 #[must_use]
56 pub const fn is_scalar(&self) -> bool {
57 self.len() == 1
58 }
59
60 #[must_use]
66 pub const fn first_field(&self) -> &'static FieldModel {
67 match self.fields {
68 PrimaryKeyModelFields::Scalar(field) => field,
69 PrimaryKeyModelFields::Ordered(fields) => fields[0],
70 }
71 }
72
73 #[must_use]
75 pub const fn fields(&self) -> PrimaryKeyModelFields {
76 self.fields
77 }
78}
79
80#[derive(Clone, Copy, Debug)]
87pub enum PrimaryKeyModelFields {
88 Scalar(&'static FieldModel),
89 Ordered(&'static [&'static FieldModel]),
90}
91
92impl PrimaryKeyModelFields {
93 #[must_use]
95 pub const fn len(self) -> usize {
96 match self {
97 Self::Scalar(_) => 1,
98 Self::Ordered(fields) => fields.len(),
99 }
100 }
101
102 #[must_use]
104 pub const fn is_empty(self) -> bool {
105 self.len() == 0
106 }
107
108 #[must_use]
110 pub fn get(self, index: usize) -> Option<&'static FieldModel> {
111 match self {
112 Self::Scalar(field) => (index == 0).then_some(field),
113 Self::Ordered(fields) => fields.get(index).copied(),
114 }
115 }
116
117 #[must_use]
119 pub const fn iter(self) -> PrimaryKeyModelFieldIter {
120 PrimaryKeyModelFieldIter {
121 fields: self,
122 index: 0,
123 }
124 }
125}
126
127#[derive(Clone, Debug)]
134pub struct PrimaryKeyModelFieldIter {
135 fields: PrimaryKeyModelFields,
136 index: usize,
137}
138
139impl Iterator for PrimaryKeyModelFieldIter {
140 type Item = &'static FieldModel;
141
142 fn next(&mut self) -> Option<Self::Item> {
143 let item = self.fields.get(self.index)?;
144 self.index += 1;
145 Some(item)
146 }
147}
148
149#[cfg(test)]
150mod primary_key_model_tests {
151 use super::{PrimaryKeyModel, PrimaryKeyModelFields};
152 use crate::model::FieldModel;
153
154 static ID_FIELD: FieldModel = FieldModel::generated("id", crate::model::FieldKind::Nat64);
155 static TENANT_FIELD: FieldModel =
156 FieldModel::generated("tenant_id", crate::model::FieldKind::Nat64);
157 static ORDERED_FIELDS: [&FieldModel; 2] = [&ID_FIELD, &TENANT_FIELD];
158
159 #[test]
160 fn scalar_primary_key_model_exposes_one_field() {
161 let model = PrimaryKeyModel::scalar(&ID_FIELD);
162
163 assert_eq!(model.len(), 1);
164 assert!(model.is_scalar());
165 assert_eq!(model.first_field().name(), "id");
166 assert_eq!(
167 model
168 .fields()
169 .iter()
170 .map(FieldModel::name)
171 .collect::<Vec<_>>(),
172 ["id"]
173 );
174 }
175
176 #[test]
177 fn ordered_primary_key_model_preserves_field_order() {
178 let model = PrimaryKeyModel::ordered(&ORDERED_FIELDS);
179
180 assert_eq!(model.len(), 2);
181 assert!(!model.is_scalar());
182 assert_eq!(model.first_field().name(), "id");
183 assert_eq!(
184 model
185 .fields()
186 .iter()
187 .map(FieldModel::name)
188 .collect::<Vec<_>>(),
189 ["id", "tenant_id"],
190 );
191 std::assert_matches!(model.fields(), PrimaryKeyModelFields::Ordered(_));
192 }
193}
194
195#[derive(Debug)]
204pub struct RelationEdgeModel {
205 name: &'static str,
206 target_path: &'static str,
207 local_fields: &'static [&'static FieldModel],
208}
209
210impl RelationEdgeModel {
211 #[must_use]
214 pub const fn generated(
215 name: &'static str,
216 target_path: &'static str,
217 local_fields: &'static [&'static FieldModel],
218 ) -> Self {
219 Self {
220 name,
221 target_path,
222 local_fields,
223 }
224 }
225
226 #[must_use]
228 pub const fn name(&self) -> &'static str {
229 self.name
230 }
231
232 #[must_use]
234 pub const fn target_path(&self) -> &'static str {
235 self.target_path
236 }
237
238 #[must_use]
240 pub const fn local_fields(&self) -> &'static [&'static FieldModel] {
241 self.local_fields
242 }
243}
244
245#[cfg(test)]
246mod relation_edge_model_tests {
247 use super::RelationEdgeModel;
248 use crate::model::{FieldKind, FieldModel};
249
250 static TENANT_FIELD: FieldModel = FieldModel::generated("tenant_id", FieldKind::Nat64);
251 static USER_FIELD: FieldModel = FieldModel::generated("user_id", FieldKind::Ulid);
252 static LOCAL_FIELDS: [&FieldModel; 2] = [&TENANT_FIELD, &USER_FIELD];
253
254 #[test]
255 fn relation_edge_model_preserves_ordered_local_fields() {
256 let relation = RelationEdgeModel::generated("author", "example::User", &LOCAL_FIELDS);
257
258 assert_eq!(relation.name(), "author");
259 assert_eq!(relation.target_path(), "example::User");
260 assert_eq!(
261 relation
262 .local_fields()
263 .iter()
264 .map(|field| field.name())
265 .collect::<Vec<_>>(),
266 ["tenant_id", "user_id"],
267 );
268 }
269}
270
271#[derive(Debug)]
279pub struct EntityModel {
280 pub(crate) path: &'static str,
282
283 pub(crate) entity_name: &'static str,
285
286 pub(crate) schema_version: u32,
288
289 pub(crate) primary_key: &'static FieldModel,
291
292 pub(crate) primary_key_slot: usize,
294
295 pub(crate) primary_key_model: PrimaryKeyModel,
297
298 pub(crate) fields: &'static [FieldModel],
300
301 pub(crate) indexes: &'static [&'static IndexModel],
303
304 pub(crate) relations: &'static [RelationEdgeModel],
306}
307
308impl EntityModel {
309 #[must_use]
315 #[doc(hidden)]
316 pub const fn generated(
317 path: &'static str,
318 entity_name: &'static str,
319 schema_version: u32,
320 primary_key: &'static FieldModel,
321 primary_key_slot: usize,
322 fields: &'static [FieldModel],
323 indexes: &'static [&'static IndexModel],
324 ) -> Self {
325 Self {
326 path,
327 entity_name,
328 schema_version,
329 primary_key,
330 primary_key_slot,
331 primary_key_model: PrimaryKeyModel::scalar(primary_key),
332 fields,
333 indexes,
334 relations: &[],
335 }
336 }
337
338 #[must_use]
341 #[doc(hidden)]
342 pub const fn generated_with_primary_key_model(
343 path: &'static str,
344 entity_name: &'static str,
345 schema_version: u32,
346 primary_key_model: PrimaryKeyModel,
347 primary_key_slot: usize,
348 fields: &'static [FieldModel],
349 indexes: &'static [&'static IndexModel],
350 ) -> Self {
351 Self::generated_with_primary_key_model_and_relations(
352 path,
353 entity_name,
354 schema_version,
355 primary_key_model,
356 primary_key_slot,
357 fields,
358 indexes,
359 &[],
360 )
361 }
362
363 #[must_use]
366 #[doc(hidden)]
367 #[expect(
368 clippy::too_many_arguments,
369 reason = "generated entity model construction keeps path, declared version, key, field, index, and relation metadata explicit"
370 )]
371 pub const fn generated_with_primary_key_model_and_relations(
372 path: &'static str,
373 entity_name: &'static str,
374 schema_version: u32,
375 primary_key_model: PrimaryKeyModel,
376 primary_key_slot: usize,
377 fields: &'static [FieldModel],
378 indexes: &'static [&'static IndexModel],
379 relations: &'static [RelationEdgeModel],
380 ) -> Self {
381 assert!(
382 schema_version > 0,
383 "generated schema_version must be positive"
384 );
385
386 Self {
387 path,
388 entity_name,
389 schema_version,
390 primary_key: primary_key_model.first_field(),
391 primary_key_slot,
392 primary_key_model,
393 fields,
394 indexes,
395 relations,
396 }
397 }
398
399 #[must_use]
401 pub const fn path(&self) -> &'static str {
402 self.path
403 }
404
405 #[must_use]
407 pub const fn name(&self) -> &'static str {
408 self.entity_name
409 }
410
411 #[must_use]
416 pub const fn declared_schema_version(&self) -> u32 {
417 self.schema_version
418 }
419
420 #[must_use]
422 pub const fn primary_key(&self) -> &'static FieldModel {
423 self.primary_key
424 }
425
426 #[must_use]
428 pub const fn primary_key_model(&self) -> &PrimaryKeyModel {
429 &self.primary_key_model
430 }
431
432 #[must_use]
434 pub fn primary_key_names(&self) -> Vec<&'static str> {
435 self.primary_key_model()
436 .fields()
437 .iter()
438 .map(crate::model::field::FieldModel::name)
439 .collect()
440 }
441
442 #[must_use]
444 pub const fn primary_key_slot(&self) -> usize {
445 self.primary_key_slot
446 }
447
448 #[must_use]
450 pub const fn fields(&self) -> &'static [FieldModel] {
451 self.fields
452 }
453
454 #[must_use]
456 pub const fn indexes(&self) -> &'static [&'static IndexModel] {
457 self.indexes
458 }
459
460 #[must_use]
462 pub const fn relations(&self) -> &'static [RelationEdgeModel] {
463 self.relations
464 }
465
466 #[must_use]
468 pub(crate) fn resolve_field_slot(&self, field_name: &str) -> Option<usize> {
469 self.fields
470 .iter()
471 .position(|field| field.name == field_name)
472 }
473}