Skip to content

Commit 6aa62a5

Browse files
authored
Fixes and additions to schema introspection, for comparative fuzzing (#899)
* Align descriptions of built-ins with graphql-js. This makes comparating testing of introspection possible * includeDeprecated argument is nullable * Introspection: serialize defaultValue without indentation. Align with graphql-js * Introspection: that field is called specifiedByURL, not specifiedBy * Introspection: __Type.possibleTypes only includes object types. If an interface type implements another interface, it is not listed here * Convenience for introspection fuzzing * Introspection: as a precaution, validate unconditionally newly crafted executable documents, rather than only with debug assertions
1 parent 1f29e09 commit 6aa62a5

14 files changed

+297
-241
lines changed

crates/apollo-compiler/src/built_in_types.graphql

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1+
"A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations."
12
type __Schema {
23
description: String
4+
"A list of all types supported by this server."
35
types: [__Type!]!
6+
"The type that query operations will be rooted at."
47
queryType: __Type!
8+
"If this server supports mutation, the type that mutation operations will be rooted at."
59
mutationType: __Type
10+
"If this server support subscription, the type that subscription operations will be rooted at."
611
subscriptionType: __Type
12+
"A list of all directives supported by this server."
713
directives: [__Directive!]!
814
}
915

16+
"The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types."
1017
type __Type {
1118
kind: __TypeKind!
1219
name: String
@@ -27,17 +34,27 @@ type __Type {
2734
specifiedByURL: String
2835
}
2936

37+
"An enum describing what kind of type a given `__Type` is."
3038
enum __TypeKind {
39+
"Indicates this type is a scalar."
3140
SCALAR
41+
"Indicates this type is an object. `fields` and `interfaces` are valid fields."
3242
OBJECT
43+
"Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields."
3344
INTERFACE
45+
"Indicates this type is a union. `possibleTypes` is a valid field."
3446
UNION
47+
"Indicates this type is an enum. `enumValues` is a valid field."
3548
ENUM
49+
"Indicates this type is an input object. `inputFields` is a valid field."
3650
INPUT_OBJECT
51+
"Indicates this type is a list. `ofType` is a valid field."
3752
LIST
53+
"Indicates this type is a non-null. `ofType` is a valid field."
3854
NON_NULL
3955
}
4056

57+
"Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type."
4158
type __Field {
4259
name: String!
4360
description: String
@@ -47,22 +64,26 @@ type __Field {
4764
deprecationReason: String
4865
}
4966

67+
"Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value."
5068
type __InputValue {
5169
name: String!
5270
description: String
5371
type: __Type!
72+
"A GraphQL-formatted string representing the default value for this input value."
5473
defaultValue: String
5574
isDeprecated: Boolean!
5675
deprecationReason: String
5776
}
5877

78+
"One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string."
5979
type __EnumValue {
6080
name: String!
6181
description: String
6282
isDeprecated: Boolean!
6383
deprecationReason: String
6484
}
6585

86+
"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor."
6687
type __Directive {
6788
name: String!
6889
description: String
@@ -71,25 +92,45 @@ type __Directive {
7192
isRepeatable: Boolean!
7293
}
7394

95+
"A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies."
7496
enum __DirectiveLocation {
97+
"Location adjacent to a query operation."
7598
QUERY
99+
"Location adjacent to a mutation operation."
76100
MUTATION
101+
"Location adjacent to a subscription operation."
77102
SUBSCRIPTION
103+
"Location adjacent to a field."
78104
FIELD
105+
"Location adjacent to a fragment definition."
79106
FRAGMENT_DEFINITION
107+
"Location adjacent to a fragment spread."
80108
FRAGMENT_SPREAD
109+
"Location adjacent to an inline fragment."
81110
INLINE_FRAGMENT
111+
"Location adjacent to a variable definition."
82112
VARIABLE_DEFINITION
113+
"Location adjacent to a schema definition."
83114
SCHEMA
115+
"Location adjacent to a scalar definition."
84116
SCALAR
117+
"Location adjacent to an object type definition."
85118
OBJECT
119+
"Location adjacent to a field definition."
86120
FIELD_DEFINITION
121+
"Location adjacent to an argument definition."
87122
ARGUMENT_DEFINITION
123+
"Location adjacent to an interface definition."
88124
INTERFACE
125+
"Location adjacent to a union definition."
89126
UNION
127+
"Location adjacent to an enum definition."
90128
ENUM
129+
"Location adjacent to an enum value definition."
91130
ENUM_VALUE
131+
"Location adjacent to an input object type definition."
92132
INPUT_OBJECT
133+
"Location adjacent to an input object field definition."
93134
INPUT_FIELD_DEFINITION
94135
}
95136

@@ -108,47 +149,36 @@ directive @include(
108149
"Marks an element of a GraphQL schema as no longer supported."
109150
directive @deprecated(
110151
"""
111-
Explains why this element was deprecated, usually also including a
112-
suggestion for how to access supported similar data. Formatted using
113-
the Markdown syntax, as specified by
114-
[CommonMark](https://commonmark.org/).
152+
Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).
115153
"""
116154
reason: String = "No longer supported"
117155
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE
118156

119-
"Exposes a URL that specifies the behaviour of this scalar."
157+
"Exposes a URL that specifies the behavior of this scalar."
120158
directive @specifiedBy(
121-
"The URL that specifies the behaviour of this scalar."
159+
"The URL that specifies the behavior of this scalar."
122160
url: String!
123161
) on SCALAR
124162

125163
"""
126-
The `Int` scalar type represents non-fractional signed whole numeric values. Int
127-
can represent values between -(2^31) and 2^31 - 1.
164+
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
128165
"""
129166
scalar Int
130167

131168
"""
132-
The `Float` scalar type represents signed double-precision fractional values as
133-
specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
169+
The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
134170
"""
135171
scalar Float
136172

137173
"""
138-
The `String` scalar type represents textual data, represented as UTF-8 character
139-
sequences. The String type is most often used by GraphQL to represent free-form
140-
human-readable text.
174+
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
141175
"""
142176
scalar String
143177

144178
"The `Boolean` scalar type represents `true` or `false`."
145179
scalar Boolean
146180

147181
"""
148-
The `ID` scalar type represents a unique identifier, often used to refetch an
149-
object or as key for a cache. The ID type appears in a JSON response as a
150-
String; however, it is not intended to be human-readable. When expected as an
151-
input type, any string (such as `\"4\"`) or integer (such as `4`) input value
152-
will be accepted as an ID.
182+
The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.
153183
"""
154184
scalar ID

crates/apollo-compiler/src/execution/introspection_execute.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use std::sync::OnceLock;
2222
/// Obtained from [`SchemaIntrospectionSplit::split`].
2323
///
2424
/// [schema introspection]: https://spec.graphql.org/October2021/#sec-Schema-Introspection
25+
#[derive(Clone, Debug)]
2526
pub struct SchemaIntrospectionQuery(pub(crate) Valid<ExecutableDocument>);
2627

2728
impl std::ops::Deref for SchemaIntrospectionQuery {
@@ -135,7 +136,7 @@ impl<'a> SchemaWithCache<'a> {
135136
.get_or_init(|| self.schema.implementers_map())
136137
.get(interface_name)
137138
.into_iter()
138-
.flat_map(Implementers::iter)
139+
.flat_map(|implementers| &implementers.objects)
139140
}
140141
}
141142

@@ -301,7 +302,7 @@ impl_resolver! {
301302
schema::ExtendedType::Enum(_) |
302303
schema::ExtendedType::InputObject(_) => return Ok(ResolvedValue::null()),
303304
};
304-
let include_deprecated = args["includeDeprecated"].as_bool().unwrap();
305+
let include_deprecated = include_deprecated(args);
305306
Ok(ResolvedValue::list(fields
306307
.values()
307308
.filter(move |def| {
@@ -353,7 +354,7 @@ impl_resolver! {
353354
let schema::ExtendedType::Enum(def) = self_.def else {
354355
return Ok(ResolvedValue::null());
355356
};
356-
let include_deprecated = args["includeDeprecated"].as_bool().unwrap();
357+
let include_deprecated = include_deprecated(args);
357358
Ok(ResolvedValue::list(def
358359
.values
359360
.values()
@@ -370,7 +371,7 @@ impl_resolver! {
370371
let schema::ExtendedType::InputObject(def) = self_.def else {
371372
return Ok(ResolvedValue::null());
372373
};
373-
let include_deprecated = args["includeDeprecated"].as_bool().unwrap();
374+
let include_deprecated = include_deprecated(args);
374375
Ok(ResolvedValue::list(def
375376
.fields
376377
.values()
@@ -433,7 +434,7 @@ impl_resolver! {
433434
fn possibleTypes() { Ok(ResolvedValue::null()) }
434435
fn enumValues() { Ok(ResolvedValue::null()) }
435436
fn inputFields() { Ok(ResolvedValue::null()) }
436-
fn specifiedBy() { Ok(ResolvedValue::null()) }
437+
fn specifiedByURL() { Ok(ResolvedValue::null()) }
437438
}
438439

439440
impl_resolver! {
@@ -450,7 +451,7 @@ impl_resolver! {
450451
}
451452

452453
fn args(&self_, args) {
453-
let include_deprecated = args["includeDeprecated"].as_bool().unwrap();
454+
let include_deprecated = include_deprecated(args);
454455
Ok(ResolvedValue::list(self_
455456
.def
456457
.arguments
@@ -489,7 +490,7 @@ impl_resolver! {
489490
}
490491

491492
fn args(&self_, args) {
492-
let include_deprecated = args["includeDeprecated"].as_bool().unwrap();
493+
let include_deprecated = include_deprecated(args);
493494
Ok(ResolvedValue::list(self_
494495
.def
495496
.arguments
@@ -556,7 +557,9 @@ impl_resolver! {
556557
}
557558

558559
fn defaultValue(&self_) {
559-
Ok(ResolvedValue::leaf(self_.def.default_value.as_ref().map(|val| val.to_string())))
560+
Ok(ResolvedValue::leaf(self_.def.default_value.as_ref().map(|val| {
561+
val.serialize().no_indent().to_string()
562+
})))
560563
}
561564

562565
fn isDeprecated(&self_) {
@@ -567,3 +570,12 @@ impl_resolver! {
567570
Ok(deprecation_reason(self_.def.directives.get("deprecated")))
568571
}
569572
}
573+
574+
/// Although it should be non-null, the `includeDeprecated: Boolean = false` argument is nullable
575+
fn include_deprecated(args: &JsonMap) -> bool {
576+
match &args["includeDeprecated"] {
577+
serde_json_bytes::Value::Bool(b) => *b,
578+
serde_json_bytes::Value::Null => false,
579+
_ => unreachable!(),
580+
}
581+
}

crates/apollo-compiler/src/execution/introspection_split.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use indexmap::map::Entry;
2626
/// Result of [`split`][Self::split]ting [schema introspection] fields from an operation.
2727
///
2828
/// [schema introspection]: https://spec.graphql.org/October2021/#sec-Schema-Introspection
29+
#[derive(Clone, Debug)]
2930
pub enum SchemaIntrospectionSplit {
3031
/// The selected operation does *not* use [schema introspection] fields.
3132
/// It should be executed unchanged.
@@ -61,6 +62,7 @@ pub enum SchemaIntrospectionSplit {
6162
},
6263
}
6364

65+
#[derive(Debug)]
6466
pub enum SchemaIntrospectionError {
6567
SuspectedValidationBug(SuspectedValidationBug),
6668
Unsupported {
@@ -237,13 +239,9 @@ fn make_single_operation_document(
237239
fragments,
238240
};
239241
new_document.operations.insert(new_operation);
240-
if cfg!(debug_assertions) {
241-
new_document
242-
.validate(schema)
243-
.expect("filtering a valid document should result in a valid document")
244-
} else {
245-
Valid::assume_valid(new_document)
246-
}
242+
new_document
243+
.validate(schema)
244+
.expect("filtering a valid document should result in a valid document")
247245
}
248246

249247
fn get_fragment<'doc>(

crates/apollo-compiler/src/execution/resolver.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ macro_rules! impl_resolver {
7979
},
8080
)*
8181
_ => Err(crate::execution::resolver::ResolverError {
82-
message: format!("unexpected field name: {field_name}")
82+
message: format!(
83+
"unexpected field name: {field_name} in type {}",
84+
self.type_name()
85+
)
8386
}),
8487
}
8588
}

crates/apollo-compiler/src/execution/response.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::Deserialize;
77
use serde::Serialize;
88

99
/// A [GraphQL response](https://spec.graphql.org/October2021/#sec-Response-Format)
10-
#[derive(Debug, Clone, Serialize, Deserialize)]
10+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1111
#[serde(deny_unknown_fields)]
1212
pub struct Response {
1313
// <https://spec.graphql.org/October2021/#note-6f005> suggests serializing this first
@@ -26,7 +26,7 @@ pub struct Response {
2626
}
2727

2828
/// The `data` entry of a [`Response`]
29-
#[derive(Debug, Clone, Deserialize)]
29+
#[derive(Debug, Clone, PartialEq, Deserialize)]
3030
#[serde(from = "Option<JsonMap>")]
3131
pub enum ResponseData {
3232
/// Execution returned an object.
@@ -143,7 +143,7 @@ impl GraphQLError {
143143

144144
impl ResponseData {
145145
/// For serde `skip_serializing_if`
146-
fn is_absent(&self) -> bool {
146+
pub fn is_absent(&self) -> bool {
147147
matches!(self, Self::Absent)
148148
}
149149

crates/apollo-compiler/src/validation/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ impl DiagnosticData {
360360
ExecutableBuildError::ConflictingFieldName(_) => "ConflictingFieldName",
361361
ExecutableBuildError::ConflictingFieldArgument(_) => "ConflictingFieldArgument",
362362
}),
363+
Details::RecursionLimitError => Some("RecursionLimitError"),
363364
_ => None,
364365
}
365366
}

0 commit comments

Comments
 (0)