Skip to content

Commit

Permalink
Merge pull request #447 from pizzacat83/extend-type
Browse files Browse the repository at this point in the history
support `extend type Foo`
  • Loading branch information
mathstuf authored Oct 28, 2023
2 parents 900c432 + 313ebd5 commit c52e89e
Show file tree
Hide file tree
Showing 6 changed files with 1,267 additions and 1 deletion.
6 changes: 6 additions & 0 deletions graphql_client_codegen/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ impl Schema {
.expect("Schema::get_object")
}

pub(crate) fn get_object_mut(&mut self, object_id: ObjectId) -> &mut StoredObject {
self.stored_objects
.get_mut(object_id.0 as usize)
.expect("Schema::get_object_mut")
}

pub(crate) fn get_field(&self, field_id: StoredFieldId) -> &StoredField {
self.stored_fields.get(field_id.0).unwrap()
}
Expand Down
52 changes: 51 additions & 1 deletion graphql_client_codegen/src/schema/graphql_parser_conversion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::{Schema, StoredInputFieldType, TypeId};
use crate::schema::resolve_field_type;
use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType};
use graphql_parser::schema::{
self as parser, Definition, Document, TypeDefinition, TypeExtension, UnionType,
};

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

objects_mut(src).for_each(|obj| ingest_object(schema, obj));
extend_object_type_extensions_mut(src)
.for_each(|ext| ingest_object_type_extension(schema, ext));

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

Expand Down Expand Up @@ -200,6 +204,40 @@ fn ingest_object<'doc, T>(
schema.push_object(object);
}

fn ingest_object_type_extension<'doc, T>(
schema: &mut Schema,
ext: &mut graphql_parser::schema::ObjectTypeExtension<'doc, T>,
) where
T: graphql_parser::query::Text<'doc>,
{
let object_id = schema
.find_type_id(ext.name.as_ref())
.as_object_id()
.unwrap();
let mut field_ids = Vec::with_capacity(ext.fields.len());

for field in ext.fields.iter_mut() {
let field = super::StoredField {
name: field.name.as_ref().into(),
r#type: resolve_field_type(schema, &field.field_type),
parent: super::StoredFieldParent::Object(object_id),
deprecation: find_deprecation(&field.directives),
};

field_ids.push(schema.push_field(field));
}

let iface_ids = ext
.implements_interfaces
.iter()
.map(|iface_name| schema.find_interface(iface_name.as_ref()))
.collect::<Vec<_>>();

let object = schema.get_object_mut(object_id);
object.implements_interfaces.extend(iface_ids);
object.fields.extend(field_ids);
}

fn ingest_scalar<'doc, T>(
schema: &mut Schema,
scalar: &mut graphql_parser::schema::ScalarType<'doc, T>,
Expand Down Expand Up @@ -328,6 +366,18 @@ where
})
}

fn extend_object_type_extensions_mut<'a, 'doc: 'a, T>(
doc: &'a mut Document<'doc, T>,
) -> impl Iterator<Item = &'a mut parser::ObjectTypeExtension<'doc, T>>
where
T: graphql_parser::query::Text<'doc>,
{
doc.definitions.iter_mut().filter_map(|def| match def {
Definition::TypeExtension(TypeExtension::Object(obj)) => Some(obj),
_ => None,
})
}

fn interfaces_mut<'a, 'doc: 'a, T>(
doc: &'a mut Document<'doc, T>,
) -> impl Iterator<Item = &'a mut parser::InterfaceType<'doc, T>>
Expand Down
1 change: 1 addition & 0 deletions graphql_client_codegen/src/schema/tests.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod extend_object;
mod github;
129 changes: 129 additions & 0 deletions graphql_client_codegen/src/schema/tests/extend_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::schema::Schema;

const SCHEMA_JSON: &str = include_str!("extend_object_schema.json");
const SCHEMA_GRAPHQL: &str = include_str!("extend_object_schema.graphql");

#[test]
fn ast_from_graphql_and_json_produce_the_same_schema() {
let json: graphql_introspection_query::introspection_response::IntrospectionResponse =
serde_json::from_str(SCHEMA_JSON).unwrap();
let graphql_parser_schema = graphql_parser::parse_schema(SCHEMA_GRAPHQL)
.unwrap()
.into_static();
let mut json = Schema::from(json);
let mut gql = Schema::from(graphql_parser_schema);

assert!(vecs_match(&json.stored_scalars, &gql.stored_scalars));

// Root objects
{
assert_eq!(
json.get_object(json.query_type()).name,
gql.get_object(gql.query_type()).name
);
assert_eq!(
json.mutation_type().map(|t| &json.get_object(t).name),
gql.mutation_type().map(|t| &gql.get_object(t).name),
"Mutation types don't match."
);
assert_eq!(
json.subscription_type().map(|t| &json.get_object(t).name),
gql.subscription_type().map(|t| &gql.get_object(t).name),
"Subscription types don't match."
);
}

// Objects
{
let mut json_stored_objects: Vec<_> = json
.stored_objects
.drain(..)
.filter(|obj| !obj.name.starts_with("__"))
.collect();

assert_eq!(
json_stored_objects.len(),
gql.stored_objects.len(),
"Objects count matches."
);

json_stored_objects.sort_by(|a, b| a.name.cmp(&b.name));
gql.stored_objects.sort_by(|a, b| a.name.cmp(&b.name));

for (j, g) in json_stored_objects
.iter_mut()
.filter(|obj| !obj.name.starts_with("__"))
.zip(gql.stored_objects.iter_mut())
{
assert_eq!(j.name, g.name);
assert_eq!(
j.implements_interfaces.len(),
g.implements_interfaces.len(),
"{}",
j.name
);
assert_eq!(j.fields.len(), g.fields.len(), "{}", j.name);
}
}

// Unions
{
assert_eq!(json.stored_unions.len(), gql.stored_unions.len());

json.stored_unions.sort_by(|a, b| a.name.cmp(&b.name));
gql.stored_unions.sort_by(|a, b| a.name.cmp(&b.name));

for (json, gql) in json.stored_unions.iter().zip(gql.stored_unions.iter()) {
assert_eq!(json.variants.len(), gql.variants.len());
}
}

// Interfaces
{
assert_eq!(json.stored_interfaces.len(), gql.stored_interfaces.len());

json.stored_interfaces.sort_by(|a, b| a.name.cmp(&b.name));
gql.stored_interfaces.sort_by(|a, b| a.name.cmp(&b.name));

for (json, gql) in json
.stored_interfaces
.iter()
.zip(gql.stored_interfaces.iter())
{
assert_eq!(json.fields.len(), gql.fields.len());
}
}

// Input objects
{
json.stored_enums = json
.stored_enums
.drain(..)
.filter(|enm| !enm.name.starts_with("__"))
.collect();
assert_eq!(json.stored_inputs.len(), gql.stored_inputs.len());

json.stored_inputs.sort_by(|a, b| a.name.cmp(&b.name));
gql.stored_inputs.sort_by(|a, b| a.name.cmp(&b.name));

for (json, gql) in json.stored_inputs.iter().zip(gql.stored_inputs.iter()) {
assert_eq!(json.fields.len(), gql.fields.len());
}
}

// Enums
{
assert_eq!(json.stored_enums.len(), gql.stored_enums.len());

json.stored_enums.sort_by(|a, b| a.name.cmp(&b.name));
gql.stored_enums.sort_by(|a, b| a.name.cmp(&b.name));

for (json, gql) in json.stored_enums.iter().zip(gql.stored_enums.iter()) {
assert_eq!(json.variants.len(), gql.variants.len());
}
}
}

fn vecs_match<T: PartialEq>(a: &[T], b: &[T]) -> bool {
a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a == b))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
schema {
query: Query
}

type Query {
foo: String
}

extend type Query {
bar: Int
}
Loading

0 comments on commit c52e89e

Please sign in to comment.