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 InvalidImplementationFieldType { .. } => "InvalidImplementationFieldType",
318 MissingInterfaceFieldArgument { .. } => "MissingInterfaceFieldArgument",
319 InvalidImplementationFieldArgumentType { .. } => {
320 "InvalidImplementationFieldArgumentType"
321 }
322 ExtraRequiredImplementationFieldArgument { .. } => {
323 "ExtraRequiredImplementationFieldArgument"
324 }
325 })
326 }
327 Details::ExecutableBuildError(error) => Some(match error {
328 ExecutableBuildError::UndefinedField { .. } => "UndefinedField",
329 ExecutableBuildError::TypeSystemDefinition { .. } => "TypeSystemDefinition",
330 ExecutableBuildError::AmbiguousAnonymousOperation => "AmbiguousAnonymousOperation",
331 ExecutableBuildError::OperationNameCollision { .. } => "OperationNameCollision",
332 ExecutableBuildError::FragmentNameCollision { .. } => "FragmentNameCollision",
333 ExecutableBuildError::UndefinedRootOperation { .. } => "UndefinedRootOperation",
334 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition { .. } => {
335 "UndefinedTypeInNamedFragmentTypeCondition"
336 }
337 ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition { .. } => {
338 "UndefinedTypeInInlineFragmentTypeCondition"
339 }
340 ExecutableBuildError::SubselectionOnScalarType { .. } => "SubselectionOnScalarType",
341 ExecutableBuildError::SubselectionOnEnumType { .. } => "SubselectionOnEnumType",
342 ExecutableBuildError::SubscriptionUsesMultipleFields { .. } => {
343 "SubscriptionUsesMultipleFields"
344 }
345 ExecutableBuildError::SubscriptionUsesIntrospection { .. } => {
346 "SubscriptionUsesIntrospection"
347 }
348 ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => {
349 "SubscriptionUsesConditionalSelection"
350 }
351 ExecutableBuildError::ConflictingFieldType(_) => "ConflictingFieldType",
352 ExecutableBuildError::ConflictingFieldName(_) => "ConflictingFieldName",
353 ExecutableBuildError::ConflictingFieldArgument(_) => "ConflictingFieldArgument",
354 }),
355 Details::RecursionLimitError => Some("RecursionLimitError"),
356 _ => None,
357 }
358 }
359
360 #[doc(hidden)]
365 pub fn unstable_compat_message(&self) -> Option<String> {
366 match &self.details {
367 Details::CompilerDiagnostic(diagnostic) => {
368 use diagnostics::DiagnosticData::*;
369 match diagnostic {
370 RecursionError { .. } => None,
371 UniqueVariable { name, .. } => Some(format!(
372 r#"There can be only one variable named "${name}"."#
373 )),
374 UniqueArgument { name, .. } => {
375 Some(format!(r#"There can be only one argument named "{name}"."#))
376 }
377 UniqueInputValue { .. } => None,
378 UndefinedArgument {
379 name, coordinate, ..
380 } => Some(format!(
381 r#"Unknown argument "{name}" on field "{coordinate}"."#
382 )),
383 UndefinedDefinition { name } => Some(format!(r#"Unknown type "{name}"."#)),
384 UndefinedDirective { name } => Some(format!(r#"Unknown directive "@{name}"."#)),
385 UndefinedVariable { name } => {
386 Some(format!(r#"Variable "${name}" is not defined."#))
387 }
388 UndefinedFragment { name } => Some(format!(r#"Unknown fragment "{name}"."#)),
389 UndefinedEnumValue {
390 value, definition, ..
391 } => Some(format!(
392 r#"Value "{value}" does not exist in "{definition}" enum."#
393 )),
394 UndefinedInputValue {
395 value, definition, ..
396 } => Some(format!(
397 r#"Field "{value}" is not defined by type "{definition}"."#
398 )),
399 MissingInterfaceField { .. } => None,
400 RequiredArgument {
401 name,
402 coordinate,
403 expected_type,
404 ..
405 } => match coordinate {
406 SchemaCoordinate::FieldArgument(coordinate) => Some(format!(
407 r#"Field "{}" argument "{name}" of type "{expected_type}" is required, but it was not provided."#,
408 coordinate.field,
409 )),
410 SchemaCoordinate::DirectiveArgument(coordinate) => Some(format!(
411 r#"Directive "@{}" argument "{name}" of type "{expected_type}" is required, but it was not provided."#,
412 coordinate.directive,
413 )),
414 _ => None,
416 },
417 RequiredField {
418 coordinate,
419 expected_type,
420 ..
421 } => Some(format!(
422 r#"Field "{coordinate}" of required type "{expected_type}" was not provided."#
423 )),
424 TransitiveImplementedInterfaces { .. } => None,
425 OutputType { .. } => None,
426 InputType { .. } => None,
427 VariableInputType { name, ty, .. } => Some(format!(
428 r#"Variable "${name}" cannot be non-input type "{ty}"."#
429 )),
430 QueryRootOperationType => None,
431 UnusedVariable { name } => {
432 Some(format!(r#"Variable "${name}" is never used."#))
433 }
434 RootOperationObjectType { .. } => None,
435 UnionMemberObjectType { .. } => None,
436 UnsupportedLocation { name, location, .. } => Some(format!(
437 r#"Directive "@{name}" may not be used on {location}."#
438 )),
439 UnsupportedValueType { ty, value, .. } => Some(format!(
440 r#"{} cannot represent value: {value}"#,
441 ty.inner_named_type()
442 )),
443 IntCoercionError { value } => {
444 let is_integer = value
445 .chars()
446 .all(|c| matches!(c, '-' | '+' | 'e' | '0'..='9'));
448 if is_integer {
449 Some(format!(
450 r#"Int cannot represent non 32-bit signed integer value: {value}"#
451 ))
452 } else {
453 Some(format!(
454 r#"Int cannot represent non-integer value: {value}"#
455 ))
456 }
457 }
458 FloatCoercionError { value } => Some(format!(
459 r#"Float cannot represent non numeric value: {value}"#
460 )),
461 UniqueDirective { name, .. } => Some(format!(
462 r#"The directive "@{name}" can only be used once at this location."#
463 )),
464 MissingSubselection { coordinate, .. } => Some(format!(
465 r#"Field "{field}" of type "{ty}" must have a selection of subfields. Did you mean "{field} {{ ... }}"?"#,
466 ty = coordinate.ty,
467 field = coordinate.attribute,
468 )),
469 InvalidFragmentTarget { name, ty } => {
470 if let Some(name) = name {
471 Some(format!(
472 r#"Fragment "{name}" cannot condition on non composite type "{ty}"."#
473 ))
474 } else {
475 Some(format!(
476 r#"Fragment cannot condition on non composite type "{ty}"."#
477 ))
478 }
479 }
480 InvalidFragmentSpread {
481 name,
482 type_name,
483 type_condition,
484 ..
485 } => {
486 if let Some(name) = name {
487 Some(format!(
488 r#"Fragment "{name}" cannot be spread here as objects of type "{type_name}" can never be of type "{type_condition}"."#
489 ))
490 } else {
491 Some(format!(
492 r#"Fragment cannot be spread here as objects of type "{type_name}" can never be of type "{type_condition}"."#
493 ))
494 }
495 }
496 UnusedFragment { name } => Some(format!(r#"Fragment "{name}" is never used."#)),
497 DisallowedVariableUsage {
498 variable,
499 variable_type,
500 argument_type,
501 ..
502 } => Some(format!(
503 r#"Variable "${variable}" of type "{variable_type}" used in position expecting type "{argument_type}"."#
504 )),
505 RecursiveDirectiveDefinition { .. } => None,
506 RecursiveInterfaceDefinition { .. } => None,
507 RecursiveInputObjectDefinition { .. } => None,
508 RecursiveFragmentDefinition { name, trace, .. } => Some(format!(
509 r#"Cannot spread fragment "{name}" within itself via {}"#,
510 trace
512 .iter()
513 .map(|spread| format!(r#""{}""#, spread.fragment_name))
514 .collect::<Vec<_>>()
515 .join(", "),
516 )),
517 DeeplyNestedType { .. } => None,
518 EmptyFieldSet { .. } => None,
519 EmptyValueSet { .. } => None,
520 EmptyMemberSet { .. } => None,
521 EmptyInputValueSet { .. } => None,
522 ReservedName { .. } => None,
523 InvalidImplementationFieldType {
524 name,
525 interface,
526 field,
527 interface_type,
528 actual_type,
529 ..
530 } => Some(format!(
531 r#"Interface field {interface}.{field} expects type {interface_type} but {name}.{field} of type {actual_type} is not a proper subtype."#
532 )),
533 MissingInterfaceFieldArgument {
534 name,
535 interface,
536 field,
537 argument,
538 ..
539 } => Some(format!(
540 r#"Interface field argument {interface}.{field}({argument}:) expected but {name}.{field} does not provide it."#
541 )),
542 InvalidImplementationFieldArgumentType {
543 name,
544 interface,
545 field,
546 argument,
547 interface_type,
548 actual_type,
549 ..
550 } => Some(format!(
551 r#"Interface field {interface}.{field}({argument}:) expects type {interface_type} but {name}.{field}({argument}:) is type {actual_type}."#
552 )),
553 ExtraRequiredImplementationFieldArgument {
554 name,
555 interface,
556 field,
557 argument,
558 ..
559 } => Some(format!(
560 r#"Object field {name}.{field} includes required argument {argument} that is missing from the Interface field {interface}.{field}."#
561 )),
562 }
563 }
564 Details::ExecutableBuildError(error) => match error {
565 ExecutableBuildError::UndefinedField {
566 type_name,
567 field_name,
568 ..
569 } => Some(format!(
570 r#"Cannot query field "{field_name}" on type "{type_name}"."#
571 )),
572 ExecutableBuildError::TypeSystemDefinition { name, .. } => {
573 if let Some(name) = name {
574 Some(format!(r#"The "{name}" definition is not executable."#))
575 } else {
576 Some("The schema definition is not executable.".to_string())
578 }
579 }
580 ExecutableBuildError::AmbiguousAnonymousOperation => {
581 Some("This anonymous operation must be the only defined operation.".to_string())
582 }
583 ExecutableBuildError::OperationNameCollision {
584 name_at_previous_location,
585 } => Some(format!(
586 r#"There can be only one operation named "{name_at_previous_location}"."#
587 )),
588 ExecutableBuildError::FragmentNameCollision {
589 name_at_previous_location,
590 } => Some(format!(
591 r#"There can be only one fragment named "{name_at_previous_location}"."#
592 )),
593 ExecutableBuildError::UndefinedRootOperation { operation_type } => Some(format!(
594 r#"The schema has no "{operation_type}" root type defined"#
596 )),
597 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition {
598 type_name,
599 ..
600 }
601 | ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition {
602 type_name,
603 ..
604 } => Some(format!(r#"Unknown type "{type_name}"."#)),
605 ExecutableBuildError::SubselectionOnScalarType { type_name, path }
606 | ExecutableBuildError::SubselectionOnEnumType { type_name, path } => {
607 #[allow(clippy::manual_map)]
608 if let Some(field) = path.nested_fields.last() {
609 Some(format!(
610 r#"Field "{field}" must not have a selection since type "{type_name}" has no subfields"#
611 ))
612 } else {
613 None }
615 }
616 ExecutableBuildError::SubscriptionUsesMultipleFields { name, .. } => {
617 if let Some(name) = name {
618 Some(format!(
619 r#"Subscription "{name}" must select only one top level field."#
620 ))
621 } else {
622 Some(
623 "Anonymous Subscription must select only one top level field."
624 .to_string(),
625 )
626 }
627 }
628 ExecutableBuildError::SubscriptionUsesIntrospection { name, .. } => {
629 if let Some(name) = name {
630 Some(format!(
631 r#"Subscription "{name}" must not select an introspection top level field."#
632 ))
633 } else {
634 Some("Anonymous Subscription must not select an introspection top level field."
635 .to_string())
636 }
637 }
638 ExecutableBuildError::SubscriptionUsesConditionalSelection { name, .. } => {
639 if let Some(name) = name {
640 Some(format!(
641 r#"Subscription "{name}" can not specify @skip or @include on root fields."#
642 ))
643 } else {
644 Some(
645 "Anonymous Subscription can not specify @skip or @include on root fields."
646 .to_string(),
647 )
648 }
649 }
650 ExecutableBuildError::ConflictingFieldType(inner) => {
651 let ConflictingFieldType {
652 alias,
653 original_type,
654 conflicting_type,
655 ..
656 } = &**inner;
657 Some(format!(
658 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."#
659 ))
660 }
661 ExecutableBuildError::ConflictingFieldName(inner) => {
662 let ConflictingFieldName {
663 alias,
664 original_selection,
665 conflicting_selection,
666 ..
667 } = &**inner;
668 Some(format!(
669 r#"Fields "{alias}" conflict because "{}" and "{}" are different fields. Use different aliases on the fields to fetch both if this was intentional."#,
670 original_selection.attribute, conflicting_selection.attribute
671 ))
672 }
673 ExecutableBuildError::ConflictingFieldArgument(inner) => {
674 let ConflictingFieldArgument { alias, .. } = &**inner;
675 Some(format!(
676 r#"Fields "{alias}" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."#
677 ))
678 }
679 },
680 _ => None,
681 }
682 }
683}
684
685impl ToCliReport for DiagnosticData {
686 fn location(&self) -> Option<SourceSpan> {
687 self.location
688 }
689
690 fn report(&self, report: &mut CliReport) {
691 if let Details::CompilerDiagnostic(diagnostic) = &self.details {
692 diagnostic.report(self.location, report);
693 return;
694 }
695
696 match &self.details {
702 Details::CompilerDiagnostic(_) => unreachable!(),
703 Details::ParserLimit { message, .. } => report.with_label_opt(self.location, message),
704 Details::SyntaxError { message, .. } => report.with_label_opt(self.location, message),
705 Details::SchemaBuildError(err) => match err {
706 SchemaBuildError::ExecutableDefinition { .. } => report.with_label_opt(
707 self.location,
708 "remove this definition, or use `parse_mixed()`",
709 ),
710 SchemaBuildError::SchemaDefinitionCollision {
711 previous_location, ..
712 } => {
713 report.with_label_opt(*previous_location, "previous `schema` definition here");
714 report.with_label_opt(self.location, "`schema` redefined here");
715 report.with_help(
716 "merge this definition with the previous one, or use `extend schema`",
717 );
718 }
719 SchemaBuildError::DirectiveDefinitionCollision {
720 previous_location,
721 name,
722 ..
723 } => {
724 report.with_label_opt(
725 *previous_location,
726 format_args!("previous definition of `@{name}` here"),
727 );
728 report.with_label_opt(self.location, format_args!("`@{name}` redefined here"));
729 report.with_help("remove or rename one of the definitions");
730 }
731 SchemaBuildError::TypeDefinitionCollision {
732 previous_location,
733 name,
734 ..
735 } => {
736 report.with_label_opt(
737 *previous_location,
738 format_args!("previous definition of `{name}` here"),
739 );
740 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
741 report.with_help("remove or rename one of the definitions, or use `extend`");
742 }
743 SchemaBuildError::BuiltInScalarTypeRedefinition => {
744 report.with_label_opt(self.location, "remove this scalar definition");
745 }
746 SchemaBuildError::OrphanSchemaExtension => {
747 report.with_label_opt(self.location, "extension here")
748 }
749 SchemaBuildError::OrphanTypeExtension { .. } => {
750 report.with_label_opt(self.location, "extension here")
751 }
752 SchemaBuildError::TypeExtensionKindMismatch { def_location, .. } => {
753 report.with_label_opt(*def_location, "type definition");
754 report.with_label_opt(self.location, "extension here")
755 }
756 SchemaBuildError::DuplicateRootOperation {
757 previous_location,
758 operation_type,
759 ..
760 } => {
761 report.with_label_opt(
762 *previous_location,
763 format_args!("previous definition of `{operation_type}` here"),
764 );
765 report.with_label_opt(
766 self.location,
767 format_args!("`{operation_type}` redefined here"),
768 );
769 }
770 SchemaBuildError::DuplicateImplementsInterfaceInObject {
771 name_at_previous_location,
772 ..
773 }
774 | SchemaBuildError::DuplicateImplementsInterfaceInInterface {
775 name_at_previous_location,
776 ..
777 } => {
778 let previous_location = &name_at_previous_location.location();
779 let name = name_at_previous_location;
780 report.with_label_opt(
781 *previous_location,
782 format_args!("previous implementation of `{name}` here"),
783 );
784 report.with_label_opt(
785 self.location,
786 format_args!("`{name}` implemented again here"),
787 );
788 }
789 SchemaBuildError::ObjectFieldNameCollision {
790 name_at_previous_location,
791 ..
792 }
793 | SchemaBuildError::InterfaceFieldNameCollision {
794 name_at_previous_location,
795 ..
796 }
797 | SchemaBuildError::EnumValueNameCollision {
798 name_at_previous_location,
799 ..
800 }
801 | SchemaBuildError::UnionMemberNameCollision {
802 name_at_previous_location,
803 ..
804 }
805 | SchemaBuildError::InputFieldNameCollision {
806 name_at_previous_location,
807 ..
808 } => {
809 let previous_location = &name_at_previous_location.location();
810 let name = name_at_previous_location;
811 report.with_label_opt(
812 *previous_location,
813 format_args!("previous definition of `{name}` here"),
814 );
815 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
816 }
817 },
818 Details::ExecutableBuildError(err) => match err {
819 ExecutableBuildError::TypeSystemDefinition { .. } => report.with_label_opt(
820 self.location,
821 "remove this definition, or use `parse_mixed()`",
822 ),
823 ExecutableBuildError::AmbiguousAnonymousOperation => {
824 report.with_label_opt(self.location, "provide a name for this definition");
825 report.with_help(
826 "GraphQL requires operations to be named if the document has more than one",
827 );
828 }
829 ExecutableBuildError::OperationNameCollision {
830 name_at_previous_location,
831 ..
832 }
833 | ExecutableBuildError::FragmentNameCollision {
834 name_at_previous_location,
835 ..
836 } => {
837 let previous_location = &name_at_previous_location.location();
838 let name = name_at_previous_location;
839 report.with_label_opt(
840 *previous_location,
841 format_args!("previous definition of `{name}` here"),
842 );
843 report.with_label_opt(self.location, format_args!("`{name}` redefined here"));
844 }
845 ExecutableBuildError::UndefinedRootOperation { operation_type, .. } => {
846 report.with_label_opt(
847 self.location,
848 format_args!(
849 "`{operation_type}` is not defined in the schema and is therefore not supported"
850 ),
851 );
852 report.with_help(format_args!(
853 "consider defining a `{operation_type}` root operation type in your schema"
854 ))
855 }
856 ExecutableBuildError::UndefinedTypeInNamedFragmentTypeCondition { .. } => {
857 report.with_label_opt(self.location, "type condition here")
858 }
859 ExecutableBuildError::UndefinedTypeInInlineFragmentTypeCondition {
860 path, ..
861 } => {
862 report.with_label_opt(self.location, "type condition here");
863 report.with_note(format_args!("path to the inline fragment: `{path} → ...`"))
864 }
865 ExecutableBuildError::SubselectionOnScalarType { path, .. }
866 | ExecutableBuildError::SubselectionOnEnumType { path, .. } => {
867 report.with_label_opt(self.location, "remove subselections here");
868 report.with_note(format_args!("path to the field: `{path}`"))
869 }
870 ExecutableBuildError::UndefinedField {
871 field_name,
872 type_name,
873 path,
874 ..
875 } => {
876 report.with_label_opt(
877 self.location,
878 format_args!("field `{field_name}` selected here"),
879 );
880 report.with_label_opt(
881 type_name.location(),
882 format_args!("type `{type_name}` defined here"),
883 );
884 report.with_note(format_args!("path to the field: `{path}`"))
885 }
886 ExecutableBuildError::SubscriptionUsesMultipleFields { fields, .. } => {
887 report.with_label_opt(
888 self.location,
889 format_args!("subscription with {} root fields", fields.len()),
890 );
891 report.with_help(format_args!(
892 "There are {} root fields: {}. This is not allowed.",
893 fields.len(),
894 CommaSeparated(fields)
895 ));
896 }
897 ExecutableBuildError::SubscriptionUsesIntrospection { field, .. } => {
898 report.with_label_opt(
899 self.location,
900 format_args!("{field} is an introspection field"),
901 );
902 }
903 ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => {
904 report.with_label_opt(self.location, "conditional directive used here");
905 }
906 ExecutableBuildError::ConflictingFieldType(inner) => {
907 let ConflictingFieldType {
908 alias,
909 original_location,
910 original_coordinate,
911 original_type,
912 conflicting_location,
913 conflicting_coordinate,
914 conflicting_type,
915 } = &**inner;
916 report.with_label_opt(
917 *original_location,
918 format_args!(
919 "`{alias}` is selected from `{original_coordinate}: {original_type}` here"
920 ),
921 );
922 report.with_label_opt(
923 *conflicting_location,
924 format_args!("`{alias}` is selected from `{conflicting_coordinate}: {conflicting_type}` here"),
925 );
926 }
927 ExecutableBuildError::ConflictingFieldArgument(inner) => {
928 let ConflictingFieldArgument {
929 alias,
930 original_location,
931 original_coordinate,
932 original_value,
933 conflicting_location,
934 conflicting_coordinate: _,
935 conflicting_value,
936 } = &**inner;
937 let argument = &original_coordinate.argument;
938 match (original_value, conflicting_value) {
939 (Some(_), Some(_)) => {
940 report.with_label_opt(
941 *original_location,
942 format_args!(
943 "`{original_coordinate}` is used with one argument value here"
944 ),
945 );
946 report.with_label_opt(
947 *conflicting_location,
948 "but a different value here",
949 );
950 }
951 (Some(_), None) => {
952 report.with_label_opt(
953 *original_location,
954 format!("`{alias}` is selected with argument `{argument}` here",),
955 );
956 report.with_label_opt(
957 *conflicting_location,
958 format!("but argument `{argument}` is not provided here"),
959 );
960 }
961 (None, Some(_)) => {
962 report.with_label_opt(
963 *conflicting_location,
964 format!("`{alias}` is selected with argument `{argument}` here",),
965 );
966 report.with_label_opt(
967 *original_location,
968 format!("but argument `{argument}` is not provided here"),
969 );
970 }
971 (None, None) => unreachable!(),
972 }
973 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");
974 }
975 ExecutableBuildError::ConflictingFieldName(inner) => {
976 let ConflictingFieldName {
977 alias: field,
978 original_selection,
979 original_location,
980 conflicting_selection,
981 conflicting_location,
982 } = &**inner;
983 report.with_label_opt(
984 *original_location,
985 format_args!("`{field}` is selected from `{original_selection}` here"),
986 );
987 report.with_label_opt(
988 *conflicting_location,
989 format_args!("`{field}` is selected from `{conflicting_selection}` here"),
990 );
991
992 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");
993 }
994 },
995 Details::RecursionLimitError => {}
996 }
997 }
998}
999
1000impl Diagnostic<'_, DiagnosticData> {
1001 #[doc(hidden)]
1006 pub fn unstable_to_json_compat(&self) -> GraphQLError {
1007 GraphQLError::new(
1008 self.error
1009 .unstable_compat_message()
1010 .unwrap_or_else(|| self.error.to_string()),
1011 self.error.location(),
1012 self.sources,
1013 )
1014 }
1015}
1016
1017impl DiagnosticList {
1018 pub fn new(sources: SourceMap) -> Self {
1020 Self {
1021 sources,
1022 diagnostics_data: Vec::new(),
1023 }
1024 }
1025
1026 pub fn is_empty(&self) -> bool {
1027 self.diagnostics_data.is_empty()
1028 }
1029
1030 pub fn len(&self) -> usize {
1031 self.diagnostics_data.len()
1032 }
1033
1034 pub fn iter(
1035 &self,
1036 ) -> impl DoubleEndedIterator<Item = Diagnostic<'_, DiagnosticData>> + ExactSizeIterator {
1037 self.diagnostics_data
1038 .iter()
1039 .map(|data| data.to_diagnostic(&self.sources))
1040 }
1041
1042 pub(crate) fn push(&mut self, location: Option<SourceSpan>, details: impl Into<Details>) {
1043 self.diagnostics_data.push(DiagnosticData {
1044 location,
1045 details: details.into(),
1046 })
1047 }
1048
1049 pub fn merge(&mut self, other: Self) {
1051 if !Arc::ptr_eq(&self.sources, &other.sources) {
1052 let sources = Arc::make_mut(&mut self.sources);
1053 for (&k, v) in &*other.sources {
1054 sources.entry(k).or_insert_with(|| v.clone());
1055 }
1056 }
1057 self.diagnostics_data.extend(other.diagnostics_data);
1058 self.sort()
1059 }
1060
1061 fn sort(&mut self) {
1062 self.diagnostics_data
1063 .sort_by_key(|err| err.location.map(|loc| (loc.file_id(), loc.offset())));
1064 }
1065
1066 pub(crate) fn into_result(mut self) -> Result<(), Self> {
1067 if self.diagnostics_data.is_empty() {
1068 Ok(())
1069 } else {
1070 self.sort();
1071 Err(self)
1072 }
1073 }
1074
1075 pub(crate) fn into_result_with<T>(self, value: T) -> Result<T, WithErrors<T>> {
1076 match self.into_result() {
1077 Ok(()) => Ok(value),
1078 Err(errors) => Err(WithErrors {
1079 partial: value,
1080 errors,
1081 }),
1082 }
1083 }
1084
1085 pub(crate) fn into_valid_result<T>(self, value: T) -> Result<Valid<T>, WithErrors<T>> {
1086 match self.into_result() {
1087 Ok(()) => Ok(Valid(value)),
1088 Err(errors) => Err(WithErrors {
1089 partial: value,
1090 errors,
1091 }),
1092 }
1093 }
1094}
1095
1096impl fmt::Display for DiagnosticList {
1098 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1099 for diagnostic in self.iter() {
1100 fmt::Display::fmt(&diagnostic, f)?
1101 }
1102 Ok(())
1103 }
1104}
1105
1106impl fmt::Debug for DiagnosticList {
1108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1109 for diagnostic in self.iter() {
1110 fmt::Debug::fmt(&diagnostic, f)?
1111 }
1112 Ok(())
1113 }
1114}
1115
1116impl From<SchemaBuildError> for Details {
1117 fn from(value: SchemaBuildError) -> Self {
1118 Details::SchemaBuildError(value)
1119 }
1120}
1121
1122impl From<ExecutableBuildError> for Details {
1123 fn from(value: ExecutableBuildError) -> Self {
1124 Details::ExecutableBuildError(value)
1125 }
1126}
1127
1128impl From<diagnostics::DiagnosticData> for Details {
1129 fn from(value: diagnostics::DiagnosticData) -> Self {
1130 Details::CompilerDiagnostic(value)
1131 }
1132}
1133
1134const DEFAULT_RECURSION_LIMIT: usize = 32;
1135
1136#[derive(Debug, Clone, thiserror::Error)]
1137#[error("Recursion limit reached")]
1138#[non_exhaustive]
1139struct RecursionLimitError {}
1140
1141#[derive(Debug)]
1143struct DepthCounter {
1144 value: usize,
1145 high: usize,
1146 limit: usize,
1147}
1148
1149impl DepthCounter {
1150 fn new() -> Self {
1151 Self {
1152 value: 0,
1153 high: 0,
1154 limit: DEFAULT_RECURSION_LIMIT,
1155 }
1156 }
1157
1158 fn with_limit(mut self, limit: usize) -> Self {
1159 self.limit = limit;
1160 self
1161 }
1162
1163 pub(crate) fn guard(&mut self) -> DepthGuard<'_> {
1165 DepthGuard(self)
1166 }
1167}
1168
1169struct DepthGuard<'a>(&'a mut DepthCounter);
1174
1175impl DepthGuard<'_> {
1176 fn increment(&mut self) -> Result<DepthGuard<'_>, RecursionLimitError> {
1178 self.0.value += 1;
1179 self.0.high = self.0.high.max(self.0.value);
1180 if self.0.value > self.0.limit {
1181 Err(RecursionLimitError {})
1182 } else {
1183 Ok(DepthGuard(self.0))
1184 }
1185 }
1186}
1187
1188impl Drop for DepthGuard<'_> {
1189 fn drop(&mut self) {
1190 self.0.value = self.0.value.saturating_sub(1);
1192 }
1193}
1194
1195#[derive(Debug)]
1197struct RecursionStack {
1198 seen: IndexSet<Name>,
1199 high: usize,
1200 limit: usize,
1201}
1202
1203impl RecursionStack {
1204 fn new() -> Self {
1205 Self {
1206 seen: IndexSet::with_hasher(Default::default()),
1207 high: 0,
1208 limit: DEFAULT_RECURSION_LIMIT,
1209 }
1210 }
1211
1212 fn with_root(root: Name) -> Self {
1213 let mut stack = Self::new();
1214 stack.seen.insert(root);
1215 stack
1216 }
1217
1218 fn with_limit(mut self, limit: usize) -> Self {
1219 self.limit = limit;
1220 self
1221 }
1222
1223 pub(crate) fn guard(&mut self) -> RecursionGuard<'_> {
1225 RecursionGuard(self)
1226 }
1227}
1228
1229struct RecursionGuard<'a>(&'a mut RecursionStack);
1235
1236impl RecursionGuard<'_> {
1237 fn push(&mut self, name: &Name) -> Result<RecursionGuard<'_>, RecursionLimitError> {
1239 let new = self.0.seen.insert(name.clone());
1240 debug_assert!(
1241 new,
1242 "cannot push the same name twice to RecursionGuard, check contains() first"
1243 );
1244 self.0.high = self.0.high.max(self.0.seen.len());
1245 if self.0.seen.len() > self.0.limit {
1246 Err(RecursionLimitError {})
1247 } else {
1248 Ok(RecursionGuard(self.0))
1249 }
1250 }
1251
1252 fn contains(&self, name: &Name) -> bool {
1254 self.0.seen.contains(name)
1255 }
1256
1257 fn first(&self) -> Option<&Name> {
1259 self.0.seen.first()
1260 }
1261}
1262
1263impl Drop for RecursionGuard<'_> {
1264 fn drop(&mut self) {
1265 let _ = self.0.seen.pop();
1267 }
1268}
1269
1270#[derive(Debug, Clone, thiserror::Error)]
1272enum CycleError<T> {
1273 #[error("Cycle detected")]
1276 Recursed(Vec<Node<T>>),
1277 #[error(transparent)]
1279 Limit(#[from] RecursionLimitError),
1280}
1281
1282impl<T> CycleError<T> {
1283 fn trace(mut self, node: &Node<T>) -> Self {
1284 if let Self::Recursed(trace) = &mut self {
1285 trace.push(node.clone());
1286 }
1287 self
1288 }
1289}
1290
1291struct CommaSeparated<'a, It>(&'a It);
1292impl<'a, T, It> fmt::Display for CommaSeparated<'a, It>
1293where
1294 T: fmt::Display,
1295 &'a It: IntoIterator<Item = T>,
1296{
1297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1298 let mut it = self.0.into_iter();
1299 if let Some(element) = it.next() {
1300 element.fmt(f)?;
1301 }
1302 for element in it {
1303 f.write_str(", ")?;
1304 element.fmt(f)?;
1305 }
1306 Ok(())
1307 }
1308}