1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
10pub struct IntermediateSchema {
11 #[serde(default = "default_version")]
13 pub version: String,
14
15 #[serde(default)]
17 pub types: Vec<IntermediateType>,
18
19 #[serde(default)]
21 pub enums: Vec<IntermediateEnum>,
22
23 #[serde(default)]
25 pub input_types: Vec<IntermediateInputObject>,
26
27 #[serde(default)]
29 pub interfaces: Vec<IntermediateInterface>,
30
31 #[serde(default)]
33 pub unions: Vec<IntermediateUnion>,
34
35 #[serde(default)]
37 pub queries: Vec<IntermediateQuery>,
38
39 #[serde(default)]
41 pub mutations: Vec<IntermediateMutation>,
42
43 #[serde(default)]
45 pub subscriptions: Vec<IntermediateSubscription>,
46
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub fragments: Option<Vec<IntermediateFragment>>,
50
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub directives: Option<Vec<IntermediateDirective>>,
54
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub fact_tables: Option<Vec<IntermediateFactTable>>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub aggregate_queries: Option<Vec<IntermediateAggregateQuery>>,
62
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub observers: Option<Vec<IntermediateObserver>>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub security: Option<serde_json::Value>,
72}
73
74fn default_version() -> String {
75 "2.0.0".to_string()
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct IntermediateType {
81 pub name: String,
83
84 pub fields: Vec<IntermediateField>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub description: Option<String>,
90
91 #[serde(default, skip_serializing_if = "Vec::is_empty")]
93 pub implements: Vec<String>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct IntermediateField {
102 pub name: String,
104
105 #[serde(rename = "type")]
109 pub field_type: String,
110
111 pub nullable: bool,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub description: Option<String>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub directives: Option<Vec<IntermediateAppliedDirective>>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
138 pub requires_scope: Option<String>,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct IntermediateEnum {
165 pub name: String,
167
168 pub values: Vec<IntermediateEnumValue>,
170
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub description: Option<String>,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct IntermediateEnumValue {
189 pub name: String,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub description: Option<String>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub deprecated: Option<IntermediateDeprecation>,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203pub struct IntermediateDeprecation {
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub reason: Option<String>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
232pub struct IntermediateInputObject {
233 pub name: String,
235
236 pub fields: Vec<IntermediateInputField>,
238
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub description: Option<String>,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257pub struct IntermediateInputField {
258 pub name: String,
260
261 #[serde(rename = "type")]
263 pub field_type: String,
264
265 #[serde(default)]
267 pub nullable: bool,
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 default: Option<serde_json::Value>,
276
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub deprecated: Option<IntermediateDeprecation>,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284pub struct IntermediateQuery {
285 pub name: String,
287
288 pub return_type: String,
290
291 #[serde(default)]
293 pub returns_list: bool,
294
295 #[serde(default)]
297 pub nullable: bool,
298
299 #[serde(default)]
301 pub arguments: Vec<IntermediateArgument>,
302
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub description: Option<String>,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub sql_source: Option<String>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub auto_params: Option<IntermediateAutoParams>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub deprecated: Option<IntermediateDeprecation>,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub struct IntermediateMutation {
323 pub name: String,
325
326 pub return_type: String,
328
329 #[serde(default)]
331 pub returns_list: bool,
332
333 #[serde(default)]
335 pub nullable: bool,
336
337 #[serde(default)]
339 pub arguments: Vec<IntermediateArgument>,
340
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub description: Option<String>,
344
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub sql_source: Option<String>,
348
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub operation: Option<String>,
352
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub deprecated: Option<IntermediateDeprecation>,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
379pub struct IntermediateInterface {
380 pub name: String,
382
383 pub fields: Vec<IntermediateField>,
385
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub description: Option<String>,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub struct IntermediateArgument {
394 pub name: String,
396
397 #[serde(rename = "type")]
401 pub arg_type: String,
402
403 pub nullable: bool,
405
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub default: Option<serde_json::Value>,
409
410 #[serde(skip_serializing_if = "Option::is_none")]
412 pub deprecated: Option<IntermediateDeprecation>,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
435pub struct IntermediateUnion {
436 pub name: String,
438
439 pub member_types: Vec<String>,
441
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub description: Option<String>,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct IntermediateAutoParams {
450 #[serde(default)]
452 pub limit: bool,
453 #[serde(default)]
455 pub offset: bool,
456 #[serde(rename = "where", default)]
458 pub where_clause: bool,
459 #[serde(default)]
461 pub order_by: bool,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
491pub struct IntermediateSubscription {
492 pub name: String,
494
495 pub return_type: String,
497
498 #[serde(default)]
500 pub arguments: Vec<IntermediateArgument>,
501
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub description: Option<String>,
505
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub topic: Option<String>,
509
510 #[serde(skip_serializing_if = "Option::is_none")]
512 pub filter: Option<IntermediateSubscriptionFilter>,
513
514 #[serde(default, skip_serializing_if = "Vec::is_empty")]
516 pub fields: Vec<String>,
517
518 #[serde(skip_serializing_if = "Option::is_none")]
520 pub deprecated: Option<IntermediateDeprecation>,
521}
522
523#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
527pub struct IntermediateSubscriptionFilter {
528 pub conditions: Vec<IntermediateFilterCondition>,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
534pub struct IntermediateFilterCondition {
535 pub argument: String,
537
538 pub path: String,
540}
541
542#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561pub struct IntermediateFragment {
562 pub name: String,
564
565 #[serde(rename = "on")]
567 pub type_condition: String,
568
569 pub fields: Vec<IntermediateFragmentField>,
571
572 #[serde(skip_serializing_if = "Option::is_none")]
574 pub description: Option<String>,
575}
576
577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
579#[serde(untagged)]
580pub enum IntermediateFragmentField {
581 Simple(String),
583
584 Complex(IntermediateFragmentFieldDef),
586}
587
588#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
590pub struct IntermediateFragmentFieldDef {
591 pub name: String,
593
594 #[serde(skip_serializing_if = "Option::is_none")]
596 pub alias: Option<String>,
597
598 #[serde(skip_serializing_if = "Option::is_none")]
600 pub fields: Option<Vec<IntermediateFragmentField>>,
601
602 #[serde(skip_serializing_if = "Option::is_none")]
604 pub spread: Option<String>,
605
606 #[serde(skip_serializing_if = "Option::is_none")]
608 pub directives: Option<Vec<IntermediateAppliedDirective>>,
609}
610
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
627pub struct IntermediateDirective {
628 pub name: String,
630
631 pub locations: Vec<String>,
633
634 #[serde(default)]
636 pub arguments: Vec<IntermediateArgument>,
637
638 #[serde(default)]
640 pub repeatable: bool,
641
642 #[serde(skip_serializing_if = "Option::is_none")]
644 pub description: Option<String>,
645}
646
647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
658pub struct IntermediateAppliedDirective {
659 pub name: String,
661
662 #[serde(default, skip_serializing_if = "Option::is_none")]
664 pub arguments: Option<serde_json::Value>,
665}
666
667#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
673pub struct IntermediateFactTable {
674 pub table_name: String,
676 pub measures: Vec<IntermediateMeasure>,
678 pub dimensions: IntermediateDimensions,
680 pub denormalized_filters: Vec<IntermediateFilter>,
682}
683
684#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
686pub struct IntermediateMeasure {
687 pub name: String,
689 pub sql_type: String,
691 pub nullable: bool,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
697pub struct IntermediateDimensions {
698 pub name: String,
700 pub paths: Vec<IntermediateDimensionPath>,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
706pub struct IntermediateDimensionPath {
707 pub name: String,
709 #[serde(alias = "path")]
711 pub json_path: String,
712 #[serde(alias = "type")]
714 pub data_type: String,
715}
716
717#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
719pub struct IntermediateFilter {
720 pub name: String,
722 pub sql_type: String,
724 pub indexed: bool,
726}
727
728#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
730pub struct IntermediateAggregateQuery {
731 pub name: String,
733 pub fact_table: String,
735 pub auto_group_by: bool,
737 pub auto_aggregates: bool,
739 #[serde(skip_serializing_if = "Option::is_none")]
741 pub description: Option<String>,
742}
743
744#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
783pub struct IntermediateObserver {
784 pub name: String,
786
787 pub entity: String,
789
790 pub event: String,
792
793 pub actions: Vec<IntermediateObserverAction>,
795
796 #[serde(skip_serializing_if = "Option::is_none")]
798 pub condition: Option<String>,
799
800 pub retry: IntermediateRetryConfig,
802}
803
804pub type IntermediateObserverAction = serde_json::Value;
809
810#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
823pub struct IntermediateRetryConfig {
824 pub max_attempts: u32,
826
827 pub backoff_strategy: String,
829
830 pub initial_delay_ms: u32,
832
833 pub max_delay_ms: u32,
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
842 fn test_parse_minimal_schema() {
843 let json = r#"{
844 "types": [],
845 "queries": [],
846 "mutations": []
847 }"#;
848
849 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
850 assert_eq!(schema.version, "2.0.0");
851 assert_eq!(schema.types.len(), 0);
852 assert_eq!(schema.queries.len(), 0);
853 assert_eq!(schema.mutations.len(), 0);
854 }
855
856 #[test]
857 fn test_parse_type_with_type_field() {
858 let json = r#"{
859 "types": [{
860 "name": "User",
861 "fields": [
862 {
863 "name": "id",
864 "type": "Int",
865 "nullable": false
866 },
867 {
868 "name": "name",
869 "type": "String",
870 "nullable": false
871 }
872 ]
873 }],
874 "queries": [],
875 "mutations": []
876 }"#;
877
878 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
879 assert_eq!(schema.types.len(), 1);
880 assert_eq!(schema.types[0].name, "User");
881 assert_eq!(schema.types[0].fields.len(), 2);
882 assert_eq!(schema.types[0].fields[0].name, "id");
883 assert_eq!(schema.types[0].fields[0].field_type, "Int");
884 assert!(!schema.types[0].fields[0].nullable);
885 }
886
887 #[test]
888 fn test_parse_query_with_arguments() {
889 let json = r#"{
890 "types": [],
891 "queries": [{
892 "name": "users",
893 "return_type": "User",
894 "returns_list": true,
895 "nullable": false,
896 "arguments": [
897 {
898 "name": "limit",
899 "type": "Int",
900 "nullable": false,
901 "default": 10
902 }
903 ],
904 "sql_source": "v_user"
905 }],
906 "mutations": []
907 }"#;
908
909 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
910 assert_eq!(schema.queries.len(), 1);
911 assert_eq!(schema.queries[0].arguments.len(), 1);
912 assert_eq!(schema.queries[0].arguments[0].arg_type, "Int");
913 assert_eq!(schema.queries[0].arguments[0].default, Some(serde_json::json!(10)));
914 }
915
916 #[test]
917 fn test_parse_fragment_simple() {
918 let json = r#"{
919 "types": [],
920 "queries": [],
921 "mutations": [],
922 "fragments": [{
923 "name": "UserFields",
924 "on": "User",
925 "fields": ["id", "name", "email"]
926 }]
927 }"#;
928
929 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
930 assert!(schema.fragments.is_some());
931 let fragments = schema.fragments.unwrap();
932 assert_eq!(fragments.len(), 1);
933 assert_eq!(fragments[0].name, "UserFields");
934 assert_eq!(fragments[0].type_condition, "User");
935 assert_eq!(fragments[0].fields.len(), 3);
936
937 match &fragments[0].fields[0] {
939 IntermediateFragmentField::Simple(name) => assert_eq!(name, "id"),
940 IntermediateFragmentField::Complex(_) => panic!("Expected simple field"),
941 }
942 }
943
944 #[test]
945 fn test_parse_fragment_with_nested_fields() {
946 let json = r#"{
947 "types": [],
948 "queries": [],
949 "mutations": [],
950 "fragments": [{
951 "name": "PostFields",
952 "on": "Post",
953 "fields": [
954 "id",
955 "title",
956 {
957 "name": "author",
958 "alias": "writer",
959 "fields": ["id", "name"]
960 }
961 ]
962 }]
963 }"#;
964
965 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
966 let fragments = schema.fragments.unwrap();
967 assert_eq!(fragments[0].fields.len(), 3);
968
969 match &fragments[0].fields[2] {
971 IntermediateFragmentField::Complex(def) => {
972 assert_eq!(def.name, "author");
973 assert_eq!(def.alias, Some("writer".to_string()));
974 assert!(def.fields.is_some());
975 assert_eq!(def.fields.as_ref().unwrap().len(), 2);
976 },
977 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
978 }
979 }
980
981 #[test]
982 fn test_parse_directive_definition() {
983 let json = r#"{
984 "types": [],
985 "queries": [],
986 "mutations": [],
987 "directives": [{
988 "name": "auth",
989 "locations": ["FIELD_DEFINITION", "OBJECT"],
990 "arguments": [
991 {"name": "role", "type": "String", "nullable": false}
992 ],
993 "description": "Requires authentication"
994 }]
995 }"#;
996
997 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
998 assert!(schema.directives.is_some());
999 let directives = schema.directives.unwrap();
1000 assert_eq!(directives.len(), 1);
1001 assert_eq!(directives[0].name, "auth");
1002 assert_eq!(directives[0].locations, vec!["FIELD_DEFINITION", "OBJECT"]);
1003 assert_eq!(directives[0].arguments.len(), 1);
1004 assert_eq!(directives[0].description, Some("Requires authentication".to_string()));
1005 }
1006
1007 #[test]
1008 fn test_parse_field_with_directive() {
1009 let json = r#"{
1010 "types": [{
1011 "name": "User",
1012 "fields": [
1013 {
1014 "name": "oldId",
1015 "type": "Int",
1016 "nullable": false,
1017 "directives": [
1018 {"name": "deprecated", "arguments": {"reason": "Use 'id' instead"}}
1019 ]
1020 }
1021 ]
1022 }],
1023 "queries": [],
1024 "mutations": []
1025 }"#;
1026
1027 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1028 let field = &schema.types[0].fields[0];
1029 assert_eq!(field.name, "oldId");
1030 assert!(field.directives.is_some());
1031 let directives = field.directives.as_ref().unwrap();
1032 assert_eq!(directives.len(), 1);
1033 assert_eq!(directives[0].name, "deprecated");
1034 assert_eq!(
1035 directives[0].arguments,
1036 Some(serde_json::json!({"reason": "Use 'id' instead"}))
1037 );
1038 }
1039
1040 #[test]
1041 fn test_parse_fragment_with_spread() {
1042 let json = r#"{
1043 "types": [],
1044 "queries": [],
1045 "mutations": [],
1046 "fragments": [
1047 {
1048 "name": "UserFields",
1049 "on": "User",
1050 "fields": ["id", "name"]
1051 },
1052 {
1053 "name": "PostWithAuthor",
1054 "on": "Post",
1055 "fields": [
1056 "id",
1057 "title",
1058 {
1059 "name": "author",
1060 "spread": "UserFields"
1061 }
1062 ]
1063 }
1064 ]
1065 }"#;
1066
1067 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1068 let fragments = schema.fragments.unwrap();
1069 assert_eq!(fragments.len(), 2);
1070
1071 match &fragments[1].fields[2] {
1073 IntermediateFragmentField::Complex(def) => {
1074 assert_eq!(def.name, "author");
1075 assert_eq!(def.spread, Some("UserFields".to_string()));
1076 },
1077 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1078 }
1079 }
1080
1081 #[test]
1082 fn test_parse_enum() {
1083 let json = r#"{
1084 "types": [],
1085 "queries": [],
1086 "mutations": [],
1087 "enums": [{
1088 "name": "OrderStatus",
1089 "values": [
1090 {"name": "PENDING"},
1091 {"name": "PROCESSING", "description": "Currently being processed"},
1092 {"name": "SHIPPED"},
1093 {"name": "DELIVERED"}
1094 ],
1095 "description": "Possible states of an order"
1096 }]
1097 }"#;
1098
1099 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1100 assert_eq!(schema.enums.len(), 1);
1101 let enum_def = &schema.enums[0];
1102 assert_eq!(enum_def.name, "OrderStatus");
1103 assert_eq!(enum_def.description, Some("Possible states of an order".to_string()));
1104 assert_eq!(enum_def.values.len(), 4);
1105 assert_eq!(enum_def.values[0].name, "PENDING");
1106 assert_eq!(enum_def.values[1].description, Some("Currently being processed".to_string()));
1107 }
1108
1109 #[test]
1110 fn test_parse_enum_with_deprecated_value() {
1111 let json = r#"{
1112 "types": [],
1113 "queries": [],
1114 "mutations": [],
1115 "enums": [{
1116 "name": "UserRole",
1117 "values": [
1118 {"name": "ADMIN"},
1119 {"name": "USER"},
1120 {"name": "GUEST", "deprecated": {"reason": "Use USER with limited permissions instead"}}
1121 ]
1122 }]
1123 }"#;
1124
1125 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1126 let enum_def = &schema.enums[0];
1127 assert_eq!(enum_def.values.len(), 3);
1128
1129 let guest = &enum_def.values[2];
1131 assert_eq!(guest.name, "GUEST");
1132 assert!(guest.deprecated.is_some());
1133 assert_eq!(
1134 guest.deprecated.as_ref().unwrap().reason,
1135 Some("Use USER with limited permissions instead".to_string())
1136 );
1137 }
1138
1139 #[test]
1140 fn test_parse_input_object() {
1141 let json = r#"{
1142 "types": [],
1143 "queries": [],
1144 "mutations": [],
1145 "input_types": [{
1146 "name": "UserFilter",
1147 "fields": [
1148 {"name": "name", "type": "String", "nullable": true},
1149 {"name": "email", "type": "String", "nullable": true},
1150 {"name": "active", "type": "Boolean", "nullable": true, "default": true}
1151 ],
1152 "description": "Filter criteria for users"
1153 }]
1154 }"#;
1155
1156 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1157 assert_eq!(schema.input_types.len(), 1);
1158 let input = &schema.input_types[0];
1159 assert_eq!(input.name, "UserFilter");
1160 assert_eq!(input.description, Some("Filter criteria for users".to_string()));
1161 assert_eq!(input.fields.len(), 3);
1162
1163 assert_eq!(input.fields[0].name, "name");
1165 assert_eq!(input.fields[0].field_type, "String");
1166 assert!(input.fields[0].nullable);
1167
1168 assert_eq!(input.fields[2].name, "active");
1170 assert_eq!(input.fields[2].default, Some(serde_json::json!(true)));
1171 }
1172
1173 #[test]
1174 fn test_parse_interface() {
1175 let json = r#"{
1176 "types": [],
1177 "queries": [],
1178 "mutations": [],
1179 "interfaces": [{
1180 "name": "Node",
1181 "fields": [
1182 {"name": "id", "type": "ID", "nullable": false}
1183 ],
1184 "description": "An object with a globally unique ID"
1185 }]
1186 }"#;
1187
1188 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1189 assert_eq!(schema.interfaces.len(), 1);
1190 let interface = &schema.interfaces[0];
1191 assert_eq!(interface.name, "Node");
1192 assert_eq!(interface.description, Some("An object with a globally unique ID".to_string()));
1193 assert_eq!(interface.fields.len(), 1);
1194 assert_eq!(interface.fields[0].name, "id");
1195 assert_eq!(interface.fields[0].field_type, "ID");
1196 assert!(!interface.fields[0].nullable);
1197 }
1198
1199 #[test]
1200 fn test_parse_type_implements_interface() {
1201 let json = r#"{
1202 "types": [{
1203 "name": "User",
1204 "fields": [
1205 {"name": "id", "type": "ID", "nullable": false},
1206 {"name": "name", "type": "String", "nullable": false}
1207 ],
1208 "implements": ["Node"]
1209 }],
1210 "queries": [],
1211 "mutations": [],
1212 "interfaces": [{
1213 "name": "Node",
1214 "fields": [
1215 {"name": "id", "type": "ID", "nullable": false}
1216 ]
1217 }]
1218 }"#;
1219
1220 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1221 assert_eq!(schema.types.len(), 1);
1222 assert_eq!(schema.types[0].name, "User");
1223 assert_eq!(schema.types[0].implements, vec!["Node"]);
1224
1225 assert_eq!(schema.interfaces.len(), 1);
1226 assert_eq!(schema.interfaces[0].name, "Node");
1227 }
1228
1229 #[test]
1230 fn test_parse_input_object_with_deprecated_field() {
1231 let json = r#"{
1232 "types": [],
1233 "queries": [],
1234 "mutations": [],
1235 "input_types": [{
1236 "name": "CreateUserInput",
1237 "fields": [
1238 {"name": "email", "type": "String!", "nullable": false},
1239 {"name": "name", "type": "String!", "nullable": false},
1240 {
1241 "name": "username",
1242 "type": "String",
1243 "nullable": true,
1244 "deprecated": {"reason": "Use email as unique identifier instead"}
1245 }
1246 ]
1247 }]
1248 }"#;
1249
1250 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1251 let input = &schema.input_types[0];
1252
1253 let username_field = &input.fields[2];
1255 assert_eq!(username_field.name, "username");
1256 assert!(username_field.deprecated.is_some());
1257 assert_eq!(
1258 username_field.deprecated.as_ref().unwrap().reason,
1259 Some("Use email as unique identifier instead".to_string())
1260 );
1261 }
1262
1263 #[test]
1264 fn test_parse_union() {
1265 let json = r#"{
1266 "types": [
1267 {"name": "User", "fields": [{"name": "id", "type": "ID", "nullable": false}]},
1268 {"name": "Post", "fields": [{"name": "id", "type": "ID", "nullable": false}]}
1269 ],
1270 "queries": [],
1271 "mutations": [],
1272 "unions": [{
1273 "name": "SearchResult",
1274 "member_types": ["User", "Post"],
1275 "description": "Result from a search query"
1276 }]
1277 }"#;
1278
1279 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1280 assert_eq!(schema.unions.len(), 1);
1281 let union_def = &schema.unions[0];
1282 assert_eq!(union_def.name, "SearchResult");
1283 assert_eq!(union_def.member_types, vec!["User", "Post"]);
1284 assert_eq!(union_def.description, Some("Result from a search query".to_string()));
1285 }
1286
1287 #[test]
1288 fn test_parse_field_with_requires_scope() {
1289 let json = r#"{
1290 "types": [{
1291 "name": "Employee",
1292 "fields": [
1293 {
1294 "name": "id",
1295 "type": "ID",
1296 "nullable": false
1297 },
1298 {
1299 "name": "name",
1300 "type": "String",
1301 "nullable": false
1302 },
1303 {
1304 "name": "salary",
1305 "type": "Float",
1306 "nullable": false,
1307 "description": "Employee salary - protected field",
1308 "requires_scope": "read:Employee.salary"
1309 },
1310 {
1311 "name": "ssn",
1312 "type": "String",
1313 "nullable": true,
1314 "description": "Social Security Number",
1315 "requires_scope": "admin"
1316 }
1317 ]
1318 }],
1319 "queries": [],
1320 "mutations": []
1321 }"#;
1322
1323 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1324 assert_eq!(schema.types.len(), 1);
1325
1326 let employee = &schema.types[0];
1327 assert_eq!(employee.name, "Employee");
1328 assert_eq!(employee.fields.len(), 4);
1329
1330 assert_eq!(employee.fields[0].name, "id");
1332 assert!(employee.fields[0].requires_scope.is_none());
1333
1334 assert_eq!(employee.fields[1].name, "name");
1336 assert!(employee.fields[1].requires_scope.is_none());
1337
1338 assert_eq!(employee.fields[2].name, "salary");
1340 assert_eq!(employee.fields[2].requires_scope, Some("read:Employee.salary".to_string()));
1341
1342 assert_eq!(employee.fields[3].name, "ssn");
1344 assert_eq!(employee.fields[3].requires_scope, Some("admin".to_string()));
1345 }
1346}