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]
36 pub const fn ordered(fields: &'static [&'static FieldModel]) -> Self {
37 assert!(!fields.is_empty(), "primary key model requires fields");
38 Self {
39 fields: PrimaryKeyModelFields::Ordered(fields),
40 }
41 }
42
43 #[must_use]
45 pub const fn len(&self) -> usize {
46 match self.fields {
47 PrimaryKeyModelFields::Scalar(_) => 1,
48 PrimaryKeyModelFields::Ordered(fields) => fields.len(),
49 }
50 }
51
52 #[must_use]
54 pub const fn is_empty(&self) -> bool {
55 self.len() == 0
56 }
57
58 #[must_use]
60 pub const fn is_scalar(&self) -> bool {
61 self.len() == 1
62 }
63
64 #[must_use]
70 pub const fn first_field(&self) -> &'static FieldModel {
71 match self.fields {
72 PrimaryKeyModelFields::Scalar(field) => field,
73 PrimaryKeyModelFields::Ordered(fields) => fields[0],
74 }
75 }
76
77 #[must_use]
79 pub const fn fields(&self) -> PrimaryKeyModelFields {
80 self.fields
81 }
82}
83
84#[derive(Clone, Copy, Debug)]
91pub enum PrimaryKeyModelFields {
92 Scalar(&'static FieldModel),
93 Ordered(&'static [&'static FieldModel]),
94}
95
96impl PrimaryKeyModelFields {
97 #[must_use]
99 pub const fn len(self) -> usize {
100 match self {
101 Self::Scalar(_) => 1,
102 Self::Ordered(fields) => fields.len(),
103 }
104 }
105
106 #[must_use]
108 pub const fn is_empty(self) -> bool {
109 self.len() == 0
110 }
111
112 #[must_use]
114 pub fn get(self, index: usize) -> Option<&'static FieldModel> {
115 match self {
116 Self::Scalar(field) => (index == 0).then_some(field),
117 Self::Ordered(fields) => fields.get(index).copied(),
118 }
119 }
120
121 #[must_use]
123 pub const fn iter(self) -> PrimaryKeyModelFieldIter {
124 PrimaryKeyModelFieldIter {
125 fields: self,
126 index: 0,
127 }
128 }
129}
130
131#[derive(Clone, Debug)]
138pub struct PrimaryKeyModelFieldIter {
139 fields: PrimaryKeyModelFields,
140 index: usize,
141}
142
143impl Iterator for PrimaryKeyModelFieldIter {
144 type Item = &'static FieldModel;
145
146 fn next(&mut self) -> Option<Self::Item> {
147 let item = self.fields.get(self.index)?;
148 self.index += 1;
149 Some(item)
150 }
151}
152
153#[cfg(test)]
154mod primary_key_model_tests {
155 use super::{PrimaryKeyModel, PrimaryKeyModelFields};
156 use crate::model::FieldModel;
157
158 static ID_FIELD: FieldModel = FieldModel::generated("id", crate::model::FieldKind::Nat64);
159 static TENANT_FIELD: FieldModel =
160 FieldModel::generated("tenant_id", crate::model::FieldKind::Nat64);
161 static ORDERED_FIELDS: [&FieldModel; 2] = [&ID_FIELD, &TENANT_FIELD];
162
163 #[test]
164 fn scalar_primary_key_model_exposes_one_field() {
165 let model = PrimaryKeyModel::scalar(&ID_FIELD);
166
167 assert_eq!(model.len(), 1);
168 assert!(model.is_scalar());
169 assert_eq!(model.first_field().name(), "id");
170 assert_eq!(
171 model
172 .fields()
173 .iter()
174 .map(FieldModel::name)
175 .collect::<Vec<_>>(),
176 ["id"]
177 );
178 }
179
180 #[test]
181 fn ordered_primary_key_model_preserves_field_order() {
182 let model = PrimaryKeyModel::ordered(&ORDERED_FIELDS);
183
184 assert_eq!(model.len(), 2);
185 assert!(!model.is_scalar());
186 assert_eq!(model.first_field().name(), "id");
187 assert_eq!(
188 model
189 .fields()
190 .iter()
191 .map(FieldModel::name)
192 .collect::<Vec<_>>(),
193 ["id", "tenant_id"],
194 );
195 std::assert_matches!(model.fields(), PrimaryKeyModelFields::Ordered(_));
196 }
197}
198
199#[derive(Debug)]
208pub struct RelationEdgeModel {
209 name: &'static str,
210 target_path: &'static str,
211 local_fields: &'static [&'static FieldModel],
212}
213
214impl RelationEdgeModel {
215 #[must_use]
218 pub const fn generated(
219 name: &'static str,
220 target_path: &'static str,
221 local_fields: &'static [&'static FieldModel],
222 ) -> Self {
223 Self {
224 name,
225 target_path,
226 local_fields,
227 }
228 }
229
230 #[must_use]
232 pub const fn name(&self) -> &'static str {
233 self.name
234 }
235
236 #[must_use]
238 pub const fn target_path(&self) -> &'static str {
239 self.target_path
240 }
241
242 #[must_use]
244 pub const fn local_fields(&self) -> &'static [&'static FieldModel] {
245 self.local_fields
246 }
247}
248
249#[cfg(test)]
250mod relation_edge_model_tests {
251 use super::RelationEdgeModel;
252 use crate::model::{FieldKind, FieldModel};
253
254 static TENANT_FIELD: FieldModel = FieldModel::generated("tenant_id", FieldKind::Nat64);
255 static USER_FIELD: FieldModel = FieldModel::generated("user_id", FieldKind::Ulid);
256 static LOCAL_FIELDS: [&FieldModel; 2] = [&TENANT_FIELD, &USER_FIELD];
257
258 #[test]
259 fn relation_edge_model_preserves_ordered_local_fields() {
260 let relation = RelationEdgeModel::generated("author", "example::User", &LOCAL_FIELDS);
261
262 assert_eq!(relation.name(), "author");
263 assert_eq!(relation.target_path(), "example::User");
264 assert_eq!(
265 relation
266 .local_fields()
267 .iter()
268 .map(|field| field.name())
269 .collect::<Vec<_>>(),
270 ["tenant_id", "user_id"],
271 );
272 }
273}
274
275#[derive(Debug)]
283pub struct EntityModel {
284 pub(crate) path: &'static str,
286
287 pub(crate) entity_name: &'static str,
289
290 pub(crate) schema_version: u32,
292
293 pub(crate) primary_key: &'static FieldModel,
295
296 pub(crate) primary_key_slot: usize,
298
299 pub(crate) primary_key_model: PrimaryKeyModel,
301
302 pub(crate) fields: &'static [FieldModel],
304
305 pub(crate) indexes: &'static [&'static IndexModel],
307
308 pub(crate) relations: &'static [RelationEdgeModel],
310}
311
312impl EntityModel {
313 #[must_use]
319 #[doc(hidden)]
320 pub const fn generated(
321 path: &'static str,
322 entity_name: &'static str,
323 schema_version: u32,
324 primary_key: &'static FieldModel,
325 primary_key_slot: usize,
326 fields: &'static [FieldModel],
327 indexes: &'static [&'static IndexModel],
328 ) -> Self {
329 Self {
330 path,
331 entity_name,
332 schema_version,
333 primary_key,
334 primary_key_slot,
335 primary_key_model: PrimaryKeyModel::scalar(primary_key),
336 fields,
337 indexes,
338 relations: &[],
339 }
340 }
341
342 #[must_use]
345 #[doc(hidden)]
346 pub const fn generated_with_primary_key_model(
347 path: &'static str,
348 entity_name: &'static str,
349 schema_version: u32,
350 primary_key_model: PrimaryKeyModel,
351 primary_key_slot: usize,
352 fields: &'static [FieldModel],
353 indexes: &'static [&'static IndexModel],
354 ) -> Self {
355 Self::generated_with_primary_key_model_and_relations(
356 path,
357 entity_name,
358 schema_version,
359 primary_key_model,
360 primary_key_slot,
361 fields,
362 indexes,
363 &[],
364 )
365 }
366
367 #[must_use]
370 #[doc(hidden)]
371 #[expect(
372 clippy::too_many_arguments,
373 reason = "generated entity model construction keeps path, declared version, key, field, index, and relation metadata explicit"
374 )]
375 pub const fn generated_with_primary_key_model_and_relations(
376 path: &'static str,
377 entity_name: &'static str,
378 schema_version: u32,
379 primary_key_model: PrimaryKeyModel,
380 primary_key_slot: usize,
381 fields: &'static [FieldModel],
382 indexes: &'static [&'static IndexModel],
383 relations: &'static [RelationEdgeModel],
384 ) -> Self {
385 assert!(
386 schema_version > 0,
387 "generated schema_version must be positive"
388 );
389
390 Self {
391 path,
392 entity_name,
393 schema_version,
394 primary_key: primary_key_model.first_field(),
395 primary_key_slot,
396 primary_key_model,
397 fields,
398 indexes,
399 relations,
400 }
401 }
402
403 #[must_use]
405 pub const fn path(&self) -> &'static str {
406 self.path
407 }
408
409 #[must_use]
411 pub const fn name(&self) -> &'static str {
412 self.entity_name
413 }
414
415 #[must_use]
420 pub const fn declared_schema_version(&self) -> u32 {
421 self.schema_version
422 }
423
424 #[must_use]
426 pub const fn primary_key(&self) -> &'static FieldModel {
427 self.primary_key
428 }
429
430 #[must_use]
432 pub const fn primary_key_model(&self) -> &PrimaryKeyModel {
433 &self.primary_key_model
434 }
435
436 #[must_use]
438 pub fn primary_key_names(&self) -> Vec<&'static str> {
439 self.primary_key_model()
440 .fields()
441 .iter()
442 .map(crate::model::field::FieldModel::name)
443 .collect()
444 }
445
446 #[must_use]
448 pub const fn primary_key_slot(&self) -> usize {
449 self.primary_key_slot
450 }
451
452 #[must_use]
454 pub const fn fields(&self) -> &'static [FieldModel] {
455 self.fields
456 }
457
458 #[must_use]
460 pub const fn indexes(&self) -> &'static [&'static IndexModel] {
461 self.indexes
462 }
463
464 #[must_use]
466 pub const fn relations(&self) -> &'static [RelationEdgeModel] {
467 self.relations
468 }
469
470 #[must_use]
472 pub(crate) fn resolve_field_slot(&self, field_name: &str) -> Option<usize> {
473 self.fields
474 .iter()
475 .position(|field| field.name == field_name)
476 }
477}