1pub(crate) mod argument;
5pub(crate) mod diagnostics;
6pub(crate) mod directive;
7pub(crate) mod enum_;
8pub(crate) mod field;
9pub(crate) mod fragment;
10pub(crate) mod input_object;
11pub(crate) mod interface;
12pub(crate) mod object;
13pub(crate) mod operation;
14pub(crate) mod scalar;
15pub(crate) mod schema;
16pub(crate) mod selection;
17pub(crate) mod union_;
18pub(crate) mod value;
19pub(crate) mod variable;
20
21use crate::collections::HashMap;
22use crate::collections::HashSet;
23use crate::collections::IndexSet;
24use crate::coordinate::SchemaCoordinate;
25use crate::diagnostic::CliReport;
26use crate::diagnostic::Diagnostic;
27use crate::diagnostic::ToCliReport;
28use crate::executable::BuildError as ExecutableBuildError;
29use crate::executable::ConflictingFieldArgument;
30use crate::executable::ConflictingFieldName;
31use crate::executable::ConflictingFieldType;
32#[cfg(doc)]
33use crate::executable::ExecutableDocument;
34use crate::executable::VariableDefinition;
35use crate::parser::SourceMap;
36use crate::parser::SourceSpan;
37use crate::response::GraphQLError;
38use crate::schema::BuildError as SchemaBuildError;
39use crate::schema::Implementers;
40use crate::Name;
41use crate::Node;
42use crate::Schema;
43use std::fmt;
44use std::sync::Arc;
45use std::sync::OnceLock;
46
47#[derive(Debug, Clone, Eq, PartialEq)]
64#[repr(transparent)]
65pub struct Valid<T>(pub(crate) T);
66
67impl<T> Valid<T> {
68 pub fn assume_valid(document: T) -> Self {
78 Self(document)
79 }
80
81 pub fn assume_valid_ref(document: &T) -> &Self {
91 let ptr: *const T = document;
92 let ptr: *const Valid<T> = ptr.cast();
93 unsafe { &*ptr }
96 }
97
98 pub fn into_inner(self) -> T {
100 self.0
101 }
102}
103
104impl<T> std::ops::Deref for Valid<T> {
105 type Target = T;
106
107 fn deref(&self) -> &Self::Target {
108 &self.0
109 }
110}
111
112impl<T> AsRef<T> for Valid<T> {
113 fn as_ref(&self) -> &T {
114 &self.0
115 }
116}
117
118impl<T: fmt::Display> fmt::Display for Valid<T> {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 self.0.fmt(f)
121 }
122}
123
124#[derive(Debug)]
126pub(crate) struct ExecutableValidationContext<'a> {
127 schema: Option<&'a Schema>,
129 implementers_map: OnceLock<HashMap<Name, Implementers>>,
131}
132
133impl<'a> ExecutableValidationContext<'a> {
134 pub(crate) fn new(schema: Option<&'a Schema>) -> Self {
135 Self {
136 schema,
137 implementers_map: Default::default(),
138 }
139 }
140
141 pub(crate) fn schema(&self) -> Option<&'a Schema> {
143 self.schema
144 }
145
146 pub(crate) fn implementers_map(&self) -> &HashMap<Name, Implementers> {
148 self.implementers_map.get_or_init(|| {
149 self.schema
150 .map(|schema| schema.implementers_map())
151 .unwrap_or_default()
152 })
153 }
154
155 pub(crate) fn operation_context<'o>(
157 &'o self,
158 variables: &'o [Node<VariableDefinition>],
159 ) -> OperationValidationContext<'o> {
160 OperationValidationContext {
161 executable: self,
162 variables,
163 validated_fragments: HashSet::default(),
164 }
165 }
166}
167
168#[derive(Debug)]
170pub(crate) struct OperationValidationContext<'a> {
171 executable: &'a ExecutableValidationContext<'a>,
174 pub(crate) variables: &'a [Node<VariableDefinition>],
176 pub(crate) validated_fragments: HashSet<Name>,
177}
178
179impl<'a> OperationValidationContext<'a> {
180 pub(crate) fn schema(&self) -> Option<&'a Schema> {
181 self.executable.schema
182 }
183
184 pub(crate) fn implementers_map(&self) -> &HashMap<Name, Implementers> {
186 self.executable.implementers_map()
187 }
188}
189
190pub struct WithErrors<T> {
197 pub partial: T,
201
202 pub errors: DiagnosticList,
205}
206
207impl<T> fmt::Debug for WithErrors<T> {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 self.errors.fmt(f)
210 }
211}
212
213impl<T> fmt::Display for WithErrors<T> {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 self.errors.fmt(f)
216 }
217}
218
219#[derive(Debug, Clone)]
229pub(crate) struct SuspectedValidationBug {
230 pub message: String,
231 pub location: Option<SourceSpan>,
232}
233
234#[derive(Clone)]
236pub struct DiagnosticList {
237 pub(crate) sources: SourceMap,
238 diagnostics_data: Vec<DiagnosticData>,
239}
240
241#[derive(thiserror::Error, Debug, Clone)]
243#[error("{details}")]
244pub struct DiagnosticData {
245 location: Option<SourceSpan>,
246 details: Details,
247}
248
249#[derive(thiserror::Error, Debug, Clone)]
250pub(crate) enum Details {
251 #[error("{message}")]
252 ParserLimit { message: String },
253 #[error("syntax error: {message}")]
254 SyntaxError { message: String },
255 #[error("{0}")]
256 SchemaBuildError(SchemaBuildError),
257 #[error("{0}")]
258 ExecutableBuildError(ExecutableBuildError),
259 #[error(transparent)]
261 CompilerDiagnostic(diagnostics::DiagnosticData),
262 #[error("too much recursion")]
263 RecursionLimitError,
264}
265
266impl DiagnosticData {
267 #[doc(hidden)]
270 pub fn unstable_error_name(&self) -> Option<&'static str> {
271 match &self.details {
272 Details::CompilerDiagnostic(diagnostic) => {
273 use diagnostics::DiagnosticData::*;
274 Some(match diagnostic {
275 RecursionError { .. } => "RecursionError",
276 UniqueVariable { .. } => "UniqueVariable",
277 UniqueArgument { .. } => "UniqueArgument",
278 UniqueInputValue { .. } => "UniqueInputValue",
279 UndefinedArgument { .. } => "UndefinedArgument",
280 UndefinedDefinition { .. } => "UndefinedDefinition",
281 UndefinedDirective { .. } => "UndefinedDirective",
282 UndefinedVariable { .. } => "UndefinedVariable",
283 UndefinedFragment { .. } => "UndefinedFragment",
284 UndefinedEnumValue { .. } => "UndefinedEnumValue",
285 UndefinedInputValue { .. } => "UndefinedInputValue",
286 MissingInterfaceField { .. } => "MissingInterfaceField",
287 RequiredArgument { .. } => "RequiredArgument",
288 RequiredField { .. } => "RequiredField",
289 TransitiveImplementedInterfaces { .. } => "TransitiveImplementedInterfaces",
290 OutputType { .. } => "OutputType",
291 InputType { .. } => "InputType",
292 VariableInputType { .. } => "VariableInputType",
293 QueryRootOperationType => "QueryRootOperationType",
294 UnusedVariable { .. } => "UnusedVariable",
295 RootOperationObjectType { .. } => "RootOperationObjectType",
296 UnionMemberObjectType { .. } => "UnionMemberObjectType",
297 UnsupportedLocation { .. } => "UnsupportedLocation",
298 UnsupportedValueType { .. } => "UnsupportedValueType",
299 IntCoercionError { .. } => "IntCoercionError",
300 FloatCoercionError { .. } => "FloatCoercionError",
301 UniqueDirective { .. } => "UniqueDirective",
302 MissingSubselection { .. } => "MissingSubselection",
303 InvalidFragmentTarget { .. } => "InvalidFragmentTarget",
304 InvalidFragmentSpread { .. } => "InvalidFragmentSpread",
305 UnusedFragment { .. } => "UnusedFragment",
306 DisallowedVariableUsage { .. } => "DisallowedVariableUsage",
307 RecursiveDirectiveDefinition { .. } => "RecursiveDirectiveDefinition",
308 RecursiveInterfaceDefinition { .. } => "RecursiveInterfaceDefinition",
309 RecursiveInputObjectDefinition { .. } => "RecursiveInputObjectDefinition",
310 RecursiveFragmentDefinition { .. } => "RecursiveFragmentDefinition",
311 DeeplyNestedType { .. } => "DeeplyNestedType",
312 EmptyFieldSet { .. } => "EmptyFieldSet",
313 EmptyValueSet { .. } => "EmptyValueSet",
314 EmptyMemberSet { .. } => "EmptyMemberSet",
315 EmptyInputValueSet { .. } => "EmptyInputValueSet",
316 ReservedName { .. } => "ReservedName",
317 })
318 }
319 Details::ExecutableBuildError(error) => Some(match error {
320 ExecutableBuildError::UndefinedField { .. } => "UndefinedField",
321 ExecutableBuildError::TypeSystemDefinition { .. } => "TypeSystemDefinition",
322 ExecutableBuildError::AmbiguousAnonymousOperation => "AmbiguousAnonymousOperation",
323 ExecutableBuildError::OperationNameCollision { .. } => "OperationNameCollision",
324 ExecutableBuildError::FragmentNameCollision { .. } => "FragmentNameCollision",
325 ExecutableBuildError::UndefinedRootOperation { .. } => "UndefinedRootOperation",
326 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition { .. } => {
327 "UndefinedTypeInNamedFragmentTypeCondition"
328 }
329 ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition { .. } => {
330 "UndefinedTypeInInlineFragmentTypeCondition"
331 }
332 ExecutableBuildError::SubselectionOnScalarType { .. } => "SubselectionOnScalarType",
333 ExecutableBuildError::SubselectionOnEnumType { .. } => "SubselectionOnEnumType",
334 ExecutableBuildError::SubscriptionUsesMultipleFields { .. } => {
335 "SubscriptionUsesMultipleFields"
336 }
337 ExecutableBuildError::SubscriptionUsesIntrospection { .. } => {
338 "SubscriptionUsesIntrospection"
339 }
340 ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => {
341 "SubscriptionUsesConditionalSelection"
342 }
343 ExecutableBuildError::ConflictingFieldType(_) => "ConflictingFieldType",
344 ExecutableBuildError::ConflictingFieldName(_) => "ConflictingFieldName",
345 ExecutableBuildError::ConflictingFieldArgument(_) => "ConflictingFieldArgument",
346 }),
347 Details::RecursionLimitError => Some("RecursionLimitError"),
348 _ => None,
349 }
350 }
351
352 #[doc(hidden)]
357 pub fn unstable_compat_message(&self) -> Option<String> {
358 match &self.details {
359 Details::CompilerDiagnostic(diagnostic) => {
360 use diagnostics::DiagnosticData::*;
361 match diagnostic {
362 RecursionError { .. } => None,
363 UniqueVariable { name, .. } => Some(format!(
364 r#"There can be only one variable named "${name}"."#
365 )),
366 UniqueArgument { name, .. } => {
367 Some(format!(r#"There can be only one argument named "{name}"."#))
368 }
369 UniqueInputValue { .. } => None,
370 UndefinedArgument {
371 name, coordinate, ..
372 } => Some(format!(
373 r#"Unknown argument "{name}" on field "{coordinate}"."#
374 )),
375 UndefinedDefinition { name } => Some(format!(r#"Unknown type "{name}"."#)),
376 UndefinedDirective { name } => Some(format!(r#"Unknown directive "@{name}"."#)),
377 UndefinedVariable { name } => {
378 Some(format!(r#"Variable "${name}" is not defined."#))
379 }
380 UndefinedFragment { name } => Some(format!(r#"Unknown fragment "{name}"."#)),
381 UndefinedEnumValue {
382 value, definition, ..
383 } => Some(format!(
384 r#"Value "{value}" does not exist in "{definition}" enum."#
385 )),
386 UndefinedInputValue {
387 value, definition, ..
388 } => Some(format!(
389 r#"Field "{value}" is not defined by type "{definition}"."#
390 )),
391 MissingInterfaceField { .. } => None,
392 RequiredArgument {
393 name,
394 coordinate,
395 expected_type,
396 ..
397 } => match coordinate {
398 SchemaCoordinate::FieldArgument(coordinate) => Some(format!(
399 r#"Field "{}" argument "{name}" of type "{expected_type}" is required, but it was not provided."#,
400 coordinate.field,
401 )),
402 SchemaCoordinate::DirectiveArgument(coordinate) => Some(format!(
403 r#"Directive "@{}" argument "{name}" of type "{expected_type}" is required, but it was not provided."#,
404 coordinate.directive,
405 )),
406 _ => None,
408 },
409 RequiredField {
410 coordinate,
411 expected_type,
412 ..
413 } => Some(format!(
414 r#"Field "{coordinate}" of required type "{expected_type}" was not provided."#
415 )),
416 TransitiveImplementedInterfaces { .. } => None,
417 OutputType { .. } => None,
418 InputType { .. } => None,
419 VariableInputType { name, ty, .. } => Some(format!(
420 r#"Variable "${name}" cannot be non-input type "{ty}"."#
421 )),
422 QueryRootOperationType => None,
423 UnusedVariable { name } => {
424 Some(format!(r#"Variable "${name}" is never used."#))
425 }
426 RootOperationObjectType { .. } => None,
427 UnionMemberObjectType { .. } => None,
428 UnsupportedLocation { name, location, .. } => Some(format!(
429 r#"Directive "@{name}" may not be used on {location}."#
430 )),
431 UnsupportedValueType { ty, value, .. } => Some(format!(
432 r#"{} cannot represent value: {value}"#,
433 ty.inner_named_type()
434 )),
435 IntCoercionError { value } => {
436 let is_integer = value
437 .chars()
438 .all(|c| matches!(c, '-' | '+' | 'e' | '0'..='9'));
440 if is_integer {
441 Some(format!(
442 r#"Int cannot represent non 32-bit signed integer value: {value}"#
443 ))
444 } else {
445 Some(format!(
446 r#"Int cannot represent non-integer value: {value}"#
447 ))
448 }
449 }
450 FloatCoercionError { value } => Some(format!(
451 r#"Float cannot represent non numeric value: {value}"#
452 )),
453 UniqueDirective { name, .. } => Some(format!(
454 r#"The directive "@{name}" can only be used once at this location."#
455 )),
456 MissingSubselection { coordinate, .. } => Some(format!(
457 r#"Field "{field}" of type "{ty}" must have a selection of subfields. Did you mean "{field} {{ ... }}"?"#,
458 ty = coordinate.ty,
459 field = coordinate.attribute,
460 )),
461 InvalidFragmentTarget { name, ty } => {
462 if let Some(name) = name {
463 Some(format!(
464 r#"Fragment "{name}" cannot condition on non composite type "{ty}"."#
465 ))
466 } else {
467 Some(format!(
468 r#"Fragment cannot condition on non composite type "{ty}"."#
469 ))
470 }
471 }
472 InvalidFragmentSpread {
473 name,
474 type_name,
475 type_condition,
476 ..
477 } => {
478 if let Some(name) = name {
479 Some(format!(
480 r#"Fragment "{name}" cannot be spread here as objects of type "{type_name}" can never be of type "{type_condition}"."#
481 ))
482 } else {
483 Some(format!(
484 r#"Fragment cannot be spread here as objects of type "{type_name}" can never be of type "{type_condition}"."#
485 ))
486 }
487 }
488 UnusedFragment { name } => Some(format!(r#"Fragment "{name}" is never used."#)),
489 DisallowedVariableUsage {
490 variable,
491 variable_type,
492 argument_type,
493 ..
494 } => Some(format!(
495 r#"Variable "${variable}" of type "{variable_type}" used in position expecting type "{argument_type}"."#
496 )),
497 RecursiveDirectiveDefinition { .. } => None,
498 RecursiveInterfaceDefinition { .. } => None,
499 RecursiveInputObjectDefinition { .. } => None,
500 RecursiveFragmentDefinition { name, trace, .. } => Some(format!(
501 r#"Cannot spread fragment "{name}" within itself via {}"#,
502 trace
504 .iter()
505 .map(|spread| format!(r#""{}""#, spread.fragment_name))
506 .collect::<Vec<_>>()
507 .join(", "),
508 )),
509 DeeplyNestedType { .. } => None,
510 EmptyFieldSet { .. } => None,
511 EmptyValueSet { .. } => None,
512 EmptyMemberSet { .. } => None,
513 EmptyInputValueSet { .. } => None,
514 ReservedName { .. } => None,
515 }
516 }
517 Details::ExecutableBuildError(error) => match error {
518 ExecutableBuildError::UndefinedField {
519 type_name,
520 field_name,
521 ..
522 } => Some(format!(
523 r#"Cannot query field "{field_name}" on type "{type_name}"."#
524 )),
525 ExecutableBuildError::TypeSystemDefinition { name, .. } => {
526 if let Some(name) = name {
527 Some(format!(r#"The "{name}" definition is not executable."#))
528 } else {
529 Some("The schema definition is not executable.".to_string())
531 }
532 }
533 ExecutableBuildError::AmbiguousAnonymousOperation => {
534 Some("This anonymous operation must be the only defined operation.".to_string())
535 }
536 ExecutableBuildError::OperationNameCollision {
537 name_at_previous_location,
538 } => Some(format!(
539 r#"There can be only one operation named "{name_at_previous_location}"."#
540 )),
541 ExecutableBuildError::FragmentNameCollision {
542 name_at_previous_location,
543 } => Some(format!(
544 r#"There can be only one fragment named "{name_at_previous_location}"."#
545 )),
546 ExecutableBuildError::UndefinedRootOperation { operation_type } => Some(format!(
547 r#"The schema has no "{operation_type}" root type defined"#
549 )),
550 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition {
551 type_name,
552 ..
553 }
554 | ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition {
555 type_name,
556 ..
557 } => Some(format!(r#"Unknown type "{type_name}"."#)),
558 ExecutableBuildError::SubselectionOnScalarType { type_name, path }
559 | ExecutableBuildError::SubselectionOnEnumType { type_name, path } => {
560 #[allow(clippy::manual_map)]
561 if let Some(field) = path.nested_fields.last() {
562 Some(format!(
563 r#"Field "{field}" must not have a selection since type "{type_name}" has no subfields"#
564 ))
565 } else {
566 None }
568 }
569 ExecutableBuildError::SubscriptionUsesMultipleFields { name, .. } => {
570 if let Some(name) = name {
571 Some(format!(
572 r#"Subscription "{name}" must select only one top level field."#
573 ))
574 } else {
575 Some(
576 "Anonymous Subscription must select only one top level field."
577 .to_string(),
578 )
579 }
580 }
581 ExecutableBuildError::SubscriptionUsesIntrospection { name, .. } => {
582 if let Some(name) = name {
583 Some(format!(
584 r#"Subscription "{name}" must not select an introspection top level field."#
585 ))
586 } else {
587 Some("Anonymous Subscription must not select an introspection top level field."
588 .to_string())
589 }
590 }
591 ExecutableBuildError::SubscriptionUsesConditionalSelection { name, .. } => {
592 if let Some(name) = name {
593 Some(format!(
594 r#"Subscription "{name}" can not specify @skip or @include on root fields."#
595 ))
596 } else {
597 Some(
598 "Anonymous Subscription can not specify @skip or @include on root fields."
599 .to_string(),
600 )
601 }
602 }
603 ExecutableBuildError::ConflictingFieldType(inner) => {
604 let ConflictingFieldType {
605 alias,
606 original_type,
607 conflicting_type,
608 ..
609 } = &**inner;
610 Some(format!(
611 r#"Fields "{alias}" conflict because they return conflicting types "{original_type} and "{conflicting_type}". Use different aliases on the fields to fetch both if this was intentional."#
612 ))
613 }
614 ExecutableBuildError::ConflictingFieldName(inner) => {
615 let ConflictingFieldName {
616 alias,
617 original_selection,
618 conflicting_selection,
619 ..
620 } = &**inner;
621 Some(format!(
622 r#"Fields "{alias}" conflict because "{}" and "{}" are different fields. Use different aliases on the fields to fetch both if this was intentional."#,
623 original_selection.attribute, conflicting_selection.attribute
624 ))
625 }
626 ExecutableBuildError::ConflictingFieldArgument(inner) => {
627 let ConflictingFieldArgument { alias, .. } = &**inner;
628 Some(format!(
629 r#"Fields "{alias}" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."#
630 ))
631 }
632 },
633 _ => None,
634 }
635 }
636}
637
638impl ToCliReport for DiagnosticData {
639 fn location(&self) -> Option<SourceSpan> {
640 self.location
641 }
642
643 fn report(&self, report: &mut CliReport) {
644 if let Details::CompilerDiagnostic(diagnostic) = &self.details {
645 diagnostic.report(self.location, report);
646 return;
647 }
648
649 match &self.details {
655 Details::CompilerDiagnostic(_) => unreachable!(),
656 Details::ParserLimit { message, .. } => report.with_label_opt(self.location, message),
657 Details::SyntaxError { message, .. } => report.with_label_opt(self.location, message),
658 Details::SchemaBuildError(err) => match err {
659 SchemaBuildError::ExecutableDefinition { .. } => report.with_label_opt(
660 self.location,
661 "remove this definition, or use `parse_mixed()`",
662 ),
663 SchemaBuildError::SchemaDefinitionCollision {
664 previous_location, ..
665 } => {
666 report.with_label_opt(*previous_location, "previous `schema` definition here");
667 report.with_label_opt(self.location, "`schema` redefined here");
668 report.with_help(
669 "merge this definition with the previous one, or use `extend schema`",
670 );
671 }
672 SchemaBuildError::DirectiveDefinitionCollision {
673 previous_location,
674 name,
675 ..
676 } => {
677 report.with_label_opt(
678 *previous_location,
679 format_args!("previous definition of `@{name}` here"),
680 );
681 report.with_label_opt(self.location, format_args!("`@{name}` redefined here"));
682 report.with_help("remove or rename one of the definitions");
683 }
684 SchemaBuildError::TypeDefinitionCollision {
685 previous_location,
686 name,
687 ..
688 } => {
689 report.with_label_opt(
690 *previous_location,
691 format_args!("previous definition of `{name}` here"),
692 );
693 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
694 report.with_help("remove or rename one of the definitions, or use `extend`");
695 }
696 SchemaBuildError::BuiltInScalarTypeRedefinition => {
697 report.with_label_opt(self.location, "remove this scalar definition");
698 }
699 SchemaBuildError::OrphanSchemaExtension => {
700 report.with_label_opt(self.location, "extension here")
701 }
702 SchemaBuildError::OrphanTypeExtension { .. } => {
703 report.with_label_opt(self.location, "extension here")
704 }
705 SchemaBuildError::TypeExtensionKindMismatch { def_location, .. } => {
706 report.with_label_opt(*def_location, "type definition");
707 report.with_label_opt(self.location, "extension here")
708 }
709 SchemaBuildError::DuplicateRootOperation {
710 previous_location,
711 operation_type,
712 ..
713 } => {
714 report.with_label_opt(
715 *previous_location,
716 format_args!("previous definition of `{operation_type}` here"),
717 );
718 report.with_label_opt(
719 self.location,
720 format_args!("`{operation_type}` redefined here"),
721 );
722 }
723 SchemaBuildError::DuplicateImplementsInterfaceInObject {
724 name_at_previous_location,
725 ..
726 }
727 | SchemaBuildError::DuplicateImplementsInterfaceInInterface {
728 name_at_previous_location,
729 ..
730 } => {
731 let previous_location = &name_at_previous_location.location();
732 let name = name_at_previous_location;
733 report.with_label_opt(
734 *previous_location,
735 format_args!("previous implementation of `{name}` here"),
736 );
737 report.with_label_opt(
738 self.location,
739 format_args!("`{name}` implemented again here"),
740 );
741 }
742 SchemaBuildError::ObjectFieldNameCollision {
743 name_at_previous_location,
744 ..
745 }
746 | SchemaBuildError::InterfaceFieldNameCollision {
747 name_at_previous_location,
748 ..
749 }
750 | SchemaBuildError::EnumValueNameCollision {
751 name_at_previous_location,
752 ..
753 }
754 | SchemaBuildError::UnionMemberNameCollision {
755 name_at_previous_location,
756 ..
757 }
758 | SchemaBuildError::InputFieldNameCollision {
759 name_at_previous_location,
760 ..
761 } => {
762 let previous_location = &name_at_previous_location.location();
763 let name = name_at_previous_location;
764 report.with_label_opt(
765 *previous_location,
766 format_args!("previous definition of `{name}` here"),
767 );
768 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
769 }
770 },
771 Details::ExecutableBuildError(err) => match err {
772 ExecutableBuildError::TypeSystemDefinition { .. } => report.with_label_opt(
773 self.location,
774 "remove this definition, or use `parse_mixed()`",
775 ),
776 ExecutableBuildError::AmbiguousAnonymousOperation => {
777 report.with_label_opt(self.location, "provide a name for this definition");
778 report.with_help(
779 "GraphQL requires operations to be named if the document has more than one",
780 );
781 }
782 ExecutableBuildError::OperationNameCollision {
783 name_at_previous_location,
784 ..
785 }
786 | ExecutableBuildError::FragmentNameCollision {
787 name_at_previous_location,
788 ..
789 } => {
790 let previous_location = &name_at_previous_location.location();
791 let name = name_at_previous_location;
792 report.with_label_opt(
793 *previous_location,
794 format_args!("previous definition of `{name}` here"),
795 );
796 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
797 }
798 ExecutableBuildError::UndefinedRootOperation { operation_type, .. } => {
799 report.with_label_opt(
800 self.location,
801 format_args!(
802 "`{operation_type}` is not defined in the schema and is therefore not supported"
803 ),
804 );
805 report.with_help(format_args!(
806 "consider defining a `{operation_type}` root operation type in your schema"
807 ))
808 }
809 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition { .. } => {
810 report.with_label_opt(self.location, "type condition here")
811 }
812 ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition {
813 path, ..
814 } => {
815 report.with_label_opt(self.location, "type condition here");
816 report.with_note(format_args!("path to the inline fragment: `{path} → ...`"))
817 }
818 ExecutableBuildError::SubselectionOnScalarType { path, .. }
819 | ExecutableBuildError::SubselectionOnEnumType { path, .. } => {
820 report.with_label_opt(self.location, "remove subselections here");
821 report.with_note(format_args!("path to the field: `{path}`"))
822 }
823 ExecutableBuildError::UndefinedField {
824 field_name,
825 type_name,
826 path,
827 ..
828 } => {
829 report.with_label_opt(
830 self.location,
831 format_args!("field `{field_name}` selected here"),
832 );
833 report.with_label_opt(
834 type_name.location(),
835 format_args!("type `{type_name}` defined here"),
836 );
837 report.with_note(format_args!("path to the field: `{path}`"))
838 }
839 ExecutableBuildError::SubscriptionUsesMultipleFields { fields, .. } => {
840 report.with_label_opt(
841 self.location,
842 format_args!("subscription with {} root fields", fields.len()),
843 );
844 report.with_help(format_args!(
845 "There are {} root fields: {}. This is not allowed.",
846 fields.len(),
847 CommaSeparated(fields)
848 ));
849 }
850 ExecutableBuildError::SubscriptionUsesIntrospection { field, .. } => {
851 report.with_label_opt(
852 self.location,
853 format_args!("{field} is an introspection field"),
854 );
855 }
856 ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => {
857 report.with_label_opt(self.location, "conditional directive used here");
858 }
859 ExecutableBuildError::ConflictingFieldType(inner) => {
860 let ConflictingFieldType {
861 alias,
862 original_location,
863 original_coordinate,
864 original_type,
865 conflicting_location,
866 conflicting_coordinate,
867 conflicting_type,
868 } = &**inner;
869 report.with_label_opt(
870 *original_location,
871 format_args!(
872 "`{alias}` is selected from `{original_coordinate}: {original_type}` here"
873 ),
874 );
875 report.with_label_opt(
876 *conflicting_location,
877 format_args!("`{alias}` is selected from `{conflicting_coordinate}: {conflicting_type}` here"),
878 );
879 }
880 ExecutableBuildError::ConflictingFieldArgument(inner) => {
881 let ConflictingFieldArgument {
882 alias,
883 original_location,
884 original_coordinate,
885 original_value,
886 conflicting_location,
887 conflicting_coordinate: _,
888 conflicting_value,
889 } = &**inner;
890 let argument = &original_coordinate.argument;
891 match (original_value, conflicting_value) {
892 (Some(_), Some(_)) => {
893 report.with_label_opt(
894 *original_location,
895 format_args!(
896 "`{original_coordinate}` is used with one argument value here"
897 ),
898 );
899 report.with_label_opt(
900 *conflicting_location,
901 "but a different value here",
902 );
903 }
904 (Some(_), None) => {
905 report.with_label_opt(
906 *original_location,
907 format!("`{alias}` is selected with argument `{argument}` here",),
908 );
909 report.with_label_opt(
910 *conflicting_location,
911 format!("but argument `{argument}` is not provided here"),
912 );
913 }
914 (None, Some(_)) => {
915 report.with_label_opt(
916 *conflicting_location,
917 format!("`{alias}` is selected with argument `{argument}` here",),
918 );
919 report.with_label_opt(
920 *original_location,
921 format!("but argument `{argument}` is not provided here"),
922 );
923 }
924 (None, None) => unreachable!(),
925 }
926 report.with_help("The same name cannot be selected multiple times with different arguments, because it's not clear which set of arguments should be used to fill the response. If you intend to use diverging arguments, consider adding an alias to differentiate");
927 }
928 ExecutableBuildError::ConflictingFieldName(inner) => {
929 let ConflictingFieldName {
930 alias: field,
931 original_selection,
932 original_location,
933 conflicting_selection,
934 conflicting_location,
935 } = &**inner;
936 report.with_label_opt(
937 *original_location,
938 format_args!("`{field}` is selected from `{original_selection}` here"),
939 );
940 report.with_label_opt(
941 *conflicting_location,
942 format_args!("`{field}` is selected from `{conflicting_selection}` here"),
943 );
944
945 report.with_help("Both fields may be present on the schema type, so it's not clear which one should be used to fill the response");
946 }
947 },
948 Details::RecursionLimitError => {}
949 }
950 }
951}
952
953impl Diagnostic<'_, DiagnosticData> {
954 #[doc(hidden)]
959 pub fn unstable_to_json_compat(&self) -> GraphQLError {
960 GraphQLError::new(
961 self.error
962 .unstable_compat_message()
963 .unwrap_or_else(|| self.error.to_string()),
964 self.error.location(),
965 self.sources,
966 )
967 }
968}
969
970impl DiagnosticList {
971 pub fn new(sources: SourceMap) -> Self {
973 Self {
974 sources,
975 diagnostics_data: Vec::new(),
976 }
977 }
978
979 pub fn is_empty(&self) -> bool {
980 self.diagnostics_data.is_empty()
981 }
982
983 pub fn len(&self) -> usize {
984 self.diagnostics_data.len()
985 }
986
987 pub fn iter(
988 &self,
989 ) -> impl DoubleEndedIterator<Item = Diagnostic<'_, DiagnosticData>> + ExactSizeIterator {
990 self.diagnostics_data
991 .iter()
992 .map(|data| data.to_diagnostic(&self.sources))
993 }
994
995 pub(crate) fn push(&mut self, location: Option<SourceSpan>, details: impl Into<Details>) {
996 self.diagnostics_data.push(DiagnosticData {
997 location,
998 details: details.into(),
999 })
1000 }
1001
1002 pub fn merge(&mut self, other: Self) {
1004 if !Arc::ptr_eq(&self.sources, &other.sources) {
1005 let sources = Arc::make_mut(&mut self.sources);
1006 for (&k, v) in &*other.sources {
1007 sources.entry(k).or_insert_with(|| v.clone());
1008 }
1009 }
1010 self.diagnostics_data.extend(other.diagnostics_data);
1011 self.sort()
1012 }
1013
1014 fn sort(&mut self) {
1015 self.diagnostics_data
1016 .sort_by_key(|err| err.location.map(|loc| (loc.file_id(), loc.offset())));
1017 }
1018
1019 pub(crate) fn into_result(mut self) -> Result<(), Self> {
1020 if self.diagnostics_data.is_empty() {
1021 Ok(())
1022 } else {
1023 self.sort();
1024 Err(self)
1025 }
1026 }
1027
1028 pub(crate) fn into_result_with<T>(self, value: T) -> Result<T, WithErrors<T>> {
1029 match self.into_result() {
1030 Ok(()) => Ok(value),
1031 Err(errors) => Err(WithErrors {
1032 partial: value,
1033 errors,
1034 }),
1035 }
1036 }
1037
1038 pub(crate) fn into_valid_result<T>(self, value: T) -> Result<Valid<T>, WithErrors<T>> {
1039 match self.into_result() {
1040 Ok(()) => Ok(Valid(value)),
1041 Err(errors) => Err(WithErrors {
1042 partial: value,
1043 errors,
1044 }),
1045 }
1046 }
1047}
1048
1049impl fmt::Display for DiagnosticList {
1051 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1052 for diagnostic in self.iter() {
1053 fmt::Display::fmt(&diagnostic, f)?
1054 }
1055 Ok(())
1056 }
1057}
1058
1059impl fmt::Debug for DiagnosticList {
1061 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1062 for diagnostic in self.iter() {
1063 fmt::Debug::fmt(&diagnostic, f)?
1064 }
1065 Ok(())
1066 }
1067}
1068
1069impl From<SchemaBuildError> for Details {
1070 fn from(value: SchemaBuildError) -> Self {
1071 Details::SchemaBuildError(value)
1072 }
1073}
1074
1075impl From<ExecutableBuildError> for Details {
1076 fn from(value: ExecutableBuildError) -> Self {
1077 Details::ExecutableBuildError(value)
1078 }
1079}
1080
1081impl From<diagnostics::DiagnosticData> for Details {
1082 fn from(value: diagnostics::DiagnosticData) -> Self {
1083 Details::CompilerDiagnostic(value)
1084 }
1085}
1086
1087const DEFAULT_RECURSION_LIMIT: usize = 32;
1088
1089#[derive(Debug, Clone, thiserror::Error)]
1090#[error("Recursion limit reached")]
1091#[non_exhaustive]
1092struct RecursionLimitError {}
1093
1094#[derive(Debug)]
1096struct DepthCounter {
1097 value: usize,
1098 high: usize,
1099 limit: usize,
1100}
1101
1102impl DepthCounter {
1103 fn new() -> Self {
1104 Self {
1105 value: 0,
1106 high: 0,
1107 limit: DEFAULT_RECURSION_LIMIT,
1108 }
1109 }
1110
1111 fn with_limit(mut self, limit: usize) -> Self {
1112 self.limit = limit;
1113 self
1114 }
1115
1116 pub(crate) fn guard(&mut self) -> DepthGuard<'_> {
1118 DepthGuard(self)
1119 }
1120}
1121
1122struct DepthGuard<'a>(&'a mut DepthCounter);
1127
1128impl DepthGuard<'_> {
1129 fn increment(&mut self) -> Result<DepthGuard<'_>, RecursionLimitError> {
1131 self.0.value += 1;
1132 self.0.high = self.0.high.max(self.0.value);
1133 if self.0.value > self.0.limit {
1134 Err(RecursionLimitError {})
1135 } else {
1136 Ok(DepthGuard(self.0))
1137 }
1138 }
1139}
1140
1141impl Drop for DepthGuard<'_> {
1142 fn drop(&mut self) {
1143 self.0.value = self.0.value.saturating_sub(1);
1145 }
1146}
1147
1148#[derive(Debug)]
1150struct RecursionStack {
1151 seen: IndexSet<Name>,
1152 high: usize,
1153 limit: usize,
1154}
1155
1156impl RecursionStack {
1157 fn new() -> Self {
1158 Self {
1159 seen: IndexSet::with_hasher(Default::default()),
1160 high: 0,
1161 limit: DEFAULT_RECURSION_LIMIT,
1162 }
1163 }
1164
1165 fn with_root(root: Name) -> Self {
1166 let mut stack = Self::new();
1167 stack.seen.insert(root);
1168 stack
1169 }
1170
1171 fn with_limit(mut self, limit: usize) -> Self {
1172 self.limit = limit;
1173 self
1174 }
1175
1176 pub(crate) fn guard(&mut self) -> RecursionGuard<'_> {
1178 RecursionGuard(self)
1179 }
1180}
1181
1182struct RecursionGuard<'a>(&'a mut RecursionStack);
1188
1189impl RecursionGuard<'_> {
1190 fn push(&mut self, name: &Name) -> Result<RecursionGuard<'_>, RecursionLimitError> {
1192 let new = self.0.seen.insert(name.clone());
1193 debug_assert!(
1194 new,
1195 "cannot push the same name twice to RecursionGuard, check contains() first"
1196 );
1197 self.0.high = self.0.high.max(self.0.seen.len());
1198 if self.0.seen.len() > self.0.limit {
1199 Err(RecursionLimitError {})
1200 } else {
1201 Ok(RecursionGuard(self.0))
1202 }
1203 }
1204
1205 fn contains(&self, name: &Name) -> bool {
1207 self.0.seen.contains(name)
1208 }
1209
1210 fn first(&self) -> Option<&Name> {
1212 self.0.seen.first()
1213 }
1214}
1215
1216impl Drop for RecursionGuard<'_> {
1217 fn drop(&mut self) {
1218 let _ = self.0.seen.pop();
1220 }
1221}
1222
1223#[derive(Debug, Clone, thiserror::Error)]
1225enum CycleError<T> {
1226 #[error("Cycle detected")]
1229 Recursed(Vec<Node<T>>),
1230 #[error(transparent)]
1232 Limit(#[from] RecursionLimitError),
1233}
1234
1235impl<T> CycleError<T> {
1236 fn trace(mut self, node: &Node<T>) -> Self {
1237 if let Self::Recursed(trace) = &mut self {
1238 trace.push(node.clone());
1239 }
1240 self
1241 }
1242}
1243
1244struct CommaSeparated<'a, It>(&'a It);
1245impl<'a, T, It> fmt::Display for CommaSeparated<'a, It>
1246where
1247 T: fmt::Display,
1248 &'a It: IntoIterator<Item = T>,
1249{
1250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1251 let mut it = self.0.into_iter();
1252 if let Some(element) = it.next() {
1253 element.fmt(f)?;
1254 }
1255 for element in it {
1256 f.write_str(", ")?;
1257 element.fmt(f)?;
1258 }
1259 Ok(())
1260 }
1261}