1use std::collections::HashMap;
18
19use serde::{Deserialize, Serialize};
20
21use super::field_type::{FieldDefinition, FieldType};
22use crate::validation::{CustomTypeRegistry, ValidationRule};
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct RoleDefinition {
41 pub name: String,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub description: Option<String>,
47
48 pub scopes: Vec<String>,
51}
52
53impl RoleDefinition {
54 #[must_use]
56 pub fn new(name: String, scopes: Vec<String>) -> Self {
57 Self {
58 name,
59 description: None,
60 scopes,
61 }
62 }
63
64 pub fn with_description(mut self, description: String) -> Self {
66 self.description = Some(description);
67 self
68 }
69
70 #[must_use]
78 pub fn has_scope(&self, required_scope: &str) -> bool {
79 self.scopes.iter().any(|scope| {
80 if scope == "*" {
81 return true; }
83
84 if scope == required_scope {
85 return true; }
87
88 if scope.ends_with(":*") {
90 let prefix = &scope[..scope.len() - 2]; return required_scope.starts_with(prefix) && required_scope.contains(':');
92 }
93
94 if scope.ends_with(".*") {
96 let prefix = &scope[..scope.len() - 1]; return required_scope.starts_with(prefix);
98 }
99
100 false
101 })
102 }
103}
104
105#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
110pub struct SecurityConfig {
111 #[serde(default, skip_serializing_if = "Vec::is_empty")]
113 pub role_definitions: Vec<RoleDefinition>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub default_role: Option<String>,
118
119 #[serde(flatten)]
121 pub additional: HashMap<String, serde_json::Value>,
122}
123
124impl SecurityConfig {
125 #[must_use]
127 pub fn new() -> Self {
128 Self::default()
129 }
130
131 pub fn add_role(&mut self, role: RoleDefinition) {
133 self.role_definitions.push(role);
134 }
135
136 #[must_use]
138 pub fn find_role(&self, name: &str) -> Option<&RoleDefinition> {
139 self.role_definitions.iter().find(|r| r.name == name)
140 }
141
142 #[must_use]
144 pub fn get_role_scopes(&self, role_name: &str) -> Vec<String> {
145 self.find_role(role_name).map(|role| role.scopes.clone()).unwrap_or_default()
146 }
147
148 #[must_use]
150 pub fn role_has_scope(&self, role_name: &str, scope: &str) -> bool {
151 self.find_role(role_name).map(|role| role.has_scope(scope)).unwrap_or(false)
152 }
153}
154
155#[derive(Debug, Clone, Default, Serialize, Deserialize)]
176pub struct CompiledSchema {
177 #[serde(default)]
179 pub types: Vec<TypeDefinition>,
180
181 #[serde(default)]
183 pub enums: Vec<EnumDefinition>,
184
185 #[serde(default)]
187 pub input_types: Vec<InputObjectDefinition>,
188
189 #[serde(default)]
191 pub interfaces: Vec<InterfaceDefinition>,
192
193 #[serde(default)]
195 pub unions: Vec<UnionDefinition>,
196
197 #[serde(default)]
199 pub queries: Vec<QueryDefinition>,
200
201 #[serde(default)]
203 pub mutations: Vec<MutationDefinition>,
204
205 #[serde(default)]
207 pub subscriptions: Vec<SubscriptionDefinition>,
208
209 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 pub directives: Vec<DirectiveDefinition>,
213
214 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
217 pub fact_tables: HashMap<String, serde_json::Value>,
218
219 #[serde(default, skip_serializing_if = "Vec::is_empty")]
221 pub observers: Vec<ObserverDefinition>,
222
223 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub federation: Option<serde_json::Value>,
226
227 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub security: Option<serde_json::Value>,
230
231 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub schema_sdl: Option<String>,
234
235 #[serde(skip)]
241 pub custom_scalars: CustomTypeRegistry,
242}
243
244impl PartialEq for CompiledSchema {
245 fn eq(&self, other: &Self) -> bool {
246 self.types == other.types
248 && self.enums == other.enums
249 && self.input_types == other.input_types
250 && self.interfaces == other.interfaces
251 && self.unions == other.unions
252 && self.queries == other.queries
253 && self.mutations == other.mutations
254 && self.subscriptions == other.subscriptions
255 && self.directives == other.directives
256 && self.fact_tables == other.fact_tables
257 && self.observers == other.observers
258 && self.federation == other.federation
259 && self.security == other.security
260 && self.schema_sdl == other.schema_sdl
261 }
262}
263
264impl CompiledSchema {
265 #[must_use]
267 pub fn new() -> Self {
268 Self::default()
269 }
270
271 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
289 serde_json::from_str(json)
290 }
291
292 pub fn to_json(&self) -> Result<String, serde_json::Error> {
298 serde_json::to_string(self)
299 }
300
301 pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
307 serde_json::to_string_pretty(self)
308 }
309
310 #[must_use]
312 pub fn find_type(&self, name: &str) -> Option<&TypeDefinition> {
313 self.types.iter().find(|t| t.name == name)
314 }
315
316 #[must_use]
318 pub fn find_enum(&self, name: &str) -> Option<&EnumDefinition> {
319 self.enums.iter().find(|e| e.name == name)
320 }
321
322 #[must_use]
324 pub fn find_input_type(&self, name: &str) -> Option<&InputObjectDefinition> {
325 self.input_types.iter().find(|i| i.name == name)
326 }
327
328 #[must_use]
330 pub fn find_interface(&self, name: &str) -> Option<&InterfaceDefinition> {
331 self.interfaces.iter().find(|i| i.name == name)
332 }
333
334 #[must_use]
336 pub fn find_implementors(&self, interface_name: &str) -> Vec<&TypeDefinition> {
337 self.types
338 .iter()
339 .filter(|t| t.implements.contains(&interface_name.to_string()))
340 .collect()
341 }
342
343 #[must_use]
345 pub fn find_union(&self, name: &str) -> Option<&UnionDefinition> {
346 self.unions.iter().find(|u| u.name == name)
347 }
348
349 #[must_use]
351 pub fn find_query(&self, name: &str) -> Option<&QueryDefinition> {
352 self.queries.iter().find(|q| q.name == name)
353 }
354
355 #[must_use]
357 pub fn find_mutation(&self, name: &str) -> Option<&MutationDefinition> {
358 self.mutations.iter().find(|m| m.name == name)
359 }
360
361 #[must_use]
363 pub fn find_subscription(&self, name: &str) -> Option<&SubscriptionDefinition> {
364 self.subscriptions.iter().find(|s| s.name == name)
365 }
366
367 #[must_use]
369 pub fn find_directive(&self, name: &str) -> Option<&DirectiveDefinition> {
370 self.directives.iter().find(|d| d.name == name)
371 }
372
373 #[must_use]
375 pub fn operation_count(&self) -> usize {
376 self.queries.len() + self.mutations.len() + self.subscriptions.len()
377 }
378
379 pub fn add_fact_table(&mut self, table_name: String, metadata: serde_json::Value) {
386 self.fact_tables.insert(table_name, metadata);
387 }
388
389 #[must_use]
399 pub fn get_fact_table(&self, name: &str) -> Option<&serde_json::Value> {
400 self.fact_tables.get(name)
401 }
402
403 #[must_use]
409 pub fn list_fact_tables(&self) -> Vec<&str> {
410 self.fact_tables.keys().map(String::as_str).collect()
411 }
412
413 #[must_use]
415 pub fn has_fact_tables(&self) -> bool {
416 !self.fact_tables.is_empty()
417 }
418
419 #[must_use]
421 pub fn find_observer(&self, name: &str) -> Option<&ObserverDefinition> {
422 self.observers.iter().find(|o| o.name == name)
423 }
424
425 #[must_use]
427 pub fn find_observers_for_entity(&self, entity: &str) -> Vec<&ObserverDefinition> {
428 self.observers.iter().filter(|o| o.entity == entity).collect()
429 }
430
431 #[must_use]
433 pub fn find_observers_for_event(&self, event: &str) -> Vec<&ObserverDefinition> {
434 self.observers.iter().filter(|o| o.event == event).collect()
435 }
436
437 #[must_use]
439 pub fn has_observers(&self) -> bool {
440 !self.observers.is_empty()
441 }
442
443 #[must_use]
445 pub fn observer_count(&self) -> usize {
446 self.observers.len()
447 }
448
449 #[must_use]
455 pub fn federation_metadata(&self) -> Option<crate::federation::FederationMetadata> {
456 self.federation
457 .as_ref()
458 .and_then(|fed_json| serde_json::from_value(fed_json.clone()).ok())
459 }
460
461 #[must_use]
467 pub fn security_config(&self) -> Option<SecurityConfig> {
468 self.security
469 .as_ref()
470 .and_then(|sec_json| serde_json::from_value(sec_json.clone()).ok())
471 }
472
473 #[must_use]
483 pub fn find_role(&self, role_name: &str) -> Option<RoleDefinition> {
484 self.security_config().and_then(|config| config.find_role(role_name).cloned())
485 }
486
487 #[must_use]
497 pub fn get_role_scopes(&self, role_name: &str) -> Vec<String> {
498 self.security_config()
499 .map(|config| config.get_role_scopes(role_name))
500 .unwrap_or_default()
501 }
502
503 #[must_use]
514 pub fn role_has_scope(&self, role_name: &str, scope: &str) -> bool {
515 self.security_config()
516 .map(|config| config.role_has_scope(role_name, scope))
517 .unwrap_or(false)
518 }
519
520 #[must_use]
526 pub fn raw_schema(&self) -> String {
527 self.schema_sdl.clone().unwrap_or_else(|| {
528 let mut sdl = String::new();
530
531 for type_def in &self.types {
533 sdl.push_str(&format!("type {} {{\n", type_def.name));
534 for field in &type_def.fields {
535 sdl.push_str(&format!(" {}: {}\n", field.name, field.field_type));
536 }
537 sdl.push_str("}\n\n");
538 }
539
540 sdl
541 })
542 }
543
544 pub fn validate(&self) -> Result<(), Vec<String>> {
555 let mut errors = Vec::new();
556
557 let mut type_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
559 for type_def in &self.types {
560 if !type_names.insert(&type_def.name) {
561 errors.push(format!("Duplicate type name: {}", type_def.name));
562 }
563 }
564
565 let mut query_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
567 for query in &self.queries {
568 if !query_names.insert(&query.name) {
569 errors.push(format!("Duplicate query name: {}", query.name));
570 }
571 }
572
573 let mut mutation_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
575 for mutation in &self.mutations {
576 if !mutation_names.insert(&mutation.name) {
577 errors.push(format!("Duplicate mutation name: {}", mutation.name));
578 }
579 }
580
581 for query in &self.queries {
583 if !type_names.contains(query.return_type.as_str())
584 && !is_builtin_type(&query.return_type)
585 {
586 errors.push(format!(
587 "Query '{}' references undefined type '{}'",
588 query.name, query.return_type
589 ));
590 }
591 }
592
593 for mutation in &self.mutations {
595 if !type_names.contains(mutation.return_type.as_str())
596 && !is_builtin_type(&mutation.return_type)
597 {
598 errors.push(format!(
599 "Mutation '{}' references undefined type '{}'",
600 mutation.name, mutation.return_type
601 ));
602 }
603 }
604
605 if errors.is_empty() {
606 Ok(())
607 } else {
608 Err(errors)
609 }
610 }
611}
612
613fn is_builtin_type(name: &str) -> bool {
615 matches!(
616 name,
617 "String"
618 | "Int"
619 | "Float"
620 | "Boolean"
621 | "ID"
622 | "DateTime"
623 | "Date"
624 | "Time"
625 | "JSON"
626 | "UUID"
627 | "Decimal"
628 )
629}
630
631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
654pub struct TypeDefinition {
655 pub name: String,
657
658 pub sql_source: String,
660
661 #[serde(default = "default_jsonb_column")]
663 pub jsonb_column: String,
664
665 #[serde(default)]
667 pub fields: Vec<FieldDefinition>,
668
669 #[serde(default, skip_serializing_if = "Option::is_none")]
671 pub description: Option<String>,
672
673 #[serde(default, skip_serializing_if = "Option::is_none")]
677 pub sql_projection_hint: Option<SqlProjectionHint>,
678
679 #[serde(default, skip_serializing_if = "Vec::is_empty")]
681 pub implements: Vec<String>,
682}
683
684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
690pub struct SqlProjectionHint {
691 pub database: String,
693
694 pub projection_template: String,
698
699 pub estimated_reduction_percent: u32,
701}
702
703fn default_jsonb_column() -> String {
704 "data".to_string()
705}
706
707impl TypeDefinition {
708 #[must_use]
710 pub fn new(name: impl Into<String>, sql_source: impl Into<String>) -> Self {
711 Self {
712 name: name.into(),
713 sql_source: sql_source.into(),
714 jsonb_column: "data".to_string(),
715 fields: Vec::new(),
716 description: None,
717 sql_projection_hint: None,
718 implements: Vec::new(),
719 }
720 }
721
722 #[must_use]
724 pub fn with_field(mut self, field: FieldDefinition) -> Self {
725 self.fields.push(field);
726 self
727 }
728
729 #[must_use]
731 pub fn with_jsonb_column(mut self, column: impl Into<String>) -> Self {
732 self.jsonb_column = column.into();
733 self
734 }
735
736 #[must_use]
738 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
739 self.description = Some(desc.into());
740 self
741 }
742
743 #[must_use]
745 pub fn find_field(&self, name: &str) -> Option<&FieldDefinition> {
746 self.fields.iter().find(|f| f.name == name)
747 }
748
749 #[must_use]
754 pub fn find_field_by_output_name(&self, output_name: &str) -> Option<&FieldDefinition> {
755 self.fields.iter().find(|f| f.output_name() == output_name)
756 }
757
758 #[must_use]
760 pub fn with_sql_projection(mut self, hint: SqlProjectionHint) -> Self {
761 self.sql_projection_hint = Some(hint);
762 self
763 }
764
765 #[must_use]
767 pub fn has_sql_projection(&self) -> bool {
768 self.sql_projection_hint.is_some()
769 }
770
771 #[must_use]
776 pub fn typename(&self) -> &str {
777 &self.name
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
807pub struct EnumDefinition {
808 pub name: String,
810
811 #[serde(default)]
813 pub values: Vec<EnumValueDefinition>,
814
815 #[serde(default, skip_serializing_if = "Option::is_none")]
817 pub description: Option<String>,
818}
819
820impl EnumDefinition {
821 #[must_use]
823 pub fn new(name: impl Into<String>) -> Self {
824 Self {
825 name: name.into(),
826 values: Vec::new(),
827 description: None,
828 }
829 }
830
831 #[must_use]
833 pub fn with_value(mut self, value: EnumValueDefinition) -> Self {
834 self.values.push(value);
835 self
836 }
837
838 #[must_use]
840 pub fn with_values(mut self, values: Vec<EnumValueDefinition>) -> Self {
841 self.values = values;
842 self
843 }
844
845 #[must_use]
847 pub fn with_description(mut self, description: impl Into<String>) -> Self {
848 self.description = Some(description.into());
849 self
850 }
851
852 #[must_use]
854 pub fn has_value(&self, name: &str) -> bool {
855 self.values.iter().any(|v| v.name == name)
856 }
857
858 #[must_use]
860 pub fn find_value(&self, name: &str) -> Option<&EnumValueDefinition> {
861 self.values.iter().find(|v| v.name == name)
862 }
863}
864
865#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
876pub struct EnumValueDefinition {
877 pub name: String,
879
880 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub description: Option<String>,
883
884 #[serde(default, skip_serializing_if = "Option::is_none")]
886 pub deprecation: Option<super::field_type::DeprecationInfo>,
887}
888
889impl EnumValueDefinition {
890 #[must_use]
892 pub fn new(name: impl Into<String>) -> Self {
893 Self {
894 name: name.into(),
895 description: None,
896 deprecation: None,
897 }
898 }
899
900 #[must_use]
902 pub fn with_description(mut self, description: impl Into<String>) -> Self {
903 self.description = Some(description.into());
904 self
905 }
906
907 #[must_use]
909 pub fn deprecated(mut self, reason: Option<String>) -> Self {
910 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
911 self
912 }
913
914 #[must_use]
916 pub fn is_deprecated(&self) -> bool {
917 self.deprecation.is_some()
918 }
919}
920
921#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
947pub struct InputObjectDefinition {
948 pub name: String,
950
951 #[serde(default)]
953 pub fields: Vec<InputFieldDefinition>,
954
955 #[serde(default, skip_serializing_if = "Option::is_none")]
957 pub description: Option<String>,
958
959 #[serde(default, skip_serializing_if = "Option::is_none")]
962 pub metadata: Option<serde_json::Value>,
963}
964
965impl InputObjectDefinition {
966 #[must_use]
968 pub fn new(name: impl Into<String>) -> Self {
969 Self {
970 name: name.into(),
971 fields: Vec::new(),
972 description: None,
973 metadata: None,
974 }
975 }
976
977 #[must_use]
979 pub fn with_field(mut self, field: InputFieldDefinition) -> Self {
980 self.fields.push(field);
981 self
982 }
983
984 #[must_use]
986 pub fn with_fields(mut self, fields: Vec<InputFieldDefinition>) -> Self {
987 self.fields = fields;
988 self
989 }
990
991 #[must_use]
993 pub fn with_description(mut self, description: impl Into<String>) -> Self {
994 self.description = Some(description.into());
995 self
996 }
997
998 #[must_use]
1000 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1001 self.metadata = Some(metadata);
1002 self
1003 }
1004
1005 #[must_use]
1007 pub fn find_field(&self, name: &str) -> Option<&InputFieldDefinition> {
1008 self.fields.iter().find(|f| f.name == name)
1009 }
1010}
1011
1012#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1024pub struct InputFieldDefinition {
1025 pub name: String,
1027
1028 pub field_type: String,
1030
1031 #[serde(default, skip_serializing_if = "Option::is_none")]
1033 pub description: Option<String>,
1034
1035 #[serde(default, skip_serializing_if = "Option::is_none")]
1037 pub default_value: Option<String>,
1038
1039 #[serde(default, skip_serializing_if = "Option::is_none")]
1041 pub deprecation: Option<super::field_type::DeprecationInfo>,
1042
1043 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1045 pub validation_rules: Vec<ValidationRule>,
1046}
1047
1048impl InputFieldDefinition {
1049 #[must_use]
1051 pub fn new(name: impl Into<String>, field_type: impl Into<String>) -> Self {
1052 Self {
1053 name: name.into(),
1054 field_type: field_type.into(),
1055 description: None,
1056 default_value: None,
1057 deprecation: None,
1058 validation_rules: Vec::new(),
1059 }
1060 }
1061
1062 #[must_use]
1064 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1065 self.description = Some(description.into());
1066 self
1067 }
1068
1069 #[must_use]
1071 pub fn with_default_value(mut self, value: impl Into<String>) -> Self {
1072 self.default_value = Some(value.into());
1073 self
1074 }
1075
1076 #[must_use]
1078 pub fn deprecated(mut self, reason: Option<String>) -> Self {
1079 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1080 self
1081 }
1082
1083 #[must_use]
1085 pub fn is_deprecated(&self) -> bool {
1086 self.deprecation.is_some()
1087 }
1088
1089 #[must_use]
1091 pub fn is_required(&self) -> bool {
1092 self.field_type.ends_with('!') && self.default_value.is_none()
1093 }
1094
1095 #[must_use]
1097 pub fn with_validation_rule(mut self, rule: ValidationRule) -> Self {
1098 self.validation_rules.push(rule);
1099 self
1100 }
1101
1102 #[must_use]
1104 pub fn with_validation_rules(mut self, rules: Vec<ValidationRule>) -> Self {
1105 self.validation_rules.extend(rules);
1106 self
1107 }
1108
1109 #[must_use]
1111 pub fn has_validation_rules(&self) -> bool {
1112 !self.validation_rules.is_empty()
1113 }
1114}
1115
1116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1140pub struct InterfaceDefinition {
1141 pub name: String,
1143
1144 #[serde(default)]
1146 pub fields: Vec<FieldDefinition>,
1147
1148 #[serde(default, skip_serializing_if = "Option::is_none")]
1150 pub description: Option<String>,
1151}
1152
1153impl InterfaceDefinition {
1154 #[must_use]
1156 pub fn new(name: impl Into<String>) -> Self {
1157 Self {
1158 name: name.into(),
1159 fields: Vec::new(),
1160 description: None,
1161 }
1162 }
1163
1164 #[must_use]
1166 pub fn with_field(mut self, field: FieldDefinition) -> Self {
1167 self.fields.push(field);
1168 self
1169 }
1170
1171 #[must_use]
1173 pub fn with_fields(mut self, fields: Vec<FieldDefinition>) -> Self {
1174 self.fields = fields;
1175 self
1176 }
1177
1178 #[must_use]
1180 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1181 self.description = Some(description.into());
1182 self
1183 }
1184
1185 #[must_use]
1187 pub fn find_field(&self, name: &str) -> Option<&FieldDefinition> {
1188 self.fields.iter().find(|f| f.name == name)
1189 }
1190}
1191
1192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1214pub struct UnionDefinition {
1215 pub name: String,
1217
1218 pub member_types: Vec<String>,
1221
1222 #[serde(default, skip_serializing_if = "Option::is_none")]
1224 pub description: Option<String>,
1225}
1226
1227impl UnionDefinition {
1228 #[must_use]
1230 pub fn new(name: impl Into<String>) -> Self {
1231 Self {
1232 name: name.into(),
1233 member_types: Vec::new(),
1234 description: None,
1235 }
1236 }
1237
1238 #[must_use]
1240 pub fn with_member(mut self, type_name: impl Into<String>) -> Self {
1241 self.member_types.push(type_name.into());
1242 self
1243 }
1244
1245 #[must_use]
1247 pub fn with_members(mut self, members: Vec<String>) -> Self {
1248 self.member_types = members;
1249 self
1250 }
1251
1252 #[must_use]
1254 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1255 self.description = Some(description.into());
1256 self
1257 }
1258
1259 #[must_use]
1261 pub fn contains_type(&self, type_name: &str) -> bool {
1262 self.member_types.iter().any(|t| t == type_name)
1263 }
1264}
1265
1266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1290pub struct QueryDefinition {
1291 pub name: String,
1293
1294 pub return_type: String,
1296
1297 #[serde(default)]
1299 pub returns_list: bool,
1300
1301 #[serde(default)]
1303 pub nullable: bool,
1304
1305 #[serde(default)]
1307 pub arguments: Vec<ArgumentDefinition>,
1308
1309 #[serde(default, skip_serializing_if = "Option::is_none")]
1311 pub sql_source: Option<String>,
1312
1313 #[serde(default, skip_serializing_if = "Option::is_none")]
1315 pub description: Option<String>,
1316
1317 #[serde(default)]
1319 pub auto_params: AutoParams,
1320
1321 #[serde(default, skip_serializing_if = "Option::is_none")]
1324 pub deprecation: Option<super::field_type::DeprecationInfo>,
1325
1326 #[serde(default = "default_jsonb_column")]
1329 pub jsonb_column: String,
1330}
1331
1332impl QueryDefinition {
1333 #[must_use]
1335 pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1336 Self {
1337 name: name.into(),
1338 return_type: return_type.into(),
1339 returns_list: false,
1340 nullable: false,
1341 arguments: Vec::new(),
1342 sql_source: None,
1343 description: None,
1344 auto_params: AutoParams::default(),
1345 deprecation: None,
1346 jsonb_column: "data".to_string(),
1347 }
1348 }
1349
1350 #[must_use]
1352 pub fn returning_list(mut self) -> Self {
1353 self.returns_list = true;
1354 self
1355 }
1356
1357 #[must_use]
1359 pub fn with_sql_source(mut self, source: impl Into<String>) -> Self {
1360 self.sql_source = Some(source.into());
1361 self
1362 }
1363
1364 #[must_use]
1376 pub fn deprecated(mut self, reason: Option<String>) -> Self {
1377 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1378 self
1379 }
1380
1381 #[must_use]
1383 pub fn is_deprecated(&self) -> bool {
1384 self.deprecation.is_some()
1385 }
1386
1387 #[must_use]
1389 pub fn deprecation_reason(&self) -> Option<&str> {
1390 self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1391 }
1392}
1393
1394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1414pub struct MutationDefinition {
1415 pub name: String,
1417
1418 pub return_type: String,
1420
1421 #[serde(default)]
1423 pub arguments: Vec<ArgumentDefinition>,
1424
1425 #[serde(default, skip_serializing_if = "Option::is_none")]
1427 pub description: Option<String>,
1428
1429 #[serde(default)]
1431 pub operation: MutationOperation,
1432
1433 #[serde(default, skip_serializing_if = "Option::is_none")]
1436 pub deprecation: Option<super::field_type::DeprecationInfo>,
1437}
1438
1439impl MutationDefinition {
1440 #[must_use]
1442 pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1443 Self {
1444 name: name.into(),
1445 return_type: return_type.into(),
1446 arguments: Vec::new(),
1447 description: None,
1448 operation: MutationOperation::default(),
1449 deprecation: None,
1450 }
1451 }
1452
1453 #[must_use]
1465 pub fn deprecated(mut self, reason: Option<String>) -> Self {
1466 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1467 self
1468 }
1469
1470 #[must_use]
1472 pub fn is_deprecated(&self) -> bool {
1473 self.deprecation.is_some()
1474 }
1475
1476 #[must_use]
1478 pub fn deprecation_reason(&self) -> Option<&str> {
1479 self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1480 }
1481}
1482
1483#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1487#[serde(rename_all = "PascalCase")]
1488pub enum MutationOperation {
1489 Insert {
1491 table: String,
1493 },
1494
1495 Update {
1497 table: String,
1499 },
1500
1501 Delete {
1503 table: String,
1505 },
1506
1507 Function {
1509 name: String,
1511 },
1512
1513 #[default]
1515 Custom,
1516}
1517
1518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1522pub struct SubscriptionDefinition {
1523 pub name: String,
1525
1526 pub return_type: String,
1528
1529 #[serde(default)]
1531 pub arguments: Vec<ArgumentDefinition>,
1532
1533 #[serde(default, skip_serializing_if = "Option::is_none")]
1535 pub description: Option<String>,
1536
1537 #[serde(default, skip_serializing_if = "Option::is_none")]
1539 pub topic: Option<String>,
1540
1541 #[serde(default, skip_serializing_if = "Option::is_none")]
1545 pub filter: Option<SubscriptionFilter>,
1546
1547 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1550 pub fields: Vec<String>,
1551
1552 #[serde(default, skip_serializing_if = "Option::is_none")]
1555 pub deprecation: Option<super::field_type::DeprecationInfo>,
1556}
1557
1558#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1560pub struct SubscriptionFilter {
1561 #[serde(default)]
1564 pub argument_paths: std::collections::HashMap<String, String>,
1565
1566 #[serde(default)]
1569 pub static_filters: Vec<StaticFilterCondition>,
1570}
1571
1572#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1574pub struct StaticFilterCondition {
1575 pub path: String,
1577 pub operator: FilterOperator,
1579 pub value: serde_json::Value,
1581}
1582
1583#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1585#[serde(rename_all = "snake_case")]
1586pub enum FilterOperator {
1587 Eq,
1589 Ne,
1591 Gt,
1593 Gte,
1595 Lt,
1597 Lte,
1599 Contains,
1601 StartsWith,
1603 EndsWith,
1605}
1606
1607impl SubscriptionDefinition {
1608 #[must_use]
1610 pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1611 Self {
1612 name: name.into(),
1613 return_type: return_type.into(),
1614 arguments: Vec::new(),
1615 description: None,
1616 topic: None,
1617 filter: None,
1618 fields: Vec::new(),
1619 deprecation: None,
1620 }
1621 }
1622
1623 #[must_use]
1635 pub fn with_topic(mut self, topic: impl Into<String>) -> Self {
1636 self.topic = Some(topic.into());
1637 self
1638 }
1639
1640 #[must_use]
1642 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1643 self.description = Some(description.into());
1644 self
1645 }
1646
1647 #[must_use]
1649 pub fn with_argument(mut self, arg: ArgumentDefinition) -> Self {
1650 self.arguments.push(arg);
1651 self
1652 }
1653
1654 #[must_use]
1656 pub fn with_filter(mut self, filter: SubscriptionFilter) -> Self {
1657 self.filter = Some(filter);
1658 self
1659 }
1660
1661 #[must_use]
1663 pub fn with_fields(mut self, fields: Vec<String>) -> Self {
1664 self.fields = fields;
1665 self
1666 }
1667
1668 #[must_use]
1670 pub fn with_field(mut self, field: impl Into<String>) -> Self {
1671 self.fields.push(field.into());
1672 self
1673 }
1674
1675 #[must_use]
1687 pub fn deprecated(mut self, reason: Option<String>) -> Self {
1688 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1689 self
1690 }
1691
1692 #[must_use]
1694 pub fn is_deprecated(&self) -> bool {
1695 self.deprecation.is_some()
1696 }
1697
1698 #[must_use]
1700 pub fn deprecation_reason(&self) -> Option<&str> {
1701 self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1702 }
1703}
1704
1705#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1707pub struct ArgumentDefinition {
1708 pub name: String,
1710
1711 pub arg_type: FieldType,
1713
1714 #[serde(default)]
1716 pub nullable: bool,
1717
1718 #[serde(default, skip_serializing_if = "Option::is_none")]
1720 pub default_value: Option<serde_json::Value>,
1721
1722 #[serde(default, skip_serializing_if = "Option::is_none")]
1724 pub description: Option<String>,
1725
1726 #[serde(default, skip_serializing_if = "Option::is_none")]
1731 pub deprecation: Option<super::field_type::DeprecationInfo>,
1732}
1733
1734impl ArgumentDefinition {
1735 #[must_use]
1737 pub fn new(name: impl Into<String>, arg_type: FieldType) -> Self {
1738 Self {
1739 name: name.into(),
1740 arg_type,
1741 nullable: false,
1742 default_value: None,
1743 description: None,
1744 deprecation: None,
1745 }
1746 }
1747
1748 #[must_use]
1750 pub fn optional(name: impl Into<String>, arg_type: FieldType) -> Self {
1751 Self {
1752 name: name.into(),
1753 arg_type,
1754 nullable: true,
1755 default_value: None,
1756 description: None,
1757 deprecation: None,
1758 }
1759 }
1760
1761 #[must_use]
1773 pub fn deprecated(mut self, reason: Option<String>) -> Self {
1774 self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1775 self
1776 }
1777
1778 #[must_use]
1780 pub fn is_deprecated(&self) -> bool {
1781 self.deprecation.is_some()
1782 }
1783
1784 #[must_use]
1786 pub fn deprecation_reason(&self) -> Option<&str> {
1787 self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1788 }
1789}
1790
1791#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1795#[allow(clippy::struct_excessive_bools)] pub struct AutoParams {
1797 #[serde(default)]
1799 pub has_where: bool,
1800
1801 #[serde(default)]
1803 pub has_order_by: bool,
1804
1805 #[serde(default)]
1807 pub has_limit: bool,
1808
1809 #[serde(default)]
1811 pub has_offset: bool,
1812}
1813
1814impl AutoParams {
1815 #[must_use]
1817 pub fn all() -> Self {
1818 Self {
1819 has_where: true,
1820 has_order_by: true,
1821 has_limit: true,
1822 has_offset: true,
1823 }
1824 }
1825
1826 #[must_use]
1828 pub fn none() -> Self {
1829 Self::default()
1830 }
1831}
1832
1833#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1860pub struct DirectiveDefinition {
1861 pub name: String,
1863
1864 #[serde(default, skip_serializing_if = "Option::is_none")]
1866 pub description: Option<String>,
1867
1868 pub locations: Vec<DirectiveLocationKind>,
1870
1871 #[serde(default)]
1873 pub arguments: Vec<ArgumentDefinition>,
1874
1875 #[serde(default)]
1877 pub is_repeatable: bool,
1878}
1879
1880impl DirectiveDefinition {
1881 #[must_use]
1883 pub fn new(name: impl Into<String>, locations: Vec<DirectiveLocationKind>) -> Self {
1884 Self {
1885 name: name.into(),
1886 description: None,
1887 locations,
1888 arguments: Vec::new(),
1889 is_repeatable: false,
1890 }
1891 }
1892
1893 #[must_use]
1895 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1896 self.description = Some(description.into());
1897 self
1898 }
1899
1900 #[must_use]
1902 pub fn with_argument(mut self, arg: ArgumentDefinition) -> Self {
1903 self.arguments.push(arg);
1904 self
1905 }
1906
1907 #[must_use]
1909 pub fn with_arguments(mut self, args: Vec<ArgumentDefinition>) -> Self {
1910 self.arguments = args;
1911 self
1912 }
1913
1914 #[must_use]
1916 pub fn repeatable(mut self) -> Self {
1917 self.is_repeatable = true;
1918 self
1919 }
1920
1921 #[must_use]
1923 pub fn valid_at(&self, location: DirectiveLocationKind) -> bool {
1924 self.locations.contains(&location)
1925 }
1926
1927 #[must_use]
1929 pub fn find_argument(&self, name: &str) -> Option<&ArgumentDefinition> {
1930 self.arguments.iter().find(|a| a.name == name)
1931 }
1932}
1933
1934#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1944#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1945pub enum DirectiveLocationKind {
1946 Query,
1949 Mutation,
1951 Subscription,
1953 Field,
1955 FragmentDefinition,
1957 FragmentSpread,
1959 InlineFragment,
1961 VariableDefinition,
1963
1964 Schema,
1967 Scalar,
1969 Object,
1971 FieldDefinition,
1973 ArgumentDefinition,
1975 Interface,
1977 Union,
1979 Enum,
1981 EnumValue,
1983 InputObject,
1985 InputFieldDefinition,
1987}
1988
1989impl DirectiveLocationKind {
1990 #[must_use]
1992 pub fn is_executable(&self) -> bool {
1993 matches!(
1994 self,
1995 Self::Query
1996 | Self::Mutation
1997 | Self::Subscription
1998 | Self::Field
1999 | Self::FragmentDefinition
2000 | Self::FragmentSpread
2001 | Self::InlineFragment
2002 | Self::VariableDefinition
2003 )
2004 }
2005
2006 #[must_use]
2008 pub fn is_type_system(&self) -> bool {
2009 !self.is_executable()
2010 }
2011}
2012
2013#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2047pub struct ObserverDefinition {
2048 pub name: String,
2050
2051 pub entity: String,
2053
2054 pub event: String,
2056
2057 #[serde(skip_serializing_if = "Option::is_none")]
2060 pub condition: Option<String>,
2061
2062 pub actions: Vec<serde_json::Value>,
2065
2066 pub retry: RetryConfig,
2068}
2069
2070impl ObserverDefinition {
2071 #[must_use]
2073 pub fn new(
2074 name: impl Into<String>,
2075 entity: impl Into<String>,
2076 event: impl Into<String>,
2077 ) -> Self {
2078 Self {
2079 name: name.into(),
2080 entity: entity.into(),
2081 event: event.into(),
2082 condition: None,
2083 actions: Vec::new(),
2084 retry: RetryConfig::default(),
2085 }
2086 }
2087
2088 #[must_use]
2090 pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
2091 self.condition = Some(condition.into());
2092 self
2093 }
2094
2095 #[must_use]
2097 pub fn with_action(mut self, action: serde_json::Value) -> Self {
2098 self.actions.push(action);
2099 self
2100 }
2101
2102 #[must_use]
2104 pub fn with_actions(mut self, actions: Vec<serde_json::Value>) -> Self {
2105 self.actions = actions;
2106 self
2107 }
2108
2109 #[must_use]
2111 pub fn with_retry(mut self, retry: RetryConfig) -> Self {
2112 self.retry = retry;
2113 self
2114 }
2115
2116 #[must_use]
2118 pub fn has_condition(&self) -> bool {
2119 self.condition.is_some()
2120 }
2121
2122 #[must_use]
2124 pub fn action_count(&self) -> usize {
2125 self.actions.len()
2126 }
2127}
2128
2129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2147pub struct RetryConfig {
2148 pub max_attempts: u32,
2150
2151 pub backoff_strategy: String,
2153
2154 pub initial_delay_ms: u32,
2156
2157 pub max_delay_ms: u32,
2159}
2160
2161impl Default for RetryConfig {
2162 fn default() -> Self {
2163 Self {
2164 max_attempts: 3,
2165 backoff_strategy: "exponential".to_string(),
2166 initial_delay_ms: 1000,
2167 max_delay_ms: 60000,
2168 }
2169 }
2170}
2171
2172impl RetryConfig {
2173 #[must_use]
2175 pub fn new(
2176 max_attempts: u32,
2177 backoff_strategy: impl Into<String>,
2178 initial_delay_ms: u32,
2179 max_delay_ms: u32,
2180 ) -> Self {
2181 Self {
2182 max_attempts,
2183 backoff_strategy: backoff_strategy.into(),
2184 initial_delay_ms,
2185 max_delay_ms,
2186 }
2187 }
2188
2189 #[must_use]
2191 pub fn exponential(max_attempts: u32, initial_delay_ms: u32, max_delay_ms: u32) -> Self {
2192 Self::new(max_attempts, "exponential", initial_delay_ms, max_delay_ms)
2193 }
2194
2195 #[must_use]
2197 pub fn linear(max_attempts: u32, initial_delay_ms: u32, max_delay_ms: u32) -> Self {
2198 Self::new(max_attempts, "linear", initial_delay_ms, max_delay_ms)
2199 }
2200
2201 #[must_use]
2203 pub fn fixed(max_attempts: u32, delay_ms: u32) -> Self {
2204 Self::new(max_attempts, "fixed", delay_ms, delay_ms)
2205 }
2206
2207 #[must_use]
2209 pub fn is_exponential(&self) -> bool {
2210 self.backoff_strategy == "exponential"
2211 }
2212
2213 #[must_use]
2215 pub fn is_linear(&self) -> bool {
2216 self.backoff_strategy == "linear"
2217 }
2218
2219 #[must_use]
2221 pub fn is_fixed(&self) -> bool {
2222 self.backoff_strategy == "fixed"
2223 }
2224}
2225
2226#[cfg(test)]
2231mod tests {
2232 use super::*;
2233
2234 #[test]
2235 fn test_compiled_schema_with_observers() {
2236 let json = r#"{
2237 "types": [],
2238 "enums": [],
2239 "input_types": [],
2240 "interfaces": [],
2241 "unions": [],
2242 "queries": [],
2243 "mutations": [],
2244 "subscriptions": [],
2245 "observers": [
2246 {
2247 "name": "onHighValueOrder",
2248 "entity": "Order",
2249 "event": "INSERT",
2250 "condition": "total > 1000",
2251 "actions": [
2252 {
2253 "type": "webhook",
2254 "url": "https://api.example.com/webhook"
2255 }
2256 ],
2257 "retry": {
2258 "max_attempts": 3,
2259 "backoff_strategy": "exponential",
2260 "initial_delay_ms": 1000,
2261 "max_delay_ms": 60000
2262 }
2263 }
2264 ]
2265 }"#;
2266
2267 let schema = CompiledSchema::from_json(json).unwrap();
2268
2269 assert!(schema.has_observers());
2270 assert_eq!(schema.observer_count(), 1);
2271
2272 let observer = schema.find_observer("onHighValueOrder").unwrap();
2273 assert_eq!(observer.entity, "Order");
2274 assert_eq!(observer.event, "INSERT");
2275 assert_eq!(observer.condition, Some("total > 1000".to_string()));
2276 assert_eq!(observer.actions.len(), 1);
2277 assert_eq!(observer.retry.max_attempts, 3);
2278 assert!(observer.retry.is_exponential());
2279 }
2280
2281 #[test]
2282 fn test_compiled_schema_backward_compatible() {
2283 let json = r#"{
2285 "types": [],
2286 "enums": [],
2287 "input_types": [],
2288 "interfaces": [],
2289 "unions": [],
2290 "queries": [],
2291 "mutations": [],
2292 "subscriptions": []
2293 }"#;
2294
2295 let schema = CompiledSchema::from_json(json).unwrap();
2296 assert!(!schema.has_observers());
2297 assert_eq!(schema.observer_count(), 0);
2298 }
2299
2300 #[test]
2301 fn test_find_observers_for_entity() {
2302 let schema = CompiledSchema {
2303 observers: vec![
2304 ObserverDefinition::new("onOrderInsert", "Order", "INSERT"),
2305 ObserverDefinition::new("onOrderUpdate", "Order", "UPDATE"),
2306 ObserverDefinition::new("onUserInsert", "User", "INSERT"),
2307 ],
2308 ..Default::default()
2309 };
2310
2311 let order_observers = schema.find_observers_for_entity("Order");
2312 assert_eq!(order_observers.len(), 2);
2313
2314 let user_observers = schema.find_observers_for_entity("User");
2315 assert_eq!(user_observers.len(), 1);
2316 }
2317
2318 #[test]
2319 fn test_find_observers_for_event() {
2320 let schema = CompiledSchema {
2321 observers: vec![
2322 ObserverDefinition::new("onOrderInsert", "Order", "INSERT"),
2323 ObserverDefinition::new("onOrderUpdate", "Order", "UPDATE"),
2324 ObserverDefinition::new("onUserInsert", "User", "INSERT"),
2325 ],
2326 ..Default::default()
2327 };
2328
2329 let insert_observers = schema.find_observers_for_event("INSERT");
2330 assert_eq!(insert_observers.len(), 2);
2331
2332 let update_observers = schema.find_observers_for_event("UPDATE");
2333 assert_eq!(update_observers.len(), 1);
2334 }
2335
2336 #[test]
2337 fn test_observer_definition_builder() {
2338 let observer = ObserverDefinition::new("test", "Order", "INSERT")
2339 .with_condition("total > 1000")
2340 .with_action(serde_json::json!({"type": "webhook", "url": "https://example.com"}))
2341 .with_retry(RetryConfig::exponential(5, 1000, 60000));
2342
2343 assert_eq!(observer.name, "test");
2344 assert_eq!(observer.entity, "Order");
2345 assert_eq!(observer.event, "INSERT");
2346 assert!(observer.has_condition());
2347 assert_eq!(observer.action_count(), 1);
2348 assert_eq!(observer.retry.max_attempts, 5);
2349 }
2350
2351 #[test]
2352 fn test_retry_config_types() {
2353 let exponential = RetryConfig::exponential(3, 1000, 60000);
2354 assert!(exponential.is_exponential());
2355 assert!(!exponential.is_linear());
2356 assert!(!exponential.is_fixed());
2357
2358 let linear = RetryConfig::linear(3, 1000, 60000);
2359 assert!(!linear.is_exponential());
2360 assert!(linear.is_linear());
2361 assert!(!linear.is_fixed());
2362
2363 let fixed = RetryConfig::fixed(3, 5000);
2364 assert!(!fixed.is_exponential());
2365 assert!(!fixed.is_linear());
2366 assert!(fixed.is_fixed());
2367 assert_eq!(fixed.initial_delay_ms, 5000);
2368 assert_eq!(fixed.max_delay_ms, 5000);
2369 }
2370}