1use indexmap::IndexMap;
7use fraiseql_core::validation::ValidationRule;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
12pub struct IntermediateSchema {
13 #[serde(default = "default_version")]
15 pub version: String,
16
17 #[serde(default)]
19 pub types: Vec<IntermediateType>,
20
21 #[serde(default)]
23 pub enums: Vec<IntermediateEnum>,
24
25 #[serde(default)]
27 pub input_types: Vec<IntermediateInputObject>,
28
29 #[serde(default)]
31 pub interfaces: Vec<IntermediateInterface>,
32
33 #[serde(default)]
35 pub unions: Vec<IntermediateUnion>,
36
37 #[serde(default)]
39 pub queries: Vec<IntermediateQuery>,
40
41 #[serde(default)]
43 pub mutations: Vec<IntermediateMutation>,
44
45 #[serde(default)]
47 pub subscriptions: Vec<IntermediateSubscription>,
48
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub fragments: Option<Vec<IntermediateFragment>>,
52
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub directives: Option<Vec<IntermediateDirective>>,
56
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub fact_tables: Option<Vec<IntermediateFactTable>>,
60
61 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub aggregate_queries: Option<Vec<IntermediateAggregateQuery>>,
64
65 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub observers: Option<Vec<IntermediateObserver>>,
68
69 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub custom_scalars: Option<Vec<IntermediateScalar>>,
76
77 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub security: Option<serde_json::Value>,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub observers_config: Option<serde_json::Value>,
89
90 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub federation_config: Option<serde_json::Value>,
96
97 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub subscriptions_config: Option<serde_json::Value>,
103
104 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub validation_config: Option<serde_json::Value>,
110
111 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub debug_config: Option<serde_json::Value>,
117
118 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub mcp_config: Option<serde_json::Value>,
124
125 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub query_defaults: Option<IntermediateQueryDefaults>,
131}
132
133fn default_version() -> String {
134 "2.0.0".to_string()
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
139pub struct IntermediateType {
140 pub name: String,
142
143 pub fields: Vec<IntermediateField>,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub description: Option<String>,
149
150 #[serde(default, skip_serializing_if = "Vec::is_empty")]
152 pub implements: Vec<String>,
153
154 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub requires_role: Option<String>,
157
158 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
160 pub is_error: bool,
161
162 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
166 pub relay: bool,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174pub struct IntermediateField {
175 pub name: String,
177
178 #[serde(rename = "type")]
182 pub field_type: String,
183
184 pub nullable: bool,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub description: Option<String>,
190
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub directives: Option<Vec<IntermediateAppliedDirective>>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
211 pub requires_scope: Option<String>,
212
213 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub on_deny: Option<String>,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241pub struct IntermediateEnum {
242 pub name: String,
244
245 pub values: Vec<IntermediateEnumValue>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub description: Option<String>,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265pub struct IntermediateEnumValue {
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 deprecated: Option<IntermediateDeprecation>,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
280pub struct IntermediateDeprecation {
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub reason: Option<String>,
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub struct IntermediateScalar {
316 pub name: String,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub description: Option<String>,
322
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub specified_by_url: Option<String>,
326
327 #[serde(default)]
329 pub validation_rules: Vec<ValidationRule>,
330
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub base_type: Option<String>,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct IntermediateInputObject {
360 pub name: String,
362
363 pub fields: Vec<IntermediateInputField>,
365
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub description: Option<String>,
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
384pub struct IntermediateInputField {
385 pub name: String,
387
388 #[serde(rename = "type")]
390 pub field_type: String,
391
392 #[serde(default)]
394 pub nullable: bool,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub description: Option<String>,
399
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub default: Option<serde_json::Value>,
403
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub deprecated: Option<IntermediateDeprecation>,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
411pub struct IntermediateQuery {
412 pub name: String,
414
415 pub return_type: String,
417
418 #[serde(default)]
420 pub returns_list: bool,
421
422 #[serde(default)]
424 pub nullable: bool,
425
426 #[serde(default)]
428 pub arguments: Vec<IntermediateArgument>,
429
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub description: Option<String>,
433
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub sql_source: Option<String>,
437
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub auto_params: Option<IntermediateAutoParams>,
441
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub deprecated: Option<IntermediateDeprecation>,
445
446 #[serde(skip_serializing_if = "Option::is_none")]
449 pub jsonb_column: Option<String>,
450
451 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
456 pub relay: bool,
457
458 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
461 pub inject: IndexMap<String, String>,
462
463 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub cache_ttl_seconds: Option<u64>,
466
467 #[serde(default, skip_serializing_if = "Vec::is_empty")]
472 pub additional_views: Vec<String>,
473
474 #[serde(default, skip_serializing_if = "Option::is_none")]
476 pub requires_role: Option<String>,
477
478 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub relay_cursor_type: Option<String>,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
486pub struct IntermediateMutation {
487 pub name: String,
489
490 pub return_type: String,
492
493 #[serde(default)]
495 pub returns_list: bool,
496
497 #[serde(default)]
499 pub nullable: bool,
500
501 #[serde(default)]
503 pub arguments: Vec<IntermediateArgument>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub description: Option<String>,
508
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub sql_source: Option<String>,
512
513 #[serde(skip_serializing_if = "Option::is_none")]
515 pub operation: Option<String>,
516
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub deprecated: Option<IntermediateDeprecation>,
520
521 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
524 pub inject: IndexMap<String, String>,
525
526 #[serde(default, skip_serializing_if = "Vec::is_empty")]
530 pub invalidates_fact_tables: Vec<String>,
531
532 #[serde(default, skip_serializing_if = "Vec::is_empty")]
535 pub invalidates_views: Vec<String>,
536}
537
538#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
559pub struct IntermediateInterface {
560 pub name: String,
562
563 pub fields: Vec<IntermediateField>,
565
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub description: Option<String>,
569}
570
571#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
573pub struct IntermediateArgument {
574 pub name: String,
576
577 #[serde(rename = "type")]
581 pub arg_type: String,
582
583 pub nullable: bool,
585
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub default: Option<serde_json::Value>,
589
590 #[serde(skip_serializing_if = "Option::is_none")]
592 pub deprecated: Option<IntermediateDeprecation>,
593}
594
595#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
615pub struct IntermediateUnion {
616 pub name: String,
618
619 pub member_types: Vec<String>,
621
622 #[serde(skip_serializing_if = "Option::is_none")]
624 pub description: Option<String>,
625}
626
627#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
632pub struct IntermediateAutoParams {
633 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub limit: Option<bool>,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub offset: Option<bool>,
639 #[serde(rename = "where", default, skip_serializing_if = "Option::is_none")]
641 pub where_clause: Option<bool>,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
644 pub order_by: Option<bool>,
645}
646
647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
655pub struct IntermediateQueryDefaults {
656 pub where_clause: bool,
658 pub order_by: bool,
660 pub limit: bool,
662 pub offset: bool,
664}
665
666impl Default for IntermediateQueryDefaults {
667 fn default() -> Self {
668 Self { where_clause: true, order_by: true, limit: true, offset: true }
669 }
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
699pub struct IntermediateSubscription {
700 pub name: String,
702
703 pub return_type: String,
705
706 #[serde(default)]
708 pub arguments: Vec<IntermediateArgument>,
709
710 #[serde(skip_serializing_if = "Option::is_none")]
712 pub description: Option<String>,
713
714 #[serde(skip_serializing_if = "Option::is_none")]
716 pub topic: Option<String>,
717
718 #[serde(skip_serializing_if = "Option::is_none")]
720 pub filter: Option<IntermediateSubscriptionFilter>,
721
722 #[serde(default, skip_serializing_if = "Vec::is_empty")]
724 pub fields: Vec<String>,
725
726 #[serde(skip_serializing_if = "Option::is_none")]
728 pub deprecated: Option<IntermediateDeprecation>,
729}
730
731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
735pub struct IntermediateSubscriptionFilter {
736 pub conditions: Vec<IntermediateFilterCondition>,
738}
739
740#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
742pub struct IntermediateFilterCondition {
743 pub argument: String,
745
746 pub path: String,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub struct IntermediateFragment {
770 pub name: String,
772
773 #[serde(rename = "on")]
775 pub type_condition: String,
776
777 pub fields: Vec<IntermediateFragmentField>,
779
780 #[serde(skip_serializing_if = "Option::is_none")]
782 pub description: Option<String>,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
787#[serde(untagged)]
788pub enum IntermediateFragmentField {
789 Simple(String),
791
792 Complex(IntermediateFragmentFieldDef),
794}
795
796#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
798pub struct IntermediateFragmentFieldDef {
799 pub name: String,
801
802 #[serde(skip_serializing_if = "Option::is_none")]
804 pub alias: Option<String>,
805
806 #[serde(skip_serializing_if = "Option::is_none")]
808 pub fields: Option<Vec<IntermediateFragmentField>>,
809
810 #[serde(skip_serializing_if = "Option::is_none")]
812 pub spread: Option<String>,
813
814 #[serde(skip_serializing_if = "Option::is_none")]
816 pub directives: Option<Vec<IntermediateAppliedDirective>>,
817}
818
819#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
835pub struct IntermediateDirective {
836 pub name: String,
838
839 pub locations: Vec<String>,
841
842 #[serde(default)]
844 pub arguments: Vec<IntermediateArgument>,
845
846 #[serde(default)]
848 pub repeatable: bool,
849
850 #[serde(skip_serializing_if = "Option::is_none")]
852 pub description: Option<String>,
853}
854
855#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
866pub struct IntermediateAppliedDirective {
867 pub name: String,
869
870 #[serde(default, skip_serializing_if = "Option::is_none")]
872 pub arguments: Option<serde_json::Value>,
873}
874
875#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
881pub struct IntermediateFactTable {
882 pub table_name: String,
884 pub measures: Vec<IntermediateMeasure>,
886 pub dimensions: IntermediateDimensions,
888 pub denormalized_filters: Vec<IntermediateFilter>,
890}
891
892#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
894pub struct IntermediateMeasure {
895 pub name: String,
897 pub sql_type: String,
899 pub nullable: bool,
901}
902
903#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
905pub struct IntermediateDimensions {
906 pub name: String,
908 pub paths: Vec<IntermediateDimensionPath>,
910}
911
912#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
914pub struct IntermediateDimensionPath {
915 pub name: String,
917 #[serde(alias = "path")]
919 pub json_path: String,
920 #[serde(alias = "type")]
922 pub data_type: String,
923}
924
925#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
927pub struct IntermediateFilter {
928 pub name: String,
930 pub sql_type: String,
932 pub indexed: bool,
934}
935
936#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
938pub struct IntermediateAggregateQuery {
939 pub name: String,
941 pub fact_table: String,
943 pub auto_group_by: bool,
945 pub auto_aggregates: bool,
947 #[serde(skip_serializing_if = "Option::is_none")]
949 pub description: Option<String>,
950}
951
952#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
991pub struct IntermediateObserver {
992 pub name: String,
994
995 pub entity: String,
997
998 pub event: String,
1000
1001 pub actions: Vec<IntermediateObserverAction>,
1003
1004 #[serde(skip_serializing_if = "Option::is_none")]
1006 pub condition: Option<String>,
1007
1008 pub retry: IntermediateRetryConfig,
1010}
1011
1012pub type IntermediateObserverAction = serde_json::Value;
1017
1018#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1031pub struct IntermediateRetryConfig {
1032 pub max_attempts: u32,
1034
1035 pub backoff_strategy: String,
1037
1038 pub initial_delay_ms: u32,
1040
1041 pub max_delay_ms: u32,
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048
1049 #[test]
1050 fn test_parse_minimal_schema() {
1051 let json = r#"{
1052 "types": [],
1053 "queries": [],
1054 "mutations": []
1055 }"#;
1056
1057 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1058 assert_eq!(schema.version, "2.0.0");
1059 assert_eq!(schema.types.len(), 0);
1060 assert_eq!(schema.queries.len(), 0);
1061 assert_eq!(schema.mutations.len(), 0);
1062 }
1063
1064 #[test]
1065 fn test_parse_type_with_type_field() {
1066 let json = r#"{
1067 "types": [{
1068 "name": "User",
1069 "fields": [
1070 {
1071 "name": "id",
1072 "type": "Int",
1073 "nullable": false
1074 },
1075 {
1076 "name": "name",
1077 "type": "String",
1078 "nullable": false
1079 }
1080 ]
1081 }],
1082 "queries": [],
1083 "mutations": []
1084 }"#;
1085
1086 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1087 assert_eq!(schema.types.len(), 1);
1088 assert_eq!(schema.types[0].name, "User");
1089 assert_eq!(schema.types[0].fields.len(), 2);
1090 assert_eq!(schema.types[0].fields[0].name, "id");
1091 assert_eq!(schema.types[0].fields[0].field_type, "Int");
1092 assert!(!schema.types[0].fields[0].nullable);
1093 }
1094
1095 #[test]
1096 fn test_parse_query_with_arguments() {
1097 let json = r#"{
1098 "types": [],
1099 "queries": [{
1100 "name": "users",
1101 "return_type": "User",
1102 "returns_list": true,
1103 "nullable": false,
1104 "arguments": [
1105 {
1106 "name": "limit",
1107 "type": "Int",
1108 "nullable": false,
1109 "default": 10
1110 }
1111 ],
1112 "sql_source": "v_user"
1113 }],
1114 "mutations": []
1115 }"#;
1116
1117 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1118 assert_eq!(schema.queries.len(), 1);
1119 assert_eq!(schema.queries[0].arguments.len(), 1);
1120 assert_eq!(schema.queries[0].arguments[0].arg_type, "Int");
1121 assert_eq!(schema.queries[0].arguments[0].default, Some(serde_json::json!(10)));
1122 }
1123
1124 #[test]
1125 fn test_parse_fragment_simple() {
1126 let json = r#"{
1127 "types": [],
1128 "queries": [],
1129 "mutations": [],
1130 "fragments": [{
1131 "name": "UserFields",
1132 "on": "User",
1133 "fields": ["id", "name", "email"]
1134 }]
1135 }"#;
1136
1137 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1138 assert!(schema.fragments.is_some());
1139 let fragments = schema.fragments.unwrap();
1140 assert_eq!(fragments.len(), 1);
1141 assert_eq!(fragments[0].name, "UserFields");
1142 assert_eq!(fragments[0].type_condition, "User");
1143 assert_eq!(fragments[0].fields.len(), 3);
1144
1145 match &fragments[0].fields[0] {
1147 IntermediateFragmentField::Simple(name) => assert_eq!(name, "id"),
1148 IntermediateFragmentField::Complex(_) => panic!("Expected simple field"),
1149 }
1150 }
1151
1152 #[test]
1153 fn test_parse_fragment_with_nested_fields() {
1154 let json = r#"{
1155 "types": [],
1156 "queries": [],
1157 "mutations": [],
1158 "fragments": [{
1159 "name": "PostFields",
1160 "on": "Post",
1161 "fields": [
1162 "id",
1163 "title",
1164 {
1165 "name": "author",
1166 "alias": "writer",
1167 "fields": ["id", "name"]
1168 }
1169 ]
1170 }]
1171 }"#;
1172
1173 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1174 let fragments = schema.fragments.unwrap();
1175 assert_eq!(fragments[0].fields.len(), 3);
1176
1177 match &fragments[0].fields[2] {
1179 IntermediateFragmentField::Complex(def) => {
1180 assert_eq!(def.name, "author");
1181 assert_eq!(def.alias, Some("writer".to_string()));
1182 assert!(def.fields.is_some());
1183 assert_eq!(def.fields.as_ref().unwrap().len(), 2);
1184 },
1185 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1186 }
1187 }
1188
1189 #[test]
1190 fn test_parse_directive_definition() {
1191 let json = r#"{
1192 "types": [],
1193 "queries": [],
1194 "mutations": [],
1195 "directives": [{
1196 "name": "auth",
1197 "locations": ["FIELD_DEFINITION", "OBJECT"],
1198 "arguments": [
1199 {"name": "role", "type": "String", "nullable": false}
1200 ],
1201 "description": "Requires authentication"
1202 }]
1203 }"#;
1204
1205 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1206 assert!(schema.directives.is_some());
1207 let directives = schema.directives.unwrap();
1208 assert_eq!(directives.len(), 1);
1209 assert_eq!(directives[0].name, "auth");
1210 assert_eq!(directives[0].locations, vec!["FIELD_DEFINITION", "OBJECT"]);
1211 assert_eq!(directives[0].arguments.len(), 1);
1212 assert_eq!(directives[0].description, Some("Requires authentication".to_string()));
1213 }
1214
1215 #[test]
1216 fn test_parse_field_with_directive() {
1217 let json = r#"{
1218 "types": [{
1219 "name": "User",
1220 "fields": [
1221 {
1222 "name": "oldId",
1223 "type": "Int",
1224 "nullable": false,
1225 "directives": [
1226 {"name": "deprecated", "arguments": {"reason": "Use 'id' instead"}}
1227 ]
1228 }
1229 ]
1230 }],
1231 "queries": [],
1232 "mutations": []
1233 }"#;
1234
1235 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1236 let field = &schema.types[0].fields[0];
1237 assert_eq!(field.name, "oldId");
1238 assert!(field.directives.is_some());
1239 let directives = field.directives.as_ref().unwrap();
1240 assert_eq!(directives.len(), 1);
1241 assert_eq!(directives[0].name, "deprecated");
1242 assert_eq!(
1243 directives[0].arguments,
1244 Some(serde_json::json!({"reason": "Use 'id' instead"}))
1245 );
1246 }
1247
1248 #[test]
1249 fn test_parse_fragment_with_spread() {
1250 let json = r#"{
1251 "types": [],
1252 "queries": [],
1253 "mutations": [],
1254 "fragments": [
1255 {
1256 "name": "UserFields",
1257 "on": "User",
1258 "fields": ["id", "name"]
1259 },
1260 {
1261 "name": "PostWithAuthor",
1262 "on": "Post",
1263 "fields": [
1264 "id",
1265 "title",
1266 {
1267 "name": "author",
1268 "spread": "UserFields"
1269 }
1270 ]
1271 }
1272 ]
1273 }"#;
1274
1275 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1276 let fragments = schema.fragments.unwrap();
1277 assert_eq!(fragments.len(), 2);
1278
1279 match &fragments[1].fields[2] {
1281 IntermediateFragmentField::Complex(def) => {
1282 assert_eq!(def.name, "author");
1283 assert_eq!(def.spread, Some("UserFields".to_string()));
1284 },
1285 IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
1286 }
1287 }
1288
1289 #[test]
1290 fn test_parse_enum() {
1291 let json = r#"{
1292 "types": [],
1293 "queries": [],
1294 "mutations": [],
1295 "enums": [{
1296 "name": "OrderStatus",
1297 "values": [
1298 {"name": "PENDING"},
1299 {"name": "PROCESSING", "description": "Currently being processed"},
1300 {"name": "SHIPPED"},
1301 {"name": "DELIVERED"}
1302 ],
1303 "description": "Possible states of an order"
1304 }]
1305 }"#;
1306
1307 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1308 assert_eq!(schema.enums.len(), 1);
1309 let enum_def = &schema.enums[0];
1310 assert_eq!(enum_def.name, "OrderStatus");
1311 assert_eq!(enum_def.description, Some("Possible states of an order".to_string()));
1312 assert_eq!(enum_def.values.len(), 4);
1313 assert_eq!(enum_def.values[0].name, "PENDING");
1314 assert_eq!(enum_def.values[1].description, Some("Currently being processed".to_string()));
1315 }
1316
1317 #[test]
1318 fn test_parse_enum_with_deprecated_value() {
1319 let json = r#"{
1320 "types": [],
1321 "queries": [],
1322 "mutations": [],
1323 "enums": [{
1324 "name": "UserRole",
1325 "values": [
1326 {"name": "ADMIN"},
1327 {"name": "USER"},
1328 {"name": "GUEST", "deprecated": {"reason": "Use USER with limited permissions instead"}}
1329 ]
1330 }]
1331 }"#;
1332
1333 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1334 let enum_def = &schema.enums[0];
1335 assert_eq!(enum_def.values.len(), 3);
1336
1337 let guest = &enum_def.values[2];
1339 assert_eq!(guest.name, "GUEST");
1340 assert!(guest.deprecated.is_some());
1341 assert_eq!(
1342 guest.deprecated.as_ref().unwrap().reason,
1343 Some("Use USER with limited permissions instead".to_string())
1344 );
1345 }
1346
1347 #[test]
1348 fn test_parse_input_object() {
1349 let json = r#"{
1350 "types": [],
1351 "queries": [],
1352 "mutations": [],
1353 "input_types": [{
1354 "name": "UserFilter",
1355 "fields": [
1356 {"name": "name", "type": "String", "nullable": true},
1357 {"name": "email", "type": "String", "nullable": true},
1358 {"name": "active", "type": "Boolean", "nullable": true, "default": true}
1359 ],
1360 "description": "Filter criteria for users"
1361 }]
1362 }"#;
1363
1364 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1365 assert_eq!(schema.input_types.len(), 1);
1366 let input = &schema.input_types[0];
1367 assert_eq!(input.name, "UserFilter");
1368 assert_eq!(input.description, Some("Filter criteria for users".to_string()));
1369 assert_eq!(input.fields.len(), 3);
1370
1371 assert_eq!(input.fields[0].name, "name");
1373 assert_eq!(input.fields[0].field_type, "String");
1374 assert!(input.fields[0].nullable);
1375
1376 assert_eq!(input.fields[2].name, "active");
1378 assert_eq!(input.fields[2].default, Some(serde_json::json!(true)));
1379 }
1380
1381 #[test]
1382 fn test_parse_interface() {
1383 let json = r#"{
1384 "types": [],
1385 "queries": [],
1386 "mutations": [],
1387 "interfaces": [{
1388 "name": "Node",
1389 "fields": [
1390 {"name": "id", "type": "ID", "nullable": false}
1391 ],
1392 "description": "An object with a globally unique ID"
1393 }]
1394 }"#;
1395
1396 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1397 assert_eq!(schema.interfaces.len(), 1);
1398 let interface = &schema.interfaces[0];
1399 assert_eq!(interface.name, "Node");
1400 assert_eq!(interface.description, Some("An object with a globally unique ID".to_string()));
1401 assert_eq!(interface.fields.len(), 1);
1402 assert_eq!(interface.fields[0].name, "id");
1403 assert_eq!(interface.fields[0].field_type, "ID");
1404 assert!(!interface.fields[0].nullable);
1405 }
1406
1407 #[test]
1408 fn test_parse_type_implements_interface() {
1409 let json = r#"{
1410 "types": [{
1411 "name": "User",
1412 "fields": [
1413 {"name": "id", "type": "ID", "nullable": false},
1414 {"name": "name", "type": "String", "nullable": false}
1415 ],
1416 "implements": ["Node"]
1417 }],
1418 "queries": [],
1419 "mutations": [],
1420 "interfaces": [{
1421 "name": "Node",
1422 "fields": [
1423 {"name": "id", "type": "ID", "nullable": false}
1424 ]
1425 }]
1426 }"#;
1427
1428 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1429 assert_eq!(schema.types.len(), 1);
1430 assert_eq!(schema.types[0].name, "User");
1431 assert_eq!(schema.types[0].implements, vec!["Node"]);
1432
1433 assert_eq!(schema.interfaces.len(), 1);
1434 assert_eq!(schema.interfaces[0].name, "Node");
1435 }
1436
1437 #[test]
1438 fn test_parse_input_object_with_deprecated_field() {
1439 let json = r#"{
1440 "types": [],
1441 "queries": [],
1442 "mutations": [],
1443 "input_types": [{
1444 "name": "CreateUserInput",
1445 "fields": [
1446 {"name": "email", "type": "String!", "nullable": false},
1447 {"name": "name", "type": "String!", "nullable": false},
1448 {
1449 "name": "username",
1450 "type": "String",
1451 "nullable": true,
1452 "deprecated": {"reason": "Use email as unique identifier instead"}
1453 }
1454 ]
1455 }]
1456 }"#;
1457
1458 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1459 let input = &schema.input_types[0];
1460
1461 let username_field = &input.fields[2];
1463 assert_eq!(username_field.name, "username");
1464 assert!(username_field.deprecated.is_some());
1465 assert_eq!(
1466 username_field.deprecated.as_ref().unwrap().reason,
1467 Some("Use email as unique identifier instead".to_string())
1468 );
1469 }
1470
1471 #[test]
1472 fn test_parse_union() {
1473 let json = r#"{
1474 "types": [
1475 {"name": "User", "fields": [{"name": "id", "type": "ID", "nullable": false}]},
1476 {"name": "Post", "fields": [{"name": "id", "type": "ID", "nullable": false}]}
1477 ],
1478 "queries": [],
1479 "mutations": [],
1480 "unions": [{
1481 "name": "SearchResult",
1482 "member_types": ["User", "Post"],
1483 "description": "Result from a search query"
1484 }]
1485 }"#;
1486
1487 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1488 assert_eq!(schema.unions.len(), 1);
1489 let union_def = &schema.unions[0];
1490 assert_eq!(union_def.name, "SearchResult");
1491 assert_eq!(union_def.member_types, vec!["User", "Post"]);
1492 assert_eq!(union_def.description, Some("Result from a search query".to_string()));
1493 }
1494
1495 #[test]
1496 fn test_parse_field_with_requires_scope() {
1497 let json = r#"{
1498 "types": [{
1499 "name": "Employee",
1500 "fields": [
1501 {
1502 "name": "id",
1503 "type": "ID",
1504 "nullable": false
1505 },
1506 {
1507 "name": "name",
1508 "type": "String",
1509 "nullable": false
1510 },
1511 {
1512 "name": "salary",
1513 "type": "Float",
1514 "nullable": false,
1515 "description": "Employee salary - protected field",
1516 "requires_scope": "read:Employee.salary"
1517 },
1518 {
1519 "name": "ssn",
1520 "type": "String",
1521 "nullable": true,
1522 "description": "Social Security Number",
1523 "requires_scope": "admin"
1524 }
1525 ]
1526 }],
1527 "queries": [],
1528 "mutations": []
1529 }"#;
1530
1531 let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
1532 assert_eq!(schema.types.len(), 1);
1533
1534 let employee = &schema.types[0];
1535 assert_eq!(employee.name, "Employee");
1536 assert_eq!(employee.fields.len(), 4);
1537
1538 assert_eq!(employee.fields[0].name, "id");
1540 assert!(employee.fields[0].requires_scope.is_none());
1541
1542 assert_eq!(employee.fields[1].name, "name");
1544 assert!(employee.fields[1].requires_scope.is_none());
1545
1546 assert_eq!(employee.fields[2].name, "salary");
1548 assert_eq!(employee.fields[2].requires_scope, Some("read:Employee.salary".to_string()));
1549
1550 assert_eq!(employee.fields[3].name, "ssn");
1552 assert_eq!(employee.fields[3].requires_scope, Some("admin".to_string()));
1553 }
1554}