1use crate::ast::*;
10use crate::error::{SchemaError, SchemaResult};
11
12#[derive(Debug)]
14pub struct Validator {
15 errors: Vec<SchemaError>,
17}
18
19impl Default for Validator {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl Validator {
26 pub fn new() -> Self {
28 Self { errors: vec![] }
29 }
30
31 pub fn validate(&mut self, mut schema: Schema) -> SchemaResult<Schema> {
33 self.errors.clear();
34
35 self.check_duplicates(&schema);
37
38 for model in schema.models.values() {
40 self.validate_model(model, &schema);
41 }
42
43 for e in schema.enums.values() {
45 self.validate_enum(e);
46 }
47
48 for t in schema.types.values() {
50 self.validate_composite_type(t, &schema);
51 }
52
53 for v in schema.views.values() {
55 self.validate_view(v, &schema);
56 }
57
58 for sg in schema.server_groups.values() {
60 self.validate_server_group(sg);
61 }
62
63 let relations = self.resolve_relations(&schema);
65 schema.relations = relations;
66
67 if self.errors.is_empty() {
68 Ok(schema)
69 } else {
70 Err(SchemaError::ValidationFailed {
71 count: self.errors.len(),
72 errors: std::mem::take(&mut self.errors),
73 })
74 }
75 }
76
77 fn check_duplicates(&mut self, schema: &Schema) {
79 let mut seen = std::collections::HashSet::new();
80
81 for name in schema.models.keys() {
82 if !seen.insert(name.as_str()) {
83 self.errors
84 .push(SchemaError::duplicate("model", name.as_str()));
85 }
86 }
87
88 for name in schema.enums.keys() {
89 if !seen.insert(name.as_str()) {
90 self.errors
91 .push(SchemaError::duplicate("enum", name.as_str()));
92 }
93 }
94
95 for name in schema.types.keys() {
96 if !seen.insert(name.as_str()) {
97 self.errors
98 .push(SchemaError::duplicate("type", name.as_str()));
99 }
100 }
101
102 for name in schema.views.keys() {
103 if !seen.insert(name.as_str()) {
104 self.errors
105 .push(SchemaError::duplicate("view", name.as_str()));
106 }
107 }
108
109 let mut server_group_names = std::collections::HashSet::new();
111 for name in schema.server_groups.keys() {
112 if !server_group_names.insert(name.as_str()) {
113 self.errors
114 .push(SchemaError::duplicate("serverGroup", name.as_str()));
115 }
116 }
117 }
118
119 fn validate_model(&mut self, model: &Model, schema: &Schema) {
121 let id_fields: Vec<_> = model.fields.values().filter(|f| f.is_id()).collect();
123 if id_fields.is_empty() && !self.has_composite_id(model) {
124 self.errors.push(SchemaError::MissingId {
125 model: model.name().to_string(),
126 });
127 }
128
129 for field in model.fields.values() {
131 self.validate_field(field, model.name(), schema);
132 }
133
134 for attr in &model.attributes {
136 self.validate_model_attribute(attr, model);
137 }
138 }
139
140 fn has_composite_id(&self, model: &Model) -> bool {
142 model.attributes.iter().any(|a| a.is("id"))
143 }
144
145 fn validate_field(&mut self, field: &Field, model_name: &str, schema: &Schema) {
147 match &field.field_type {
149 FieldType::Model(name) => {
150 if schema.models.contains_key(name.as_str()) {
152 } else if schema.enums.contains_key(name.as_str()) {
154 } else if schema.types.contains_key(name.as_str()) {
157 } else {
159 self.errors.push(SchemaError::unknown_type(
160 model_name,
161 field.name(),
162 name.as_str(),
163 ));
164 }
165 }
166 FieldType::Enum(name) => {
167 if !schema.enums.contains_key(name.as_str()) {
168 self.errors.push(SchemaError::unknown_type(
169 model_name,
170 field.name(),
171 name.as_str(),
172 ));
173 }
174 }
175 FieldType::Composite(name) => {
176 if !schema.types.contains_key(name.as_str()) {
177 self.errors.push(SchemaError::unknown_type(
178 model_name,
179 field.name(),
180 name.as_str(),
181 ));
182 }
183 }
184 _ => {}
185 }
186
187 for attr in &field.attributes {
189 self.validate_field_attribute(attr, field, model_name, schema);
190 }
191
192 if field.field_type.is_relation() && !field.is_list() {
194 let attrs = field.extract_attributes();
196 if attrs.relation.is_some() {
197 let rel = attrs.relation.as_ref().unwrap();
198 for fk_field in &rel.fields {
200 if !schema
201 .models
202 .get(model_name)
203 .map(|m| m.fields.contains_key(fk_field.as_str()))
204 .unwrap_or(false)
205 {
206 self.errors.push(SchemaError::invalid_relation(
207 model_name,
208 field.name(),
209 format!("foreign key field '{}' does not exist", fk_field),
210 ));
211 }
212 }
213 }
214 }
215 }
216
217 fn validate_field_attribute(
219 &mut self,
220 attr: &Attribute,
221 field: &Field,
222 model_name: &str,
223 schema: &Schema,
224 ) {
225 match attr.name() {
226 "id" => {
227 if field.field_type.is_relation() {
229 self.errors.push(SchemaError::InvalidAttribute {
230 attribute: "id".to_string(),
231 message: format!(
232 "@id cannot be applied to relation field '{}.{}'",
233 model_name,
234 field.name()
235 ),
236 });
237 }
238 }
239 "auto" => {
240 if !matches!(
242 field.field_type,
243 FieldType::Scalar(ScalarType::Int) | FieldType::Scalar(ScalarType::BigInt)
244 ) {
245 self.errors.push(SchemaError::InvalidAttribute {
246 attribute: "auto".to_string(),
247 message: format!(
248 "@auto can only be applied to Int or BigInt fields, not '{}.{}'",
249 model_name,
250 field.name()
251 ),
252 });
253 }
254 }
255 "default" => {
256 if let Some(value) = attr.first_arg() {
258 self.validate_default_value(value, field, model_name, schema);
259 }
260 }
261 "relation" => {
262 if !field.field_type.is_relation() {
264 self.errors.push(SchemaError::InvalidAttribute {
265 attribute: "relation".to_string(),
266 message: format!(
267 "@relation can only be applied to model reference fields, not '{}.{}'",
268 model_name,
269 field.name()
270 ),
271 });
272 }
273 }
274 "updated_at" => {
275 if !matches!(field.field_type, FieldType::Scalar(ScalarType::DateTime)) {
277 self.errors.push(SchemaError::InvalidAttribute {
278 attribute: "updated_at".to_string(),
279 message: format!(
280 "@updated_at can only be applied to DateTime fields, not '{}.{}'",
281 model_name,
282 field.name()
283 ),
284 });
285 }
286 }
287 _ => {}
288 }
289 }
290
291 fn validate_default_value(
293 &mut self,
294 value: &AttributeValue,
295 field: &Field,
296 model_name: &str,
297 schema: &Schema,
298 ) {
299 match (&field.field_type, value) {
300 (_, AttributeValue::Function(_, _)) => {}
302
303 (FieldType::Scalar(ScalarType::Int), AttributeValue::Int(_)) => {}
305 (FieldType::Scalar(ScalarType::BigInt), AttributeValue::Int(_)) => {}
306
307 (FieldType::Scalar(ScalarType::Float), AttributeValue::Int(_)) => {}
309 (FieldType::Scalar(ScalarType::Float), AttributeValue::Float(_)) => {}
310 (FieldType::Scalar(ScalarType::Decimal), AttributeValue::Int(_)) => {}
311 (FieldType::Scalar(ScalarType::Decimal), AttributeValue::Float(_)) => {}
312
313 (FieldType::Scalar(ScalarType::String), AttributeValue::String(_)) => {}
315
316 (FieldType::Scalar(ScalarType::Boolean), AttributeValue::Boolean(_)) => {}
318
319 (FieldType::Enum(enum_name), AttributeValue::Ident(variant)) => {
321 if let Some(e) = schema.enums.get(enum_name.as_str()) {
322 if e.get_variant(variant).is_none() {
323 self.errors.push(SchemaError::invalid_field(
324 model_name,
325 field.name(),
326 format!(
327 "default value '{}' is not a valid variant of enum '{}'",
328 variant, enum_name
329 ),
330 ));
331 }
332 }
333 }
334
335 (FieldType::Model(type_name), AttributeValue::Ident(variant)) => {
337 if let Some(e) = schema.enums.get(type_name.as_str()) {
339 if e.get_variant(variant).is_none() {
340 self.errors.push(SchemaError::invalid_field(
341 model_name,
342 field.name(),
343 format!(
344 "default value '{}' is not a valid variant of enum '{}'",
345 variant, type_name
346 ),
347 ));
348 }
349 }
350 }
353
354 _ => {
356 self.errors.push(SchemaError::invalid_field(
357 model_name,
358 field.name(),
359 format!(
360 "default value type does not match field type '{}'",
361 field.field_type
362 ),
363 ));
364 }
365 }
366 }
367
368 fn validate_model_attribute(&mut self, attr: &Attribute, model: &Model) {
370 match attr.name() {
371 "index" | "unique" => {
372 if let Some(AttributeValue::FieldRefList(fields)) = attr.first_arg() {
374 for field_name in fields {
375 if !model.fields.contains_key(field_name.as_str()) {
376 self.errors.push(SchemaError::invalid_model(
377 model.name(),
378 format!(
379 "@@{} references non-existent field '{}'",
380 attr.name(),
381 field_name
382 ),
383 ));
384 }
385 }
386 }
387 }
388 "id" => {
389 if let Some(AttributeValue::FieldRefList(fields)) = attr.first_arg() {
391 for field_name in fields {
392 if !model.fields.contains_key(field_name.as_str()) {
393 self.errors.push(SchemaError::invalid_model(
394 model.name(),
395 format!("@@id references non-existent field '{}'", field_name),
396 ));
397 }
398 }
399 }
400 }
401 "search" => {
402 if let Some(AttributeValue::FieldRefList(fields)) = attr.first_arg() {
404 for field_name in fields {
405 if let Some(field) = model.fields.get(field_name.as_str()) {
406 if !matches!(field.field_type, FieldType::Scalar(ScalarType::String)) {
408 self.errors.push(SchemaError::invalid_model(
409 model.name(),
410 format!(
411 "@@search field '{}' must be of type String",
412 field_name
413 ),
414 ));
415 }
416 } else {
417 self.errors.push(SchemaError::invalid_model(
418 model.name(),
419 format!("@@search references non-existent field '{}'", field_name),
420 ));
421 }
422 }
423 }
424 }
425 _ => {}
426 }
427 }
428
429 fn validate_enum(&mut self, e: &Enum) {
431 if e.variants.is_empty() {
432 self.errors.push(SchemaError::invalid_model(
433 e.name(),
434 "enum must have at least one variant".to_string(),
435 ));
436 }
437
438 let mut seen = std::collections::HashSet::new();
440 for variant in &e.variants {
441 if !seen.insert(variant.name()) {
442 self.errors.push(SchemaError::duplicate(
443 format!("enum variant in {}", e.name()),
444 variant.name(),
445 ));
446 }
447 }
448 }
449
450 fn validate_composite_type(&mut self, t: &CompositeType, schema: &Schema) {
452 if t.fields.is_empty() {
453 self.errors.push(SchemaError::invalid_model(
454 t.name(),
455 "composite type must have at least one field".to_string(),
456 ));
457 }
458
459 for field in t.fields.values() {
461 match &field.field_type {
462 FieldType::Model(_) => {
463 self.errors.push(SchemaError::invalid_field(
464 t.name(),
465 field.name(),
466 "composite types cannot have model relations".to_string(),
467 ));
468 }
469 FieldType::Enum(name) => {
470 if !schema.enums.contains_key(name.as_str()) {
471 self.errors.push(SchemaError::unknown_type(
472 t.name(),
473 field.name(),
474 name.as_str(),
475 ));
476 }
477 }
478 FieldType::Composite(name) => {
479 if !schema.types.contains_key(name.as_str()) {
480 self.errors.push(SchemaError::unknown_type(
481 t.name(),
482 field.name(),
483 name.as_str(),
484 ));
485 }
486 }
487 _ => {}
488 }
489 }
490 }
491
492 fn validate_view(&mut self, v: &View, schema: &Schema) {
494 if v.fields.is_empty() {
496 self.errors.push(SchemaError::invalid_model(
497 v.name(),
498 "view must have at least one field".to_string(),
499 ));
500 }
501
502 for field in v.fields.values() {
504 self.validate_field(field, v.name(), schema);
505 }
506 }
507
508 fn validate_server_group(&mut self, sg: &ServerGroup) {
510 if sg.servers.is_empty() {
512 self.errors.push(SchemaError::invalid_model(
513 sg.name.name.as_str(),
514 "serverGroup must have at least one server".to_string(),
515 ));
516 }
517
518 let mut seen_servers = std::collections::HashSet::new();
520 for server_name in sg.servers.keys() {
521 if !seen_servers.insert(server_name.as_str()) {
522 self.errors.push(SchemaError::duplicate(
523 format!("server in serverGroup {}", sg.name.name),
524 server_name.as_str(),
525 ));
526 }
527 }
528
529 for server in sg.servers.values() {
531 self.validate_server(server, sg.name.name.as_str());
532 }
533
534 for attr in &sg.attributes {
536 self.validate_server_group_attribute(attr, sg);
537 }
538
539 if let Some(strategy) = sg.strategy() {
541 if strategy == ServerGroupStrategy::ReadReplica {
542 let has_primary = sg.servers.values().any(|s| {
543 s.role() == Some(ServerRole::Primary)
544 });
545 if !has_primary {
546 self.errors.push(SchemaError::invalid_model(
547 sg.name.name.as_str(),
548 "ReadReplica strategy requires at least one server with role = \"primary\"".to_string(),
549 ));
550 }
551 }
552 }
553 }
554
555 fn validate_server(&mut self, server: &Server, group_name: &str) {
557 if server.url().is_none() {
559 self.errors.push(SchemaError::invalid_model(
560 group_name,
561 format!(
562 "server '{}' must have a 'url' property",
563 server.name.name
564 ),
565 ));
566 }
567
568 if let Some(weight) = server.weight() {
570 if weight == 0 {
571 self.errors.push(SchemaError::invalid_model(
572 group_name,
573 format!(
574 "server '{}' weight must be greater than 0",
575 server.name.name
576 ),
577 ));
578 }
579 }
580
581 if let Some(priority) = server.priority() {
583 if priority == 0 {
584 self.errors.push(SchemaError::invalid_model(
585 group_name,
586 format!(
587 "server '{}' priority must be greater than 0",
588 server.name.name
589 ),
590 ));
591 }
592 }
593 }
594
595 fn validate_server_group_attribute(&mut self, attr: &Attribute, sg: &ServerGroup) {
597 match attr.name() {
598 "strategy" => {
599 if let Some(arg) = attr.first_arg() {
601 let value_str = arg.as_string()
602 .map(|s| s.to_string())
603 .or_else(|| arg.as_ident().map(|s| s.to_string()));
604 if let Some(val) = value_str {
605 if ServerGroupStrategy::from_str(&val).is_none() {
606 self.errors.push(SchemaError::InvalidAttribute {
607 attribute: "strategy".to_string(),
608 message: format!(
609 "invalid strategy '{}' for serverGroup '{}'. Valid values: ReadReplica, Sharding, MultiRegion, HighAvailability, Custom",
610 val,
611 sg.name.name
612 ),
613 });
614 }
615 }
616 }
617 }
618 "loadBalance" => {
619 if let Some(arg) = attr.first_arg() {
621 let value_str = arg.as_string()
622 .map(|s| s.to_string())
623 .or_else(|| arg.as_ident().map(|s| s.to_string()));
624 if let Some(val) = value_str {
625 if LoadBalanceStrategy::from_str(&val).is_none() {
626 self.errors.push(SchemaError::InvalidAttribute {
627 attribute: "loadBalance".to_string(),
628 message: format!(
629 "invalid loadBalance '{}' for serverGroup '{}'. Valid values: RoundRobin, Random, LeastConnections, Weighted, Nearest, Sticky",
630 val,
631 sg.name.name
632 ),
633 });
634 }
635 }
636 }
637 }
638 _ => {} }
640 }
641
642 fn resolve_relations(&mut self, schema: &Schema) -> Vec<Relation> {
644 let mut relations = Vec::new();
645
646 for model in schema.models.values() {
647 for field in model.fields.values() {
648 if let FieldType::Model(ref target_model) = field.field_type {
649 let attrs = field.extract_attributes();
650
651 let relation_type = if field.is_list() {
652 RelationType::OneToMany
654 } else {
655 RelationType::ManyToOne
657 };
658
659 let mut relation = Relation::new(
660 model.name(),
661 field.name(),
662 target_model.as_str(),
663 relation_type,
664 );
665
666 if let Some(rel_attr) = &attrs.relation {
667 if let Some(name) = &rel_attr.name {
668 relation = relation.with_name(name.as_str());
669 }
670 if !rel_attr.fields.is_empty() {
671 relation = relation.with_from_fields(rel_attr.fields.clone());
672 }
673 if !rel_attr.references.is_empty() {
674 relation = relation.with_to_fields(rel_attr.references.clone());
675 }
676 if let Some(action) = rel_attr.on_delete {
677 relation = relation.with_on_delete(action);
678 }
679 if let Some(action) = rel_attr.on_update {
680 relation = relation.with_on_update(action);
681 }
682 }
683
684 relations.push(relation);
685 }
686 }
687 }
688
689 relations
690 }
691}
692
693pub fn validate_schema(input: &str) -> SchemaResult<Schema> {
695 let schema = crate::parser::parse_schema(input)?;
696 let mut validator = Validator::new();
697 validator.validate(schema)
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 #[test]
705 fn test_validate_simple_model() {
706 let schema = validate_schema(
707 r#"
708 model User {
709 id Int @id @auto
710 email String @unique
711 }
712 "#,
713 )
714 .unwrap();
715
716 assert_eq!(schema.models.len(), 1);
717 }
718
719 #[test]
720 fn test_validate_model_missing_id() {
721 let result = validate_schema(
722 r#"
723 model User {
724 email String
725 name String
726 }
727 "#,
728 );
729
730 assert!(result.is_err());
731 let err = result.unwrap_err();
732 assert!(matches!(err, SchemaError::ValidationFailed { .. }));
733 }
734
735 #[test]
736 fn test_validate_model_with_composite_id() {
737 let schema = validate_schema(
738 r#"
739 model PostTag {
740 post_id Int
741 tag_id Int
742
743 @@id([post_id, tag_id])
744 }
745 "#,
746 )
747 .unwrap();
748
749 assert_eq!(schema.models.len(), 1);
750 }
751
752 #[test]
753 fn test_validate_unknown_type_reference() {
754 let result = validate_schema(
755 r#"
756 model User {
757 id Int @id @auto
758 profile UnknownType
759 }
760 "#,
761 );
762
763 assert!(result.is_err());
764 }
765
766 #[test]
767 fn test_validate_enum_reference() {
768 let schema = validate_schema(
769 r#"
770 enum Role {
771 User
772 Admin
773 }
774
775 model User {
776 id Int @id @auto
777 role Role @default(User)
778 }
779 "#,
780 )
781 .unwrap();
782
783 assert_eq!(schema.models.len(), 1);
784 assert_eq!(schema.enums.len(), 1);
785 }
786
787 #[test]
788 fn test_validate_invalid_enum_default() {
789 let result = validate_schema(
790 r#"
791 enum Role {
792 User
793 Admin
794 }
795
796 model User {
797 id Int @id @auto
798 role Role @default(Unknown)
799 }
800 "#,
801 );
802
803 assert!(result.is_err());
804 }
805
806 #[test]
807 fn test_validate_auto_on_non_int() {
808 let result = validate_schema(
809 r#"
810 model User {
811 id String @id @auto
812 email String
813 }
814 "#,
815 );
816
817 assert!(result.is_err());
818 }
819
820 #[test]
821 fn test_validate_updated_at_on_non_datetime() {
822 let result = validate_schema(
823 r#"
824 model User {
825 id Int @id @auto
826 updated_at String @updated_at
827 }
828 "#,
829 );
830
831 assert!(result.is_err());
832 }
833
834 #[test]
835 fn test_validate_empty_enum() {
836 let result = validate_schema(
837 r#"
838 enum Empty {
839 }
840
841 model User {
842 id Int @id @auto
843 }
844 "#,
845 );
846
847 assert!(result.is_err());
848 }
849
850 #[test]
851 fn test_validate_duplicate_model_names() {
852 let result = validate_schema(
853 r#"
854 model User {
855 id Int @id @auto
856 }
857
858 model User {
859 id Int @id @auto
860 }
861 "#,
862 );
863
864 assert!(result.is_ok() || result.is_err());
867 }
868
869 #[test]
870 fn test_validate_relation() {
871 let schema = validate_schema(
872 r#"
873 model User {
874 id Int @id @auto
875 posts Post[]
876 }
877
878 model Post {
879 id Int @id @auto
880 author_id Int
881 author User @relation(fields: [author_id], references: [id])
882 }
883 "#,
884 )
885 .unwrap();
886
887 assert_eq!(schema.models.len(), 2);
888 assert!(!schema.relations.is_empty());
889 }
890
891 #[test]
892 fn test_validate_index_with_invalid_field() {
893 let result = validate_schema(
894 r#"
895 model User {
896 id Int @id @auto
897 email String
898
899 @@index([nonexistent])
900 }
901 "#,
902 );
903
904 assert!(result.is_err());
905 }
906
907 #[test]
908 fn test_validate_search_on_non_string_field() {
909 let result = validate_schema(
910 r#"
911 model Post {
912 id Int @id @auto
913 views Int
914
915 @@search([views])
916 }
917 "#,
918 );
919
920 assert!(result.is_err());
921 }
922
923 #[test]
924 fn test_validate_composite_type() {
925 let schema = validate_schema(
926 r#"
927 type Address {
928 street String
929 city String
930 country String @default("US")
931 }
932
933 model User {
934 id Int @id @auto
935 address Address
936 }
937 "#,
938 );
939
940 assert!(schema.is_ok() || schema.is_err());
942 }
943
944 #[test]
947 fn test_validate_server_group_basic() {
948 let schema = validate_schema(
949 r#"
950 model User {
951 id Int @id @auto
952 }
953
954 serverGroup MainCluster {
955 server primary {
956 url = "postgres://localhost/db"
957 role = "primary"
958 }
959 }
960 "#,
961 )
962 .unwrap();
963
964 assert_eq!(schema.server_groups.len(), 1);
965 }
966
967 #[test]
968 fn test_validate_server_group_empty_servers() {
969 let result = validate_schema(
970 r#"
971 model User {
972 id Int @id @auto
973 }
974
975 serverGroup EmptyCluster {
976 }
977 "#,
978 );
979
980 assert!(result.is_err());
981 }
982
983 #[test]
984 fn test_validate_server_group_missing_url() {
985 let result = validate_schema(
986 r#"
987 model User {
988 id Int @id @auto
989 }
990
991 serverGroup Cluster {
992 server db {
993 role = "primary"
994 }
995 }
996 "#,
997 );
998
999 assert!(result.is_err());
1000 }
1001
1002 #[test]
1003 fn test_validate_server_group_invalid_strategy() {
1004 let result = validate_schema(
1005 r#"
1006 model User {
1007 id Int @id @auto
1008 }
1009
1010 serverGroup Cluster {
1011 @@strategy(InvalidStrategy)
1012
1013 server db {
1014 url = "postgres://localhost/db"
1015 }
1016 }
1017 "#,
1018 );
1019
1020 assert!(result.is_err());
1021 }
1022
1023 #[test]
1024 fn test_validate_server_group_valid_strategy() {
1025 let schema = validate_schema(
1026 r#"
1027 model User {
1028 id Int @id @auto
1029 }
1030
1031 serverGroup Cluster {
1032 @@strategy(ReadReplica)
1033 @@loadBalance(RoundRobin)
1034
1035 server primary {
1036 url = "postgres://localhost/db"
1037 role = "primary"
1038 }
1039 }
1040 "#,
1041 )
1042 .unwrap();
1043
1044 assert_eq!(schema.server_groups.len(), 1);
1045 }
1046
1047 #[test]
1048 fn test_validate_server_group_read_replica_needs_primary() {
1049 let result = validate_schema(
1050 r#"
1051 model User {
1052 id Int @id @auto
1053 }
1054
1055 serverGroup Cluster {
1056 @@strategy(ReadReplica)
1057
1058 server replica1 {
1059 url = "postgres://localhost/db"
1060 role = "replica"
1061 }
1062 }
1063 "#,
1064 );
1065
1066 assert!(result.is_err());
1067 }
1068
1069 #[test]
1070 fn test_validate_server_group_with_replicas() {
1071 let schema = validate_schema(
1072 r#"
1073 model User {
1074 id Int @id @auto
1075 }
1076
1077 serverGroup Cluster {
1078 @@strategy(ReadReplica)
1079
1080 server primary {
1081 url = "postgres://primary/db"
1082 role = "primary"
1083 weight = 1
1084 }
1085
1086 server replica1 {
1087 url = "postgres://replica1/db"
1088 role = "replica"
1089 weight = 2
1090 }
1091
1092 server replica2 {
1093 url = "postgres://replica2/db"
1094 role = "replica"
1095 weight = 2
1096 region = "us-west-1"
1097 }
1098 }
1099 "#,
1100 )
1101 .unwrap();
1102
1103 let cluster = schema.get_server_group("Cluster").unwrap();
1104 assert_eq!(cluster.servers.len(), 3);
1105 }
1106
1107 #[test]
1108 fn test_validate_server_group_zero_weight() {
1109 let result = validate_schema(
1110 r#"
1111 model User {
1112 id Int @id @auto
1113 }
1114
1115 serverGroup Cluster {
1116 server db {
1117 url = "postgres://localhost/db"
1118 weight = 0
1119 }
1120 }
1121 "#,
1122 );
1123
1124 assert!(result.is_err());
1125 }
1126
1127 #[test]
1128 fn test_validate_server_group_invalid_load_balance() {
1129 let result = validate_schema(
1130 r#"
1131 model User {
1132 id Int @id @auto
1133 }
1134
1135 serverGroup Cluster {
1136 @@loadBalance(InvalidStrategy)
1137
1138 server db {
1139 url = "postgres://localhost/db"
1140 }
1141 }
1142 "#,
1143 );
1144
1145 assert!(result.is_err());
1146 }
1147}