1use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5use smol_str::SmolStr;
6
7use super::{Attribute, Documentation, Field, Ident, Span};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct Model {
12 pub name: Ident,
14 pub fields: IndexMap<SmolStr, Field>,
16 pub attributes: Vec<Attribute>,
18 pub documentation: Option<Documentation>,
20 pub span: Span,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub source_id: Option<crate::loader::SourceId>,
25}
26
27impl Model {
28 pub fn new(name: Ident, span: Span) -> Self {
30 Self {
31 name,
32 fields: IndexMap::new(),
33 attributes: vec![],
34 documentation: None,
35 span,
36 source_id: None,
37 }
38 }
39
40 pub fn name(&self) -> &str {
42 self.name.as_str()
43 }
44
45 pub fn add_field(&mut self, field: Field) {
47 self.fields.insert(field.name.name.clone(), field);
48 }
49
50 pub fn get_field(&self, name: &str) -> Option<&Field> {
52 self.fields.get(name)
53 }
54
55 pub fn id_fields(&self) -> Vec<&Field> {
57 self.fields.values().filter(|f| f.is_id()).collect()
58 }
59
60 pub fn relation_fields(&self) -> Vec<&Field> {
62 self.fields.values().filter(|f| f.is_relation()).collect()
63 }
64
65 pub fn scalar_fields(&self) -> Vec<&Field> {
67 self.fields.values().filter(|f| !f.is_relation()).collect()
68 }
69
70 pub fn has_attribute(&self, name: &str) -> bool {
72 self.attributes.iter().any(|a| a.is(name))
73 }
74
75 pub fn get_attribute(&self, name: &str) -> Option<&Attribute> {
77 self.attributes.iter().find(|a| a.is(name))
78 }
79
80 pub fn table_name(&self) -> &str {
82 self.get_attribute("map")
83 .and_then(|a| a.first_arg())
84 .and_then(|v| v.as_string())
85 .unwrap_or_else(|| self.name())
86 }
87
88 pub fn with_documentation(mut self, doc: Documentation) -> Self {
90 self.documentation = Some(doc);
91 self
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct Enum {
98 pub name: Ident,
100 pub variants: Vec<EnumVariant>,
102 pub attributes: Vec<Attribute>,
104 pub documentation: Option<Documentation>,
106 pub span: Span,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub source_id: Option<crate::loader::SourceId>,
111}
112
113impl Enum {
114 pub fn new(name: Ident, span: Span) -> Self {
116 Self {
117 name,
118 variants: vec![],
119 attributes: vec![],
120 documentation: None,
121 span,
122 source_id: None,
123 }
124 }
125
126 pub fn name(&self) -> &str {
128 self.name.as_str()
129 }
130
131 pub fn database_name(&self) -> &str {
136 self.attributes
137 .iter()
138 .find(|a| a.name() == "map")
139 .and_then(|a| a.first_arg())
140 .and_then(|v| v.as_string())
141 .unwrap_or_else(|| self.name.as_str())
142 }
143
144 pub fn add_variant(&mut self, variant: EnumVariant) {
146 self.variants.push(variant);
147 }
148
149 pub fn get_variant(&self, name: &str) -> Option<&EnumVariant> {
151 self.variants.iter().find(|v| v.name.as_str() == name)
152 }
153
154 pub fn db_name(&self) -> &str {
156 self.attributes
157 .iter()
158 .find(|a| a.is("map"))
159 .and_then(|a| a.first_arg())
160 .and_then(|v| v.as_string())
161 .unwrap_or_else(|| self.name())
162 }
163
164 pub fn with_documentation(mut self, doc: Documentation) -> Self {
166 self.documentation = Some(doc);
167 self
168 }
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct EnumVariant {
174 pub name: Ident,
176 pub attributes: Vec<Attribute>,
178 pub documentation: Option<Documentation>,
180 pub span: Span,
182}
183
184impl EnumVariant {
185 pub fn new(name: Ident, span: Span) -> Self {
187 Self {
188 name,
189 attributes: vec![],
190 documentation: None,
191 span,
192 }
193 }
194
195 pub fn name(&self) -> &str {
197 self.name.as_str()
198 }
199
200 pub fn db_value(&self) -> &str {
202 self.attributes
203 .iter()
204 .find(|a| a.is("map"))
205 .and_then(|a| a.first_arg())
206 .and_then(|v| v.as_string())
207 .unwrap_or_else(|| self.name())
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct CompositeType {
214 pub name: Ident,
216 pub fields: IndexMap<SmolStr, Field>,
218 pub documentation: Option<Documentation>,
220 pub span: Span,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub source_id: Option<crate::loader::SourceId>,
225}
226
227impl CompositeType {
228 pub fn new(name: Ident, span: Span) -> Self {
230 Self {
231 name,
232 fields: IndexMap::new(),
233 documentation: None,
234 span,
235 source_id: None,
236 }
237 }
238
239 pub fn name(&self) -> &str {
241 self.name.as_str()
242 }
243
244 pub fn add_field(&mut self, field: Field) {
246 self.fields.insert(field.name.name.clone(), field);
247 }
248
249 pub fn get_field(&self, name: &str) -> Option<&Field> {
251 self.fields.get(name)
252 }
253
254 pub fn with_documentation(mut self, doc: Documentation) -> Self {
256 self.documentation = Some(doc);
257 self
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub struct View {
264 pub name: Ident,
266 pub fields: IndexMap<SmolStr, Field>,
268 pub attributes: Vec<Attribute>,
270 pub documentation: Option<Documentation>,
272 pub span: Span,
274 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub source_id: Option<crate::loader::SourceId>,
277}
278
279impl View {
280 pub fn new(name: Ident, span: Span) -> Self {
282 Self {
283 name,
284 fields: IndexMap::new(),
285 attributes: vec![],
286 documentation: None,
287 span,
288 source_id: None,
289 }
290 }
291
292 pub fn name(&self) -> &str {
294 self.name.as_str()
295 }
296
297 pub fn add_field(&mut self, field: Field) {
299 self.fields.insert(field.name.name.clone(), field);
300 }
301
302 pub fn view_name(&self) -> &str {
304 self.attributes
305 .iter()
306 .find(|a| a.is("map"))
307 .and_then(|a| a.first_arg())
308 .and_then(|v| v.as_string())
309 .unwrap_or_else(|| self.name())
310 }
311
312 pub fn sql_query(&self) -> Option<&str> {
314 self.attributes
315 .iter()
316 .find(|a| a.is("sql"))
317 .and_then(|a| a.first_arg())
318 .and_then(|v| v.as_string())
319 }
320
321 pub fn is_materialized(&self) -> bool {
323 self.attributes.iter().any(|a| a.is("materialized"))
324 }
325
326 pub fn refresh_interval(&self) -> Option<&str> {
328 self.attributes
329 .iter()
330 .find(|a| a.is("refreshInterval"))
331 .and_then(|a| a.first_arg())
332 .and_then(|v| v.as_string())
333 }
334
335 pub fn with_documentation(mut self, doc: Documentation) -> Self {
337 self.documentation = Some(doc);
338 self
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use crate::ast::{
346 Attribute, AttributeArg, AttributeValue, FieldType, ScalarType, TypeModifier,
347 };
348
349 fn make_span() -> Span {
350 Span::new(0, 10)
351 }
352
353 fn make_ident(name: &str) -> Ident {
354 Ident::new(name, make_span())
355 }
356
357 fn make_field(name: &str, field_type: FieldType, modifier: TypeModifier) -> Field {
358 Field::new(make_ident(name), field_type, modifier, vec![], make_span())
359 }
360
361 fn make_id_field() -> Field {
362 let mut field = make_field(
363 "id",
364 FieldType::Scalar(ScalarType::Int),
365 TypeModifier::Required,
366 );
367 field
368 .attributes
369 .push(Attribute::simple(make_ident("id"), make_span()));
370 field
371 .attributes
372 .push(Attribute::simple(make_ident("auto"), make_span()));
373 field
374 }
375
376 fn make_attribute(name: &str) -> Attribute {
377 Attribute::simple(make_ident(name), make_span())
378 }
379
380 fn make_attribute_with_string(name: &str, value: &str) -> Attribute {
381 Attribute::new(
382 make_ident(name),
383 vec![AttributeArg::positional(
384 AttributeValue::String(value.into()),
385 make_span(),
386 )],
387 make_span(),
388 )
389 }
390
391 #[test]
394 fn test_model_new() {
395 let model = Model::new(make_ident("User"), make_span());
396
397 assert_eq!(model.name(), "User");
398 assert!(model.fields.is_empty());
399 assert!(model.attributes.is_empty());
400 assert!(model.documentation.is_none());
401 }
402
403 #[test]
404 fn test_model_name() {
405 let model = Model::new(make_ident("BlogPost"), make_span());
406 assert_eq!(model.name(), "BlogPost");
407 }
408
409 #[test]
410 fn test_model_add_field() {
411 let mut model = Model::new(make_ident("User"), make_span());
412 let field = make_field(
413 "email",
414 FieldType::Scalar(ScalarType::String),
415 TypeModifier::Required,
416 );
417
418 model.add_field(field);
419
420 assert_eq!(model.fields.len(), 1);
421 assert!(model.fields.contains_key("email"));
422 }
423
424 #[test]
425 fn test_model_add_multiple_fields() {
426 let mut model = Model::new(make_ident("User"), make_span());
427 model.add_field(make_id_field());
428 model.add_field(make_field(
429 "email",
430 FieldType::Scalar(ScalarType::String),
431 TypeModifier::Required,
432 ));
433 model.add_field(make_field(
434 "name",
435 FieldType::Scalar(ScalarType::String),
436 TypeModifier::Optional,
437 ));
438
439 assert_eq!(model.fields.len(), 3);
440 }
441
442 #[test]
443 fn test_model_get_field() {
444 let mut model = Model::new(make_ident("User"), make_span());
445 model.add_field(make_field(
446 "email",
447 FieldType::Scalar(ScalarType::String),
448 TypeModifier::Required,
449 ));
450
451 let field = model.get_field("email");
452 assert!(field.is_some());
453 assert_eq!(field.unwrap().name(), "email");
454
455 assert!(model.get_field("nonexistent").is_none());
456 }
457
458 #[test]
459 fn test_model_id_fields() {
460 let mut model = Model::new(make_ident("User"), make_span());
461 model.add_field(make_id_field());
462 model.add_field(make_field(
463 "email",
464 FieldType::Scalar(ScalarType::String),
465 TypeModifier::Required,
466 ));
467
468 let id_fields = model.id_fields();
469 assert_eq!(id_fields.len(), 1);
470 assert_eq!(id_fields[0].name(), "id");
471 }
472
473 #[test]
474 fn test_model_id_fields_none() {
475 let mut model = Model::new(make_ident("User"), make_span());
476 model.add_field(make_field(
477 "email",
478 FieldType::Scalar(ScalarType::String),
479 TypeModifier::Required,
480 ));
481
482 let id_fields = model.id_fields();
483 assert!(id_fields.is_empty());
484 }
485
486 #[test]
487 fn test_model_relation_fields() {
488 let mut model = Model::new(make_ident("Post"), make_span());
489 model.add_field(make_id_field());
490 model.add_field(make_field(
491 "title",
492 FieldType::Scalar(ScalarType::String),
493 TypeModifier::Required,
494 ));
495 model.add_field(make_field(
496 "author",
497 FieldType::Model("User".into()),
498 TypeModifier::Required,
499 ));
500
501 let rel_fields = model.relation_fields();
502 assert_eq!(rel_fields.len(), 1);
503 assert_eq!(rel_fields[0].name(), "author");
504 }
505
506 #[test]
507 fn test_model_scalar_fields() {
508 let mut model = Model::new(make_ident("Post"), make_span());
509 model.add_field(make_id_field());
510 model.add_field(make_field(
511 "title",
512 FieldType::Scalar(ScalarType::String),
513 TypeModifier::Required,
514 ));
515 model.add_field(make_field(
516 "author",
517 FieldType::Model("User".into()),
518 TypeModifier::Required,
519 ));
520
521 let scalar_fields = model.scalar_fields();
522 assert_eq!(scalar_fields.len(), 2);
523 }
524
525 #[test]
526 fn test_model_has_attribute() {
527 let mut model = Model::new(make_ident("User"), make_span());
528 model.attributes.push(make_attribute("map"));
529
530 assert!(model.has_attribute("map"));
531 assert!(!model.has_attribute("index"));
532 }
533
534 #[test]
535 fn test_model_get_attribute() {
536 let mut model = Model::new(make_ident("User"), make_span());
537 model
538 .attributes
539 .push(make_attribute_with_string("map", "users"));
540
541 let attr = model.get_attribute("map");
542 assert!(attr.is_some());
543 assert!(attr.unwrap().is("map"));
544
545 assert!(model.get_attribute("index").is_none());
546 }
547
548 #[test]
549 fn test_model_table_name_default() {
550 let model = Model::new(make_ident("User"), make_span());
551 assert_eq!(model.table_name(), "User");
552 }
553
554 #[test]
555 fn test_model_table_name_mapped() {
556 let mut model = Model::new(make_ident("User"), make_span());
557 model
558 .attributes
559 .push(make_attribute_with_string("map", "app_users"));
560
561 assert_eq!(model.table_name(), "app_users");
562 }
563
564 #[test]
565 fn test_model_with_documentation() {
566 let model = Model::new(make_ident("User"), make_span())
567 .with_documentation(Documentation::new("Represents a user", make_span()));
568
569 assert!(model.documentation.is_some());
570 assert_eq!(model.documentation.unwrap().text, "Represents a user");
571 }
572
573 #[test]
576 fn test_enum_new() {
577 let e = Enum::new(make_ident("Role"), make_span());
578
579 assert_eq!(e.name(), "Role");
580 assert!(e.variants.is_empty());
581 assert!(e.attributes.is_empty());
582 assert!(e.documentation.is_none());
583 }
584
585 #[test]
586 fn test_enum_add_variant() {
587 let mut e = Enum::new(make_ident("Role"), make_span());
588 e.add_variant(EnumVariant::new(make_ident("Admin"), make_span()));
589 e.add_variant(EnumVariant::new(make_ident("User"), make_span()));
590
591 assert_eq!(e.variants.len(), 2);
592 }
593
594 #[test]
595 fn test_enum_get_variant() {
596 let mut e = Enum::new(make_ident("Role"), make_span());
597 e.add_variant(EnumVariant::new(make_ident("Admin"), make_span()));
598 e.add_variant(EnumVariant::new(make_ident("User"), make_span()));
599
600 let variant = e.get_variant("Admin");
601 assert!(variant.is_some());
602 assert_eq!(variant.unwrap().name(), "Admin");
603
604 assert!(e.get_variant("Moderator").is_none());
605 }
606
607 #[test]
608 fn test_enum_db_name_default() {
609 let e = Enum::new(make_ident("Role"), make_span());
610 assert_eq!(e.db_name(), "Role");
611 }
612
613 #[test]
614 fn test_enum_db_name_mapped() {
615 let mut e = Enum::new(make_ident("Role"), make_span());
616 e.attributes
617 .push(make_attribute_with_string("map", "user_role"));
618
619 assert_eq!(e.db_name(), "user_role");
620 }
621
622 #[test]
623 fn test_enum_with_documentation() {
624 let e = Enum::new(make_ident("Role"), make_span())
625 .with_documentation(Documentation::new("User roles", make_span()));
626
627 assert!(e.documentation.is_some());
628 }
629
630 #[test]
633 fn test_enum_variant_new() {
634 let variant = EnumVariant::new(make_ident("Admin"), make_span());
635
636 assert_eq!(variant.name(), "Admin");
637 assert!(variant.attributes.is_empty());
638 assert!(variant.documentation.is_none());
639 }
640
641 #[test]
642 fn test_enum_variant_db_value_default() {
643 let variant = EnumVariant::new(make_ident("Admin"), make_span());
644 assert_eq!(variant.db_value(), "Admin");
645 }
646
647 #[test]
648 fn test_enum_variant_db_value_mapped() {
649 let mut variant = EnumVariant::new(make_ident("Admin"), make_span());
650 variant
651 .attributes
652 .push(make_attribute_with_string("map", "ADMIN_USER"));
653
654 assert_eq!(variant.db_value(), "ADMIN_USER");
655 }
656
657 #[test]
660 fn test_composite_type_new() {
661 let ct = CompositeType::new(make_ident("Address"), make_span());
662
663 assert_eq!(ct.name(), "Address");
664 assert!(ct.fields.is_empty());
665 assert!(ct.documentation.is_none());
666 }
667
668 #[test]
669 fn test_composite_type_add_field() {
670 let mut ct = CompositeType::new(make_ident("Address"), make_span());
671 ct.add_field(make_field(
672 "street",
673 FieldType::Scalar(ScalarType::String),
674 TypeModifier::Required,
675 ));
676 ct.add_field(make_field(
677 "city",
678 FieldType::Scalar(ScalarType::String),
679 TypeModifier::Required,
680 ));
681
682 assert_eq!(ct.fields.len(), 2);
683 }
684
685 #[test]
686 fn test_composite_type_get_field() {
687 let mut ct = CompositeType::new(make_ident("Address"), make_span());
688 ct.add_field(make_field(
689 "city",
690 FieldType::Scalar(ScalarType::String),
691 TypeModifier::Required,
692 ));
693
694 let field = ct.get_field("city");
695 assert!(field.is_some());
696 assert_eq!(field.unwrap().name(), "city");
697
698 assert!(ct.get_field("country").is_none());
699 }
700
701 #[test]
702 fn test_composite_type_with_documentation() {
703 let ct = CompositeType::new(make_ident("Address"), make_span())
704 .with_documentation(Documentation::new("Mailing address", make_span()));
705
706 assert!(ct.documentation.is_some());
707 }
708
709 #[test]
712 fn test_view_new() {
713 let view = View::new(make_ident("UserStats"), make_span());
714
715 assert_eq!(view.name(), "UserStats");
716 assert!(view.fields.is_empty());
717 assert!(view.attributes.is_empty());
718 assert!(view.documentation.is_none());
719 }
720
721 #[test]
722 fn test_view_add_field() {
723 let mut view = View::new(make_ident("UserStats"), make_span());
724 view.add_field(make_field(
725 "user_id",
726 FieldType::Scalar(ScalarType::Int),
727 TypeModifier::Required,
728 ));
729 view.add_field(make_field(
730 "post_count",
731 FieldType::Scalar(ScalarType::Int),
732 TypeModifier::Required,
733 ));
734
735 assert_eq!(view.fields.len(), 2);
736 }
737
738 #[test]
739 fn test_view_view_name_default() {
740 let view = View::new(make_ident("UserStats"), make_span());
741 assert_eq!(view.view_name(), "UserStats");
742 }
743
744 #[test]
745 fn test_view_view_name_mapped() {
746 let mut view = View::new(make_ident("UserStats"), make_span());
747 view.attributes
748 .push(make_attribute_with_string("map", "v_user_statistics"));
749
750 assert_eq!(view.view_name(), "v_user_statistics");
751 }
752
753 #[test]
754 fn test_view_with_documentation() {
755 let view = View::new(make_ident("UserStats"), make_span()).with_documentation(
756 Documentation::new("Aggregated user statistics", make_span()),
757 );
758
759 assert!(view.documentation.is_some());
760 }
761
762 #[test]
765 fn test_model_equality() {
766 let model1 = Model::new(make_ident("User"), make_span());
767 let model2 = Model::new(make_ident("User"), make_span());
768
769 assert_eq!(model1, model2);
770 }
771
772 #[test]
773 fn test_model_inequality() {
774 let model1 = Model::new(make_ident("User"), make_span());
775 let model2 = Model::new(make_ident("Post"), make_span());
776
777 assert_ne!(model1, model2);
778 }
779
780 #[test]
781 fn test_enum_equality() {
782 let enum1 = Enum::new(make_ident("Role"), make_span());
783 let enum2 = Enum::new(make_ident("Role"), make_span());
784
785 assert_eq!(enum1, enum2);
786 }
787
788 #[test]
789 fn test_enum_variant_equality() {
790 let v1 = EnumVariant::new(make_ident("Admin"), make_span());
791 let v2 = EnumVariant::new(make_ident("Admin"), make_span());
792 let v3 = EnumVariant::new(make_ident("User"), make_span());
793
794 assert_eq!(v1, v2);
795 assert_ne!(v1, v3);
796 }
797
798 #[test]
799 fn test_composite_type_equality() {
800 let ct1 = CompositeType::new(make_ident("Address"), make_span());
801 let ct2 = CompositeType::new(make_ident("Address"), make_span());
802
803 assert_eq!(ct1, ct2);
804 }
805
806 #[test]
807 fn test_view_equality() {
808 let v1 = View::new(make_ident("Stats"), make_span());
809 let v2 = View::new(make_ident("Stats"), make_span());
810
811 assert_eq!(v1, v2);
812 }
813}