1use async_graphql::{Response, ServerError};
38use serde_json::{json, Map, Value};
39use std::collections::HashMap;
40
41fn code_for(error: &ServerError) -> &'static str {
49 if let Some(extensions) = &error.extensions {
50 if let Some(Value::String(code)) = extensions.get("code").map(value_of) {
51 return match code.as_str() {
52 "validation-failed" => "validation-failed",
53 "bad-request" => "bad-request",
54 "access-denied" => "access-denied",
58 "invalid-jwt" => "invalid-jwt",
60 "jwt-missing-role-claims" => "jwt-missing-role-claims",
62 "not-found" => "not-found",
66 "permission-error" => "permission-error",
67 "constraint-violation" => "constraint-violation",
68 "data-exception" => "data-exception",
69 "not-supported" => "not-supported",
70 "parse-failed" => "parse-failed",
71 _ => "unexpected",
72 };
73 }
74 }
75
76 let message = error.message.to_ascii_lowercase();
77 if message.contains("invalid value for argument")
81 || message.contains("does not contain the value")
82 || message.contains("is not defined by operation")
83 || message.contains("not configured for mutations")
88 || message.contains("not configured for subscriptions")
89 {
90 "validation-failed"
91 } else if message.contains("permission") || message.contains("not allowed") {
92 "permission-error"
93 } else if message.contains("violates") || message.contains("constraint") {
94 "constraint-violation"
95 } else if message.contains("unknown argument")
96 || message.contains("cannot query field")
97 || message.contains("expected")
98 || message.contains("unknown field")
99 || message.starts_with("variable \"")
103 {
104 "validation-failed"
105 } else {
106 "postgres-error"
107 }
108}
109
110fn value_of(value: &async_graphql::Value) -> Value {
111 serde_json::to_value(value).unwrap_or(Value::Null)
112}
113
114fn path_for(error: &ServerError) -> String {
120 use async_graphql::PathSegment;
121
122 if let Some(Value::String(path)) = error
127 .extensions
128 .as_ref()
129 .and_then(|extensions| extensions.get("path"))
130 .map(value_of)
131 {
132 return path;
133 }
134
135 if error.path.is_empty() {
136 return "$".to_string();
137 }
138 let mut rendered = String::from("$");
148 for segment in &error.path {
149 if let PathSegment::Field(name) = segment {
150 rendered.push_str(".selectionSet.");
151 rendered.push_str(name);
152 }
153 }
154 rendered
155}
156
157pub fn envelope(response: Response) -> Value {
159 if !response.errors.is_empty() {
160 let errors: Vec<Value> = response
161 .errors
162 .iter()
163 .map(|error| {
164 json!({
165 "message": error.message,
166 "extensions": {
167 "path": path_for(error),
168 "code": code_for(error),
169 }
170 })
171 })
172 .collect();
173 return json!({ "errors": errors });
175 }
176
177 let mut body = Map::new();
178 body.insert("data".to_string(), value_of(&response.data));
179 Value::Object(body)
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum Endpoint {
189 Current,
191 Legacy,
193}
194
195pub fn status_for(body: &Value, endpoint: Endpoint) -> u16 {
201 if endpoint == Endpoint::Current {
202 return 200;
203 }
204 let refused = body
205 .get("errors")
206 .and_then(Value::as_array)
207 .is_some_and(|errors| {
208 errors.iter().any(|error| {
209 error.get("message").and_then(Value::as_str) == Some(crate::role::CHECK_FAILED)
210 })
211 });
212 match refused {
213 true => 400,
214 false => 200,
215 }
216}
217
218pub fn denied(message: &str) -> Value {
225 refused(message, "access-denied")
226}
227
228#[doc(hidden)]
238pub fn invalid_jwt(reason: &str) -> Value {
239 refused(&format!("Could not verify JWT: {reason}"), "invalid-jwt")
240}
241
242#[doc(hidden)]
250pub fn missing_role_claim(claim: &str) -> Value {
251 refused(
252 &format!("JWT claim does not contain {claim}"),
253 "jwt-missing-role-claims",
254 )
255}
256
257fn refused(message: &str, code: &str) -> Value {
258 let mut error = ServerError::new(message, None);
259 let mut extensions = async_graphql::ErrorExtensionValues::default();
260 extensions.set("code", code);
261 error.extensions = Some(extensions);
262
263 let mut response = Response::new(async_graphql::Value::Null);
264 response.errors = vec![error];
265 envelope(response)
266}
267
268pub fn persisted_query(request: &async_graphql::Request) -> bool {
277 request.extensions.contains_key("persistedQuery")
278}
279
280pub fn not_supported(message: &str) -> Value {
282 let mut error = ServerError::new(message, None);
283 let mut extensions = async_graphql::ErrorExtensionValues::default();
284 extensions.set("code", "not-supported");
285 extensions.set("path", "$");
286 error.extensions = Some(extensions);
287
288 let mut response = Response::new(async_graphql::Value::Null);
289 response.errors = vec![error];
290 envelope(response)
291}
292
293pub fn malformed(message: &str) -> Value {
299 let mut error = ServerError::new(message, None);
300 let mut extensions = async_graphql::ErrorExtensionValues::default();
301 extensions.set("code", "bad-request");
302 extensions.set("path", "$");
303 error.extensions = Some(extensions);
304
305 let mut response = Response::new(async_graphql::Value::Null);
306 response.errors = vec![error];
307 envelope(response)
308}
309
310pub fn unavailable(message: &str) -> Value {
317 let mut error = ServerError::new(message, None);
318 let mut extensions = async_graphql::ErrorExtensionValues::default();
319 extensions.set("code", "unexpected");
320 extensions.set("path", "$");
321 error.extensions = Some(extensions);
322
323 let mut response = Response::new(async_graphql::Value::Null);
324 response.errors = vec![error];
325 envelope(response)
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use async_graphql::{PathSegment, ServerError};
332 use pretty_assertions::assert_eq;
333
334 fn error_at(message: &str, path: Vec<PathSegment>) -> ServerError {
335 let mut error = ServerError::new(message, None);
336 error.path = path;
337 error
338 }
339
340 #[test]
341 fn only_a_refused_write_is_answered_with_a_failure_status() {
342 let refused = json!({"errors": [{"message": crate::role::CHECK_FAILED,
345 "extensions": {"code": "permission-error"}}]});
346 assert_eq!(status_for(&refused, Endpoint::Legacy), 400);
347 assert_eq!(status_for(&refused, Endpoint::Current), 200);
349
350 let denied = json!({"errors": [{"message": "not allowed",
352 "extensions": {"code": "permission-error"}}]});
353 assert_eq!(status_for(&denied, Endpoint::Legacy), 200);
354 assert_eq!(
355 status_for(&json!({"data": {"article": []}}), Endpoint::Legacy),
356 200
357 );
358 }
359
360 #[test]
361 fn a_role_may_be_refused_the_schema_without_being_refused_its_rows() {
362 let asked = |query: &str| {
363 let doc = async_graphql::parser::parse_query(query).unwrap();
364 introspection_refusal(&doc, "reader").map(|e| e.message)
365 };
366 let refusal = Some("introspection is disabled for role: \"reader\"".to_string());
367 assert_eq!(asked("{ __schema { types { name } } }"), refusal);
368 assert_eq!(asked("{ __type(name: \"article\") { name } }"), refusal);
369 assert_eq!(asked("{ article { __typename id } }"), None);
372 assert_eq!(asked("{ article { id } }"), None);
373 }
374
375 #[test]
376 fn a_failed_response_has_no_data_key() {
377 let mut response = Response::new(async_graphql::Value::Null);
378 response.errors = vec![error_at("no", vec![])];
379 let body = envelope(response);
380 assert!(body.get("data").is_none());
381 assert!(body.get("errors").is_some());
382 }
383
384 #[test]
385 fn a_successful_response_has_no_errors_key() {
386 let body = envelope(Response::new(async_graphql::Value::Null));
387 assert!(body.get("errors").is_none());
388 assert!(body.get("data").is_some());
389 }
390
391 #[test]
392 fn path_is_rooted_at_the_selection_set() {
393 let error = error_at(
394 "boom",
395 vec![
396 PathSegment::Field("insert_author".to_string()),
397 PathSegment::Index(0),
398 PathSegment::Field("name".to_string()),
399 ],
400 );
401 assert_eq!(
402 path_for(&error),
403 "$.selectionSet.insert_author.selectionSet.name"
404 );
405 }
406
407 #[test]
414 fn a_persisted_query_is_refused_as_unsupported() {
415 let plain = async_graphql::Request::new("{ author { id } }");
416 assert!(!persisted_query(&plain));
417
418 let mut carried = async_graphql::Request::new("{ author { id } }");
419 carried.extensions.0.insert(
420 "persistedQuery".to_string(),
421 async_graphql::Value::Object(Default::default()),
422 );
423 assert!(persisted_query(&carried));
424
425 assert_eq!(
426 not_supported("PersistedQueryNotSupported"),
427 json!({"errors": [{
428 "message": "PersistedQueryNotSupported",
429 "extensions": {"path": "$", "code": "not-supported"}
430 }]})
431 );
432 }
433
434 #[test]
435 fn a_path_given_with_the_error_is_the_one_answered() {
436 let mut error = error_at(
437 "boom",
438 vec![PathSegment::Field("insert_author".to_string())],
439 );
440 let mut extensions = async_graphql::ErrorExtensionValues::default();
441 extensions.set("code", "permission-error");
442 extensions.set("path", "$.selectionSet.insert_author.args.objects");
443 error.extensions = Some(extensions);
444 assert_eq!(
445 path_for(&error),
446 "$.selectionSet.insert_author.args.objects"
447 );
448 }
449
450 #[test]
451 fn an_error_with_no_path_is_rooted_at_the_document() {
452 assert_eq!(path_for(&error_at("boom", vec![])), "$");
453 }
454
455 #[test]
456 fn a_validation_message_is_coded_as_one() {
457 let error = ServerError::new("Unknown argument \"filter\" on field \"author\"", None);
458 assert_eq!(code_for(&error), "validation-failed");
459 }
460
461 #[test]
462 fn a_constraint_message_is_coded_as_one() {
463 let error = ServerError::new("duplicate key value violates unique constraint", None);
464 assert_eq!(code_for(&error), "constraint-violation");
465 }
466}
467
468pub fn allow_unused_variables(request: async_graphql::Request) -> async_graphql::Request {
484 prepare(None, request, None).unwrap_or_else(|(request, _)| request)
485}
486
487#[allow(clippy::result_large_err)]
506pub fn prepare(
507 schema: Option<&async_graphql::dynamic::Schema>,
508 request: async_graphql::Request,
509 introspection_disabled_for: Option<&str>,
514) -> Result<async_graphql::Request, (async_graphql::Request, Vec<ServerError>)> {
515 let mut request = rewrite_document(schema, request);
516 let doc = match async_graphql::parser::parse_query(&request.query) {
527 Ok(doc) => doc,
528 Err(_) => {
529 return Err((
530 request,
531 vec![coded(
532 "not a valid graphql query".to_string(),
533 async_graphql::Pos::default(),
534 "$.query",
535 )],
536 ))
537 }
538 };
539 if let Some(schema) = schema {
540 if let Some(role) = introspection_disabled_for {
541 if let Some(error) = introspection_refusal(&doc, role) {
542 return Err((request, vec![error]));
543 }
544 }
545 let mut errors = variable_errors(&doc, schema.registry(), &request.variables);
546 errors.truncate(1);
559 if !errors.is_empty() {
560 return Err((request, errors));
561 }
562 }
563 let _ = &mut request;
564 Ok(request)
565}
566
567fn introspection_refusal(
576 doc: &async_graphql::parser::types::ExecutableDocument,
577 role: &str,
578) -> Option<ServerError> {
579 use async_graphql::parser::types::{OperationType, Selection};
580
581 let operations: Vec<_> = match &doc.operations {
582 async_graphql::parser::types::DocumentOperations::Single(one) => vec![one],
583 async_graphql::parser::types::DocumentOperations::Multiple(many) => many.values().collect(),
584 };
585
586 let asks = operations.iter().any(|operation| {
587 operation.node.ty == OperationType::Query
588 && operation
589 .node
590 .selection_set
591 .node
592 .items
593 .iter()
594 .any(|item| match &item.node {
595 Selection::Field(field) => {
596 matches!(field.node.name.node.as_str(), "__schema" | "__type")
597 }
598 _ => false,
599 })
600 });
601
602 match asks {
603 false => None,
604 true => {
605 let mut error = ServerError::new(
606 format!("introspection is disabled for role: \"{}\"", role),
607 None,
608 );
609 let mut extensions = async_graphql::ErrorExtensionValues::default();
610 extensions.set("code", "not-supported");
611 error.extensions = Some(extensions);
612 Some(error)
613 }
614 }
615}
616
617fn rewrite_document(
626 schema: Option<&async_graphql::dynamic::Schema>,
627 mut request: async_graphql::Request,
628) -> async_graphql::Request {
629 use async_graphql::parser::types::{
630 DocumentOperations, ExecutableDocument, Selection, SelectionSet,
631 };
632 use async_graphql::Name;
633 use async_graphql_value::Value;
636
637 let Ok(mut doc): Result<ExecutableDocument, _> =
638 async_graphql::parser::parse_query(&request.query)
639 else {
640 return request;
641 };
642
643 fn from_value(value: &Value, used: &mut std::collections::HashSet<Name>) {
644 match value {
645 Value::Variable(name) => {
646 used.insert(name.clone());
647 }
648 Value::List(items) => items.iter().for_each(|item| from_value(item, used)),
649 Value::Object(fields) => fields.values().for_each(|field| from_value(field, used)),
650 _ => {}
651 }
652 }
653
654 fn from_selection_set(set: &SelectionSet, used: &mut std::collections::HashSet<Name>) {
655 for selection in &set.items {
656 let (directives, arguments, nested) = match &selection.node {
657 Selection::Field(field) => (
658 &field.node.directives,
659 Some(&field.node.arguments),
660 Some(&field.node.selection_set),
661 ),
662 Selection::FragmentSpread(spread) => (&spread.node.directives, None, None),
663 Selection::InlineFragment(fragment) => (
664 &fragment.node.directives,
665 None,
666 Some(&fragment.node.selection_set),
667 ),
668 };
669 for directive in directives {
670 for (_, value) in &directive.node.arguments {
671 from_value(&value.node, used);
672 }
673 }
674 for (_, value) in arguments.into_iter().flatten() {
675 from_value(&value.node, used);
676 }
677 if let Some(nested) = nested {
678 from_selection_set(&nested.node, used);
679 }
680 }
681 }
682
683 let mut used = std::collections::HashSet::new();
688 for (_, operation) in doc.operations.iter() {
689 for directive in &operation.node.directives {
690 for (_, value) in &directive.node.arguments {
691 from_value(&value.node, &mut used);
692 }
693 }
694 from_selection_set(&operation.node.selection_set.node, &mut used);
695 }
696 for fragment in doc.fragments.values() {
697 from_selection_set(&fragment.node.selection_set.node, &mut used);
698 }
699
700 let mut edited = false;
701 {
702 let mut prune = |operation: &mut async_graphql::Positioned<
703 async_graphql::parser::types::OperationDefinition,
704 >| {
705 let before = operation.node.variable_definitions.len();
706 operation
707 .node
708 .variable_definitions
709 .retain(|definition| used.contains(&definition.node.name.node));
710 edited |= operation.node.variable_definitions.len() != before;
711 };
712 match &mut doc.operations {
713 DocumentOperations::Single(operation) => prune(operation),
714 DocumentOperations::Multiple(operations) => operations.values_mut().for_each(prune),
715 }
716 }
717
718 if let Some(schema) = schema {
729 let registry = schema.registry();
730 {
731 let mut coerce = |operation: &mut async_graphql::Positioned<
732 async_graphql::parser::types::OperationDefinition,
733 >| {
734 use async_graphql::parser::types::OperationType;
735 let root = match operation.node.ty {
736 OperationType::Query => Some(registry.query_type.as_str()),
737 OperationType::Mutation => registry.mutation_type.as_deref(),
738 OperationType::Subscription => registry.subscription_type.as_deref(),
739 };
740 if let Some(root) = root {
741 edited |= coerce_selection_set(
742 registry,
743 &mut operation.node.selection_set.node,
744 root,
745 );
746 }
747 };
748 match &mut doc.operations {
749 DocumentOperations::Single(operation) => coerce(operation),
750 DocumentOperations::Multiple(operations) => {
751 operations.values_mut().for_each(coerce)
752 }
753 }
754 }
755 for fragment in doc.fragments.values_mut() {
759 let on = fragment.node.type_condition.node.on.node.to_string();
760 edited |= coerce_selection_set(registry, &mut fragment.node.selection_set.node, &on);
761 }
762 }
763
764 if edited {
765 request.set_parsed_query(doc);
766 }
767 request
768}
769
770fn coerce_selection_set(
777 registry: &async_graphql::registry::Registry,
778 set: &mut async_graphql::parser::types::SelectionSet,
779 type_name: &str,
780) -> bool {
781 use async_graphql::parser::types::Selection;
782 use async_graphql::registry::MetaTypeName;
783
784 let mut edited = false;
785 for selection in &mut set.items {
786 match &mut selection.node {
787 Selection::Field(field) => {
788 let meta = registry
789 .types
790 .get(type_name)
791 .and_then(|ty| ty.field_by_name(field.node.name.node.as_str()));
792 for (name, value) in &mut field.node.arguments {
793 if name.node.as_str() == "limit" {
796 continue;
797 }
798 let Some(argument) = meta.and_then(|meta| meta.args.get(name.node.as_str()))
799 else {
800 continue;
801 };
802 let ty = argument.ty.clone();
803 edited |= coerce_value(registry, &mut value.node, &ty);
804 }
805 if let Some(meta) = meta {
806 let inner = MetaTypeName::concrete_typename(&meta.ty).to_string();
807 edited |=
808 coerce_selection_set(registry, &mut field.node.selection_set.node, &inner);
809 }
810 }
811 Selection::InlineFragment(fragment) => {
812 let inner = fragment
813 .node
814 .type_condition
815 .as_ref()
816 .map(|condition| condition.node.on.node.to_string())
817 .unwrap_or_else(|| type_name.to_string());
818 edited |=
819 coerce_selection_set(registry, &mut fragment.node.selection_set.node, &inner);
820 }
821 Selection::FragmentSpread(_) => {}
822 }
823 }
824 edited
825}
826
827fn coerce_value(
829 registry: &async_graphql::registry::Registry,
830 value: &mut async_graphql_value::Value,
831 expected: &str,
832) -> bool {
833 use async_graphql::registry::{MetaType, MetaTypeName};
834 use async_graphql_value::Value;
835
836 match value {
837 Value::List(items) => {
838 let inner = match MetaTypeName::create(expected).unwrap_non_null() {
841 MetaTypeName::List(inner) => inner.to_string(),
842 _ => expected.to_string(),
843 };
844 let mut edited = false;
845 for item in items {
846 edited |= coerce_value(registry, item, &inner);
847 }
848 edited
849 }
850 Value::Object(fields) => {
851 let name = MetaTypeName::concrete_typename(expected);
852 let Some(MetaType::InputObject { input_fields, .. }) = registry.types.get(name) else {
853 return false;
854 };
855 let types: Vec<(async_graphql::Name, String)> = fields
856 .keys()
857 .filter_map(|key| {
858 input_fields
859 .get(key.as_str())
860 .map(|field| (key.clone(), field.ty.clone()))
861 })
862 .collect();
863 let mut edited = false;
864 for (key, ty) in types {
865 if let Some(item) = fields.get_mut(&key) {
866 edited |= coerce_value(registry, item, &ty);
867 }
868 }
869 edited
870 }
871 Value::String(text) => {
872 let Some(coerced) = as_written(text, MetaTypeName::concrete_typename(expected)) else {
873 return false;
874 };
875 *value = coerced;
876 true
877 }
878 _ => false,
879 }
880}
881
882fn as_written(text: &str, expected: &str) -> Option<async_graphql_value::Value> {
887 use async_graphql_value::{Number, Value};
888 match expected {
889 "Int" => text.parse::<i64>().ok().map(|n| Value::Number(n.into())),
890 "Float" => text
891 .parse::<f64>()
892 .ok()
893 .and_then(Number::from_f64)
894 .map(Value::Number),
895 "Boolean" => match text {
896 "true" => Some(Value::Boolean(true)),
897 "false" => Some(Value::Boolean(false)),
898 _ => None,
899 },
900 _ => None,
901 }
902}
903
904#[cfg(test)]
905mod unused_variable_tests {
906 use super::*;
907
908 fn declarations(query: &str) -> Vec<String> {
909 let request = allow_unused_variables(async_graphql::Request::new(query));
910 let mut request = request;
913 let doc = request.parsed_query().expect("the query parses");
914 doc.operations
915 .iter()
916 .flat_map(|(_, operation)| operation.node.variable_definitions.iter())
917 .map(|definition| definition.node.name.node.to_string())
918 .collect()
919 }
920
921 #[test]
922 fn a_variable_nothing_names_is_dropped() {
923 assert_eq!(
924 declarations("query ($tags: jsonb) { article(where: {id: {_eq: 1}}) { id } }"),
925 Vec::<String>::new()
926 );
927 }
928
929 #[test]
930 fn a_variable_an_argument_names_is_kept() {
931 assert_eq!(
932 declarations("query ($id: Int) { article(where: {id: {_eq: $id}}) { id } }"),
933 vec!["id".to_string()]
934 );
935 }
936
937 #[test]
938 fn a_variable_named_inside_a_list_is_kept() {
939 assert_eq!(
940 declarations("query ($id: Int) { article(where: {_or: [{id: {_eq: $id}}]}) { id } }"),
941 vec!["id".to_string()]
942 );
943 }
944
945 #[test]
946 fn a_variable_only_a_fragment_names_is_kept() {
947 assert_eq!(
948 declarations(
949 "query ($n: Int) { author { ...rows } } \
950 fragment rows on author { articles(limit: $n) { id } }"
951 ),
952 vec!["n".to_string()]
953 );
954 }
955
956 #[test]
957 fn a_document_that_does_not_parse_is_left_alone() {
958 let request = allow_unused_variables(async_graphql::Request::new("query ("));
959 assert_eq!(request.query, "query (");
960 }
961}
962
963fn variable_errors(
977 doc: &async_graphql::parser::types::ExecutableDocument,
978 registry: &async_graphql::registry::Registry,
979 variables: &async_graphql::Variables,
980) -> Vec<ServerError> {
981 use async_graphql::parser::types::{DocumentOperations, OperationType};
982
983 let mut errors: Vec<ServerError> = Vec::new();
984
985 let operations: Vec<
986 &async_graphql::Positioned<async_graphql::parser::types::OperationDefinition>,
987 > = match &doc.operations {
988 DocumentOperations::Single(operation) => vec![operation],
989 DocumentOperations::Multiple(operations) => operations.values().collect(),
990 };
991
992 for operation in operations {
993 let root = match operation.node.ty {
994 OperationType::Query => Some(registry.query_type.as_str()),
995 OperationType::Mutation => registry.mutation_type.as_deref(),
996 OperationType::Subscription => registry.subscription_type.as_deref(),
997 };
998 let Some(root) = root else {
999 if operation.node.ty == OperationType::Mutation {
1003 errors.push(coded("no mutations exist".to_string(), operation.pos, "$"));
1004 }
1005 continue;
1006 };
1007
1008 let mut declared: HashMap<&str, String> = HashMap::new();
1010 for definition in &operation.node.variable_definitions {
1011 let name = definition.node.name.node.as_str();
1012 let written = definition.node.var_type.node.to_string();
1013 let defaulted = definition
1019 .node
1020 .default_value
1021 .as_ref()
1022 .is_some_and(|value| !matches!(value.node, async_graphql::Value::Null));
1023 let effective = match (definition.node.var_type.node.nullable, defaulted) {
1024 (true, true) => format!("{}!", written),
1025 _ => written.clone(),
1026 };
1027 declared.insert(name, effective);
1028
1029 let given = variables.get(&async_graphql::Name::new(name));
1033 if !definition.node.var_type.node.nullable
1034 && matches!(given, Some(async_graphql::Value::Null))
1035 {
1036 errors.push(coded(
1037 format!("null value found for non-nullable type: \"{}\"", written),
1038 definition.pos,
1039 "$",
1040 ));
1041 }
1042 }
1043 let mut scope = Usage {
1047 registry,
1048 fragments: &doc.fragments,
1049 declared: &declared,
1050 variables,
1051 errors: &mut errors,
1052 spreading: Vec::new(),
1053 expansions: 0,
1054 };
1055 scope.selection_set(&operation.node.selection_set.node, root, "$");
1056 }
1057
1058 errors
1059}
1060
1061struct Usage<'a> {
1063 registry: &'a async_graphql::registry::Registry,
1064 fragments: &'a std::collections::HashMap<
1065 async_graphql::Name,
1066 async_graphql::Positioned<async_graphql::parser::types::FragmentDefinition>,
1067 >,
1068 declared: &'a HashMap<&'a str, String>,
1069 variables: &'a async_graphql::Variables,
1072 errors: &'a mut Vec<ServerError>,
1073 spreading: Vec<String>,
1082 expansions: usize,
1086}
1087
1088impl Usage<'_> {
1089 fn selection_set(
1094 &mut self,
1095 set: &async_graphql::parser::types::SelectionSet,
1096 type_name: &str,
1097 path: &str,
1098 ) {
1099 use async_graphql::parser::types::Selection;
1100 use async_graphql::registry::MetaTypeName;
1101
1102 for selection in &set.items {
1103 match &selection.node {
1104 Selection::Field(field) => {
1105 let named = field.node.name.node.as_str();
1106 let parent = self.registry.types.get(type_name);
1107 let meta = parent.and_then(|ty| ty.field_by_name(named));
1108 let here = format!("{}.selectionSet.{}", path, field.node.name.node);
1109 if meta.is_none() && parent.is_some() && !named.starts_with("__") {
1118 self.errors.push(coded(
1119 format!("field '{}' not found in type: '{}'", named, type_name),
1120 field.pos,
1121 &here,
1122 ));
1123 continue;
1124 }
1125 for (name, value) in &field.node.arguments {
1126 let Some(argument) =
1127 meta.and_then(|meta| meta.args.get(name.node.as_str()))
1128 else {
1129 if meta.is_some() {
1134 self.errors.push(coded(
1135 format!("'{}' has no argument named '{}'", named, name.node),
1136 name.pos,
1137 &here,
1138 ));
1139 }
1140 continue;
1141 };
1142 let expected = relax(&argument.ty, argument.default_value.is_some());
1147 let at = format!("{}.args.{}", here, name.node);
1148 self.rows_asked_for(
1149 name.node.as_str(),
1150 &value.node,
1151 &expected,
1152 value.pos,
1153 &at,
1154 );
1155 self.object_expected(&value.node, &expected, value.pos, &at);
1156 self.value(&value.node, &expected, value.pos, &at);
1157 }
1158 self.one_operator_per_column(&field.node.arguments, &here);
1159 self.directives(&field.node.directives, &here);
1160 if let Some(meta) = meta {
1161 let inner = MetaTypeName::concrete_typename(&meta.ty).to_string();
1162 self.selection_set(&field.node.selection_set.node, &inner, &here);
1163 }
1164 }
1165 Selection::InlineFragment(fragment) => {
1166 let inner = fragment
1167 .node
1168 .type_condition
1169 .as_ref()
1170 .map(|condition| condition.node.on.node.to_string())
1171 .unwrap_or_else(|| type_name.to_string());
1172 self.directives(&fragment.node.directives, path);
1173 self.selection_set(&fragment.node.selection_set.node, &inner, path);
1174 }
1175 Selection::FragmentSpread(spread) => {
1176 let name = spread.node.fragment_name.node.to_string();
1177 self.directives(&spread.node.directives, path);
1178 if let Some(began) = self.spreading.iter().position(|held| held == &name) {
1185 let cycle: Vec<&str> =
1186 self.spreading[began..].iter().map(String::as_str).collect();
1187 self.errors.push(coded(
1188 format!("the fragment definition(s) {} form a cycle", listed(&cycle)),
1189 spread.pos,
1190 &format!("{}.selectionSet", path),
1194 ));
1195 continue;
1196 }
1197 self.expansions += 1;
1198 if self.expansions > SPREAD_BUDGET {
1199 continue;
1200 }
1201 if let Some(fragment) = self.fragments.get(&async_graphql::Name::new(&name)) {
1202 let on = fragment.node.type_condition.node.on.node.to_string();
1203 let here = format!("{}.selectionSet.{}", path, name);
1207 self.spreading.push(name);
1208 self.selection_set(&fragment.node.selection_set.node, &on, &here);
1209 self.spreading.pop();
1210 }
1211 }
1212 }
1213 }
1214 }
1215
1216 fn directives(
1217 &mut self,
1218 directives: &[async_graphql::Positioned<async_graphql::parser::types::Directive>],
1219 path: &str,
1220 ) {
1221 for directive in directives {
1222 let meta = self
1223 .registry
1224 .directives
1225 .get(directive.node.name.node.as_str());
1226 for (name, value) in &directive.node.arguments {
1227 let Some(argument) = meta.and_then(|meta| meta.args.get(name.node.as_str())) else {
1228 continue;
1229 };
1230 let expected = relax(&argument.ty, argument.default_value.is_some());
1231 let at = format!("{}.args.{}", path, name.node);
1232 self.value(&value.node, &expected, value.pos, &at);
1233 }
1234 }
1235 }
1236
1237 fn stands_for_null(&self, value: &async_graphql_value::Value) -> bool {
1244 use async_graphql_value::Value;
1245 match value {
1246 Value::Null => true,
1247 Value::Variable(name) => matches!(
1248 self.variables.get(&async_graphql::Name::new(name)),
1249 Some(async_graphql::Value::Null)
1250 ),
1251 _ => false,
1252 }
1253 }
1254
1255 fn value(
1257 &mut self,
1258 value: &async_graphql_value::Value,
1259 expected: &str,
1260 pos: async_graphql::Pos,
1261 path: &str,
1262 ) {
1263 use async_graphql::registry::{MetaType, MetaTypeName};
1264 use async_graphql_value::Value;
1265
1266 match value {
1267 Value::Variable(name) => {
1268 let Some(declared) = self.declared.get(name.as_str()) else {
1269 return;
1271 };
1272 if !MetaTypeName::create(expected).is_subtype(&MetaTypeName::create(declared)) {
1273 self.errors.push(coded(
1274 format!(
1275 "variable '{}' is declared as '{}', but used where '{}' is expected",
1276 name, declared, expected
1277 ),
1278 pos,
1279 path,
1280 ));
1281 return;
1282 }
1283 if let Some(given) = self.variables.get(&async_graphql::Name::new(name)) {
1288 let given: async_graphql_value::Value = given.clone().into();
1289 self.enumerated(&given, expected, pos, path, true);
1290 self.label_path(&given, expected, pos, path);
1291 self.raster_hex(&given, expected, pos, path);
1292 }
1293 }
1294 Value::Enum(_) | Value::String(_) => {
1295 self.enumerated(value, expected, pos, path, false);
1296 self.label_path(value, expected, pos, path);
1297 self.raster_hex(value, expected, pos, path);
1298 }
1299 Value::Number(_) => self.integral(value, expected, pos, path),
1300 Value::List(items) => {
1301 let inner = match MetaTypeName::create(expected).unwrap_non_null() {
1305 MetaTypeName::List(inner) => inner.to_string(),
1306 _ => expected.to_string(),
1307 };
1308 for (index, item) in items.iter().enumerate() {
1309 self.value(item, &inner, pos, &format!("{}[{}]", path, index));
1310 }
1311 }
1312 Value::Object(fields) => {
1313 let name = MetaTypeName::concrete_typename(expected);
1314 let Some(MetaType::InputObject { input_fields, .. }) =
1315 self.registry.types.get(name)
1316 else {
1317 return;
1318 };
1319 let comparison = name.ends_with("_comparison_exp");
1327 let base = match MetaTypeName::create(expected).unwrap_non_null() {
1332 MetaTypeName::List(_) => format!("{}[0]", path),
1333 _ => path.to_string(),
1334 };
1335 for (key, item) in fields {
1336 let at = format!("{}.{}", base, key.as_str());
1337 let Some(field) = input_fields.get(key.as_str()) else {
1338 self.errors.push(coded(
1343 format!("field '{}' not found in type: '{}'", key.as_str(), name),
1344 pos,
1345 &at,
1346 ));
1347 continue;
1348 };
1349 if comparison && self.stands_for_null(item) {
1350 self.errors.push(coded(
1351 format!(
1352 "unexpected null value for type '{}'",
1353 MetaTypeName::concrete_typename(&field.ty)
1354 ),
1355 pos,
1356 &at,
1357 ));
1358 continue;
1359 }
1360 let ty = relax(&field.ty, field.default_value.is_some());
1361 self.value(item, &ty, pos, &at);
1362 }
1363 }
1364 _ => {}
1365 }
1366 }
1367}
1368
1369impl Usage<'_> {
1370 fn object_expected(
1382 &mut self,
1383 value: &async_graphql_value::Value,
1384 expected: &str,
1385 pos: async_graphql::Pos,
1386 path: &str,
1387 ) {
1388 use async_graphql::registry::{MetaType, MetaTypeName};
1389
1390 if !matches!(MetaTypeName::create(expected), MetaTypeName::NonNull(_)) {
1391 return;
1392 }
1393 if !self.stands_for_null(value) {
1394 return;
1395 }
1396 let name = MetaTypeName::concrete_typename(expected);
1397 if !matches!(
1398 self.registry.types.get(name),
1399 Some(MetaType::InputObject { .. })
1400 ) {
1401 return;
1402 }
1403 self.errors.push(coded(
1404 format!("expected an object for type '{}', but found null", name),
1405 pos,
1406 path,
1407 ));
1408 }
1409
1410 fn one_operator_per_column(
1423 &mut self,
1424 arguments: &[(
1425 async_graphql::Positioned<async_graphql::Name>,
1426 async_graphql::Positioned<async_graphql_value::Value>,
1427 )],
1428 path: &str,
1429 ) {
1430 use async_graphql_value::Value;
1431
1432 const OPERATORS: [&str; 7] = [
1433 "_set",
1434 "_inc",
1435 "_append",
1436 "_prepend",
1437 "_delete_key",
1438 "_delete_elem",
1439 "_delete_at_path",
1440 ];
1441
1442 let mut seen: Vec<&str> = Vec::new();
1443 let mut twice: Vec<&str> = Vec::new();
1444 for (name, value) in arguments {
1445 if !OPERATORS.contains(&name.node.as_str()) {
1446 continue;
1447 }
1448 let Value::Object(columns) = &value.node else {
1449 continue;
1450 };
1451 for column in columns.keys() {
1452 let column = column.as_str();
1453 match seen.contains(&column) {
1454 true if !twice.contains(&column) => twice.push(column),
1455 true => {}
1456 false => seen.push(column),
1457 }
1458 }
1459 }
1460 if twice.is_empty() {
1461 return;
1462 }
1463 let listed = twice
1466 .iter()
1467 .map(|column| format!("'{}'", column))
1468 .collect::<Vec<String>>()
1469 .join(", ");
1470 self.errors.push(coded(
1471 format!("Column found in multiple operators: [{}].", listed),
1472 arguments
1473 .first()
1474 .map(|(name, _)| name.pos)
1475 .unwrap_or_default(),
1476 &format!("{}.args", path),
1477 ));
1478 }
1479
1480 fn integral(
1488 &mut self,
1489 value: &async_graphql_value::Value,
1490 expected: &str,
1491 pos: async_graphql::Pos,
1492 path: &str,
1493 ) {
1494 use async_graphql::registry::MetaTypeName;
1495 use async_graphql_value::Value;
1496
1497 if MetaTypeName::concrete_typename(expected) != "Int" {
1498 return;
1499 }
1500 let Value::Number(number) = value else {
1501 return;
1502 };
1503 if number.as_i64().is_some_and(|n| i32::try_from(n).is_ok()) {
1504 return;
1505 }
1506 let Some(shown) = number.as_f64().map(shown_as_haskell_would) else {
1507 return;
1508 };
1509 self.errors.push(coded_as(
1510 "parse-failed",
1511 format!(
1512 "The value {} lies outside the bounds or is not an integer. \
1513 Maybe it is a float, or is there integer overflow?",
1514 shown
1515 ),
1516 pos,
1517 path,
1518 ));
1519 }
1520
1521 fn rows_asked_for(
1535 &mut self,
1536 argument: &str,
1537 value: &async_graphql_value::Value,
1538 expected: &str,
1539 pos: async_graphql::Pos,
1540 path: &str,
1541 ) {
1542 use async_graphql::registry::MetaTypeName;
1543 use async_graphql_value::Value;
1544
1545 if argument != "limit" || MetaTypeName::concrete_typename(expected) != "Int" {
1546 return;
1547 }
1548 let value = match value {
1550 Value::Variable(name) => {
1551 match self.variables.get(&async_graphql::Name::new(name.as_str())) {
1552 Some(given) => std::borrow::Cow::Owned(given.clone().into()),
1553 None => return,
1554 }
1555 }
1556 other => std::borrow::Cow::Borrowed(other),
1557 };
1558
1559 let found = match value.as_ref() {
1560 Value::String(_) => "a string",
1561 Value::Number(number) => match number.as_i64() {
1562 None => return,
1566 Some(written) if i32::try_from(written).is_err() => return,
1567 Some(written) if written >= 0 => return,
1569 Some(_) => "an integer",
1570 },
1571 _ => return,
1572 };
1573 self.errors.push(coded(
1574 format!(
1575 "expected a non-negative 32-bit integer for type 'Int', but found {}",
1576 found
1577 ),
1578 pos,
1579 path,
1580 ));
1581 }
1582
1583 fn raster_hex(
1597 &mut self,
1598 value: &async_graphql_value::Value,
1599 expected: &str,
1600 pos: async_graphql::Pos,
1601 path: &str,
1602 ) {
1603 use async_graphql::registry::MetaTypeName;
1604 use async_graphql_value::Value;
1605
1606 if MetaTypeName::concrete_typename(expected) != "raster" {
1607 return;
1608 }
1609 let Value::String(written) = value else {
1610 return;
1611 };
1612 if written.len() % 2 == 0 && written.chars().all(|c| c.is_ascii_hexdigit()) {
1613 return;
1614 }
1615 self.errors.push(coded_as(
1616 "parse-failed",
1617 "invalid hexadecimal representation of raster well known binary format".to_string(),
1618 pos,
1619 path,
1620 ));
1621 }
1622
1623 fn label_path(
1639 &mut self,
1640 value: &async_graphql_value::Value,
1641 expected: &str,
1642 pos: async_graphql::Pos,
1643 path: &str,
1644 ) {
1645 use async_graphql::registry::MetaTypeName;
1646 use async_graphql_value::Value;
1647
1648 if MetaTypeName::concrete_typename(expected) != "ltree" {
1649 return;
1650 }
1651 let Value::String(written) = value else {
1652 return;
1653 };
1654 if written.is_empty() || written.split('.').all(|label| !label.is_empty()) {
1657 return;
1658 }
1659 self.errors.push(coded_as(
1660 "parse-failed",
1661 "Expecting label path: a sequence of zero or more labels separated by \
1662 dots, for example L1.L2.L3"
1663 .to_string(),
1664 pos,
1665 path,
1666 ));
1667 }
1668
1669 fn enumerated(
1675 &mut self,
1676 value: &async_graphql_value::Value,
1677 expected: &str,
1678 pos: async_graphql::Pos,
1679 path: &str,
1680 from_variable: bool,
1686 ) {
1687 use async_graphql::registry::{MetaType, MetaTypeName};
1688 use async_graphql_value::Value;
1689
1690 let name = MetaTypeName::concrete_typename(expected);
1691 let Some(MetaType::Enum { enum_values, .. }) = self.registry.types.get(name) else {
1692 return;
1693 };
1694 let written = match value {
1695 Value::Enum(written) => written.as_str(),
1696 Value::String(written) if from_variable => written.as_str(),
1697 Value::String(_) => {
1698 self.errors.push(coded(
1699 format!(
1700 "expected an enum value for type '{}', but found a string",
1701 name
1702 ),
1703 pos,
1704 path,
1705 ));
1706 return;
1707 }
1708 _ => return,
1709 };
1710 if enum_values.contains_key(written) {
1711 return;
1712 }
1713 let mut allowed: Vec<&str> = enum_values.keys().map(String::as_str).collect();
1716 allowed.sort_unstable();
1717 let listed = allowed
1718 .iter()
1719 .map(|value| format!("'{}'", value))
1720 .collect::<Vec<_>>()
1721 .join(", ");
1722 self.errors.push(coded(
1723 format!(
1724 "expected one of the values [{}] for type '{}', but found '{}'",
1725 listed, name, written
1726 ),
1727 pos,
1728 path,
1729 ));
1730 }
1731}
1732
1733const SPREAD_BUDGET: usize = 1000;
1741
1742fn listed(names: &[&str]) -> String {
1745 match names {
1746 [] => String::new(),
1747 [one] => one.to_string(),
1748 [rest @ .., last] => format!("{} and {}", rest.join(", "), last),
1749 }
1750}
1751
1752fn shown_as_haskell_would(value: f64) -> String {
1763 if !value.is_finite() {
1764 return value.to_string();
1765 }
1766 let magnitude = value.abs();
1767 let with_a_point = |rendered: String| match rendered.contains('.') {
1768 true => rendered,
1769 false => format!("{}.0", rendered),
1770 };
1771 if magnitude != 0.0 && (magnitude < 0.1 || magnitude >= 1e7) {
1772 let rendered = format!("{:e}", value);
1773 let (mantissa, exponent) = match rendered.split_once('e') {
1774 Some(halves) => halves,
1775 None => return with_a_point(rendered),
1776 };
1777 return format!("{}e{}", with_a_point(mantissa.to_string()), exponent);
1778 }
1779 with_a_point(format!("{}", value))
1780}
1781
1782fn relax(ty: &str, has_default: bool) -> String {
1788 match has_default {
1789 true => ty.strip_suffix('!').unwrap_or(ty).to_string(),
1790 false => ty.to_string(),
1791 }
1792}
1793
1794fn coded(message: String, pos: async_graphql::Pos, path: &str) -> ServerError {
1801 coded_as("validation-failed", message, pos, path)
1802}
1803
1804fn coded_as(
1806 code: &'static str,
1807 message: String,
1808 pos: async_graphql::Pos,
1809 path: &str,
1810) -> ServerError {
1811 let mut error = ServerError::new(message, Some(pos));
1812 let mut extensions = async_graphql::ErrorExtensionValues::default();
1813 extensions.set("code", code);
1814 extensions.set("path", path);
1815 error.extensions = Some(extensions);
1816 error
1817}
1818
1819#[cfg(test)]
1820mod variable_position_tests {
1821 use super::*;
1822 use async_graphql::dynamic::*;
1823
1824 fn schema() -> async_graphql::dynamic::Schema {
1828 let filter = InputObject::new("author_bool_exp")
1829 .field(InputValue::new("id", TypeRef::named("Int")))
1830 .field(InputValue::new(
1831 "name",
1832 TypeRef::named("String_comparison_exp"),
1833 ));
1834 let comparison = InputObject::new("String_comparison_exp")
1835 .field(InputValue::new("_eq", TypeRef::named("String")))
1836 .field(InputValue::new("_in", TypeRef::named_nn_list("String")));
1837 let ltree = InputObject::new("ltree_comparison_exp")
1840 .field(InputValue::new("_ancestor", TypeRef::named("ltree")))
1841 .field(InputValue::new(
1842 "_ancestor_any",
1843 TypeRef::named_list("ltree"),
1844 ));
1845 let kind = Enum::new("kind_enum").item("leaf").item("branch");
1846 let kind_comparison = InputObject::new("kind_enum_comparison_exp")
1847 .field(InputValue::new("_eq", TypeRef::named("kind_enum")));
1848 let raster_comparison = InputObject::new("raster_comparison_exp").field(InputValue::new(
1849 "_st_intersects_rast",
1850 TypeRef::named("raster"),
1851 ));
1852 let tree_filter = InputObject::new("tree_bool_exp")
1853 .field(InputValue::new(
1854 "rast",
1855 TypeRef::named("raster_comparison_exp"),
1856 ))
1857 .field(InputValue::new(
1858 "path",
1859 TypeRef::named("ltree_comparison_exp"),
1860 ))
1861 .field(InputValue::new(
1862 "kind",
1863 TypeRef::named("kind_enum_comparison_exp"),
1864 ));
1865 let insert = InputObject::new("author_insert_input")
1866 .field(InputValue::new("name", TypeRef::named("String")))
1867 .field(InputValue::new("count", TypeRef::named("Int")));
1868 let query = Object::new("query_root")
1869 .field(
1870 Field::new("author", TypeRef::named_nn_list_nn("author"), |_| {
1871 FieldFuture::new(async move { Ok(Some(async_graphql::Value::List(vec![]))) })
1872 })
1873 .argument(InputValue::new("limit", TypeRef::named("Int")))
1874 .argument(InputValue::new("where", TypeRef::named("author_bool_exp"))),
1875 )
1876 .field(
1877 Field::new("tree", TypeRef::named_nn_list_nn("tree"), |_| {
1878 FieldFuture::new(async move { Ok(Some(async_graphql::Value::List(vec![]))) })
1879 })
1880 .argument(InputValue::new("where", TypeRef::named("tree_bool_exp"))),
1881 );
1882 let mutation = Object::new("mutation_root")
1883 .field(
1884 Field::new("insert_author_one", TypeRef::named("author"), |_| {
1885 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
1886 })
1887 .argument(InputValue::new(
1888 "object",
1889 TypeRef::named_nn("author_insert_input"),
1890 )),
1891 )
1892 .field(
1893 Field::new("insert_author", TypeRef::named("author"), |_| {
1894 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
1895 })
1896 .argument(InputValue::new(
1897 "objects",
1898 TypeRef::named_nn_list_nn("author_insert_input"),
1899 ))
1900 .argument(
1901 InputValue::new("update_columns", TypeRef::named_nn_list_nn("String"))
1902 .default_value(async_graphql::Value::List(Vec::new())),
1903 ),
1904 )
1905 .field(
1906 Field::new(
1907 "update_author",
1908 TypeRef::named("author_mutation_response"),
1909 |_| FieldFuture::new(async move { Ok(None::<async_graphql::Value>) }),
1910 )
1911 .argument(InputValue::new(
1912 "where",
1913 TypeRef::named_nn("author_bool_exp"),
1914 ))
1915 .argument(InputValue::new("_set", TypeRef::named("author_set_input")))
1916 .argument(InputValue::new("_inc", TypeRef::named("author_inc_input"))),
1917 );
1918 let author = Object::new("author").field(Field::new("id", TypeRef::named("Int"), |_| {
1919 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
1920 }));
1921 let tree = Object::new("tree")
1922 .field(Field::new("id", TypeRef::named("Int"), |_| {
1923 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
1924 }))
1925 .field(Field::new("path", TypeRef::named("ltree"), |_| {
1926 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
1927 }));
1928 let set = InputObject::new("author_set_input")
1930 .field(InputValue::new("name", TypeRef::named("String")))
1931 .field(InputValue::new("count", TypeRef::named("Int")));
1932 let inc = InputObject::new("author_inc_input")
1933 .field(InputValue::new("count", TypeRef::named("Int")));
1934 let response = Object::new("author_mutation_response").field(Field::new(
1935 "affected_rows",
1936 TypeRef::named_nn("Int"),
1937 |_| FieldFuture::new(async move { Ok(None::<async_graphql::Value>) }),
1938 ));
1939 Schema::build("query_root", Some("mutation_root"), None)
1940 .register(Scalar::new("ltree"))
1941 .register(Scalar::new("raster"))
1942 .register(raster_comparison)
1943 .register(kind)
1944 .register(kind_comparison)
1945 .register(filter)
1946 .register(comparison)
1947 .register(ltree)
1948 .register(tree_filter)
1949 .register(insert)
1950 .register(set)
1951 .register(inc)
1952 .register(response)
1953 .register(author)
1954 .register(tree)
1955 .register(query)
1956 .register(mutation)
1957 .finish()
1958 .expect("the test schema builds")
1959 }
1960
1961 fn refusals(query: &str, variables: &str) -> Vec<String> {
1962 let request =
1963 async_graphql::Request::new(query).variables(async_graphql::Variables::from_json(
1964 serde_json::from_str(variables).expect("the variables are JSON"),
1965 ));
1966 match prepare(Some(&schema()), request, None) {
1967 Ok(_) => Vec::new(),
1968 Err((_, errors)) => errors.into_iter().map(|e| e.message).collect(),
1969 }
1970 }
1971
1972 fn refusals_with_paths(query: &str, variables: &str) -> Vec<String> {
1974 let request =
1975 async_graphql::Request::new(query).variables(async_graphql::Variables::from_json(
1976 serde_json::from_str(variables).expect("the variables are JSON"),
1977 ));
1978 match prepare(Some(&schema()), request, None) {
1979 Ok(_) => Vec::new(),
1980 Err((_, errors)) => errors.iter().map(path_for).collect(),
1981 }
1982 }
1983
1984 #[test]
1987 fn a_null_written_into_a_comparison_is_refused() {
1988 assert_eq!(
1989 refusals("{ author(where: {name: {_eq: null}}) { id } }", "{}"),
1990 vec!["unexpected null value for type 'String'"]
1991 );
1992 }
1993
1994 #[test]
1995 fn a_variable_standing_for_a_null_is_refused_there_too() {
1996 assert_eq!(
1997 refusals(
1998 "query ($n: String) { author(where: {name: {_eq: $n}}) { id } }",
1999 r#"{"n": null}"#
2000 ),
2001 vec!["unexpected null value for type 'String'"]
2002 );
2003 }
2004
2005 #[test]
2010 fn a_refusal_names_the_argument_it_was_found_in() {
2011 let paths =
2012 |query: &str, variables: &str| -> Vec<String> { refusals_with_paths(query, variables) };
2013 assert_eq!(
2015 paths(
2016 "mutation ($n: Int!) { insert_author_one(object: {name: $n}) { id } }",
2017 r#"{"n": 1}"#
2018 ),
2019 vec!["$.selectionSet.insert_author_one.args.object.name"]
2020 );
2021 assert_eq!(
2023 paths("{ author(where: {name: {_eq: null}}) { id } }", "{}"),
2024 vec!["$.selectionSet.author.args.where.name._eq"]
2025 );
2026 assert_eq!(
2029 paths(
2030 "mutation ($n: Int!) { insert_author(objects: {name: $n}) { id } }",
2031 r#"{"n": 1}"#
2032 ),
2033 vec!["$.selectionSet.insert_author.args.objects[0].name"]
2034 );
2035 assert_eq!(
2037 paths(
2038 "mutation ($n: Int!) { insert_author(objects: [{name: \"a\"}, \
2039 {name: $n}]) { id } }",
2040 r#"{"n": 1}"#
2041 ),
2042 vec!["$.selectionSet.insert_author.args.objects[1].name"]
2043 );
2044 }
2045
2046 #[test]
2049 fn a_null_written_where_an_object_is_required_is_refused_as_one() {
2050 assert_eq!(
2051 refusals(
2052 "mutation { update_author(where: null, _set: {name: \"a\"}) \
2053 { affected_rows } }",
2054 "{}"
2055 ),
2056 vec!["expected an object for type 'author_bool_exp', but found null"]
2057 );
2058 assert_eq!(
2059 refusals_with_paths(
2060 "mutation { update_author(where: null, _set: {name: \"a\"}) \
2061 { affected_rows } }",
2062 "{}"
2063 ),
2064 vec!["$.selectionSet.update_author.args.where"]
2065 );
2066 assert_eq!(
2070 refusals(
2071 "mutation ($w: author_bool_exp!) { update_author(where: $w, \
2072 _set: {name: \"a\"}) { affected_rows } }",
2073 r#"{"w": null}"#
2074 ),
2075 vec!["null value found for non-nullable type: \"author_bool_exp!\""]
2076 );
2077 }
2078
2079 #[test]
2082 fn a_null_written_where_an_object_is_optional_is_left_alone() {
2083 assert_eq!(
2084 refusals("{ author(where: null) { id } }", "{}"),
2085 Vec::<String>::new()
2086 );
2087 }
2088
2089 #[test]
2092 fn a_column_written_by_two_operators_is_refused_before_the_statement() {
2093 assert_eq!(
2094 refusals(
2095 "mutation { update_author(_set: {name: \"a\", count: 1}, \
2096 _inc: {count: 2}, where: {}) { affected_rows } }",
2097 "{}"
2098 ),
2099 vec!["Column found in multiple operators: ['count']."]
2100 );
2101 assert_eq!(
2102 refusals_with_paths(
2103 "mutation { update_author(_set: {count: 1}, _inc: {count: 2}, \
2104 where: {}) { affected_rows } }",
2105 "{}"
2106 ),
2107 vec!["$.selectionSet.update_author.args"]
2108 );
2109 assert_eq!(
2111 refusals(
2112 "mutation { update_author(_set: {name: \"a\"}, _inc: {count: 2}, \
2113 where: {}) { affected_rows } }",
2114 "{}"
2115 ),
2116 Vec::<String>::new()
2117 );
2118 }
2119
2120 #[test]
2123 fn a_request_is_refused_once_however_many_faults_it_has() {
2124 assert_eq!(
2125 refusals("{ author { nope } tree { alsoNope } }", "{}"),
2126 vec!["field 'nope' not found in type: 'author'"]
2127 );
2128 assert_eq!(
2132 refusals(
2133 "mutation { insert_author(objects: [{nope: 1}], unknownArg: 2) { id } }",
2134 "{}"
2135 ),
2136 vec!["field 'nope' not found in type: 'author_insert_input'"]
2137 );
2138 }
2139
2140 #[test]
2143 fn a_raster_that_is_not_hexadecimal_is_refused_as_that() {
2144 let invalid = "invalid hexadecimal representation of raster well known binary format";
2145 assert_eq!(
2146 refusals(
2147 "query ($r: raster) { tree(where: {rast: {_st_intersects_rast: $r}}) { path } }",
2148 r#"{"r": "this is invalid raster value"}"#
2149 ),
2150 vec![invalid]
2151 );
2152 assert_eq!(
2153 refusals_with_paths(
2154 "{ tree(where: {rast: {_st_intersects_rast: \"zz\"}}) { path } }",
2155 "{}"
2156 ),
2157 vec!["$.selectionSet.tree.args.where.rast._st_intersects_rast"]
2158 );
2159 assert_eq!(
2161 refusals(
2162 "{ tree(where: {rast: {_st_intersects_rast: \"abc\"}}) { path } }",
2163 "{}"
2164 ),
2165 vec![invalid]
2166 );
2167 assert!(refusals(
2170 "{ tree(where: {rast: {_st_intersects_rast: \"0100\"}}) { path } }",
2171 "{}"
2172 )
2173 .is_empty());
2174 }
2175
2176 #[test]
2180 fn fragments_that_spread_each_other_are_named_as_a_cycle() {
2181 let cycle = "query { author { ...a } } \
2182 fragment a on author { id ...b } \
2183 fragment b on author { id ...a }";
2184 assert_eq!(
2185 refusals(cycle, "{}"),
2186 vec!["the fragment definition(s) a and b form a cycle"]
2187 );
2188 assert_eq!(
2191 refusals_with_paths(cycle, "{}"),
2192 vec!["$.selectionSet.author.selectionSet.a.selectionSet.b.selectionSet"]
2193 );
2194 assert_eq!(
2196 refusals(
2197 "query { author { ...a } } fragment a on author { id ...a }",
2198 "{}"
2199 ),
2200 vec!["the fragment definition(s) a form a cycle"]
2201 );
2202 assert!(refusals(
2204 "query { author { ...a } articles: author { ...a } } \
2205 fragment a on author { id }",
2206 "{}"
2207 )
2208 .is_empty());
2209 }
2210
2211 #[test]
2214 fn a_number_that_is_not_an_int_is_refused_as_one() {
2215 let bounds = |value: &str| {
2216 format!(
2217 "The value {} lies outside the bounds or is not an integer. \
2218 Maybe it is a float, or is there integer overflow?",
2219 value
2220 )
2221 };
2222 assert_eq!(
2223 refusals(
2224 "mutation { insert_author_one(object: {count: 2147483648}) { id } }",
2225 "{}"
2226 ),
2227 vec![bounds("2.147483648e9")]
2228 );
2229 assert_eq!(
2230 refusals_with_paths(
2231 "mutation { insert_author(objects: [{count: 2147483648}]) { id } }",
2232 "{}"
2233 ),
2234 vec!["$.selectionSet.insert_author.args.objects[0].count"]
2235 );
2236 for good in ["2147483647", "-2147483648", "0"] {
2238 assert!(
2239 refusals(
2240 &format!(
2241 "mutation {{ insert_author_one(object: {{count: {}}}) {{ id }} }}",
2242 good
2243 ),
2244 "{}"
2245 )
2246 .is_empty(),
2247 "{} was refused",
2248 good
2249 );
2250 }
2251 }
2252
2253 #[test]
2256 fn a_number_is_shown_the_way_the_message_spells_it() {
2257 assert_eq!(shown_as_haskell_would(2147483648.0), "2.147483648e9");
2258 assert_eq!(shown_as_haskell_would(1.5), "1.5");
2259 assert_eq!(shown_as_haskell_would(1.0), "1.0");
2260 assert_eq!(shown_as_haskell_would(0.0), "0.0");
2261 assert_eq!(shown_as_haskell_would(-2147483649.0), "-2.147483649e9");
2262 assert_eq!(shown_as_haskell_would(1e-3), "1.0e-3");
2263 assert_eq!(shown_as_haskell_would(9999999.0), "9999999.0");
2266 assert_eq!(shown_as_haskell_would(10000000.0), "1.0e7");
2267 assert_eq!(shown_as_haskell_would(0.1), "0.1");
2268 }
2269
2270 #[test]
2273 fn a_limit_is_a_number_of_rows_and_not_any_integer() {
2274 let refused = |q: &str| refusals(q, "{}");
2275 assert_eq!(
2276 refused("{ author(limit: -1) { id } }"),
2277 vec!["expected a non-negative 32-bit integer for type 'Int', but found an integer"]
2278 );
2279 assert_eq!(
2280 refused("{ author(limit: \"3\") { id } }"),
2281 vec!["expected a non-negative 32-bit integer for type 'Int', but found a string"]
2282 );
2283 assert_eq!(
2284 refusals_with_paths("{ author(limit: -1) { id } }", "{}"),
2285 vec!["$.selectionSet.author.args.limit"]
2286 );
2287 assert_eq!(
2289 refusals(
2290 "query ($n: Int) { author(limit: $n) { id } }",
2291 r#"{"n": -1}"#
2292 ),
2293 vec!["expected a non-negative 32-bit integer for type 'Int', but found an integer"]
2294 );
2295 assert!(refused("{ author(limit: 0) { id } }").is_empty());
2297 assert!(refused("{ author(limit: 2147483647) { id } }").is_empty());
2298 assert!(refused("{ author(limit: null) { id } }").is_empty());
2299 assert_eq!(
2302 refused("{ author(limit: 4294967295) { id } }"),
2303 vec![
2304 "The value 4.294967295e9 lies outside the bounds or is not an \
2305 integer. Maybe it is a float, or is there integer overflow?"
2306 ]
2307 );
2308 }
2309
2310 #[test]
2313 fn a_quoted_enum_value_is_not_a_member_that_is_missing() {
2314 assert_eq!(
2315 refusals("{ tree(where: {kind: {_eq: \"leaf\"}}) { path } }", "{}"),
2316 vec!["expected an enum value for type 'kind_enum', but found a string"]
2317 );
2318 assert_eq!(
2319 refusals("{ tree(where: {kind: {_eq: twig}}) { path } }", "{}"),
2320 vec!["expected one of the values ['branch', 'leaf'] for type 'kind_enum', but found 'twig'"]
2321 );
2322 assert_eq!(
2325 refusals(
2326 "query ($k: kind_enum) { tree(where: {kind: {_eq: $k}}) { path } }",
2327 r#"{"k": "twig"}"#
2328 ),
2329 vec!["expected one of the values ['branch', 'leaf'] for type 'kind_enum', but found 'twig'"]
2330 );
2331 assert!(refusals(
2332 "query ($k: kind_enum) { tree(where: {kind: {_eq: $k}}) { path } }",
2333 r#"{"k": "leaf"}"#
2334 )
2335 .is_empty());
2336 }
2337
2338 #[test]
2342 fn a_document_that_does_not_parse_is_not_a_query() {
2343 assert_eq!(
2344 refusals("{ author(where: {name: {_eq: \"unclosed}}) { id } }", "{}"),
2345 vec!["not a valid graphql query"]
2346 );
2347 assert_eq!(
2348 refusals_with_paths("query { author(", "{}"),
2349 vec!["$.query"]
2350 );
2351 assert_eq!(
2354 refusals("{ nothing_here { id } }", "{}"),
2355 vec!["field 'nothing_here' not found in type: 'query_root'"]
2356 );
2357 }
2358
2359 #[test]
2363 fn a_label_path_with_an_empty_label_is_refused() {
2364 let empty = "Expecting label path: a sequence of zero or more labels \
2365 separated by dots, for example L1.L2.L3";
2366 assert_eq!(
2367 refusals(
2368 "{ tree(where: {path: {_ancestor: \"a.b.\"}}) { path } }",
2369 "{}"
2370 ),
2371 vec![empty]
2372 );
2373 assert_eq!(
2374 refusals(
2375 "{ tree(where: {path: {_ancestor: \".a\"}}) { path } }",
2376 "{}"
2377 ),
2378 vec![empty]
2379 );
2380 assert_eq!(
2381 refusals(
2382 "{ tree(where: {path: {_ancestor: \"a..b\"}}) { path } }",
2383 "{}"
2384 ),
2385 vec![empty]
2386 );
2387 assert_eq!(
2389 refusals_with_paths(
2390 "{ tree(where: {path: {_ancestor_any: [\"a\", \"b.\"]}}) { path } }",
2391 "{}"
2392 ),
2393 vec!["$.selectionSet.tree.args.where.path._ancestor_any[1]"]
2394 );
2395 for good in ["a", "a.b", "", "a-b", "a_b", "1.2", "Ünï", "a@b"] {
2398 assert!(
2399 refusals(
2400 &format!(
2401 "{{ tree(where: {{path: {{_ancestor: \"{}\"}}}}) {{ path }} }}",
2402 good
2403 ),
2404 "{}"
2405 )
2406 .is_empty(),
2407 "{} was refused",
2408 good
2409 );
2410 }
2411 }
2412
2413 #[test]
2414 fn a_variable_that_was_not_given_is_not_a_null() {
2415 assert!(refusals(
2416 "query ($n: String) { author(where: {name: {_eq: $n}}) { id } }",
2417 "{}"
2418 )
2419 .is_empty());
2420 }
2421
2422 #[test]
2424 fn a_null_written_anywhere_else_is_left_alone() {
2425 assert!(refusals(
2426 "mutation { insert_author_one(object: {name: null}) { id } }",
2427 "{}"
2428 )
2429 .is_empty());
2430 }
2431
2432 #[test]
2433 fn a_variable_of_the_wrong_type_is_refused() {
2434 assert_eq!(
2435 refusals("query ($s: String) { author(limit: $s) { id } }", "{}"),
2436 vec!["variable 's' is declared as 'String', but used where 'Int' is expected"]
2437 );
2438 }
2439
2440 #[test]
2441 fn the_check_reaches_inside_an_input_object() {
2442 assert_eq!(
2443 refusals(
2444 "query ($s: String) { author(where: {id: $s}) { id } }",
2445 "{}"
2446 ),
2447 vec!["variable 's' is declared as 'String', but used where 'Int' is expected"]
2448 );
2449 }
2450
2451 #[test]
2452 fn the_check_reaches_inside_a_list() {
2453 assert_eq!(
2454 refusals(
2455 "mutation ($n: Int) { insert_author(objects: [{name: $n}]) { id } }",
2456 "{}"
2457 ),
2458 vec!["variable 'n' is declared as 'Int', but used where 'String' is expected"]
2459 );
2460 }
2461
2462 #[test]
2463 fn a_nullable_variable_cannot_fill_a_non_null_place() {
2464 assert_eq!(
2465 refusals(
2466 "mutation ($a: author_insert_input) { insert_author_one(object: $a) { id } }",
2467 "{}"
2468 ),
2469 vec![
2470 "variable 'a' is declared as 'author_insert_input', but used where \
2471 'author_insert_input!' is expected"
2472 ]
2473 );
2474 }
2475
2476 #[test]
2477 fn a_default_lets_it() {
2478 assert!(refusals(
2481 "mutation ($a: author_insert_input = {name: \"x\"}) \
2482 { insert_author_one(object: $a) { id } }",
2483 "{}"
2484 )
2485 .is_empty());
2486 }
2487
2488 #[test]
2489 fn a_default_of_null_does_not() {
2490 assert_eq!(
2491 refusals(
2492 "mutation ($a: author_insert_input = null) \
2493 { insert_author_one(object: $a) { id } }",
2494 "{}"
2495 ),
2496 vec![
2497 "variable 'a' is declared as 'author_insert_input', but used where \
2498 'author_insert_input!' is expected"
2499 ]
2500 );
2501 }
2502
2503 #[test]
2504 fn a_default_on_the_place_lets_it_too() {
2505 assert!(refusals(
2508 "mutation ($c: [String!]) { insert_author(objects: [], update_columns: $c) { id } }",
2509 "{}"
2510 )
2511 .is_empty());
2512 }
2513
2514 #[test]
2515 fn a_non_null_variable_fits_a_nullable_place() {
2516 assert!(refusals(
2517 "query ($n: Int!) { author(limit: $n) { id } }",
2518 "{\"n\": 1}"
2519 )
2520 .is_empty());
2521 }
2522
2523 #[test]
2524 fn a_non_null_variable_given_null_is_refused() {
2525 assert_eq!(
2526 refusals(
2527 "query ($n: Int! = 1) { author(limit: $n) { id } }",
2528 "{\"n\": null}"
2529 ),
2530 vec!["null value found for non-nullable type: \"Int!\""]
2531 );
2532 }
2533
2534 #[test]
2535 fn a_nullable_variable_given_null_is_not() {
2536 assert!(refusals(
2537 "query ($n: Int) { author(limit: $n) { id } }",
2538 "{\"n\": null}"
2539 )
2540 .is_empty());
2541 }
2542
2543 #[test]
2544 fn a_variable_used_through_a_fragment_is_checked() {
2545 assert_eq!(
2546 refusals(
2547 "query ($s: String) { author { ...rows } } \
2548 fragment rows on author { id }",
2549 "{}"
2550 ),
2551 Vec::<String>::new()
2552 );
2553 assert_eq!(
2554 refusals(
2555 "query ($s: String) { ...roots } \
2556 fragment roots on query_root { author(limit: $s) { id } }",
2557 "{}"
2558 ),
2559 vec!["variable 's' is declared as 'String', but used where 'Int' is expected"]
2560 );
2561 }
2562}
2563
2564#[cfg(test)]
2565mod coercion_tests {
2566 use super::*;
2567 use async_graphql::dynamic::*;
2568
2569 fn schema() -> async_graphql::dynamic::Schema {
2572 let insert = InputObject::new("test_types_insert_input")
2573 .field(InputValue::new("c1_smallint", TypeRef::named("Int")))
2574 .field(InputValue::new("c6_real", TypeRef::named("Float")))
2575 .field(InputValue::new("c20_boolean", TypeRef::named("Boolean")))
2576 .field(InputValue::new("c13_text", TypeRef::named("String")));
2577 let row = Object::new("test_types").field(Field::new(
2578 "c1_smallint",
2579 TypeRef::named("Int"),
2580 |_| FieldFuture::new(async move { Ok(None::<async_graphql::Value>) }),
2581 ));
2582 let query = Object::new("query_root").field(
2583 Field::new(
2584 "test_types",
2585 TypeRef::named_nn_list_nn("test_types"),
2586 |_| FieldFuture::new(async move { Ok(Some(async_graphql::Value::List(vec![]))) }),
2587 )
2588 .argument(InputValue::new("limit", TypeRef::named("Int")))
2589 .argument(InputValue::new("offset", TypeRef::named("Int"))),
2590 );
2591 let mutation = Object::new("mutation_root").field(
2592 Field::new("insert_test_types", TypeRef::named("test_types"), |_| {
2593 FieldFuture::new(async move { Ok(None::<async_graphql::Value>) })
2594 })
2595 .argument(InputValue::new(
2596 "objects",
2597 TypeRef::named_nn_list_nn("test_types_insert_input"),
2598 )),
2599 );
2600 Schema::build("query_root", Some("mutation_root"), None)
2601 .register(insert)
2602 .register(row)
2603 .register(query)
2604 .register(mutation)
2605 .finish()
2606 .expect("the schema builds")
2607 }
2608
2609 fn rewritten(query: &str) -> String {
2610 let schema = schema();
2611 let request = prepare(Some(&schema), async_graphql::Request::new(query), None)
2612 .unwrap_or_else(|(request, _)| request);
2613 let mut request = request;
2614 format!("{:?}", request.parsed_query().expect("the query parses"))
2615 }
2616
2617 #[test]
2618 fn an_offset_written_as_a_string_becomes_a_number() {
2619 let printed = rewritten("{ test_types(offset: \"1\") { c1_smallint } }");
2620 assert!(printed.contains("Number(1)"), "{}", printed);
2621 }
2622
2623 #[test]
2626 fn a_limit_written_as_a_string_is_left_alone() {
2627 let printed = rewritten("{ test_types(limit: \"3\") { c1_smallint } }");
2628 assert!(printed.contains("String(\"3\")"), "{}", printed);
2629 }
2630
2631 #[test]
2632 fn a_column_written_as_a_string_becomes_what_the_column_is() {
2633 let printed = rewritten(
2634 "mutation { insert_test_types(objects: [{ \
2635 c1_smallint: \"32767\", c6_real: \"0.5\", c20_boolean: \"true\" }]) \
2636 { c1_smallint } }",
2637 );
2638 assert!(printed.contains("Number(32767)"), "{}", printed);
2639 assert!(printed.contains("Number(0.5)"), "{}", printed);
2640 assert!(printed.contains("Boolean(true)"), "{}", printed);
2641 }
2642
2643 #[test]
2646 fn a_string_written_where_a_string_belongs_stays_one() {
2647 let printed = rewritten(
2648 "mutation { insert_test_types(objects: [{ c13_text: \"32767\" }]) { c1_smallint } }",
2649 );
2650 assert!(printed.contains("String(\"32767\")"), "{}", printed);
2651 }
2652}