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) primary_key: &'static FieldModel,
288
289 pub(crate) primary_key_slot: usize,
291
292 pub(crate) primary_key_model: PrimaryKeyModel,
294
295 pub(crate) fields: &'static [FieldModel],
297
298 pub(crate) indexes: &'static [&'static IndexModel],
300
301 pub(crate) relations: &'static [RelationEdgeModel],
303}
304
305impl EntityModel {
306 #[must_use]
312 #[doc(hidden)]
313 pub const fn generated(
314 path: &'static str,
315 entity_name: &'static str,
316 primary_key: &'static FieldModel,
317 primary_key_slot: usize,
318 fields: &'static [FieldModel],
319 indexes: &'static [&'static IndexModel],
320 ) -> Self {
321 Self {
322 path,
323 entity_name,
324 primary_key,
325 primary_key_slot,
326 primary_key_model: PrimaryKeyModel::scalar(primary_key),
327 fields,
328 indexes,
329 relations: &[],
330 }
331 }
332
333 #[must_use]
336 #[doc(hidden)]
337 pub const fn generated_with_primary_key_model(
338 path: &'static str,
339 entity_name: &'static str,
340 primary_key_model: PrimaryKeyModel,
341 primary_key_slot: usize,
342 fields: &'static [FieldModel],
343 indexes: &'static [&'static IndexModel],
344 ) -> Self {
345 Self::generated_with_primary_key_model_and_relations(
346 path,
347 entity_name,
348 primary_key_model,
349 primary_key_slot,
350 fields,
351 indexes,
352 &[],
353 )
354 }
355
356 #[must_use]
359 #[doc(hidden)]
360 pub const fn generated_with_primary_key_model_and_relations(
361 path: &'static str,
362 entity_name: &'static str,
363 primary_key_model: PrimaryKeyModel,
364 primary_key_slot: usize,
365 fields: &'static [FieldModel],
366 indexes: &'static [&'static IndexModel],
367 relations: &'static [RelationEdgeModel],
368 ) -> Self {
369 Self {
370 path,
371 entity_name,
372 primary_key: primary_key_model.first_field(),
373 primary_key_slot,
374 primary_key_model,
375 fields,
376 indexes,
377 relations,
378 }
379 }
380
381 #[must_use]
383 pub const fn path(&self) -> &'static str {
384 self.path
385 }
386
387 #[must_use]
389 pub const fn name(&self) -> &'static str {
390 self.entity_name
391 }
392
393 #[must_use]
395 pub const fn primary_key(&self) -> &'static FieldModel {
396 self.primary_key
397 }
398
399 #[must_use]
401 pub const fn primary_key_model(&self) -> &PrimaryKeyModel {
402 &self.primary_key_model
403 }
404
405 #[must_use]
407 pub fn primary_key_names(&self) -> Vec<&'static str> {
408 self.primary_key_model()
409 .fields()
410 .iter()
411 .map(crate::model::field::FieldModel::name)
412 .collect()
413 }
414
415 #[must_use]
417 pub const fn primary_key_slot(&self) -> usize {
418 self.primary_key_slot
419 }
420
421 #[must_use]
423 pub const fn fields(&self) -> &'static [FieldModel] {
424 self.fields
425 }
426
427 #[must_use]
429 pub const fn indexes(&self) -> &'static [&'static IndexModel] {
430 self.indexes
431 }
432
433 #[must_use]
435 pub const fn relations(&self) -> &'static [RelationEdgeModel] {
436 self.relations
437 }
438
439 #[must_use]
441 pub(crate) fn resolve_field_slot(&self, field_name: &str) -> Option<usize> {
442 self.fields
443 .iter()
444 .position(|field| field.name == field_name)
445 }
446}