1use fraiseql_core::validation::ValidationRule;
7use serde::{Deserialize, Serialize};
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 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub observers_config: Option<serde_json::Value>,
88
89 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub federation_config: Option<serde_json::Value>,
95}
96
97fn default_version() -> String {
98 "2.0.0".to_string()
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct IntermediateType {
104 pub name: String,
106
107 pub fields: Vec<IntermediateField>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub description: Option<String>,
113
114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
116 pub implements: Vec<String>,
117
118 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
120 pub is_error: bool,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct IntermediateField {
129 pub name: String,
131
132 #[serde(rename = "type")]
136 pub field_type: String,
137
138 pub nullable: bool,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub description: Option<String>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub directives: Option<Vec<IntermediateAppliedDirective>>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
165 pub requires_scope: Option<String>,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
191pub struct IntermediateEnum {
192 pub name: String,
194
195 pub values: Vec<IntermediateEnumValue>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub description: Option<String>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
215pub struct IntermediateEnumValue {
216 pub name: String,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub description: Option<String>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub deprecated: Option<IntermediateDeprecation>,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230pub struct IntermediateDeprecation {
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub reason: Option<String>,
234}
235
236#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265pub struct IntermediateScalar {
266 pub name: String,
268
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub description: Option<String>,
272
273 #[serde(skip_serializing_if = "Option::is_none")]
275 pub specified_by_url: Option<String>,
276
277 #[serde(default)]
279 pub validation_rules: Vec<ValidationRule>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub base_type: Option<String>,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
309pub struct IntermediateInputObject {
310 pub name: String,
312
313 pub fields: Vec<IntermediateInputField>,
315
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub description: Option<String>,
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
334pub struct IntermediateInputField {
335 pub name: String,
337
338 #[serde(rename = "type")]
340 pub field_type: String,
341
342 #[serde(default)]
344 pub nullable: bool,
345
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub description: Option<String>,
349
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub default: Option<serde_json::Value>,
353
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub deprecated: Option<IntermediateDeprecation>,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
361pub struct IntermediateQuery {
362 pub name: String,
364
365 pub return_type: String,
367
368 #[serde(default)]
370 pub returns_list: bool,
371
372 #[serde(default)]
374 pub nullable: bool,
375
376 #[serde(default)]
378 pub arguments: Vec<IntermediateArgument>,
379
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub description: Option<String>,
383
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub sql_source: Option<String>,
387
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub auto_params: Option<IntermediateAutoParams>,
391
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub deprecated: Option<IntermediateDeprecation>,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
399 pub jsonb_column: Option<String>,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404pub struct IntermediateMutation {
405 pub name: String,
407
408 pub return_type: String,
410
411 #[serde(default)]
413 pub returns_list: bool,
414
415 #[serde(default)]
417 pub nullable: bool,
418
419 #[serde(default)]
421 pub arguments: Vec<IntermediateArgument>,
422
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub description: Option<String>,
426
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub sql_source: Option<String>,
430
431 #[serde(skip_serializing_if = "Option::is_none")]
433 pub operation: Option<String>,
434
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub deprecated: Option<IntermediateDeprecation>,
438}
439
440#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
461pub struct IntermediateInterface {
462 pub name: String,
464
465 pub fields: Vec<IntermediateField>,
467
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub description: Option<String>,
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
475pub struct IntermediateArgument {
476 pub name: String,
478
479 #[serde(rename = "type")]
483 pub arg_type: String,
484
485 pub nullable: bool,
487
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub default: Option<serde_json::Value>,
491
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub deprecated: Option<IntermediateDeprecation>,
495}
496
497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
517pub struct IntermediateUnion {
518 pub name: String,
520
521 pub member_types: Vec<String>,
523
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub description: Option<String>,
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
531pub struct IntermediateAutoParams {
532 #[serde(default)]
534 pub limit: bool,
535 #[serde(default)]
537 pub offset: bool,
538 #[serde(rename = "where", default)]
540 pub where_clause: bool,
541 #[serde(default)]
543 pub order_by: bool,
544}
545
546#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
573pub struct IntermediateSubscription {
574 pub name: String,
576
577 pub return_type: String,
579
580 #[serde(default)]
582 pub arguments: Vec<IntermediateArgument>,
583
584 #[serde(skip_serializing_if = "Option::is_none")]
586 pub description: Option<String>,
587
588 #[serde(skip_serializing_if = "Option::is_none")]
590 pub topic: Option<String>,
591
592 #[serde(skip_serializing_if = "Option::is_none")]
594 pub filter: Option<IntermediateSubscriptionFilter>,
595
596 #[serde(default, skip_serializing_if = "Vec::is_empty")]
598 pub fields: Vec<String>,
599
600 #[serde(skip_serializing_if = "Option::is_none")]
602 pub deprecated: Option<IntermediateDeprecation>,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
609pub struct IntermediateSubscriptionFilter {
610 pub conditions: Vec<IntermediateFilterCondition>,
612}
613
614#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
616pub struct IntermediateFilterCondition {
617 pub argument: String,
619
620 pub path: String,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
643pub struct IntermediateFragment {
644 pub name: String,
646
647 #[serde(rename = "on")]
649 pub type_condition: String,
650
651 pub fields: Vec<IntermediateFragmentField>,
653
654 #[serde(skip_serializing_if = "Option::is_none")]
656 pub description: Option<String>,
657}
658
659#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
661#[serde(untagged)]
662pub enum IntermediateFragmentField {
663 Simple(String),
665
666 Complex(IntermediateFragmentFieldDef),
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
672pub struct IntermediateFragmentFieldDef {
673 pub name: String,
675
676 #[serde(skip_serializing_if = "Option::is_none")]
678 pub alias: Option<String>,
679
680 #[serde(skip_serializing_if = "Option::is_none")]
682 pub fields: Option<Vec<IntermediateFragmentField>>,
683
684 #[serde(skip_serializing_if = "Option::is_none")]
686 pub spread: Option<String>,
687
688 #[serde(skip_serializing_if = "Option::is_none")]
690 pub directives: Option<Vec<IntermediateAppliedDirective>>,
691}
692
693#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
709pub struct IntermediateDirective {
710 pub name: String,
712
713 pub locations: Vec<String>,
715
716 #[serde(default)]
718 pub arguments: Vec<IntermediateArgument>,
719
720 #[serde(default)]
722 pub repeatable: bool,
723
724 #[serde(skip_serializing_if = "Option::is_none")]
726 pub description: Option<String>,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
740pub struct IntermediateAppliedDirective {
741 pub name: String,
743
744 #[serde(default, skip_serializing_if = "Option::is_none")]
746 pub arguments: Option<serde_json::Value>,
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
755pub struct IntermediateFactTable {
756 pub table_name: String,
758 pub measures: Vec<IntermediateMeasure>,
760 pub dimensions: IntermediateDimensions,
762 pub denormalized_filters: Vec<IntermediateFilter>,
764}
765
766#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
768pub struct IntermediateMeasure {
769 pub name: String,
771 pub sql_type: String,
773 pub nullable: bool,
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct IntermediateDimensions {
780 pub name: String,
782 pub paths: Vec<IntermediateDimensionPath>,
784}
785
786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
788pub struct IntermediateDimensionPath {
789 pub name: String,
791 #[serde(alias = "path")]
793 pub json_path: String,
794 #[serde(alias = "type")]
796 pub data_type: String,
797}
798
799#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
801pub struct IntermediateFilter {
802 pub name: String,
804 pub sql_type: String,
806 pub indexed: bool,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
812pub struct IntermediateAggregateQuery {
813 pub name: String,
815 pub fact_table: String,
817 pub auto_group_by: bool,
819 pub auto_aggregates: bool,
821 #[serde(skip_serializing_if = "Option::is_none")]
823 pub description: Option<String>,
824}
825
826#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
865pub struct IntermediateObserver {
866 pub name: String,
868
869 pub entity: String,
871
872 pub event: String,
874
875 pub actions: Vec<IntermediateObserverAction>,
877
878 #[serde(skip_serializing_if = "Option::is_none")]
880 pub condition: Option<String>,
881
882 pub retry: IntermediateRetryConfig,
884}
885
886pub type IntermediateObserverAction = serde_json::Value;
891
892#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
905pub struct IntermediateRetryConfig {
906 pub max_attempts: u32,
908
909 pub backoff_strategy: String,
911
912 pub initial_delay_ms: u32,
914
915 pub max_delay_ms: u32,
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922
923 #[test]
924 fn test_parse_minimal_schema() {
925 let json = r#"{
926 "types": [],
927 "queries": [],
928 "mutations": []
929 }"#;
930
931 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
932 assert_eq!(schema.version, "2.0.0");
933 assert_eq!(schema.types.len(), 0);
934 assert_eq!(schema.queries.len(), 0);
935 assert_eq!(schema.mutations.len(), 0);
936 }
937
938 #[test]
939 fn test_parse_type_with_type_field() {
940 let json = r#"{
941 "types": [{
942 "name": "User",
943 "fields": [
944 {
945 "name": "id",
946 "type": "Int",
947 "nullable": false
948 },
949 {
950 "name": "name",
951 "type": "String",
952 "nullable": false
953 }
954 ]
955 }],
956 "queries": [],
957 "mutations": []
958 }"#;
959
960 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
961 assert_eq!(schema.types.len(), 1);
962 assert_eq!(schema.types[0].name, "User");
963 assert_eq!(schema.types[0].fields.len(), 2);
964 assert_eq!(schema.types[0].fields[0].name, "id");
965 assert_eq!(schema.types[0].fields[0].field_type, "Int");
966 assert!(!schema.types[0].fields[0].nullable);
967 }
968
969 #[test]
970 fn test_parse_query_with_arguments() {
971 let json = r#"{
972 "types": [],
973 "queries": [{
974 "name": "users",
975 "return_type": "User",
976 "returns_list": true,
977 "nullable": false,
978 "arguments": [
979 {
980 "name": "limit",
981 "type": "Int",
982 "nullable": false,
983 "default": 10
984 }
985 ],
986 "sql_source": "v_user"
987 }],
988 "mutations": []
989 }"#;
990
991 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
992 assert_eq!(schema.queries.len(), 1);
993 assert_eq!(schema.queries[0].arguments.len(), 1);
994 assert_eq!(schema.queries[0].arguments[0].arg_type, "Int");
995 assert_eq!(schema.queries[0].arguments[0].default, Some(serde_json::json!(10)));
996 }
997
998 #[test]
999 fn test_parse_fragment_simple() {
1000 let json = r#"{
1001 "types": [],
1002 "queries": [],
1003 "mutations": [],
1004 "fragments": [{
1005 "name": "UserFields",
1006 "on": "User",
1007 "fields": ["id", "name", "email"]
1008 }]
1009 }"#;
1010
1011 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1012 assert!(schema.fragments.is_some());
1013 let fragments = schema.fragments.unwrap();
1014 assert_eq!(fragments.len(), 1);
1015 assert_eq!(fragments[0].name, "UserFields");
1016 assert_eq!(fragments[0].type_condition, "User");
1017 assert_eq!(fragments[0].fields.len(), 3);
1018
1019 match &fragments[0].fields[0] {
1021 IntermediateFragmentField::Simple(name) => assert_eq!(name, "id"),
1022 IntermediateFragmentField::Complex(_) => panic!("Expected simple field"),
1023 }
1024 }
1025
1026 #[test]
1027 fn test_parse_fragment_with_nested_fields() {
1028 let json = r#"{
1029 "types": [],
1030 "queries": [],
1031 "mutations": [],
1032 "fragments": [{
1033 "name": "PostFields",
1034 "on": "Post",
1035 "fields": [
1036 "id",
1037 "title",
1038 {
1039 "name": "author",
1040 "alias": "writer",
1041 "fields": ["id", "name"]
1042 }
1043 ]
1044 }]
1045 }"#;
1046
1047 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1048 let fragments = schema.fragments.unwrap();
1049 assert_eq!(fragments[0].fields.len(), 3);
1050
1051 match &fragments[0].fields[2] {
1053 IntermediateFragmentField::Complex(def) => {
1054 assert_eq!(def.name, "author");
1055 assert_eq!(def.alias, Some("writer".to_string()));
1056 assert!(def.fields.is_some());
1057 assert_eq!(def.fields.as_ref().unwrap().len(), 2);
1058 },
1059 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1060 }
1061 }
1062
1063 #[test]
1064 fn test_parse_directive_definition() {
1065 let json = r#"{
1066 "types": [],
1067 "queries": [],
1068 "mutations": [],
1069 "directives": [{
1070 "name": "auth",
1071 "locations": ["FIELD_DEFINITION", "OBJECT"],
1072 "arguments": [
1073 {"name": "role", "type": "String", "nullable": false}
1074 ],
1075 "description": "Requires authentication"
1076 }]
1077 }"#;
1078
1079 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1080 assert!(schema.directives.is_some());
1081 let directives = schema.directives.unwrap();
1082 assert_eq!(directives.len(), 1);
1083 assert_eq!(directives[0].name, "auth");
1084 assert_eq!(directives[0].locations, vec!["FIELD_DEFINITION", "OBJECT"]);
1085 assert_eq!(directives[0].arguments.len(), 1);
1086 assert_eq!(directives[0].description, Some("Requires authentication".to_string()));
1087 }
1088
1089 #[test]
1090 fn test_parse_field_with_directive() {
1091 let json = r#"{
1092 "types": [{
1093 "name": "User",
1094 "fields": [
1095 {
1096 "name": "oldId",
1097 "type": "Int",
1098 "nullable": false,
1099 "directives": [
1100 {"name": "deprecated", "arguments": {"reason": "Use 'id' instead"}}
1101 ]
1102 }
1103 ]
1104 }],
1105 "queries": [],
1106 "mutations": []
1107 }"#;
1108
1109 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1110 let field = &schema.types[0].fields[0];
1111 assert_eq!(field.name, "oldId");
1112 assert!(field.directives.is_some());
1113 let directives = field.directives.as_ref().unwrap();
1114 assert_eq!(directives.len(), 1);
1115 assert_eq!(directives[0].name, "deprecated");
1116 assert_eq!(
1117 directives[0].arguments,
1118 Some(serde_json::json!({"reason": "Use 'id' instead"}))
1119 );
1120 }
1121
1122 #[test]
1123 fn test_parse_fragment_with_spread() {
1124 let json = r#"{
1125 "types": [],
1126 "queries": [],
1127 "mutations": [],
1128 "fragments": [
1129 {
1130 "name": "UserFields",
1131 "on": "User",
1132 "fields": ["id", "name"]
1133 },
1134 {
1135 "name": "PostWithAuthor",
1136 "on": "Post",
1137 "fields": [
1138 "id",
1139 "title",
1140 {
1141 "name": "author",
1142 "spread": "UserFields"
1143 }
1144 ]
1145 }
1146 ]
1147 }"#;
1148
1149 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1150 let fragments = schema.fragments.unwrap();
1151 assert_eq!(fragments.len(), 2);
1152
1153 match &fragments[1].fields[2] {
1155 IntermediateFragmentField::Complex(def) => {
1156 assert_eq!(def.name, "author");
1157 assert_eq!(def.spread, Some("UserFields".to_string()));
1158 },
1159 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1160 }
1161 }
1162
1163 #[test]
1164 fn test_parse_enum() {
1165 let json = r#"{
1166 "types": [],
1167 "queries": [],
1168 "mutations": [],
1169 "enums": [{
1170 "name": "OrderStatus",
1171 "values": [
1172 {"name": "PENDING"},
1173 {"name": "PROCESSING", "description": "Currently being processed"},
1174 {"name": "SHIPPED"},
1175 {"name": "DELIVERED"}
1176 ],
1177 "description": "Possible states of an order"
1178 }]
1179 }"#;
1180
1181 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1182 assert_eq!(schema.enums.len(), 1);
1183 let enum_def = &schema.enums[0];
1184 assert_eq!(enum_def.name, "OrderStatus");
1185 assert_eq!(enum_def.description, Some("Possible states of an order".to_string()));
1186 assert_eq!(enum_def.values.len(), 4);
1187 assert_eq!(enum_def.values[0].name, "PENDING");
1188 assert_eq!(enum_def.values[1].description, Some("Currently being processed".to_string()));
1189 }
1190
1191 #[test]
1192 fn test_parse_enum_with_deprecated_value() {
1193 let json = r#"{
1194 "types": [],
1195 "queries": [],
1196 "mutations": [],
1197 "enums": [{
1198 "name": "UserRole",
1199 "values": [
1200 {"name": "ADMIN"},
1201 {"name": "USER"},
1202 {"name": "GUEST", "deprecated": {"reason": "Use USER with limited permissions instead"}}
1203 ]
1204 }]
1205 }"#;
1206
1207 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1208 let enum_def = &schema.enums[0];
1209 assert_eq!(enum_def.values.len(), 3);
1210
1211 let guest = &enum_def.values[2];
1213 assert_eq!(guest.name, "GUEST");
1214 assert!(guest.deprecated.is_some());
1215 assert_eq!(
1216 guest.deprecated.as_ref().unwrap().reason,
1217 Some("Use USER with limited permissions instead".to_string())
1218 );
1219 }
1220
1221 #[test]
1222 fn test_parse_input_object() {
1223 let json = r#"{
1224 "types": [],
1225 "queries": [],
1226 "mutations": [],
1227 "input_types": [{
1228 "name": "UserFilter",
1229 "fields": [
1230 {"name": "name", "type": "String", "nullable": true},
1231 {"name": "email", "type": "String", "nullable": true},
1232 {"name": "active", "type": "Boolean", "nullable": true, "default": true}
1233 ],
1234 "description": "Filter criteria for users"
1235 }]
1236 }"#;
1237
1238 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1239 assert_eq!(schema.input_types.len(), 1);
1240 let input = &schema.input_types[0];
1241 assert_eq!(input.name, "UserFilter");
1242 assert_eq!(input.description, Some("Filter criteria for users".to_string()));
1243 assert_eq!(input.fields.len(), 3);
1244
1245 assert_eq!(input.fields[0].name, "name");
1247 assert_eq!(input.fields[0].field_type, "String");
1248 assert!(input.fields[0].nullable);
1249
1250 assert_eq!(input.fields[2].name, "active");
1252 assert_eq!(input.fields[2].default, Some(serde_json::json!(true)));
1253 }
1254
1255 #[test]
1256 fn test_parse_interface() {
1257 let json = r#"{
1258 "types": [],
1259 "queries": [],
1260 "mutations": [],
1261 "interfaces": [{
1262 "name": "Node",
1263 "fields": [
1264 {"name": "id", "type": "ID", "nullable": false}
1265 ],
1266 "description": "An object with a globally unique ID"
1267 }]
1268 }"#;
1269
1270 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1271 assert_eq!(schema.interfaces.len(), 1);
1272 let interface = &schema.interfaces[0];
1273 assert_eq!(interface.name, "Node");
1274 assert_eq!(interface.description, Some("An object with a globally unique ID".to_string()));
1275 assert_eq!(interface.fields.len(), 1);
1276 assert_eq!(interface.fields[0].name, "id");
1277 assert_eq!(interface.fields[0].field_type, "ID");
1278 assert!(!interface.fields[0].nullable);
1279 }
1280
1281 #[test]
1282 fn test_parse_type_implements_interface() {
1283 let json = r#"{
1284 "types": [{
1285 "name": "User",
1286 "fields": [
1287 {"name": "id", "type": "ID", "nullable": false},
1288 {"name": "name", "type": "String", "nullable": false}
1289 ],
1290 "implements": ["Node"]
1291 }],
1292 "queries": [],
1293 "mutations": [],
1294 "interfaces": [{
1295 "name": "Node",
1296 "fields": [
1297 {"name": "id", "type": "ID", "nullable": false}
1298 ]
1299 }]
1300 }"#;
1301
1302 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1303 assert_eq!(schema.types.len(), 1);
1304 assert_eq!(schema.types[0].name, "User");
1305 assert_eq!(schema.types[0].implements, vec!["Node"]);
1306
1307 assert_eq!(schema.interfaces.len(), 1);
1308 assert_eq!(schema.interfaces[0].name, "Node");
1309 }
1310
1311 #[test]
1312 fn test_parse_input_object_with_deprecated_field() {
1313 let json = r#"{
1314 "types": [],
1315 "queries": [],
1316 "mutations": [],
1317 "input_types": [{
1318 "name": "CreateUserInput",
1319 "fields": [
1320 {"name": "email", "type": "String!", "nullable": false},
1321 {"name": "name", "type": "String!", "nullable": false},
1322 {
1323 "name": "username",
1324 "type": "String",
1325 "nullable": true,
1326 "deprecated": {"reason": "Use email as unique identifier instead"}
1327 }
1328 ]
1329 }]
1330 }"#;
1331
1332 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1333 let input = &schema.input_types[0];
1334
1335 let username_field = &input.fields[2];
1337 assert_eq!(username_field.name, "username");
1338 assert!(username_field.deprecated.is_some());
1339 assert_eq!(
1340 username_field.deprecated.as_ref().unwrap().reason,
1341 Some("Use email as unique identifier instead".to_string())
1342 );
1343 }
1344
1345 #[test]
1346 fn test_parse_union() {
1347 let json = r#"{
1348 "types": [
1349 {"name": "User", "fields": [{"name": "id", "type": "ID", "nullable": false}]},
1350 {"name": "Post", "fields": [{"name": "id", "type": "ID", "nullable": false}]}
1351 ],
1352 "queries": [],
1353 "mutations": [],
1354 "unions": [{
1355 "name": "SearchResult",
1356 "member_types": ["User", "Post"],
1357 "description": "Result from a search query"
1358 }]
1359 }"#;
1360
1361 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1362 assert_eq!(schema.unions.len(), 1);
1363 let union_def = &schema.unions[0];
1364 assert_eq!(union_def.name, "SearchResult");
1365 assert_eq!(union_def.member_types, vec!["User", "Post"]);
1366 assert_eq!(union_def.description, Some("Result from a search query".to_string()));
1367 }
1368
1369 #[test]
1370 fn test_parse_field_with_requires_scope() {
1371 let json = r#"{
1372 "types": [{
1373 "name": "Employee",
1374 "fields": [
1375 {
1376 "name": "id",
1377 "type": "ID",
1378 "nullable": false
1379 },
1380 {
1381 "name": "name",
1382 "type": "String",
1383 "nullable": false
1384 },
1385 {
1386 "name": "salary",
1387 "type": "Float",
1388 "nullable": false,
1389 "description": "Employee salary - protected field",
1390 "requires_scope": "read:Employee.salary"
1391 },
1392 {
1393 "name": "ssn",
1394 "type": "String",
1395 "nullable": true,
1396 "description": "Social Security Number",
1397 "requires_scope": "admin"
1398 }
1399 ]
1400 }],
1401 "queries": [],
1402 "mutations": []
1403 }"#;
1404
1405 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1406 assert_eq!(schema.types.len(), 1);
1407
1408 let employee = &schema.types[0];
1409 assert_eq!(employee.name, "Employee");
1410 assert_eq!(employee.fields.len(), 4);
1411
1412 assert_eq!(employee.fields[0].name, "id");
1414 assert!(employee.fields[0].requires_scope.is_none());
1415
1416 assert_eq!(employee.fields[1].name, "name");
1418 assert!(employee.fields[1].requires_scope.is_none());
1419
1420 assert_eq!(employee.fields[2].name, "salary");
1422 assert_eq!(employee.fields[2].requires_scope, Some("read:Employee.salary".to_string()));
1423
1424 assert_eq!(employee.fields[3].name, "ssn");
1426 assert_eq!(employee.fields[3].requires_scope, Some("admin".to_string()));
1427 }
1428}