1use serde::{Deserialize, Serialize};
7use fraiseql_core::validation::ValidationRule;
8
9#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
11pub struct IntermediateSchema {
12 #[serde(default = "default_version")]
14 pub version: String,
15
16 #[serde(default)]
18 pub types: Vec<IntermediateType>,
19
20 #[serde(default)]
22 pub enums: Vec<IntermediateEnum>,
23
24 #[serde(default)]
26 pub input_types: Vec<IntermediateInputObject>,
27
28 #[serde(default)]
30 pub interfaces: Vec<IntermediateInterface>,
31
32 #[serde(default)]
34 pub unions: Vec<IntermediateUnion>,
35
36 #[serde(default)]
38 pub queries: Vec<IntermediateQuery>,
39
40 #[serde(default)]
42 pub mutations: Vec<IntermediateMutation>,
43
44 #[serde(default)]
46 pub subscriptions: Vec<IntermediateSubscription>,
47
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub fragments: Option<Vec<IntermediateFragment>>,
51
52 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub directives: Option<Vec<IntermediateDirective>>,
55
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub fact_tables: Option<Vec<IntermediateFactTable>>,
59
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub aggregate_queries: Option<Vec<IntermediateAggregateQuery>>,
63
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub observers: Option<Vec<IntermediateObserver>>,
67
68 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub custom_scalars: Option<Vec<IntermediateScalar>>,
75
76 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub security: Option<serde_json::Value>,
81}
82
83fn default_version() -> String {
84 "2.0.0".to_string()
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct IntermediateType {
90 pub name: String,
92
93 pub fields: Vec<IntermediateField>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub description: Option<String>,
99
100 #[serde(default, skip_serializing_if = "Vec::is_empty")]
102 pub implements: Vec<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct IntermediateField {
111 pub name: String,
113
114 #[serde(rename = "type")]
118 pub field_type: String,
119
120 pub nullable: bool,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub description: Option<String>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub directives: Option<Vec<IntermediateAppliedDirective>>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
147 pub requires_scope: Option<String>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct IntermediateEnum {
174 pub name: String,
176
177 pub values: Vec<IntermediateEnumValue>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub description: Option<String>,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197pub struct IntermediateEnumValue {
198 pub name: String,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub description: Option<String>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub deprecated: Option<IntermediateDeprecation>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212pub struct IntermediateDeprecation {
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub reason: Option<String>,
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct IntermediateScalar {
248 pub name: String,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub description: Option<String>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub specified_by_url: Option<String>,
258
259 #[serde(default)]
261 pub validation_rules: Vec<ValidationRule>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub base_type: Option<String>,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291pub struct IntermediateInputObject {
292 pub name: String,
294
295 pub fields: Vec<IntermediateInputField>,
297
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub description: Option<String>,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316pub struct IntermediateInputField {
317 pub name: String,
319
320 #[serde(rename = "type")]
322 pub field_type: String,
323
324 #[serde(default)]
326 pub nullable: bool,
327
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub description: Option<String>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub default: Option<serde_json::Value>,
335
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub deprecated: Option<IntermediateDeprecation>,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
343pub struct IntermediateQuery {
344 pub name: String,
346
347 pub return_type: String,
349
350 #[serde(default)]
352 pub returns_list: bool,
353
354 #[serde(default)]
356 pub nullable: bool,
357
358 #[serde(default)]
360 pub arguments: Vec<IntermediateArgument>,
361
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub description: Option<String>,
365
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub sql_source: Option<String>,
369
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub auto_params: Option<IntermediateAutoParams>,
373
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub deprecated: Option<IntermediateDeprecation>,
377
378 #[serde(skip_serializing_if = "Option::is_none")]
381 pub jsonb_column: Option<String>,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
386pub struct IntermediateMutation {
387 pub name: String,
389
390 pub return_type: String,
392
393 #[serde(default)]
395 pub returns_list: bool,
396
397 #[serde(default)]
399 pub nullable: bool,
400
401 #[serde(default)]
403 pub arguments: Vec<IntermediateArgument>,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub description: Option<String>,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub sql_source: Option<String>,
412
413 #[serde(skip_serializing_if = "Option::is_none")]
415 pub operation: Option<String>,
416
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub deprecated: Option<IntermediateDeprecation>,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443pub struct IntermediateInterface {
444 pub name: String,
446
447 pub fields: Vec<IntermediateField>,
449
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub description: Option<String>,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
457pub struct IntermediateArgument {
458 pub name: String,
460
461 #[serde(rename = "type")]
465 pub arg_type: String,
466
467 pub nullable: bool,
469
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub default: Option<serde_json::Value>,
473
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub deprecated: Option<IntermediateDeprecation>,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
499pub struct IntermediateUnion {
500 pub name: String,
502
503 pub member_types: Vec<String>,
505
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub description: Option<String>,
509}
510
511#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
513pub struct IntermediateAutoParams {
514 #[serde(default)]
516 pub limit: bool,
517 #[serde(default)]
519 pub offset: bool,
520 #[serde(rename = "where", default)]
522 pub where_clause: bool,
523 #[serde(default)]
525 pub order_by: bool,
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
555pub struct IntermediateSubscription {
556 pub name: String,
558
559 pub return_type: String,
561
562 #[serde(default)]
564 pub arguments: Vec<IntermediateArgument>,
565
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub description: Option<String>,
569
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub topic: Option<String>,
573
574 #[serde(skip_serializing_if = "Option::is_none")]
576 pub filter: Option<IntermediateSubscriptionFilter>,
577
578 #[serde(default, skip_serializing_if = "Vec::is_empty")]
580 pub fields: Vec<String>,
581
582 #[serde(skip_serializing_if = "Option::is_none")]
584 pub deprecated: Option<IntermediateDeprecation>,
585}
586
587#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
591pub struct IntermediateSubscriptionFilter {
592 pub conditions: Vec<IntermediateFilterCondition>,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
598pub struct IntermediateFilterCondition {
599 pub argument: String,
601
602 pub path: String,
604}
605
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
625pub struct IntermediateFragment {
626 pub name: String,
628
629 #[serde(rename = "on")]
631 pub type_condition: String,
632
633 pub fields: Vec<IntermediateFragmentField>,
635
636 #[serde(skip_serializing_if = "Option::is_none")]
638 pub description: Option<String>,
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
643#[serde(untagged)]
644pub enum IntermediateFragmentField {
645 Simple(String),
647
648 Complex(IntermediateFragmentFieldDef),
650}
651
652#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
654pub struct IntermediateFragmentFieldDef {
655 pub name: String,
657
658 #[serde(skip_serializing_if = "Option::is_none")]
660 pub alias: Option<String>,
661
662 #[serde(skip_serializing_if = "Option::is_none")]
664 pub fields: Option<Vec<IntermediateFragmentField>>,
665
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub spread: Option<String>,
669
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub directives: Option<Vec<IntermediateAppliedDirective>>,
673}
674
675#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
691pub struct IntermediateDirective {
692 pub name: String,
694
695 pub locations: Vec<String>,
697
698 #[serde(default)]
700 pub arguments: Vec<IntermediateArgument>,
701
702 #[serde(default)]
704 pub repeatable: bool,
705
706 #[serde(skip_serializing_if = "Option::is_none")]
708 pub description: Option<String>,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
722pub struct IntermediateAppliedDirective {
723 pub name: String,
725
726 #[serde(default, skip_serializing_if = "Option::is_none")]
728 pub arguments: Option<serde_json::Value>,
729}
730
731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737pub struct IntermediateFactTable {
738 pub table_name: String,
740 pub measures: Vec<IntermediateMeasure>,
742 pub dimensions: IntermediateDimensions,
744 pub denormalized_filters: Vec<IntermediateFilter>,
746}
747
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
750pub struct IntermediateMeasure {
751 pub name: String,
753 pub sql_type: String,
755 pub nullable: bool,
757}
758
759#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
761pub struct IntermediateDimensions {
762 pub name: String,
764 pub paths: Vec<IntermediateDimensionPath>,
766}
767
768#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
770pub struct IntermediateDimensionPath {
771 pub name: String,
773 #[serde(alias = "path")]
775 pub json_path: String,
776 #[serde(alias = "type")]
778 pub data_type: String,
779}
780
781#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
783pub struct IntermediateFilter {
784 pub name: String,
786 pub sql_type: String,
788 pub indexed: bool,
790}
791
792#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
794pub struct IntermediateAggregateQuery {
795 pub name: String,
797 pub fact_table: String,
799 pub auto_group_by: bool,
801 pub auto_aggregates: bool,
803 #[serde(skip_serializing_if = "Option::is_none")]
805 pub description: Option<String>,
806}
807
808#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
847pub struct IntermediateObserver {
848 pub name: String,
850
851 pub entity: String,
853
854 pub event: String,
856
857 pub actions: Vec<IntermediateObserverAction>,
859
860 #[serde(skip_serializing_if = "Option::is_none")]
862 pub condition: Option<String>,
863
864 pub retry: IntermediateRetryConfig,
866}
867
868pub type IntermediateObserverAction = serde_json::Value;
873
874#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
887pub struct IntermediateRetryConfig {
888 pub max_attempts: u32,
890
891 pub backoff_strategy: String,
893
894 pub initial_delay_ms: u32,
896
897 pub max_delay_ms: u32,
899}
900
901#[cfg(test)]
902mod tests {
903 use super::*;
904
905 #[test]
906 fn test_parse_minimal_schema() {
907 let json = r#"{
908 "types": [],
909 "queries": [],
910 "mutations": []
911 }"#;
912
913 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
914 assert_eq!(schema.version, "2.0.0");
915 assert_eq!(schema.types.len(), 0);
916 assert_eq!(schema.queries.len(), 0);
917 assert_eq!(schema.mutations.len(), 0);
918 }
919
920 #[test]
921 fn test_parse_type_with_type_field() {
922 let json = r#"{
923 "types": [{
924 "name": "User",
925 "fields": [
926 {
927 "name": "id",
928 "type": "Int",
929 "nullable": false
930 },
931 {
932 "name": "name",
933 "type": "String",
934 "nullable": false
935 }
936 ]
937 }],
938 "queries": [],
939 "mutations": []
940 }"#;
941
942 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
943 assert_eq!(schema.types.len(), 1);
944 assert_eq!(schema.types[0].name, "User");
945 assert_eq!(schema.types[0].fields.len(), 2);
946 assert_eq!(schema.types[0].fields[0].name, "id");
947 assert_eq!(schema.types[0].fields[0].field_type, "Int");
948 assert!(!schema.types[0].fields[0].nullable);
949 }
950
951 #[test]
952 fn test_parse_query_with_arguments() {
953 let json = r#"{
954 "types": [],
955 "queries": [{
956 "name": "users",
957 "return_type": "User",
958 "returns_list": true,
959 "nullable": false,
960 "arguments": [
961 {
962 "name": "limit",
963 "type": "Int",
964 "nullable": false,
965 "default": 10
966 }
967 ],
968 "sql_source": "v_user"
969 }],
970 "mutations": []
971 }"#;
972
973 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
974 assert_eq!(schema.queries.len(), 1);
975 assert_eq!(schema.queries[0].arguments.len(), 1);
976 assert_eq!(schema.queries[0].arguments[0].arg_type, "Int");
977 assert_eq!(schema.queries[0].arguments[0].default, Some(serde_json::json!(10)));
978 }
979
980 #[test]
981 fn test_parse_fragment_simple() {
982 let json = r#"{
983 "types": [],
984 "queries": [],
985 "mutations": [],
986 "fragments": [{
987 "name": "UserFields",
988 "on": "User",
989 "fields": ["id", "name", "email"]
990 }]
991 }"#;
992
993 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
994 assert!(schema.fragments.is_some());
995 let fragments = schema.fragments.unwrap();
996 assert_eq!(fragments.len(), 1);
997 assert_eq!(fragments[0].name, "UserFields");
998 assert_eq!(fragments[0].type_condition, "User");
999 assert_eq!(fragments[0].fields.len(), 3);
1000
1001 match &fragments[0].fields[0] {
1003 IntermediateFragmentField::Simple(name) => assert_eq!(name, "id"),
1004 IntermediateFragmentField::Complex(_) => panic!("Expected simple field"),
1005 }
1006 }
1007
1008 #[test]
1009 fn test_parse_fragment_with_nested_fields() {
1010 let json = r#"{
1011 "types": [],
1012 "queries": [],
1013 "mutations": [],
1014 "fragments": [{
1015 "name": "PostFields",
1016 "on": "Post",
1017 "fields": [
1018 "id",
1019 "title",
1020 {
1021 "name": "author",
1022 "alias": "writer",
1023 "fields": ["id", "name"]
1024 }
1025 ]
1026 }]
1027 }"#;
1028
1029 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1030 let fragments = schema.fragments.unwrap();
1031 assert_eq!(fragments[0].fields.len(), 3);
1032
1033 match &fragments[0].fields[2] {
1035 IntermediateFragmentField::Complex(def) => {
1036 assert_eq!(def.name, "author");
1037 assert_eq!(def.alias, Some("writer".to_string()));
1038 assert!(def.fields.is_some());
1039 assert_eq!(def.fields.as_ref().unwrap().len(), 2);
1040 },
1041 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1042 }
1043 }
1044
1045 #[test]
1046 fn test_parse_directive_definition() {
1047 let json = r#"{
1048 "types": [],
1049 "queries": [],
1050 "mutations": [],
1051 "directives": [{
1052 "name": "auth",
1053 "locations": ["FIELD_DEFINITION", "OBJECT"],
1054 "arguments": [
1055 {"name": "role", "type": "String", "nullable": false}
1056 ],
1057 "description": "Requires authentication"
1058 }]
1059 }"#;
1060
1061 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1062 assert!(schema.directives.is_some());
1063 let directives = schema.directives.unwrap();
1064 assert_eq!(directives.len(), 1);
1065 assert_eq!(directives[0].name, "auth");
1066 assert_eq!(directives[0].locations, vec!["FIELD_DEFINITION", "OBJECT"]);
1067 assert_eq!(directives[0].arguments.len(), 1);
1068 assert_eq!(directives[0].description, Some("Requires authentication".to_string()));
1069 }
1070
1071 #[test]
1072 fn test_parse_field_with_directive() {
1073 let json = r#"{
1074 "types": [{
1075 "name": "User",
1076 "fields": [
1077 {
1078 "name": "oldId",
1079 "type": "Int",
1080 "nullable": false,
1081 "directives": [
1082 {"name": "deprecated", "arguments": {"reason": "Use 'id' instead"}}
1083 ]
1084 }
1085 ]
1086 }],
1087 "queries": [],
1088 "mutations": []
1089 }"#;
1090
1091 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1092 let field = &schema.types[0].fields[0];
1093 assert_eq!(field.name, "oldId");
1094 assert!(field.directives.is_some());
1095 let directives = field.directives.as_ref().unwrap();
1096 assert_eq!(directives.len(), 1);
1097 assert_eq!(directives[0].name, "deprecated");
1098 assert_eq!(
1099 directives[0].arguments,
1100 Some(serde_json::json!({"reason": "Use 'id' instead"}))
1101 );
1102 }
1103
1104 #[test]
1105 fn test_parse_fragment_with_spread() {
1106 let json = r#"{
1107 "types": [],
1108 "queries": [],
1109 "mutations": [],
1110 "fragments": [
1111 {
1112 "name": "UserFields",
1113 "on": "User",
1114 "fields": ["id", "name"]
1115 },
1116 {
1117 "name": "PostWithAuthor",
1118 "on": "Post",
1119 "fields": [
1120 "id",
1121 "title",
1122 {
1123 "name": "author",
1124 "spread": "UserFields"
1125 }
1126 ]
1127 }
1128 ]
1129 }"#;
1130
1131 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1132 let fragments = schema.fragments.unwrap();
1133 assert_eq!(fragments.len(), 2);
1134
1135 match &fragments[1].fields[2] {
1137 IntermediateFragmentField::Complex(def) => {
1138 assert_eq!(def.name, "author");
1139 assert_eq!(def.spread, Some("UserFields".to_string()));
1140 },
1141 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1142 }
1143 }
1144
1145 #[test]
1146 fn test_parse_enum() {
1147 let json = r#"{
1148 "types": [],
1149 "queries": [],
1150 "mutations": [],
1151 "enums": [{
1152 "name": "OrderStatus",
1153 "values": [
1154 {"name": "PENDING"},
1155 {"name": "PROCESSING", "description": "Currently being processed"},
1156 {"name": "SHIPPED"},
1157 {"name": "DELIVERED"}
1158 ],
1159 "description": "Possible states of an order"
1160 }]
1161 }"#;
1162
1163 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1164 assert_eq!(schema.enums.len(), 1);
1165 let enum_def = &schema.enums[0];
1166 assert_eq!(enum_def.name, "OrderStatus");
1167 assert_eq!(enum_def.description, Some("Possible states of an order".to_string()));
1168 assert_eq!(enum_def.values.len(), 4);
1169 assert_eq!(enum_def.values[0].name, "PENDING");
1170 assert_eq!(enum_def.values[1].description, Some("Currently being processed".to_string()));
1171 }
1172
1173 #[test]
1174 fn test_parse_enum_with_deprecated_value() {
1175 let json = r#"{
1176 "types": [],
1177 "queries": [],
1178 "mutations": [],
1179 "enums": [{
1180 "name": "UserRole",
1181 "values": [
1182 {"name": "ADMIN"},
1183 {"name": "USER"},
1184 {"name": "GUEST", "deprecated": {"reason": "Use USER with limited permissions instead"}}
1185 ]
1186 }]
1187 }"#;
1188
1189 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1190 let enum_def = &schema.enums[0];
1191 assert_eq!(enum_def.values.len(), 3);
1192
1193 let guest = &enum_def.values[2];
1195 assert_eq!(guest.name, "GUEST");
1196 assert!(guest.deprecated.is_some());
1197 assert_eq!(
1198 guest.deprecated.as_ref().unwrap().reason,
1199 Some("Use USER with limited permissions instead".to_string())
1200 );
1201 }
1202
1203 #[test]
1204 fn test_parse_input_object() {
1205 let json = r#"{
1206 "types": [],
1207 "queries": [],
1208 "mutations": [],
1209 "input_types": [{
1210 "name": "UserFilter",
1211 "fields": [
1212 {"name": "name", "type": "String", "nullable": true},
1213 {"name": "email", "type": "String", "nullable": true},
1214 {"name": "active", "type": "Boolean", "nullable": true, "default": true}
1215 ],
1216 "description": "Filter criteria for users"
1217 }]
1218 }"#;
1219
1220 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1221 assert_eq!(schema.input_types.len(), 1);
1222 let input = &schema.input_types[0];
1223 assert_eq!(input.name, "UserFilter");
1224 assert_eq!(input.description, Some("Filter criteria for users".to_string()));
1225 assert_eq!(input.fields.len(), 3);
1226
1227 assert_eq!(input.fields[0].name, "name");
1229 assert_eq!(input.fields[0].field_type, "String");
1230 assert!(input.fields[0].nullable);
1231
1232 assert_eq!(input.fields[2].name, "active");
1234 assert_eq!(input.fields[2].default, Some(serde_json::json!(true)));
1235 }
1236
1237 #[test]
1238 fn test_parse_interface() {
1239 let json = r#"{
1240 "types": [],
1241 "queries": [],
1242 "mutations": [],
1243 "interfaces": [{
1244 "name": "Node",
1245 "fields": [
1246 {"name": "id", "type": "ID", "nullable": false}
1247 ],
1248 "description": "An object with a globally unique ID"
1249 }]
1250 }"#;
1251
1252 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1253 assert_eq!(schema.interfaces.len(), 1);
1254 let interface = &schema.interfaces[0];
1255 assert_eq!(interface.name, "Node");
1256 assert_eq!(interface.description, Some("An object with a globally unique ID".to_string()));
1257 assert_eq!(interface.fields.len(), 1);
1258 assert_eq!(interface.fields[0].name, "id");
1259 assert_eq!(interface.fields[0].field_type, "ID");
1260 assert!(!interface.fields[0].nullable);
1261 }
1262
1263 #[test]
1264 fn test_parse_type_implements_interface() {
1265 let json = r#"{
1266 "types": [{
1267 "name": "User",
1268 "fields": [
1269 {"name": "id", "type": "ID", "nullable": false},
1270 {"name": "name", "type": "String", "nullable": false}
1271 ],
1272 "implements": ["Node"]
1273 }],
1274 "queries": [],
1275 "mutations": [],
1276 "interfaces": [{
1277 "name": "Node",
1278 "fields": [
1279 {"name": "id", "type": "ID", "nullable": false}
1280 ]
1281 }]
1282 }"#;
1283
1284 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1285 assert_eq!(schema.types.len(), 1);
1286 assert_eq!(schema.types[0].name, "User");
1287 assert_eq!(schema.types[0].implements, vec!["Node"]);
1288
1289 assert_eq!(schema.interfaces.len(), 1);
1290 assert_eq!(schema.interfaces[0].name, "Node");
1291 }
1292
1293 #[test]
1294 fn test_parse_input_object_with_deprecated_field() {
1295 let json = r#"{
1296 "types": [],
1297 "queries": [],
1298 "mutations": [],
1299 "input_types": [{
1300 "name": "CreateUserInput",
1301 "fields": [
1302 {"name": "email", "type": "String!", "nullable": false},
1303 {"name": "name", "type": "String!", "nullable": false},
1304 {
1305 "name": "username",
1306 "type": "String",
1307 "nullable": true,
1308 "deprecated": {"reason": "Use email as unique identifier instead"}
1309 }
1310 ]
1311 }]
1312 }"#;
1313
1314 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1315 let input = &schema.input_types[0];
1316
1317 let username_field = &input.fields[2];
1319 assert_eq!(username_field.name, "username");
1320 assert!(username_field.deprecated.is_some());
1321 assert_eq!(
1322 username_field.deprecated.as_ref().unwrap().reason,
1323 Some("Use email as unique identifier instead".to_string())
1324 );
1325 }
1326
1327 #[test]
1328 fn test_parse_union() {
1329 let json = r#"{
1330 "types": [
1331 {"name": "User", "fields": [{"name": "id", "type": "ID", "nullable": false}]},
1332 {"name": "Post", "fields": [{"name": "id", "type": "ID", "nullable": false}]}
1333 ],
1334 "queries": [],
1335 "mutations": [],
1336 "unions": [{
1337 "name": "SearchResult",
1338 "member_types": ["User", "Post"],
1339 "description": "Result from a search query"
1340 }]
1341 }"#;
1342
1343 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1344 assert_eq!(schema.unions.len(), 1);
1345 let union_def = &schema.unions[0];
1346 assert_eq!(union_def.name, "SearchResult");
1347 assert_eq!(union_def.member_types, vec!["User", "Post"]);
1348 assert_eq!(union_def.description, Some("Result from a search query".to_string()));
1349 }
1350
1351 #[test]
1352 fn test_parse_field_with_requires_scope() {
1353 let json = r#"{
1354 "types": [{
1355 "name": "Employee",
1356 "fields": [
1357 {
1358 "name": "id",
1359 "type": "ID",
1360 "nullable": false
1361 },
1362 {
1363 "name": "name",
1364 "type": "String",
1365 "nullable": false
1366 },
1367 {
1368 "name": "salary",
1369 "type": "Float",
1370 "nullable": false,
1371 "description": "Employee salary - protected field",
1372 "requires_scope": "read:Employee.salary"
1373 },
1374 {
1375 "name": "ssn",
1376 "type": "String",
1377 "nullable": true,
1378 "description": "Social Security Number",
1379 "requires_scope": "admin"
1380 }
1381 ]
1382 }],
1383 "queries": [],
1384 "mutations": []
1385 }"#;
1386
1387 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1388 assert_eq!(schema.types.len(), 1);
1389
1390 let employee = &schema.types[0];
1391 assert_eq!(employee.name, "Employee");
1392 assert_eq!(employee.fields.len(), 4);
1393
1394 assert_eq!(employee.fields[0].name, "id");
1396 assert!(employee.fields[0].requires_scope.is_none());
1397
1398 assert_eq!(employee.fields[1].name, "name");
1400 assert!(employee.fields[1].requires_scope.is_none());
1401
1402 assert_eq!(employee.fields[2].name, "salary");
1404 assert_eq!(employee.fields[2].requires_scope, Some("read:Employee.salary".to_string()));
1405
1406 assert_eq!(employee.fields[3].name, "ssn");
1408 assert_eq!(employee.fields[3].requires_scope, Some("admin".to_string()));
1409 }
1410}