1use crate::algebra::{Algebra, Term, TriplePattern, Variable};
5use oxirs_core::model::NamedNode;
6use scirs2_core::metrics::MetricsRegistry;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10use thiserror::Error;
11
12#[allow(dead_code)]
13#[derive(Error, Debug)]
15pub enum TranslationError {
16 #[error("Unsupported GraphQL operation: {0}")]
17 UnsupportedOperation(String),
18
19 #[error("Invalid field mapping: {0}")]
20 InvalidFieldMapping(String),
21
22 #[error("Unknown GraphQL type: {0}")]
23 UnknownType(String),
24
25 #[error("Fragment not found: {0}")]
26 FragmentNotFound(String),
27
28 #[error("Invalid argument: {0}")]
29 InvalidArgument(String),
30
31 #[error("Schema mapping error: {0}")]
32 SchemaMappingError(String),
33
34 #[error("Variable resolution error: {0}")]
35 VariableResolutionError(String),
36
37 #[error("Directive processing error: {0}")]
38 DirectiveError(String),
39
40 #[error("Nested query too deep: {0}")]
41 QueryTooDeep(usize),
42
43 #[error("Translation failed: {0}")]
44 TranslationFailed(String),
45}
46
47pub type TranslationResult<T> = std::result::Result<T, TranslationError>;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52pub enum GraphQLOperationType {
53 Query,
54 Mutation,
55 Subscription,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct GraphQLField {
61 pub name: String,
62 pub alias: Option<String>,
63 pub arguments: HashMap<String, GraphQLValue>,
64 pub directives: Vec<GraphQLDirective>,
65 pub selection_set: Vec<GraphQLSelection>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum GraphQLSelection {
71 Field(GraphQLField),
72 FragmentSpread {
73 name: String,
74 directives: Vec<GraphQLDirective>,
75 },
76 InlineFragment {
77 type_condition: Option<String>,
78 directives: Vec<GraphQLDirective>,
79 selection_set: Vec<GraphQLSelection>,
80 },
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct GraphQLDirective {
86 pub name: String,
87 pub arguments: HashMap<String, GraphQLValue>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub enum GraphQLValue {
93 Null,
94 Int(i64),
95 Float(f64),
96 String(String),
97 Boolean(bool),
98 Enum(String),
99 List(Vec<GraphQLValue>),
100 Object(HashMap<String, GraphQLValue>),
101 Variable(String),
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct GraphQLOperation {
107 pub operation_type: GraphQLOperationType,
108 pub name: Option<String>,
109 pub variables: HashMap<String, GraphQLVariableDefinition>,
110 pub directives: Vec<GraphQLDirective>,
111 pub selection_set: Vec<GraphQLSelection>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct GraphQLVariableDefinition {
117 pub var_type: String,
118 pub default_value: Option<GraphQLValue>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct GraphQLFragment {
124 pub name: String,
125 pub type_condition: String,
126 pub directives: Vec<GraphQLDirective>,
127 pub selection_set: Vec<GraphQLSelection>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct GraphQLDocument {
133 pub operations: Vec<GraphQLOperation>,
134 pub fragments: HashMap<String, GraphQLFragment>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct SchemaMapping {
140 pub type_to_class: HashMap<String, String>,
142
143 pub field_to_property: HashMap<String, String>,
145
146 pub prefixes: HashMap<String, String>,
148
149 pub query_root_type: String,
151
152 pub mutation_root_type: String,
154
155 pub rdf_type_property: String,
157
158 pub auto_case_conversion: bool,
160}
161
162impl Default for SchemaMapping {
163 fn default() -> Self {
164 let mut prefixes = HashMap::new();
165 prefixes.insert(
166 "rdf".to_string(),
167 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
168 );
169 prefixes.insert(
170 "rdfs".to_string(),
171 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
172 );
173 prefixes.insert(
174 "xsd".to_string(),
175 "http://www.w3.org/2001/XMLSchema#".to_string(),
176 );
177
178 Self {
179 type_to_class: HashMap::new(),
180 field_to_property: HashMap::new(),
181 prefixes,
182 query_root_type: "Query".to_string(),
183 mutation_root_type: "Mutation".to_string(),
184 rdf_type_property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
185 auto_case_conversion: true,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct TranslatorConfig {
193 pub schema_mapping: SchemaMapping,
195
196 pub max_query_depth: usize,
198
199 pub enable_optimization: bool,
201
202 pub generate_comments: bool,
204
205 pub process_directives: bool,
207}
208
209impl Default for TranslatorConfig {
210 fn default() -> Self {
211 Self {
212 schema_mapping: SchemaMapping::default(),
213 max_query_depth: 10,
214 enable_optimization: true,
215 generate_comments: true,
216 process_directives: true,
217 }
218 }
219}
220
221#[derive(Debug, Clone)]
223struct TranslationContext {
224 depth: usize,
226
227 var_counter: usize,
229
230 fragments: HashMap<String, GraphQLFragment>,
232
233 variables: HashMap<String, GraphQLValue>,
235
236 current_subject: Option<Variable>,
238
239 sparql_variables: HashSet<Variable>,
241
242 #[allow(dead_code)]
244 patterns: Vec<Algebra>,
245}
246
247impl TranslationContext {
248 fn new(
249 fragments: HashMap<String, GraphQLFragment>,
250 variables: HashMap<String, GraphQLValue>,
251 ) -> Self {
252 Self {
253 depth: 0,
254 var_counter: 0,
255 fragments,
256 variables,
257 current_subject: None,
258 sparql_variables: HashSet::new(),
259 patterns: Vec::new(),
260 }
261 }
262
263 fn next_var(&mut self, prefix: &str) -> Variable {
264 self.var_counter += 1;
265 let var = Variable::new(format!("{}{}", prefix, self.var_counter))
266 .expect("Variable name should be valid");
267 self.sparql_variables.insert(var.clone());
268 var
269 }
270
271 fn enter_scope(&mut self) -> TranslationResult<()> {
272 self.depth += 1;
273 Ok(())
274 }
275
276 fn exit_scope(&mut self) {
277 self.depth = self.depth.saturating_sub(1);
278 }
279}
280
281#[derive(Debug, Clone, Default, Serialize, Deserialize)]
283pub struct TranslationStats {
284 pub queries_translated: usize,
285 pub mutations_translated: usize,
286 pub fields_translated: usize,
287 pub fragments_expanded: usize,
288 pub directives_processed: usize,
289 pub average_query_depth: f64,
290 pub translation_errors: usize,
291}
292
293pub struct GraphQLTranslator {
295 config: TranslatorConfig,
296 stats: TranslationStats,
297 #[allow(dead_code)]
299 metrics: Arc<MetricsRegistry>,
300}
301
302impl GraphQLTranslator {
303 pub fn new() -> Self {
305 Self {
306 config: TranslatorConfig::default(),
307 stats: TranslationStats::default(),
308 metrics: Arc::new(MetricsRegistry::new()),
309 }
310 }
311
312 pub fn with_config(config: TranslatorConfig) -> Self {
314 Self {
315 config,
316 stats: TranslationStats::default(),
317 metrics: Arc::new(MetricsRegistry::new()),
318 }
319 }
320
321 pub fn translate_document(
323 &mut self,
324 document: GraphQLDocument,
325 ) -> TranslationResult<Vec<Algebra>> {
326 let mut algebras = Vec::new();
327
328 for operation in document.operations {
329 let algebra = self.translate_operation(&operation, &document.fragments)?;
330 algebras.push(algebra);
331 }
332
333 Ok(algebras)
334 }
335
336 pub fn translate_operation(
338 &mut self,
339 operation: &GraphQLOperation,
340 fragments: &HashMap<String, GraphQLFragment>,
341 ) -> TranslationResult<Algebra> {
342 let mut context = TranslationContext::new(fragments.clone(), HashMap::new());
343
344 match operation.operation_type {
345 GraphQLOperationType::Query => {
346 self.stats.queries_translated += 1;
347 self.translate_query(operation, &mut context)
348 }
349 GraphQLOperationType::Mutation => {
350 self.stats.mutations_translated += 1;
351 self.translate_mutation(operation, &mut context)
352 }
353 GraphQLOperationType::Subscription => Err(TranslationError::UnsupportedOperation(
354 "Subscriptions are not yet supported".to_string(),
355 )),
356 }
357 }
358
359 fn translate_query(
361 &mut self,
362 operation: &GraphQLOperation,
363 context: &mut TranslationContext,
364 ) -> TranslationResult<Algebra> {
365 context.enter_scope()?;
366
367 let patterns = self.translate_selection_set(&operation.selection_set, context)?;
369
370 let bgp = if patterns.is_empty() {
372 Algebra::Bgp(vec![])
373 } else if patterns.len() == 1 {
374 patterns
375 .into_iter()
376 .next()
377 .expect("collection validated to be non-empty")
378 } else {
379 patterns
381 .into_iter()
382 .reduce(|acc, pattern| Algebra::Join {
383 left: Box::new(acc),
384 right: Box::new(pattern),
385 })
386 .expect("collection validated to be non-empty")
387 };
388
389 let variables: Vec<Variable> = context.sparql_variables.iter().cloned().collect();
391 let result = Algebra::Project {
392 pattern: Box::new(bgp),
393 variables,
394 };
395
396 context.exit_scope();
397 Ok(result)
398 }
399
400 fn translate_mutation(
402 &mut self,
403 _operation: &GraphQLOperation,
404 _context: &mut TranslationContext,
405 ) -> TranslationResult<Algebra> {
406 Err(TranslationError::UnsupportedOperation(
409 "Mutations require SPARQL UPDATE support".to_string(),
410 ))
411 }
412
413 fn translate_selection_set(
415 &mut self,
416 selections: &[GraphQLSelection],
417 context: &mut TranslationContext,
418 ) -> TranslationResult<Vec<Algebra>> {
419 if context.depth > self.config.max_query_depth {
420 return Err(TranslationError::QueryTooDeep(context.depth));
421 }
422
423 let mut patterns = Vec::new();
424
425 for selection in selections {
426 match selection {
427 GraphQLSelection::Field(field) => {
428 let pattern = self.translate_field(field, context)?;
429 patterns.push(pattern);
430 self.stats.fields_translated += 1;
431 }
432 GraphQLSelection::FragmentSpread { name, directives } => {
433 if self.config.process_directives
434 && self.should_skip_by_directives(directives, context)?
435 {
436 continue;
437 }
438
439 let fragment = context
440 .fragments
441 .get(name)
442 .ok_or_else(|| TranslationError::FragmentNotFound(name.clone()))?;
443
444 let selection_set = fragment.selection_set.clone();
446 let fragment_patterns =
447 self.translate_selection_set(&selection_set, context)?;
448 patterns.extend(fragment_patterns);
449 self.stats.fragments_expanded += 1;
450 }
451 GraphQLSelection::InlineFragment {
452 type_condition,
453 directives,
454 selection_set,
455 } => {
456 if self.config.process_directives
457 && self.should_skip_by_directives(directives, context)?
458 {
459 continue;
460 }
461
462 if let Some(type_name) = type_condition {
464 if let Some(class_uri) =
465 self.config.schema_mapping.type_to_class.get(type_name)
466 {
467 let subject = context
469 .current_subject
470 .clone()
471 .unwrap_or_else(|| context.next_var("subject"));
472
473 let type_property =
474 NamedNode::new(&self.config.schema_mapping.rdf_type_property)
475 .expect("Invalid RDF type property URI");
476 let class_node = NamedNode::new(class_uri).expect("Invalid class URI");
477
478 let type_pattern = Algebra::Bgp(vec![TriplePattern::new(
479 Term::Variable(subject.clone()),
480 Term::Iri(type_property),
481 Term::Iri(class_node),
482 )]);
483 patterns.push(type_pattern);
484 }
485 }
486
487 let inline_patterns = self.translate_selection_set(selection_set, context)?;
488 patterns.extend(inline_patterns);
489 }
490 }
491 }
492
493 Ok(patterns)
494 }
495
496 fn translate_field(
498 &mut self,
499 field: &GraphQLField,
500 context: &mut TranslationContext,
501 ) -> TranslationResult<Algebra> {
502 if self.config.process_directives
504 && self.should_skip_by_directives(&field.directives, context)?
505 {
506 return Ok(Algebra::Bgp(vec![]));
507 }
508
509 let subject = context
511 .current_subject
512 .clone()
513 .unwrap_or_else(|| context.next_var("subject"));
514
515 let property_uri = self.map_field_to_property(&field.name)?;
517
518 let object_var_name = field.alias.as_ref().unwrap_or(&field.name);
520 let object = context.next_var(object_var_name);
521
522 let property_node = NamedNode::new(&property_uri).expect("Invalid property URI");
524
525 let triple_pattern = Algebra::Bgp(vec![TriplePattern::new(
526 Term::Variable(subject.clone()),
527 Term::Iri(property_node),
528 Term::Variable(object.clone()),
529 )]);
530
531 if !field.selection_set.is_empty() {
533 let old_subject = context.current_subject.replace(object.clone());
534 context.enter_scope()?;
535
536 let nested_patterns = self.translate_selection_set(&field.selection_set, context)?;
537
538 context.exit_scope();
539 context.current_subject = old_subject;
540
541 if nested_patterns.is_empty() {
543 return Ok(triple_pattern);
544 }
545
546 let nested_algebra = nested_patterns
547 .into_iter()
548 .reduce(|acc, pattern| Algebra::Join {
549 left: Box::new(acc),
550 right: Box::new(pattern),
551 })
552 .expect("nested_patterns validated to be non-empty");
553
554 return Ok(Algebra::Join {
555 left: Box::new(triple_pattern),
556 right: Box::new(nested_algebra),
557 });
558 }
559
560 if !field.arguments.is_empty() {
562 let filters = self.translate_arguments(&field.arguments, &object, context)?;
563 if !filters.is_empty() {
564 let combined_filter = filters
566 .into_iter()
567 .reduce(|acc, filter| {
568 Algebra::Join {
571 left: Box::new(acc),
572 right: Box::new(filter),
573 }
574 })
575 .expect("filters validated to be non-empty");
576
577 return Ok(Algebra::Join {
578 left: Box::new(triple_pattern),
579 right: Box::new(combined_filter),
580 });
581 }
582 }
583
584 Ok(triple_pattern)
585 }
586
587 fn translate_arguments(
589 &mut self,
590 arguments: &HashMap<String, GraphQLValue>,
591 _object: &Variable,
592 _context: &mut TranslationContext,
593 ) -> TranslationResult<Vec<Algebra>> {
594 let filters = Vec::new();
595
596 for (arg_name, arg_value) in arguments {
597 let _filter_expr = format!("FILTER for {} = {:?}", arg_name, arg_value);
600 }
602
603 Ok(filters)
604 }
605
606 fn should_skip_by_directives(
608 &mut self,
609 directives: &[GraphQLDirective],
610 context: &mut TranslationContext,
611 ) -> TranslationResult<bool> {
612 for directive in directives {
613 self.stats.directives_processed += 1;
614
615 match directive.name.as_str() {
616 "skip" => {
617 if let Some(GraphQLValue::Boolean(should_skip)) = directive.arguments.get("if")
618 {
619 if *should_skip {
620 return Ok(true);
621 }
622 } else if let Some(GraphQLValue::Variable(var_name)) =
623 directive.arguments.get("if")
624 {
625 if let Some(GraphQLValue::Boolean(should_skip)) =
626 context.variables.get(var_name)
627 {
628 if *should_skip {
629 return Ok(true);
630 }
631 }
632 }
633 }
634 "include" => {
635 if let Some(GraphQLValue::Boolean(should_include)) =
636 directive.arguments.get("if")
637 {
638 if !*should_include {
639 return Ok(true);
640 }
641 } else if let Some(GraphQLValue::Variable(var_name)) =
642 directive.arguments.get("if")
643 {
644 if let Some(GraphQLValue::Boolean(should_include)) =
645 context.variables.get(var_name)
646 {
647 if !*should_include {
648 return Ok(true);
649 }
650 }
651 }
652 }
653 _ => {
654 }
656 }
657 }
658
659 Ok(false)
660 }
661
662 fn map_field_to_property(&self, field_name: &str) -> TranslationResult<String> {
664 if let Some(property_uri) = self.config.schema_mapping.field_to_property.get(field_name) {
666 return Ok(property_uri.clone());
667 }
668
669 if self.config.schema_mapping.auto_case_conversion {
671 let snake_case = self.camel_to_snake_case(field_name);
672 if let Some(property_uri) = self
673 .config
674 .schema_mapping
675 .field_to_property
676 .get(&snake_case)
677 {
678 return Ok(property_uri.clone());
679 }
680 }
681
682 Ok(format!("http://example.org/property/{}", field_name))
684 }
685
686 fn camel_to_snake_case(&self, s: &str) -> String {
689 let mut result = String::new();
690 let chars: Vec<char> = s.chars().collect();
691
692 for (i, &c) in chars.iter().enumerate() {
693 if c.is_uppercase() {
694 let should_add_underscore = i > 0
699 && (chars[i - 1].is_lowercase()
700 || (i + 1 < chars.len() && chars[i + 1].is_lowercase()));
701
702 if should_add_underscore && !result.is_empty() && !result.ends_with('_') {
703 result.push('_');
704 }
705 result.push(c.to_ascii_lowercase());
706 } else {
707 result.push(c);
708 }
709 }
710
711 result
712 }
713
714 pub fn get_stats(&self) -> &TranslationStats {
716 &self.stats
717 }
718
719 pub fn reset_stats(&mut self) {
721 self.stats = TranslationStats::default();
722 }
723
724 pub fn update_schema_mapping(&mut self, mapping: SchemaMapping) {
726 self.config.schema_mapping = mapping;
727 }
728
729 pub fn add_type_mapping(&mut self, graphql_type: String, rdf_class: String) {
731 self.config
732 .schema_mapping
733 .type_to_class
734 .insert(graphql_type, rdf_class);
735 }
736
737 pub fn add_field_mapping(&mut self, graphql_field: String, rdf_property: String) {
739 self.config
740 .schema_mapping
741 .field_to_property
742 .insert(graphql_field, rdf_property);
743 }
744
745 pub fn add_prefix(&mut self, prefix: String, namespace: String) {
747 self.config
748 .schema_mapping
749 .prefixes
750 .insert(prefix, namespace);
751 }
752}
753
754impl Default for GraphQLTranslator {
755 fn default() -> Self {
756 Self::new()
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_translator_creation() {
766 let translator = GraphQLTranslator::new();
767 assert_eq!(translator.stats.queries_translated, 0);
768 }
769
770 #[test]
771 fn test_camel_to_snake_case() {
772 let translator = GraphQLTranslator::new();
773 assert_eq!(translator.camel_to_snake_case("firstName"), "first_name");
774 assert_eq!(translator.camel_to_snake_case("userName"), "user_name");
775 assert_eq!(translator.camel_to_snake_case("id"), "id");
776 assert_eq!(
777 translator.camel_to_snake_case("HTTPResponse"),
778 "http_response"
779 );
780 assert_eq!(translator.camel_to_snake_case("XMLParser"), "xml_parser");
781 assert_eq!(translator.camel_to_snake_case("IOError"), "io_error");
782 }
783
784 #[test]
785 fn test_schema_mapping_default() {
786 let mapping = SchemaMapping::default();
787 assert_eq!(mapping.query_root_type, "Query");
788 assert_eq!(mapping.mutation_root_type, "Mutation");
789 assert!(mapping.prefixes.contains_key("rdf"));
790 assert!(mapping.prefixes.contains_key("rdfs"));
791 assert!(mapping.prefixes.contains_key("xsd"));
792 }
793
794 #[test]
795 fn test_add_type_mapping() {
796 let mut translator = GraphQLTranslator::new();
797 translator.add_type_mapping("User".to_string(), "http://example.org/User".to_string());
798 assert_eq!(
799 translator.config.schema_mapping.type_to_class.get("User"),
800 Some(&"http://example.org/User".to_string())
801 );
802 }
803
804 #[test]
805 fn test_add_field_mapping() {
806 let mut translator = GraphQLTranslator::new();
807 translator.add_field_mapping(
808 "name".to_string(),
809 "http://xmlns.com/foaf/0.1/name".to_string(),
810 );
811 assert_eq!(
812 translator
813 .config
814 .schema_mapping
815 .field_to_property
816 .get("name"),
817 Some(&"http://xmlns.com/foaf/0.1/name".to_string())
818 );
819 }
820
821 #[test]
822 fn test_map_field_to_property_explicit() {
823 let mut translator = GraphQLTranslator::new();
824 translator.add_field_mapping(
825 "email".to_string(),
826 "http://xmlns.com/foaf/0.1/mbox".to_string(),
827 );
828
829 let property = translator.map_field_to_property("email").unwrap();
830 assert_eq!(property, "http://xmlns.com/foaf/0.1/mbox");
831 }
832
833 #[test]
834 fn test_map_field_to_property_default() {
835 let translator = GraphQLTranslator::new();
836 let property = translator.map_field_to_property("unknownField").unwrap();
837 assert_eq!(property, "http://example.org/property/unknownField");
838 }
839
840 #[test]
841 fn test_translation_context_variable_generation() {
842 let mut context = TranslationContext::new(HashMap::new(), HashMap::new());
843 let var1 = context.next_var("test");
844 let var2 = context.next_var("test");
845
846 assert_ne!(var1.name(), var2.name());
847 assert_eq!(context.sparql_variables.len(), 2);
848 }
849
850 #[test]
851 fn test_simple_query_translation() {
852 let mut translator = GraphQLTranslator::new();
853
854 let operation = GraphQLOperation {
855 operation_type: GraphQLOperationType::Query,
856 name: Some("GetUsers".to_string()),
857 variables: HashMap::new(),
858 directives: vec![],
859 selection_set: vec![GraphQLSelection::Field(GraphQLField {
860 name: "users".to_string(),
861 alias: None,
862 arguments: HashMap::new(),
863 directives: vec![],
864 selection_set: vec![
865 GraphQLSelection::Field(GraphQLField {
866 name: "id".to_string(),
867 alias: None,
868 arguments: HashMap::new(),
869 directives: vec![],
870 selection_set: vec![],
871 }),
872 GraphQLSelection::Field(GraphQLField {
873 name: "name".to_string(),
874 alias: None,
875 arguments: HashMap::new(),
876 directives: vec![],
877 selection_set: vec![],
878 }),
879 ],
880 })],
881 };
882
883 let fragments = HashMap::new();
884 let result = translator.translate_operation(&operation, &fragments);
885 assert!(result.is_ok());
886 assert_eq!(translator.stats.queries_translated, 1);
887 assert_eq!(translator.stats.fields_translated, 3); }
889
890 #[test]
891 fn test_skip_directive() {
892 let mut translator = GraphQLTranslator::new();
893 let mut context = TranslationContext::new(HashMap::new(), HashMap::new());
894
895 let mut skip_args = HashMap::new();
896 skip_args.insert("if".to_string(), GraphQLValue::Boolean(true));
897
898 let directives = vec![GraphQLDirective {
899 name: "skip".to_string(),
900 arguments: skip_args,
901 }];
902
903 let should_skip = translator
904 .should_skip_by_directives(&directives, &mut context)
905 .unwrap();
906 assert!(should_skip);
907 }
908
909 #[test]
910 fn test_include_directive() {
911 let mut translator = GraphQLTranslator::new();
912 let mut context = TranslationContext::new(HashMap::new(), HashMap::new());
913
914 let mut include_args = HashMap::new();
915 include_args.insert("if".to_string(), GraphQLValue::Boolean(false));
916
917 let directives = vec![GraphQLDirective {
918 name: "include".to_string(),
919 arguments: include_args,
920 }];
921
922 let should_skip = translator
923 .should_skip_by_directives(&directives, &mut context)
924 .unwrap();
925 assert!(should_skip);
926 }
927
928 #[test]
929 fn test_unsupported_subscription() {
930 let mut translator = GraphQLTranslator::new();
931
932 let operation = GraphQLOperation {
933 operation_type: GraphQLOperationType::Subscription,
934 name: Some("OnUserCreated".to_string()),
935 variables: HashMap::new(),
936 directives: vec![],
937 selection_set: vec![],
938 };
939
940 let fragments = HashMap::new();
941 let result = translator.translate_operation(&operation, &fragments);
942 assert!(result.is_err());
943 assert!(matches!(
944 result.unwrap_err(),
945 TranslationError::UnsupportedOperation(_)
946 ));
947 }
948
949 #[test]
950 fn test_stats_tracking() {
951 let mut translator = GraphQLTranslator::new();
952
953 translator.stats.queries_translated = 5;
955 translator.stats.mutations_translated = 3;
956 translator.stats.fields_translated = 42;
957
958 let stats = translator.get_stats();
959 assert_eq!(stats.queries_translated, 5);
960 assert_eq!(stats.mutations_translated, 3);
961 assert_eq!(stats.fields_translated, 42);
962
963 translator.reset_stats();
964 assert_eq!(translator.stats.queries_translated, 0);
965 }
966}