Skip to content

Commit c52e89e

Browse files
authored
Merge pull request #447 from pizzacat83/extend-type
support `extend type Foo`
2 parents 900c432 + 313ebd5 commit c52e89e

File tree

6 files changed

+1267
-1
lines changed

6 files changed

+1267
-1
lines changed

graphql_client_codegen/src/schema.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ impl Schema {
327327
.expect("Schema::get_object")
328328
}
329329

330+
pub(crate) fn get_object_mut(&mut self, object_id: ObjectId) -> &mut StoredObject {
331+
self.stored_objects
332+
.get_mut(object_id.0 as usize)
333+
.expect("Schema::get_object_mut")
334+
}
335+
330336
pub(crate) fn get_field(&self, field_id: StoredFieldId) -> &StoredField {
331337
self.stored_fields.get(field_id.0).unwrap()
332338
}

graphql_client_codegen/src/schema/graphql_parser_conversion.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::{Schema, StoredInputFieldType, TypeId};
22
use crate::schema::resolve_field_type;
3-
use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType};
3+
use graphql_parser::schema::{
4+
self as parser, Definition, Document, TypeDefinition, TypeExtension, UnionType,
5+
};
46

57
pub(super) fn build_schema<'doc, T>(
68
mut src: graphql_parser::schema::Document<'doc, T>,
@@ -36,6 +38,8 @@ where
3638
interfaces_mut(src).for_each(|iface| ingest_interface(schema, iface));
3739

3840
objects_mut(src).for_each(|obj| ingest_object(schema, obj));
41+
extend_object_type_extensions_mut(src)
42+
.for_each(|ext| ingest_object_type_extension(schema, ext));
3943

4044
inputs_mut(src).for_each(|input| ingest_input(schema, input));
4145

@@ -200,6 +204,40 @@ fn ingest_object<'doc, T>(
200204
schema.push_object(object);
201205
}
202206

207+
fn ingest_object_type_extension<'doc, T>(
208+
schema: &mut Schema,
209+
ext: &mut graphql_parser::schema::ObjectTypeExtension<'doc, T>,
210+
) where
211+
T: graphql_parser::query::Text<'doc>,
212+
{
213+
let object_id = schema
214+
.find_type_id(ext.name.as_ref())
215+
.as_object_id()
216+
.unwrap();
217+
let mut field_ids = Vec::with_capacity(ext.fields.len());
218+
219+
for field in ext.fields.iter_mut() {
220+
let field = super::StoredField {
221+
name: field.name.as_ref().into(),
222+
r#type: resolve_field_type(schema, &field.field_type),
223+
parent: super::StoredFieldParent::Object(object_id),
224+
deprecation: find_deprecation(&field.directives),
225+
};
226+
227+
field_ids.push(schema.push_field(field));
228+
}
229+
230+
let iface_ids = ext
231+
.implements_interfaces
232+
.iter()
233+
.map(|iface_name| schema.find_interface(iface_name.as_ref()))
234+
.collect::<Vec<_>>();
235+
236+
let object = schema.get_object_mut(object_id);
237+
object.implements_interfaces.extend(iface_ids);
238+
object.fields.extend(field_ids);
239+
}
240+
203241
fn ingest_scalar<'doc, T>(
204242
schema: &mut Schema,
205243
scalar: &mut graphql_parser::schema::ScalarType<'doc, T>,
@@ -328,6 +366,18 @@ where
328366
})
329367
}
330368

369+
fn extend_object_type_extensions_mut<'a, 'doc: 'a, T>(
370+
doc: &'a mut Document<'doc, T>,
371+
) -> impl Iterator<Item = &'a mut parser::ObjectTypeExtension<'doc, T>>
372+
where
373+
T: graphql_parser::query::Text<'doc>,
374+
{
375+
doc.definitions.iter_mut().filter_map(|def| match def {
376+
Definition::TypeExtension(TypeExtension::Object(obj)) => Some(obj),
377+
_ => None,
378+
})
379+
}
380+
331381
fn interfaces_mut<'a, 'doc: 'a, T>(
332382
doc: &'a mut Document<'doc, T>,
333383
) -> impl Iterator<Item = &'a mut parser::InterfaceType<'doc, T>>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
mod extend_object;
12
mod github;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use crate::schema::Schema;
2+
3+
const SCHEMA_JSON: &str = include_str!("extend_object_schema.json");
4+
const SCHEMA_GRAPHQL: &str = include_str!("extend_object_schema.graphql");
5+
6+
#[test]
7+
fn ast_from_graphql_and_json_produce_the_same_schema() {
8+
let json: graphql_introspection_query::introspection_response::IntrospectionResponse =
9+
serde_json::from_str(SCHEMA_JSON).unwrap();
10+
let graphql_parser_schema = graphql_parser::parse_schema(SCHEMA_GRAPHQL)
11+
.unwrap()
12+
.into_static();
13+
let mut json = Schema::from(json);
14+
let mut gql = Schema::from(graphql_parser_schema);
15+
16+
assert!(vecs_match(&json.stored_scalars, &gql.stored_scalars));
17+
18+
// Root objects
19+
{
20+
assert_eq!(
21+
json.get_object(json.query_type()).name,
22+
gql.get_object(gql.query_type()).name
23+
);
24+
assert_eq!(
25+
json.mutation_type().map(|t| &json.get_object(t).name),
26+
gql.mutation_type().map(|t| &gql.get_object(t).name),
27+
"Mutation types don't match."
28+
);
29+
assert_eq!(
30+
json.subscription_type().map(|t| &json.get_object(t).name),
31+
gql.subscription_type().map(|t| &gql.get_object(t).name),
32+
"Subscription types don't match."
33+
);
34+
}
35+
36+
// Objects
37+
{
38+
let mut json_stored_objects: Vec<_> = json
39+
.stored_objects
40+
.drain(..)
41+
.filter(|obj| !obj.name.starts_with("__"))
42+
.collect();
43+
44+
assert_eq!(
45+
json_stored_objects.len(),
46+
gql.stored_objects.len(),
47+
"Objects count matches."
48+
);
49+
50+
json_stored_objects.sort_by(|a, b| a.name.cmp(&b.name));
51+
gql.stored_objects.sort_by(|a, b| a.name.cmp(&b.name));
52+
53+
for (j, g) in json_stored_objects
54+
.iter_mut()
55+
.filter(|obj| !obj.name.starts_with("__"))
56+
.zip(gql.stored_objects.iter_mut())
57+
{
58+
assert_eq!(j.name, g.name);
59+
assert_eq!(
60+
j.implements_interfaces.len(),
61+
g.implements_interfaces.len(),
62+
"{}",
63+
j.name
64+
);
65+
assert_eq!(j.fields.len(), g.fields.len(), "{}", j.name);
66+
}
67+
}
68+
69+
// Unions
70+
{
71+
assert_eq!(json.stored_unions.len(), gql.stored_unions.len());
72+
73+
json.stored_unions.sort_by(|a, b| a.name.cmp(&b.name));
74+
gql.stored_unions.sort_by(|a, b| a.name.cmp(&b.name));
75+
76+
for (json, gql) in json.stored_unions.iter().zip(gql.stored_unions.iter()) {
77+
assert_eq!(json.variants.len(), gql.variants.len());
78+
}
79+
}
80+
81+
// Interfaces
82+
{
83+
assert_eq!(json.stored_interfaces.len(), gql.stored_interfaces.len());
84+
85+
json.stored_interfaces.sort_by(|a, b| a.name.cmp(&b.name));
86+
gql.stored_interfaces.sort_by(|a, b| a.name.cmp(&b.name));
87+
88+
for (json, gql) in json
89+
.stored_interfaces
90+
.iter()
91+
.zip(gql.stored_interfaces.iter())
92+
{
93+
assert_eq!(json.fields.len(), gql.fields.len());
94+
}
95+
}
96+
97+
// Input objects
98+
{
99+
json.stored_enums = json
100+
.stored_enums
101+
.drain(..)
102+
.filter(|enm| !enm.name.starts_with("__"))
103+
.collect();
104+
assert_eq!(json.stored_inputs.len(), gql.stored_inputs.len());
105+
106+
json.stored_inputs.sort_by(|a, b| a.name.cmp(&b.name));
107+
gql.stored_inputs.sort_by(|a, b| a.name.cmp(&b.name));
108+
109+
for (json, gql) in json.stored_inputs.iter().zip(gql.stored_inputs.iter()) {
110+
assert_eq!(json.fields.len(), gql.fields.len());
111+
}
112+
}
113+
114+
// Enums
115+
{
116+
assert_eq!(json.stored_enums.len(), gql.stored_enums.len());
117+
118+
json.stored_enums.sort_by(|a, b| a.name.cmp(&b.name));
119+
gql.stored_enums.sort_by(|a, b| a.name.cmp(&b.name));
120+
121+
for (json, gql) in json.stored_enums.iter().zip(gql.stored_enums.iter()) {
122+
assert_eq!(json.variants.len(), gql.variants.len());
123+
}
124+
}
125+
}
126+
127+
fn vecs_match<T: PartialEq>(a: &[T], b: &[T]) -> bool {
128+
a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a == b))
129+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
schema {
2+
query: Query
3+
}
4+
5+
type Query {
6+
foo: String
7+
}
8+
9+
extend type Query {
10+
bar: Int
11+
}

0 commit comments

Comments
 (0)