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