1use std::collections::HashSet;
9
10use super::{
11 ir::{
12 AuthoringIR, IRArgument, IREnum, IREnumValue, IRField, IRInputField, IRInputType,
13 IRInterface, IRMutation, IRUnion,
14 },
15 lowering::SqlTemplate,
16};
17use crate::{
18 error::Result,
19 schema::{
20 ArgumentDefinition, AutoParams as SchemaAutoParams, CompiledSchema, DeprecationInfo,
21 EnumDefinition, EnumValueDefinition, FieldDefinition, FieldType, InputFieldDefinition,
22 InputObjectDefinition, InterfaceDefinition, MutationDefinition, QueryDefinition,
23 SubscriptionDefinition, TypeDefinition, UnionDefinition,
24 },
25 validation::{CustomTypeDef, CustomTypeRegistry, CustomTypeRegistryConfig},
26};
27
28pub struct CodeGenerator {
30 optimize: bool,
31}
32
33impl CodeGenerator {
34 #[must_use]
36 pub fn new(optimize: bool) -> Self {
37 Self { optimize }
38 }
39
40 pub fn generate(&self, ir: &AuthoringIR, _templates: &[SqlTemplate]) -> Result<CompiledSchema> {
103 let known_types: HashSet<String> = ir.types.iter().map(|t| t.name.clone()).collect();
105
106 let types = ir
107 .types
108 .iter()
109 .map(|t| {
110 TypeDefinition {
111 name: t.name.clone(),
112 sql_source: t.sql_source.clone().unwrap_or_else(|| t.name.clone()),
113 jsonb_column: "data".to_string(),
114 fields: Self::map_fields(&t.fields, &known_types),
115 description: t.description.clone(),
116 sql_projection_hint: None, implements: Vec::new(), is_error: false,
120 }
121 })
122 .collect();
123
124 let queries = ir
125 .queries
126 .iter()
127 .map(|q| {
128 QueryDefinition {
129 name: q.name.clone(),
130 return_type: q.return_type.clone(),
131 returns_list: q.returns_list,
132 nullable: q.nullable,
133 arguments: Self::map_arguments(&q.arguments, &known_types),
134 sql_source: q.sql_source.clone(),
135 description: q.description.clone(),
136 auto_params: SchemaAutoParams {
137 has_where: q.auto_params.has_where,
138 has_order_by: q.auto_params.has_order_by,
139 has_limit: q.auto_params.has_limit,
140 has_offset: q.auto_params.has_offset,
141 },
142 deprecation: None, jsonb_column: "data".to_string(), }
145 })
146 .collect();
147
148 let mutations = ir.mutations.iter().map(|m| Self::map_mutation(m, &known_types)).collect();
149
150 let subscriptions = ir
151 .subscriptions
152 .iter()
153 .map(|s| {
154 SubscriptionDefinition {
155 name: s.name.clone(),
156 return_type: s.return_type.clone(),
157 arguments: Self::map_arguments(&s.arguments, &known_types),
158 description: s.description.clone(),
159 topic: None, filter: None, fields: Vec::new(), deprecation: None, }
164 })
165 .collect();
166
167 let enums = ir.enums.iter().map(|e| Self::map_enum(e)).collect();
169
170 let interfaces =
172 ir.interfaces.iter().map(|i| Self::map_interface(i, &known_types)).collect();
173
174 let unions = ir.unions.iter().map(|u| Self::map_union(u)).collect();
176
177 let input_types =
179 ir.input_types.iter().map(|i| Self::map_input_type(i, &known_types)).collect();
180
181 let custom_scalars = Self::build_custom_type_registry(&ir.scalars)?;
183
184 Ok(CompiledSchema {
185 types,
186 enums,
187 input_types,
188 interfaces,
189 unions,
190 queries,
191 mutations,
192 subscriptions,
193 directives: Vec::new(),
197 fact_tables: std::collections::HashMap::new(),
202 observers: Vec::new(),
205 federation: None,
207 security: None,
209 observers_config: None,
211 schema_sdl: None,
212 custom_scalars,
214 })
215 }
216
217 fn map_fields(ir_fields: &[IRField], known_types: &HashSet<String>) -> Vec<FieldDefinition> {
219 ir_fields
220 .iter()
221 .map(|f| {
222 let field_type = FieldType::parse(&f.field_type, known_types);
223 FieldDefinition {
224 name: f.name.clone(),
225 field_type,
226 nullable: f.nullable,
227 description: f.description.clone(),
228 default_value: None, vector_config: None, alias: None, deprecation: None, requires_scope: None, encryption: None,
234 }
235 })
236 .collect()
237 }
238
239 fn map_arguments(
241 ir_args: &[IRArgument],
242 known_types: &HashSet<String>,
243 ) -> Vec<ArgumentDefinition> {
244 ir_args
245 .iter()
246 .map(|a| {
247 let arg_type = FieldType::parse(&a.arg_type, known_types);
248 ArgumentDefinition {
249 name: a.name.clone(),
250 arg_type,
251 nullable: a.nullable,
252 default_value: a.default_value.clone(),
253 description: a.description.clone(),
254 deprecation: None, }
256 })
257 .collect()
258 }
259
260 fn map_mutation(m: &IRMutation, known_types: &HashSet<String>) -> MutationDefinition {
262 use super::ir::MutationOperation as IRMutationOp;
263 use crate::schema::MutationOperation;
264
265 let operation = match m.operation {
268 IRMutationOp::Create => MutationOperation::Insert {
269 table: m.return_type.to_lowercase(), },
271 IRMutationOp::Update => MutationOperation::Update {
272 table: m.return_type.to_lowercase(),
273 },
274 IRMutationOp::Delete => MutationOperation::Delete {
275 table: m.return_type.to_lowercase(),
276 },
277 IRMutationOp::Custom => MutationOperation::Custom,
278 };
279
280 MutationDefinition {
281 name: m.name.clone(),
282 return_type: m.return_type.clone(),
283 arguments: Self::map_arguments(&m.arguments, known_types),
284 description: m.description.clone(),
285 operation,
286 deprecation: None, sql_source: None,
288 }
289 }
290
291 fn map_enum(e: &IREnum) -> EnumDefinition {
293 EnumDefinition {
294 name: e.name.clone(),
295 values: e.values.iter().map(|v| Self::map_enum_value(v)).collect(),
296 description: e.description.clone(),
297 }
298 }
299
300 fn map_enum_value(v: &IREnumValue) -> EnumValueDefinition {
302 EnumValueDefinition {
303 name: v.name.clone(),
304 description: v.description.clone(),
305 deprecation: v.deprecation_reason.as_ref().map(|reason| DeprecationInfo {
306 reason: Some(reason.clone()),
307 }),
308 }
309 }
310
311 fn map_interface(i: &IRInterface, known_types: &HashSet<String>) -> InterfaceDefinition {
313 InterfaceDefinition {
314 name: i.name.clone(),
315 fields: Self::map_fields(&i.fields, known_types),
316 description: i.description.clone(),
317 }
318 }
319
320 fn map_union(u: &IRUnion) -> UnionDefinition {
322 UnionDefinition {
323 name: u.name.clone(),
324 member_types: u.types.clone(),
325 description: u.description.clone(),
326 }
327 }
328
329 fn map_input_type(i: &IRInputType, known_types: &HashSet<String>) -> InputObjectDefinition {
331 InputObjectDefinition {
332 name: i.name.clone(),
333 fields: Self::map_input_fields(&i.fields, known_types),
334 description: i.description.clone(),
335 metadata: None,
336 }
337 }
338
339 fn map_input_fields(
341 ir_fields: &[IRInputField],
342 _known_types: &HashSet<String>,
343 ) -> Vec<InputFieldDefinition> {
344 ir_fields
345 .iter()
346 .map(|f| {
347 InputFieldDefinition {
348 name: f.name.clone(),
349 field_type: f.field_type.clone(), description: f.description.clone(),
352 default_value: f.default_value.as_ref().map(|v| v.to_string()),
353 deprecation: None, validation_rules: Vec::new(), }
357 })
358 .collect()
359 }
360
361 fn build_custom_type_registry(
363 ir_scalars: &[super::ir::IRScalar],
364 ) -> Result<CustomTypeRegistry> {
365 let registry = CustomTypeRegistry::new(CustomTypeRegistryConfig::default());
366
367 for ir_scalar in ir_scalars {
368 let def = CustomTypeDef {
369 name: ir_scalar.name.clone(),
370 description: ir_scalar.description.clone(),
371 specified_by_url: ir_scalar.specified_by_url.clone(),
372 validation_rules: ir_scalar.validation_rules.clone(),
373 elo_expression: None, base_type: ir_scalar.base_type.clone(),
375 };
376
377 registry.register(ir_scalar.name.clone(), def)?;
378 }
379
380 Ok(registry)
381 }
382
383 #[must_use]
385 pub const fn optimize(&self) -> bool {
386 self.optimize
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::{
393 super::ir::{AutoParams, IRArgument, IRField, IRQuery, IRSubscription, IRType},
394 *,
395 };
396
397 #[test]
398 fn test_code_generator_new() {
399 let generator = CodeGenerator::new(true);
400 assert!(generator.optimize());
401
402 let generator = CodeGenerator::new(false);
403 assert!(!generator.optimize());
404 }
405
406 #[test]
407 fn test_generate_empty_schema() {
408 let generator = CodeGenerator::new(true);
409 let ir = AuthoringIR::new();
410 let templates = Vec::new();
411
412 let result = generator.generate(&ir, &templates);
413 assert!(result.is_ok());
414
415 let schema = result.unwrap();
416 assert!(schema.types.is_empty());
417 assert!(schema.queries.is_empty());
418 }
419
420 #[test]
421 fn test_generate_types_with_fields() {
422 let generator = CodeGenerator::new(true);
423 let mut ir = AuthoringIR::new();
424
425 ir.types.push(IRType {
426 name: "User".to_string(),
427 fields: vec![
428 IRField {
429 name: "id".to_string(),
430 field_type: "ID!".to_string(),
431 nullable: false,
432 description: Some("User ID".to_string()),
433 sql_column: Some("id".to_string()),
434 },
435 IRField {
436 name: "name".to_string(),
437 field_type: "String".to_string(),
438 nullable: true,
439 description: None,
440 sql_column: None,
441 },
442 IRField {
443 name: "age".to_string(),
444 field_type: "Int".to_string(),
445 nullable: true,
446 description: None,
447 sql_column: None,
448 },
449 ],
450 sql_source: Some("v_user".to_string()),
451 description: Some("User type".to_string()),
452 });
453
454 let result = generator.generate(&ir, &[]);
455 assert!(result.is_ok());
456
457 let schema = result.unwrap();
458 assert_eq!(schema.types.len(), 1);
459
460 let user_type = &schema.types[0];
461 assert_eq!(user_type.name, "User");
462 assert_eq!(user_type.sql_source, "v_user");
463 assert_eq!(user_type.fields.len(), 3);
464
465 assert_eq!(user_type.fields[0].name, "id");
467 assert_eq!(user_type.fields[0].field_type, FieldType::Id);
468 assert!(!user_type.fields[0].nullable);
469
470 assert_eq!(user_type.fields[1].name, "name");
471 assert_eq!(user_type.fields[1].field_type, FieldType::String);
472
473 assert_eq!(user_type.fields[2].name, "age");
474 assert_eq!(user_type.fields[2].field_type, FieldType::Int);
475 }
476
477 #[test]
478 fn test_generate_queries_with_arguments() {
479 let generator = CodeGenerator::new(true);
480 let mut ir = AuthoringIR::new();
481
482 ir.types.push(IRType {
483 name: "User".to_string(),
484 fields: vec![],
485 sql_source: None,
486 description: None,
487 });
488
489 ir.queries.push(IRQuery {
490 name: "user".to_string(),
491 return_type: "User".to_string(),
492 returns_list: false,
493 nullable: true,
494 arguments: vec![IRArgument {
495 name: "id".to_string(),
496 arg_type: "ID!".to_string(),
497 nullable: false,
498 default_value: None,
499 description: Some("User ID to fetch".to_string()),
500 }],
501 sql_source: Some("v_user".to_string()),
502 description: Some("Fetch a single user".to_string()),
503 auto_params: AutoParams::default(),
504 });
505
506 ir.queries.push(IRQuery {
507 name: "users".to_string(),
508 return_type: "User".to_string(),
509 returns_list: true,
510 nullable: false,
511 arguments: vec![IRArgument {
512 name: "limit".to_string(),
513 arg_type: "Int".to_string(),
514 nullable: true,
515 default_value: Some(serde_json::json!(10)),
516 description: None,
517 }],
518 sql_source: Some("v_user".to_string()),
519 description: None,
520 auto_params: AutoParams {
521 has_where: true,
522 has_order_by: true,
523 has_limit: true,
524 has_offset: true,
525 },
526 });
527
528 let result = generator.generate(&ir, &[]);
529 assert!(result.is_ok());
530
531 let schema = result.unwrap();
532 assert_eq!(schema.queries.len(), 2);
533
534 let user_query = &schema.queries[0];
536 assert_eq!(user_query.name, "user");
537 assert!(!user_query.returns_list);
538 assert!(user_query.nullable);
539 assert_eq!(user_query.arguments.len(), 1);
540 assert_eq!(user_query.arguments[0].name, "id");
541 assert_eq!(user_query.arguments[0].arg_type, FieldType::Id);
542
543 let users_query = &schema.queries[1];
545 assert_eq!(users_query.name, "users");
546 assert!(users_query.returns_list);
547 assert!(users_query.auto_params.has_where);
548 assert!(users_query.auto_params.has_order_by);
549 assert_eq!(users_query.arguments[0].default_value, Some(serde_json::json!(10)));
550 }
551
552 #[test]
553 fn test_generate_mutations() {
554 use super::super::ir::MutationOperation as IRMutationOp;
555
556 let generator = CodeGenerator::new(true);
557 let mut ir = AuthoringIR::new();
558
559 ir.mutations.push(IRMutation {
560 name: "createUser".to_string(),
561 return_type: "User".to_string(),
562 nullable: false,
563 arguments: vec![IRArgument {
564 name: "name".to_string(),
565 arg_type: "String!".to_string(),
566 nullable: false,
567 default_value: None,
568 description: None,
569 }],
570 description: Some("Create a new user".to_string()),
571 operation: IRMutationOp::Create,
572 });
573
574 let result = generator.generate(&ir, &[]);
575 assert!(result.is_ok());
576
577 let schema = result.unwrap();
578 assert_eq!(schema.mutations.len(), 1);
579
580 let mutation = &schema.mutations[0];
581 assert_eq!(mutation.name, "createUser");
582 assert!(matches!(
584 &mutation.operation,
585 crate::schema::MutationOperation::Insert { table } if table == "user"
586 ));
587 assert_eq!(mutation.arguments.len(), 1);
588 }
589
590 #[test]
591 fn test_generate_subscriptions() {
592 let generator = CodeGenerator::new(true);
593 let mut ir = AuthoringIR::new();
594
595 ir.subscriptions.push(IRSubscription {
596 name: "userCreated".to_string(),
597 return_type: "User".to_string(),
598 arguments: vec![IRArgument {
599 name: "tenantId".to_string(),
600 arg_type: "ID!".to_string(),
601 nullable: false,
602 default_value: None,
603 description: None,
604 }],
605 description: Some("Subscribe to user creation events".to_string()),
606 });
607
608 let result = generator.generate(&ir, &[]);
609 assert!(result.is_ok());
610
611 let schema = result.unwrap();
612 assert_eq!(schema.subscriptions.len(), 1);
613
614 let subscription = &schema.subscriptions[0];
615 assert_eq!(subscription.name, "userCreated");
616 assert_eq!(subscription.return_type, "User");
617 assert_eq!(subscription.arguments.len(), 1);
618 assert_eq!(subscription.arguments[0].name, "tenantId");
619 }
620
621 #[test]
622 fn test_field_type_parsing_list_types() {
623 let generator = CodeGenerator::new(true);
624 let mut ir = AuthoringIR::new();
625
626 ir.types.push(IRType {
627 name: "Post".to_string(),
628 fields: vec![
629 IRField {
630 name: "tags".to_string(),
631 field_type: "[String]".to_string(),
632 nullable: true,
633 description: None,
634 sql_column: None,
635 },
636 IRField {
637 name: "comments".to_string(),
638 field_type: "[Comment!]!".to_string(),
639 nullable: false,
640 description: None,
641 sql_column: None,
642 },
643 ],
644 sql_source: None,
645 description: None,
646 });
647
648 let result = generator.generate(&ir, &[]);
649 assert!(result.is_ok());
650
651 let schema = result.unwrap();
652 let post_type = &schema.types[0];
653
654 assert!(matches!(
656 &post_type.fields[0].field_type,
657 FieldType::List(inner) if **inner == FieldType::String
658 ));
659
660 assert!(matches!(
662 &post_type.fields[1].field_type,
663 FieldType::List(inner) if matches!(**inner, FieldType::Object(ref name) if name == "Comment")
664 ));
665 }
666
667 #[test]
668 fn test_generate_enums() {
669 use super::super::ir::{IREnum, IREnumValue};
670
671 let generator = CodeGenerator::new(true);
672 let mut ir = AuthoringIR::new();
673
674 ir.enums.push(IREnum {
675 name: "OrderStatus".to_string(),
676 values: vec![
677 IREnumValue {
678 name: "PENDING".to_string(),
679 description: Some("Order is pending".to_string()),
680 deprecation_reason: None,
681 },
682 IREnumValue {
683 name: "COMPLETED".to_string(),
684 description: None,
685 deprecation_reason: None,
686 },
687 IREnumValue {
688 name: "CANCELLED".to_string(),
689 description: None,
690 deprecation_reason: Some("Use REJECTED instead".to_string()),
691 },
692 ],
693 description: Some("Possible order statuses".to_string()),
694 });
695
696 let result = generator.generate(&ir, &[]);
697 assert!(result.is_ok());
698
699 let schema = result.unwrap();
700 assert_eq!(schema.enums.len(), 1);
701
702 let order_status = &schema.enums[0];
703 assert_eq!(order_status.name, "OrderStatus");
704 assert_eq!(order_status.values.len(), 3);
705 assert_eq!(order_status.values[0].name, "PENDING");
706 assert_eq!(order_status.values[0].description, Some("Order is pending".to_string()));
707 assert!(order_status.values[2].deprecation.is_some());
708 }
709
710 #[test]
711 fn test_generate_interfaces() {
712 use super::super::ir::IRInterface;
713
714 let generator = CodeGenerator::new(true);
715 let mut ir = AuthoringIR::new();
716
717 ir.interfaces.push(IRInterface {
718 name: "Node".to_string(),
719 fields: vec![IRField {
720 name: "id".to_string(),
721 field_type: "ID!".to_string(),
722 nullable: false,
723 description: Some("Unique identifier".to_string()),
724 sql_column: None,
725 }],
726 description: Some("An object with an ID".to_string()),
727 });
728
729 let result = generator.generate(&ir, &[]);
730 assert!(result.is_ok());
731
732 let schema = result.unwrap();
733 assert_eq!(schema.interfaces.len(), 1);
734
735 let node = &schema.interfaces[0];
736 assert_eq!(node.name, "Node");
737 assert_eq!(node.fields.len(), 1);
738 assert_eq!(node.fields[0].name, "id");
739 assert_eq!(node.fields[0].field_type, FieldType::Id);
740 }
741
742 #[test]
743 fn test_generate_unions() {
744 use super::super::ir::IRUnion;
745
746 let generator = CodeGenerator::new(true);
747 let mut ir = AuthoringIR::new();
748
749 ir.unions.push(IRUnion {
750 name: "SearchResult".to_string(),
751 types: vec![
752 "User".to_string(),
753 "Post".to_string(),
754 "Comment".to_string(),
755 ],
756 description: Some("Possible search result types".to_string()),
757 });
758
759 let result = generator.generate(&ir, &[]);
760 assert!(result.is_ok());
761
762 let schema = result.unwrap();
763 assert_eq!(schema.unions.len(), 1);
764
765 let search_result = &schema.unions[0];
766 assert_eq!(search_result.name, "SearchResult");
767 assert_eq!(search_result.member_types.len(), 3);
768 assert_eq!(search_result.member_types[0], "User");
769 }
770
771 #[test]
772 fn test_generate_input_types() {
773 use super::super::ir::{IRInputField, IRInputType};
774
775 let generator = CodeGenerator::new(true);
776 let mut ir = AuthoringIR::new();
777
778 ir.input_types.push(IRInputType {
779 name: "CreateUserInput".to_string(),
780 fields: vec![
781 IRInputField {
782 name: "name".to_string(),
783 field_type: "String!".to_string(),
784 nullable: false,
785 default_value: None,
786 description: Some("User's name".to_string()),
787 },
788 IRInputField {
789 name: "age".to_string(),
790 field_type: "Int".to_string(),
791 nullable: true,
792 default_value: Some(serde_json::json!(18)),
793 description: None,
794 },
795 ],
796 description: Some("Input for creating a user".to_string()),
797 });
798
799 let result = generator.generate(&ir, &[]);
800 assert!(result.is_ok());
801
802 let schema = result.unwrap();
803 assert_eq!(schema.input_types.len(), 1);
804
805 let create_user = &schema.input_types[0];
806 assert_eq!(create_user.name, "CreateUserInput");
807 assert_eq!(create_user.fields.len(), 2);
808 assert_eq!(create_user.fields[0].name, "name");
809 assert_eq!(create_user.fields[1].default_value, Some("18".to_string()));
810 }
811
812 #[test]
813 fn test_generate_with_empty_scalars() {
814 let generator = CodeGenerator::new(true);
815 let mut ir = AuthoringIR::new();
816 ir.scalars = vec![];
817
818 let result = generator.generate(&ir, &[]);
819 assert!(result.is_ok());
820
821 let schema = result.unwrap();
822 assert_eq!(schema.custom_scalars.count(), 0);
823 }
824
825 #[test]
826 fn test_generate_with_single_custom_scalar() {
827 use super::super::ir::IRScalar;
828
829 let generator = CodeGenerator::new(true);
830 let mut ir = AuthoringIR::new();
831
832 ir.scalars.push(IRScalar {
833 name: "LibraryCode".to_string(),
834 description: Some("Unique library book identifier".to_string()),
835 specified_by_url: None,
836 validation_rules: vec![],
837 base_type: Some("String".to_string()),
838 });
839
840 let result = generator.generate(&ir, &[]);
841 assert!(result.is_ok());
842
843 let schema = result.unwrap();
844 assert_eq!(schema.custom_scalars.count(), 1);
845 assert!(schema.custom_scalars.exists("LibraryCode"));
846
847 let def = schema.custom_scalars.get("LibraryCode").unwrap();
848 assert_eq!(def.name, "LibraryCode");
849 assert_eq!(def.description, Some("Unique library book identifier".to_string()));
850 assert_eq!(def.base_type, Some("String".to_string()));
851 }
852
853 #[test]
854 fn test_generate_with_multiple_custom_scalars() {
855 use super::super::ir::IRScalar;
856
857 let generator = CodeGenerator::new(true);
858 let mut ir = AuthoringIR::new();
859
860 ir.scalars.push(IRScalar {
861 name: "LibraryCode".to_string(),
862 description: None,
863 specified_by_url: None,
864 validation_rules: vec![],
865 base_type: Some("String".to_string()),
866 });
867
868 ir.scalars.push(IRScalar {
869 name: "StudentID".to_string(),
870 description: Some("University student identifier".to_string()),
871 specified_by_url: None,
872 validation_rules: vec![],
873 base_type: None,
874 });
875
876 ir.scalars.push(IRScalar {
877 name: "PatientID".to_string(),
878 description: Some("Hospital patient identifier".to_string()),
879 specified_by_url: Some("https://hl7.org/".to_string()),
880 validation_rules: vec![],
881 base_type: Some("String".to_string()),
882 });
883
884 let result = generator.generate(&ir, &[]);
885 assert!(result.is_ok());
886
887 let schema = result.unwrap();
888 assert_eq!(schema.custom_scalars.count(), 3);
889
890 assert!(schema.custom_scalars.exists("LibraryCode"));
892 assert!(schema.custom_scalars.exists("StudentID"));
893 assert!(schema.custom_scalars.exists("PatientID"));
894
895 let patient_id = schema.custom_scalars.get("PatientID").unwrap();
897 assert_eq!(patient_id.specified_by_url, Some("https://hl7.org/".to_string()));
898 }
899
900 #[test]
901 fn test_generate_preserves_scalar_description() {
902 use super::super::ir::IRScalar;
903
904 let generator = CodeGenerator::new(true);
905 let mut ir = AuthoringIR::new();
906
907 ir.scalars.push(IRScalar {
908 name: "Email".to_string(),
909 description: Some("RFC 5322 compliant email address".to_string()),
910 specified_by_url: Some("https://tools.ietf.org/html/rfc5322".to_string()),
911 validation_rules: vec![],
912 base_type: Some("String".to_string()),
913 });
914
915 let result = generator.generate(&ir, &[]);
916 assert!(result.is_ok());
917
918 let schema = result.unwrap();
919 let email_def = schema.custom_scalars.get("Email").unwrap();
920
921 assert_eq!(email_def.description, Some("RFC 5322 compliant email address".to_string()));
922 assert_eq!(
923 email_def.specified_by_url,
924 Some("https://tools.ietf.org/html/rfc5322".to_string())
925 );
926 }
927
928 #[test]
929 fn test_generate_scalars_with_validation_rules() {
930 use super::super::ir::IRScalar;
931 use crate::validation::ValidationRule;
932
933 let generator = CodeGenerator::new(true);
934 let mut ir = AuthoringIR::new();
935
936 ir.scalars.push(IRScalar {
937 name: "StudentID".to_string(),
938 description: None,
939 specified_by_url: None,
940 validation_rules: vec![ValidationRule::Length {
941 min: Some(5),
942 max: Some(15),
943 }],
944 base_type: Some("String".to_string()),
945 });
946
947 let result = generator.generate(&ir, &[]);
948 assert!(result.is_ok());
949
950 let schema = result.unwrap();
951 let student_id = schema.custom_scalars.get("StudentID").unwrap();
952
953 assert_eq!(student_id.validation_rules.len(), 1);
954 }
955}