1use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::Instant;
8
9use super::sql_generator::FilterOperator;
10use super::{
11 ErrorCode, ExecutionContext, Filter, GraphQLConfig, GraphQLMetrics, GraphQLSchema,
12 OperationType, QueryPlan, QueryValidator, Selection, SqlGenerator,
13};
14
15#[derive(Debug, Clone)]
17pub struct GraphQLRequest {
18 pub query: String,
20 pub operation_name: Option<String>,
22 pub variables: Option<HashMap<String, serde_json::Value>>,
24 pub extensions: Option<HashMap<String, serde_json::Value>>,
26}
27
28impl GraphQLRequest {
29 pub fn new(query: impl Into<String>) -> Self {
31 Self {
32 query: query.into(),
33 operation_name: None,
34 variables: None,
35 extensions: None,
36 }
37 }
38
39 pub fn with_operation(mut self, name: impl Into<String>) -> Self {
41 self.operation_name = Some(name.into());
42 self
43 }
44
45 pub fn with_variables(mut self, vars: HashMap<String, serde_json::Value>) -> Self {
47 self.variables = Some(vars);
48 self
49 }
50
51 pub fn var(mut self, name: impl Into<String>, value: impl Into<serde_json::Value>) -> Self {
53 let vars = self.variables.get_or_insert_with(HashMap::new);
54 vars.insert(name.into(), value.into());
55 self
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct GraphQLResponse {
62 pub data: Option<serde_json::Value>,
64 pub errors: Option<Vec<GraphQLError>>,
66 pub extensions: Option<HashMap<String, serde_json::Value>>,
68}
69
70impl GraphQLResponse {
71 pub fn success(data: serde_json::Value) -> Self {
73 Self {
74 data: Some(data),
75 errors: None,
76 extensions: None,
77 }
78 }
79
80 pub fn error(error: GraphQLError) -> Self {
82 Self {
83 data: None,
84 errors: Some(vec![error]),
85 extensions: None,
86 }
87 }
88
89 pub fn errors(errors: Vec<GraphQLError>) -> Self {
91 Self {
92 data: None,
93 errors: Some(errors),
94 extensions: None,
95 }
96 }
97
98 pub fn with_extension(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
100 let extensions = self.extensions.get_or_insert_with(HashMap::new);
101 extensions.insert(key.into(), value);
102 self
103 }
104
105 pub fn has_errors(&self) -> bool {
107 self.errors.as_ref().map(|e| !e.is_empty()).unwrap_or(false)
108 }
109
110 pub fn to_json(&self) -> serde_json::Value {
112 let mut result = serde_json::Map::new();
113
114 if let Some(ref data) = self.data {
115 result.insert("data".to_string(), data.clone());
116 }
117
118 if let Some(ref errors) = self.errors {
119 let error_array: Vec<_> = errors.iter().map(|e| e.to_json()).collect();
120 result.insert("errors".to_string(), serde_json::Value::Array(error_array));
121 }
122
123 if let Some(ref extensions) = self.extensions {
124 result.insert(
125 "extensions".to_string(),
126 serde_json::Value::Object(
127 extensions
128 .iter()
129 .map(|(k, v)| (k.clone(), v.clone()))
130 .collect(),
131 ),
132 );
133 }
134
135 serde_json::Value::Object(result)
136 }
137}
138
139#[derive(Debug, Clone)]
141pub struct GraphQLError {
142 pub message: String,
144 pub locations: Option<Vec<ErrorLocation>>,
146 pub path: Option<Vec<PathSegment>>,
148 pub extensions: Option<HashMap<String, serde_json::Value>>,
150 pub code: ErrorCode,
152}
153
154impl GraphQLError {
155 pub fn new(message: impl Into<String>, code: ErrorCode) -> Self {
157 Self {
158 message: message.into(),
159 locations: None,
160 path: None,
161 extensions: None,
162 code,
163 }
164 }
165
166 pub fn parse_error(message: impl Into<String>) -> Self {
168 Self::new(message, ErrorCode::ParseError)
169 }
170
171 pub fn validation_error(message: impl Into<String>) -> Self {
173 Self::new(message, ErrorCode::ValidationError)
174 }
175
176 pub fn unauthorized(message: impl Into<String>) -> Self {
178 Self::new(message, ErrorCode::Unauthorized)
179 }
180
181 pub fn not_found(message: impl Into<String>) -> Self {
183 Self::new(message, ErrorCode::NotFound)
184 }
185
186 pub fn internal(message: impl Into<String>) -> Self {
188 Self::new(message, ErrorCode::InternalError)
189 }
190
191 pub fn with_location(mut self, line: u32, column: u32) -> Self {
193 self.locations = Some(vec![ErrorLocation { line, column }]);
194 self
195 }
196
197 pub fn with_path(mut self, path: Vec<PathSegment>) -> Self {
199 self.path = Some(path);
200 self
201 }
202
203 pub fn with_extension(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
205 let extensions = self.extensions.get_or_insert_with(HashMap::new);
206 extensions.insert(key.into(), value);
207 self
208 }
209
210 pub fn to_json(&self) -> serde_json::Value {
212 let mut result = serde_json::Map::new();
213 result.insert(
214 "message".to_string(),
215 serde_json::Value::String(self.message.clone()),
216 );
217
218 if let Some(ref locations) = self.locations {
219 let loc_array: Vec<_> = locations
220 .iter()
221 .map(|l| {
222 let mut loc = serde_json::Map::new();
223 loc.insert("line".to_string(), serde_json::Value::Number(l.line.into()));
224 loc.insert(
225 "column".to_string(),
226 serde_json::Value::Number(l.column.into()),
227 );
228 serde_json::Value::Object(loc)
229 })
230 .collect();
231 result.insert("locations".to_string(), serde_json::Value::Array(loc_array));
232 }
233
234 if let Some(ref path) = self.path {
235 let path_array: Vec<_> = path.iter().map(|s| s.to_json()).collect();
236 result.insert("path".to_string(), serde_json::Value::Array(path_array));
237 }
238
239 let mut extensions = self.extensions.clone().unwrap_or_default();
240 extensions.insert(
241 "code".to_string(),
242 serde_json::Value::String(format!("{:?}", self.code)),
243 );
244 result.insert(
245 "extensions".to_string(),
246 serde_json::Value::Object(extensions.into_iter().collect()),
247 );
248
249 serde_json::Value::Object(result)
250 }
251}
252
253impl std::fmt::Display for GraphQLError {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 write!(f, "{}", self.message)
256 }
257}
258
259impl std::error::Error for GraphQLError {}
260
261#[derive(Debug, Clone, Copy)]
263pub struct ErrorLocation {
264 pub line: u32,
266 pub column: u32,
268}
269
270#[derive(Debug, Clone)]
272pub enum PathSegment {
273 Field(String),
275 Index(usize),
277}
278
279impl PathSegment {
280 pub fn to_json(&self) -> serde_json::Value {
282 match self {
283 PathSegment::Field(name) => serde_json::Value::String(name.clone()),
284 PathSegment::Index(idx) => serde_json::Value::Number((*idx).into()),
285 }
286 }
287}
288
289#[derive(Debug, Clone)]
291pub struct ParsedDocument {
292 pub operation_type: OperationType,
294 pub operation_name: Option<String>,
296 pub selections: Vec<ParsedSelection>,
298 pub variable_definitions: Vec<VariableDefinition>,
300 pub fragments: HashMap<String, FragmentDefinition>,
302}
303
304#[derive(Debug, Clone)]
306pub struct ParsedSelection {
307 pub name: String,
309 pub alias: Option<String>,
311 pub arguments: HashMap<String, serde_json::Value>,
313 pub selections: Vec<ParsedSelection>,
315 pub directives: Vec<Directive>,
317}
318
319impl ParsedSelection {
320 pub fn response_key(&self) -> &str {
322 self.alias.as_deref().unwrap_or(&self.name)
323 }
324}
325
326#[derive(Debug, Clone)]
328pub struct VariableDefinition {
329 pub name: String,
331 pub var_type: String,
333 pub default_value: Option<serde_json::Value>,
335}
336
337#[derive(Debug, Clone)]
339pub struct FragmentDefinition {
340 pub name: String,
342 pub type_condition: String,
344 pub selections: Vec<ParsedSelection>,
346}
347
348#[derive(Debug, Clone)]
350pub struct Directive {
351 pub name: String,
353 pub arguments: HashMap<String, serde_json::Value>,
355}
356
357#[derive(Debug)]
361pub struct GraphQLEngine {
362 config: Arc<GraphQLConfig>,
364 schema: Arc<GraphQLSchema>,
366 sql_generator: Arc<SqlGenerator>,
368 validator: QueryValidator,
370 metrics: Arc<GraphQLMetrics>,
372 backend: Option<crate::backend::BackendConfig>,
375}
376
377impl GraphQLEngine {
378 pub fn new(config: GraphQLConfig, schema: GraphQLSchema) -> Self {
380 let config = Arc::new(config);
381 let schema = Arc::new(schema);
382
383 Self {
384 sql_generator: Arc::new(SqlGenerator::new(schema.clone())),
385 validator: QueryValidator::new(config.clone()),
386 metrics: Arc::new(GraphQLMetrics::new()),
387 config,
388 schema,
389 backend: None,
390 }
391 }
392
393 pub fn with_backend(mut self, backend: crate::backend::BackendConfig) -> Self {
395 self.backend = Some(backend);
396 self
397 }
398
399 pub async fn execute(&self, request: GraphQLRequest) -> GraphQLResponse {
401 self.execute_with_context(request, ExecutionContext::default())
402 .await
403 }
404
405 pub async fn execute_with_context(
407 &self,
408 request: GraphQLRequest,
409 context: ExecutionContext,
410 ) -> GraphQLResponse {
411 let start = Instant::now();
412
413 let document = match self.parse(&request.query) {
415 Ok(doc) => doc,
416 Err(e) => {
417 self.metrics.record_error(&e);
418 return GraphQLResponse::error(e);
419 }
420 };
421
422 if let Err(e) = self.validate(&document) {
424 self.metrics.record_error(&e);
425 return GraphQLResponse::error(e);
426 }
427
428 if let Err(e) = self.authorize(&document, &context) {
430 self.metrics.record_error(&e);
431 return GraphQLResponse::error(e);
432 }
433
434 let plan = match self.plan(&document, &request.variables) {
436 Ok(p) => p,
437 Err(e) => {
438 self.metrics.record_error(&e);
439 return GraphQLResponse::error(e);
440 }
441 };
442
443 let sql_queries = match self.sql_generator.generate(&plan) {
445 Ok(queries) => queries,
446 Err(e) => {
447 let error = GraphQLError::internal(format!("SQL generation failed: {}", e));
448 self.metrics.record_error(&error);
449 return GraphQLResponse::error(error);
450 }
451 };
452
453 let results = match self.execute_queries(&sql_queries, &context).await {
455 Ok(r) => r,
456 Err(e) => {
457 self.metrics.record_error(&e);
458 return GraphQLResponse::error(e);
459 }
460 };
461
462 let data = match self.shape_response(&document, &results) {
464 Ok(d) => d,
465 Err(e) => {
466 self.metrics.record_error(&e);
467 return GraphQLResponse::error(e);
468 }
469 };
470
471 let elapsed = start.elapsed();
472 self.metrics.record_query(elapsed, document.operation_type);
473
474 GraphQLResponse::success(data).with_extension(
475 "timing",
476 serde_json::json!({
477 "durationMs": elapsed.as_millis()
478 }),
479 )
480 }
481
482 #[allow(clippy::result_large_err)]
484 fn parse(&self, query: &str) -> Result<ParsedDocument, GraphQLError> {
485 let query = query.trim();
488
489 let (operation_type, remaining) = if query.starts_with("mutation") {
491 (
492 OperationType::Mutation,
493 query.strip_prefix("mutation").unwrap_or(query),
494 )
495 } else if query.starts_with("subscription") {
496 (
497 OperationType::Subscription,
498 query.strip_prefix("subscription").unwrap_or(query),
499 )
500 } else if query.starts_with("query") {
501 (
502 OperationType::Query,
503 query.strip_prefix("query").unwrap_or(query),
504 )
505 } else if query.starts_with("{") {
506 (OperationType::Query, query)
507 } else {
508 return Err(GraphQLError::parse_error("Invalid query format"));
509 };
510
511 let remaining = remaining.trim();
513 let (operation_name, remaining) = if remaining.starts_with('{') {
514 (None, remaining)
515 } else if let Some(brace_pos) = remaining.find('{') {
516 let name_part = remaining[..brace_pos].trim();
517 let name = name_part.split('(').next().unwrap_or(name_part).trim();
519 if name.is_empty() {
520 (None, &remaining[brace_pos..])
521 } else {
522 (Some(name.to_string()), &remaining[brace_pos..])
523 }
524 } else {
525 return Err(GraphQLError::parse_error("Missing selection set"));
526 };
527
528 let selections = self.parse_selection_set(remaining)?;
530
531 Ok(ParsedDocument {
532 operation_type,
533 operation_name,
534 selections,
535 variable_definitions: Vec::new(),
536 fragments: HashMap::new(),
537 })
538 }
539
540 #[allow(clippy::result_large_err)]
542 fn parse_selection_set(&self, input: &str) -> Result<Vec<ParsedSelection>, GraphQLError> {
543 let input = input.trim();
544
545 if !input.starts_with('{') {
546 return Err(GraphQLError::parse_error("Expected '{'"));
547 }
548
549 let mut depth = 0;
551 let mut end_pos = 0;
552 for (i, c) in input.chars().enumerate() {
553 match c {
554 '{' => depth += 1,
555 '}' => {
556 depth -= 1;
557 if depth == 0 {
558 end_pos = i;
559 break;
560 }
561 }
562 _ => {}
563 }
564 }
565
566 if depth != 0 {
567 return Err(GraphQLError::parse_error("Unmatched braces"));
568 }
569
570 let inner = &input[1..end_pos].trim();
571 self.parse_fields(inner)
572 }
573
574 #[allow(clippy::result_large_err)]
576 fn parse_fields(&self, input: &str) -> Result<Vec<ParsedSelection>, GraphQLError> {
577 let mut selections = Vec::new();
578 let mut current_pos = 0;
579 let chars: Vec<char> = input.chars().collect();
580
581 while current_pos < chars.len() {
582 while current_pos < chars.len() && chars[current_pos].is_whitespace() {
584 current_pos += 1;
585 }
586
587 if current_pos >= chars.len() {
588 break;
589 }
590
591 let field_start = current_pos;
593 while current_pos < chars.len()
594 && (chars[current_pos].is_alphanumeric() || chars[current_pos] == '_')
595 {
596 current_pos += 1;
597 }
598
599 if current_pos == field_start {
600 current_pos += 1;
601 continue;
602 }
603
604 let mut field_name: String = chars[field_start..current_pos].iter().collect();
605 let mut alias = None;
606
607 while current_pos < chars.len() && chars[current_pos].is_whitespace() {
609 current_pos += 1;
610 }
611
612 if current_pos < chars.len() && chars[current_pos] == ':' {
613 alias = Some(field_name);
614 current_pos += 1;
615
616 while current_pos < chars.len() && chars[current_pos].is_whitespace() {
618 current_pos += 1;
619 }
620
621 let name_start = current_pos;
623 while current_pos < chars.len()
624 && (chars[current_pos].is_alphanumeric() || chars[current_pos] == '_')
625 {
626 current_pos += 1;
627 }
628 field_name = chars[name_start..current_pos].iter().collect();
629 }
630
631 while current_pos < chars.len() && chars[current_pos].is_whitespace() {
633 current_pos += 1;
634 }
635
636 let mut arguments = HashMap::new();
638 if current_pos < chars.len() && chars[current_pos] == '(' {
639 let args_start = current_pos;
640 let mut depth = 1;
641 current_pos += 1;
642
643 while current_pos < chars.len() && depth > 0 {
644 match chars[current_pos] {
645 '(' => depth += 1,
646 ')' => depth -= 1,
647 _ => {}
648 }
649 current_pos += 1;
650 }
651
652 let args_str: String = chars[args_start + 1..current_pos - 1].iter().collect();
653 arguments = self.parse_arguments(&args_str)?;
654 }
655
656 while current_pos < chars.len() && chars[current_pos].is_whitespace() {
658 current_pos += 1;
659 }
660
661 let nested_selections = if current_pos < chars.len() && chars[current_pos] == '{' {
663 let nested_start = current_pos;
664 let mut depth = 1;
665 current_pos += 1;
666
667 while current_pos < chars.len() && depth > 0 {
668 match chars[current_pos] {
669 '{' => depth += 1,
670 '}' => depth -= 1,
671 _ => {}
672 }
673 current_pos += 1;
674 }
675
676 let nested_str: String = chars[nested_start..current_pos].iter().collect();
677 self.parse_selection_set(&nested_str)?
678 } else {
679 Vec::new()
680 };
681
682 selections.push(ParsedSelection {
683 name: field_name,
684 alias,
685 arguments,
686 selections: nested_selections,
687 directives: Vec::new(),
688 });
689 }
690
691 Ok(selections)
692 }
693
694 #[allow(clippy::result_large_err)]
696 fn parse_arguments(
697 &self,
698 input: &str,
699 ) -> Result<HashMap<String, serde_json::Value>, GraphQLError> {
700 let mut arguments = HashMap::new();
701
702 for part in input.split(',') {
703 let part = part.trim();
704 if part.is_empty() {
705 continue;
706 }
707
708 if let Some(colon_pos) = part.find(':') {
709 let key = part[..colon_pos].trim().to_string();
710 let value_str = part[colon_pos + 1..].trim();
711
712 let value = self.parse_value(value_str)?;
713 arguments.insert(key, value);
714 }
715 }
716
717 Ok(arguments)
718 }
719
720 #[allow(clippy::result_large_err)]
722 fn parse_value(&self, input: &str) -> Result<serde_json::Value, GraphQLError> {
723 let input = input.trim();
724
725 if input == "null" {
726 Ok(serde_json::Value::Null)
727 } else if input == "true" {
728 Ok(serde_json::Value::Bool(true))
729 } else if input == "false" {
730 Ok(serde_json::Value::Bool(false))
731 } else if input.starts_with('"') && input.ends_with('"') {
732 Ok(serde_json::Value::String(
733 input[1..input.len() - 1].to_string(),
734 ))
735 } else if let Ok(n) = input.parse::<i64>() {
736 Ok(serde_json::Value::Number(n.into()))
737 } else if let Ok(n) = input.parse::<f64>() {
738 Ok(serde_json::json!(n))
739 } else {
740 Ok(serde_json::Value::String(input.to_string()))
742 }
743 }
744
745 #[allow(clippy::result_large_err)]
747 fn validate(&self, document: &ParsedDocument) -> Result<(), GraphQLError> {
748 self.validator.validate(document, &self.schema)
749 }
750
751 #[allow(clippy::result_large_err)]
753 fn authorize(
754 &self,
755 _document: &ParsedDocument,
756 _context: &ExecutionContext,
757 ) -> Result<(), GraphQLError> {
758 Ok(())
761 }
762
763 #[allow(clippy::result_large_err)]
765 fn plan(
766 &self,
767 document: &ParsedDocument,
768 _variables: &Option<HashMap<String, serde_json::Value>>,
769 ) -> Result<QueryPlan, GraphQLError> {
770 let selections: Vec<_> = document
772 .selections
773 .iter()
774 .map(|s| self.selection_to_plan(s))
775 .collect();
776
777 Ok(QueryPlan::Multiple { plans: selections })
778 }
779
780 fn selection_to_plan(&self, selection: &ParsedSelection) -> QueryPlan {
782 let filters = self.extract_filters(&selection.arguments);
784
785 let limit = selection
787 .arguments
788 .get("limit")
789 .and_then(|v| v.as_u64())
790 .map(|v| v as u32);
791 let offset = selection
792 .arguments
793 .get("offset")
794 .and_then(|v| v.as_u64())
795 .map(|v| v as u32);
796
797 let sel = Selection {
799 table_name: super::to_snake_case(&selection.name),
800 fields: selection
801 .selections
802 .iter()
803 .filter(|s| s.selections.is_empty())
804 .map(|s| s.name.clone())
805 .collect(),
806 relationships: selection
807 .selections
808 .iter()
809 .filter(|s| !s.selections.is_empty())
810 .map(|s| (s.name.clone(), self.selection_to_plan(s)))
811 .collect(),
812 };
813
814 QueryPlan::Single {
815 selection: sel,
816 filters,
817 limit,
818 offset,
819 }
820 }
821
822 fn extract_filters(&self, arguments: &HashMap<String, serde_json::Value>) -> Vec<Filter> {
824 let mut filters = Vec::new();
825
826 if let Some(id) = arguments.get("id") {
828 filters.push(Filter {
829 field: "id".to_string(),
830 operator: FilterOperator::Eq,
831 value: id.clone(),
832 });
833 }
834
835 if let Some(where_obj) = arguments.get("where") {
837 if let Some(obj) = where_obj.as_object() {
838 for (field, condition) in obj {
839 if let Some(cond_obj) = condition.as_object() {
840 for (op, value) in cond_obj {
841 let operator = match op.as_str() {
842 "eq" => FilterOperator::Eq,
843 "ne" => FilterOperator::Ne,
844 "gt" => FilterOperator::Gt,
845 "gte" => FilterOperator::Gte,
846 "lt" => FilterOperator::Lt,
847 "lte" => FilterOperator::Lte,
848 "contains" => FilterOperator::Contains,
849 "startsWith" => FilterOperator::StartsWith,
850 "endsWith" => FilterOperator::EndsWith,
851 "in" => FilterOperator::In,
852 _ => continue,
853 };
854
855 filters.push(Filter {
856 field: field.clone(),
857 operator,
858 value: value.clone(),
859 });
860 }
861 }
862 }
863 }
864 }
865
866 filters
867 }
868
869 async fn execute_queries(
873 &self,
874 queries: &[super::SqlQuery],
875 _context: &ExecutionContext,
876 ) -> Result<Vec<Vec<serde_json::Value>>, GraphQLError> {
877 let Some(bcfg) = self.backend.clone() else {
878 return Ok(queries.iter().map(|_| Vec::new()).collect());
879 };
880 use crate::backend::BackendClient;
881
882 let mut out = Vec::with_capacity(queries.len());
883 for q in queries {
884 let mut client = BackendClient::connect(&bcfg)
885 .await
886 .map_err(|e| GraphQLError::internal(format!("backend connect: {}", e)))?;
887 let qr = client
888 .simple_query(&q.sql)
889 .await
890 .map_err(|e| GraphQLError::internal(format!("backend query: {}", e)))?;
891 let rows: Vec<serde_json::Value> = qr
892 .rows
893 .iter()
894 .map(|row| {
895 let mut obj = serde_json::Map::new();
896 for (i, c) in qr.columns.iter().enumerate() {
897 let v = row
898 .get(i)
899 .map(graphql_cell_to_json)
900 .unwrap_or(serde_json::Value::Null);
901 obj.insert(c.name.clone(), v);
902 }
903 serde_json::Value::Object(obj)
904 })
905 .collect();
906 out.push(rows);
907 }
908 Ok(out)
909 }
910
911 #[allow(clippy::result_large_err)]
915 fn shape_response(
916 &self,
917 document: &ParsedDocument,
918 results: &[Vec<serde_json::Value>],
919 ) -> Result<serde_json::Value, GraphQLError> {
920 let mut data = serde_json::Map::new();
921 for (i, selection) in document.selections.iter().enumerate() {
922 let key = selection.response_key().to_string();
923 let rows = results.get(i).cloned().unwrap_or_default();
924 data.insert(key, serde_json::Value::Array(rows));
925 }
926 Ok(serde_json::Value::Object(data))
927 }
928
929 pub fn schema(&self) -> &GraphQLSchema {
931 &self.schema
932 }
933
934 pub fn config(&self) -> &GraphQLConfig {
936 &self.config
937 }
938
939 pub fn metrics(&self) -> &GraphQLMetrics {
941 &self.metrics
942 }
943
944 pub fn generate_sdl(&self) -> String {
946 self.schema.to_sdl()
947 }
948}
949
950fn graphql_cell_to_json(v: &crate::backend::TextValue) -> serde_json::Value {
952 match v {
953 crate::backend::TextValue::Null => serde_json::Value::Null,
954 crate::backend::TextValue::Text(s) => serde_json::Value::String(s.clone()),
955 }
956}
957
958impl Clone for GraphQLEngine {
959 fn clone(&self) -> Self {
960 Self {
961 config: self.config.clone(),
962 schema: self.schema.clone(),
963 sql_generator: self.sql_generator.clone(),
964 validator: QueryValidator::new(self.config.clone()),
965 metrics: self.metrics.clone(),
966 backend: self.backend.clone(),
967 }
968 }
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974 use crate::graphql::introspector::GraphQLSchema;
975
976 fn create_test_engine() -> GraphQLEngine {
977 let config = GraphQLConfig::default();
978 let schema = GraphQLSchema::new();
979 GraphQLEngine::new(config, schema)
980 }
981
982 #[test]
983 fn test_parse_simple_query() {
984 let engine = create_test_engine();
985 let query = "query { users { id name } }";
986
987 let doc = engine.parse(query).unwrap();
988 assert_eq!(doc.operation_type, OperationType::Query);
989 assert_eq!(doc.selections.len(), 1);
990 assert_eq!(doc.selections[0].name, "users");
991 assert_eq!(doc.selections[0].selections.len(), 2);
992 }
993
994 #[tokio::test]
995 async fn offline_execute_shapes_field_key() {
996 use crate::graphql::introspector::{ColumnDefinition, SchemaIntrospector, TableDefinition};
997 let tabledef = TableDefinition {
998 name: "gqlitem".to_string(),
999 schema: "public".to_string(),
1000 columns: vec![ColumnDefinition {
1001 name: "id".to_string(),
1002 data_type: "text".to_string(),
1003 nullable: true,
1004 is_primary_key: true,
1005 has_default: false,
1006 }],
1007 foreign_keys: Vec::new(),
1008 };
1009 let schema = SchemaIntrospector::new().build_schema(&[tabledef]);
1010 let engine = GraphQLEngine::new(GraphQLConfig::default(), schema);
1014 let resp = engine
1015 .execute(GraphQLRequest::new("{ gqlitems { id } }"))
1016 .await;
1017 assert!(
1018 resp.errors.is_none(),
1019 "unexpected errors: {:?}",
1020 resp.errors
1021 );
1022 let data = resp.data.expect("data present");
1023 assert!(
1024 data.get("gqlitems").is_some(),
1025 "missing field key: {}",
1026 data
1027 );
1028 assert!(data["gqlitems"].is_array(), "field should be an array");
1029 }
1030
1031 #[test]
1032 fn test_parse_named_query() {
1033 let engine = create_test_engine();
1034 let query = "query GetUsers { users { id } }";
1035
1036 let doc = engine.parse(query).unwrap();
1037 assert_eq!(doc.operation_name, Some("GetUsers".to_string()));
1038 }
1039
1040 #[test]
1041 fn test_parse_mutation() {
1042 let engine = create_test_engine();
1043 let query = "mutation { createUser(name: \"test\") { id } }";
1044
1045 let doc = engine.parse(query).unwrap();
1046 assert_eq!(doc.operation_type, OperationType::Mutation);
1047 }
1048
1049 #[test]
1050 fn test_parse_with_arguments() {
1051 let engine = create_test_engine();
1052 let query = "{ user(id: \"123\") { name } }";
1053
1054 let doc = engine.parse(query).unwrap();
1055 let user_selection = &doc.selections[0];
1056 assert_eq!(user_selection.name, "user");
1057 assert!(user_selection.arguments.contains_key("id"));
1058 }
1059
1060 #[test]
1061 fn test_parse_with_alias() {
1062 let engine = create_test_engine();
1063 let query = "{ myUser: user(id: \"123\") { name } }";
1064
1065 let doc = engine.parse(query).unwrap();
1066 let selection = &doc.selections[0];
1067 assert_eq!(selection.alias, Some("myUser".to_string()));
1068 assert_eq!(selection.name, "user");
1069 assert_eq!(selection.response_key(), "myUser");
1070 }
1071
1072 #[test]
1073 fn test_graphql_request_builder() {
1074 let request = GraphQLRequest::new("{ users { id } }")
1075 .with_operation("GetUsers")
1076 .var("limit", 10);
1077
1078 assert_eq!(request.query, "{ users { id } }");
1079 assert_eq!(request.operation_name, Some("GetUsers".to_string()));
1080 assert!(request.variables.unwrap().contains_key("limit"));
1081 }
1082
1083 #[test]
1084 fn test_graphql_response_success() {
1085 let response = GraphQLResponse::success(serde_json::json!({"users": []}));
1086
1087 assert!(response.data.is_some());
1088 assert!(!response.has_errors());
1089 }
1090
1091 #[test]
1092 fn test_graphql_response_error() {
1093 let error = GraphQLError::parse_error("Syntax error");
1094 let response = GraphQLResponse::error(error);
1095
1096 assert!(response.data.is_none());
1097 assert!(response.has_errors());
1098 }
1099
1100 #[test]
1101 fn test_graphql_error_to_json() {
1102 let error = GraphQLError::validation_error("Field not found")
1103 .with_location(1, 10)
1104 .with_path(vec![PathSegment::Field("users".to_string())]);
1105
1106 let json = error.to_json();
1107 assert_eq!(json["message"], "Field not found");
1108 assert!(json["locations"].is_array());
1109 assert!(json["path"].is_array());
1110 }
1111
1112 #[tokio::test]
1113 async fn test_execute_simple_query() {
1114 let engine = create_test_engine();
1115 let request = GraphQLRequest::new("{ users { id name } }");
1116
1117 let response = engine.execute(request).await;
1118
1119 assert!(!response.has_errors());
1121 assert!(response.data.is_some());
1122 }
1123
1124 #[test]
1125 fn test_parse_nested_selections() {
1126 let engine = create_test_engine();
1127 let query = "{ users { id posts { title comments { content } } } }";
1128
1129 let doc = engine.parse(query).unwrap();
1130 let users = &doc.selections[0];
1131 assert_eq!(users.selections.len(), 2); let posts = &users.selections[1];
1134 assert_eq!(posts.name, "posts");
1135 assert_eq!(posts.selections.len(), 2); }
1137
1138 #[test]
1139 fn test_parse_value() {
1140 let engine = create_test_engine();
1141
1142 assert_eq!(engine.parse_value("null").unwrap(), serde_json::Value::Null);
1143 assert_eq!(
1144 engine.parse_value("true").unwrap(),
1145 serde_json::Value::Bool(true)
1146 );
1147 assert_eq!(
1148 engine.parse_value("false").unwrap(),
1149 serde_json::Value::Bool(false)
1150 );
1151 assert_eq!(
1152 engine.parse_value("\"hello\"").unwrap(),
1153 serde_json::Value::String("hello".to_string())
1154 );
1155 assert_eq!(engine.parse_value("42").unwrap(), serde_json::json!(42));
1156 }
1157}