From 27a4a54927a1e210e6a29d7c5360a61113f9f7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 17 Jan 2020 19:53:04 +0100 Subject: [PATCH 01/93] Start refactoring the schema to a normalized graph-like datastructure --- .../src/introspection_response.rs | 12 +- graphql_client_codegen/src/codegen.rs | 298 +++---- graphql_client_codegen/src/enums.rs | 5 +- graphql_client_codegen/src/field_type.rs | 265 +++--- graphql_client_codegen/src/fragments.rs | 29 +- .../src/generated_module.rs | 2 +- graphql_client_codegen/src/inputs.rs | 454 +++++----- graphql_client_codegen/src/interfaces.rs | 88 +- graphql_client_codegen/src/lib.rs | 4 +- graphql_client_codegen/src/objects.rs | 107 +-- graphql_client_codegen/src/operations.rs | 69 +- graphql_client_codegen/src/query.rs | 61 +- graphql_client_codegen/src/schema.rs | 794 +++++++++++------- .../src/schema/graphql_parser_conversion.rs | 268 ++++++ .../src/schema/json_conversion.rs | 392 +++++++++ graphql_client_codegen/src/selection.rs | 97 +-- graphql_client_codegen/src/shared.rs | 4 +- graphql_client_codegen/src/unions.rs | 106 +-- graphql_client_codegen/src/variables.rs | 108 +-- 19 files changed, 1977 insertions(+), 1186 deletions(-) create mode 100644 graphql_client_codegen/src/schema/graphql_parser_conversion.rs create mode 100644 graphql_client_codegen/src/schema/json_conversion.rs diff --git a/graphql-introspection-query/src/introspection_response.rs b/graphql-introspection-query/src/introspection_response.rs index 9e7d1fe8e..3e3dab4ca 100644 --- a/graphql-introspection-query/src/introspection_response.rs +++ b/graphql-introspection-query/src/introspection_response.rs @@ -78,7 +78,7 @@ impl<'de> Deserialize<'de> for __DirectiveLocation { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum __TypeKind { SCALAR, OBJECT, @@ -130,11 +130,11 @@ pub struct FullType { pub kind: Option<__TypeKind>, pub name: Option, pub description: Option, - pub fields: Option>>, - pub input_fields: Option>>, - pub interfaces: Option>>, - pub enum_values: Option>>, - pub possible_types: Option>>, + pub fields: Option>, + pub input_fields: Option>, + pub interfaces: Option>, + pub enum_values: Option>, + pub possible_types: Option>, } #[derive(Clone, Debug, Deserialize)] diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 3ab9a0ba4..d457f80fd 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -3,7 +3,7 @@ use crate::normalization::Normalization; use crate::operations::Operation; use crate::query::QueryContext; use crate::schema; -use crate::selection::Selection; +// use crate::selection::Selection; use failure::*; use graphql_parser::query; use proc_macro2::TokenStream; @@ -36,156 +36,156 @@ pub(crate) fn all_operations(query: &query::Document) -> Vec> { /// The main code generation function. pub(crate) fn response_for_query( - schema: &schema::Schema<'_>, + schema: &schema::Schema, query: &query::Document, operation: &Operation<'_>, options: &crate::GraphQLClientCodegenOptions, ) -> Result { - let mut context = QueryContext::new( - schema, - options.deprecation_strategy(), - options.normalization(), - ); - - if let Some(derives) = options.variables_derives() { - context.ingest_variables_derives(&derives)?; - } - - if let Some(derives) = options.response_derives() { - context.ingest_response_derives(&derives)?; - } - - let mut definitions = Vec::new(); - - for definition in &query.definitions { - match definition { - query::Definition::Operation(_op) => (), - query::Definition::Fragment(fragment) => { - let &query::TypeCondition::On(ref on) = &fragment.type_condition; - let on = schema.fragment_target(on).ok_or_else(|| { - format_err!( - "Fragment {} is defined on unknown type: {}", - &fragment.name, - on, - ) - })?; - context.fragments.insert( - &fragment.name, - GqlFragment { - name: &fragment.name, - selection: Selection::from(&fragment.selection_set), - on, - is_required: false.into(), - }, - ); - } - } - } - - let response_data_fields = { - let root_name = operation.root_name(&context.schema); - let opt_definition = context.schema.objects.get(&root_name); - let definition = if let Some(definition) = opt_definition { - definition - } else { - panic!( - "operation type '{:?}' not in schema", - operation.operation_type - ); - }; - let prefix = &operation.name; - let selection = &operation.selection; - - if operation.is_subscription() && selection.len() > 1 { - return Err(format_err!( - "{}", - crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR - )); - } - - definitions.extend(definition.field_impls_for_selection(&context, &selection, &prefix)?); - definition.response_fields_for_selection(&context, &selection, &prefix)? - }; - - let enum_definitions = context.schema.enums.values().filter_map(|enm| { - if enm.is_required.get() { - Some(enm.to_rust(&context)) - } else { - None - } - }); - let fragment_definitions: Result, _> = context - .fragments - .values() - .filter_map(|fragment| { - if fragment.is_required.get() { - Some(fragment.to_rust(&context)) - } else { - None - } - }) - .collect(); - let fragment_definitions = fragment_definitions?; - let variables_struct = operation.expand_variables(&context); - - let input_object_definitions: Result, _> = context - .schema - .inputs - .values() - .filter_map(|i| { - if i.is_required.get() { - Some(i.to_rust(&context)) - } else { - None - } - }) - .collect(); - let input_object_definitions = input_object_definitions?; - - let scalar_definitions: Vec = context - .schema - .scalars - .values() - .filter_map(|s| { - if s.is_required.get() { - Some(s.to_rust(context.normalization)) - } else { - None - } - }) - .collect(); - - let response_derives = context.response_derives(); - - Ok(quote! { - use serde::{Serialize, Deserialize}; - - #[allow(dead_code)] - type Boolean = bool; - #[allow(dead_code)] - type Float = f64; - #[allow(dead_code)] - type Int = i64; - #[allow(dead_code)] - type ID = String; - - #(#scalar_definitions)* - - #(#input_object_definitions)* - - #(#enum_definitions)* - - #(#fragment_definitions)* - - #(#definitions)* - - #variables_struct - - #response_derives - - pub struct ResponseData { - #(#response_data_fields,)* - } - - }) + todo!() + + // let mut context = QueryContext::new( + // schema, + // options.deprecation_strategy(), + // options.normalization(), + // ); + + // if let Some(derives) = options.variables_derives() { + // context.ingest_variables_derives(&derives)?; + // } + + // if let Some(derives) = options.response_derives() { + // context.ingest_response_derives(&derives)?; + // } + + // let mut definitions = Vec::new(); + + // for definition in &query.definitions { + // match definition { + // query::Definition::Operation(_op) => (), + // query::Definition::Fragment(fragment) => { + // let &query::TypeCondition::On(ref on) = &fragment.type_condition; + // let on = schema.lookup_type(on).ok_or_else(|| { + // format_err!( + // "Fragment {} is defined on unknown type: {}", + // &fragment.name, + // on, + // ) + // })?; + // context.fragments.insert( + // &fragment.name, + // GqlFragment { + // name: &fragment.name, + // selection: Selection::from(&fragment.selection_set), + // on, + // is_required: false.into(), + // }, + // ); + // } + // } + // } + + // let response_data_fields = { + // let root_name = operation.root_name(&schema); + // let opt_definition = schema.get_object_by_name(&root_name); + // let definition = if let Some(definition) = opt_definition { + // definition + // } else { + // panic!( + // "operation type '{:?}' not in schema", + // operation.operation_type + // ); + // }; + // let prefix = &operation.name; + // let selection = &operation.selection; + + // if operation.is_subscription() && selection.len() > 1 { + // return Err(format_err!( + // "{}", + // crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR + // )); + // } + + // definitions.extend(definition.field_impls_for_selection(&context, &selection, &prefix)?); + // definition.response_fields_for_selection(&context, &selection, &prefix)? + // }; + + // let enum_definitions = schema.enums.values().filter_map(|enm| { + // if enm.is_required.get() { + // Some(enm.to_rust(&context)) + // } else { + // None + // } + // }); + // let fragment_definitions: Result, _> = context + // .fragments + // .values() + // .filter_map(|fragment| { + // if fragment.is_required.get() { + // Some(fragment.to_rust(&context)) + // } else { + // None + // } + // }) + // .collect(); + // let fragment_definitions = fragment_definitions?; + // let variables_struct = operation.expand_variables(&context); + + // let input_object_definitions: Result, _> = schema + // .inputs + // .values() + // .filter_map(|i| { + // if i.is_required.get() { + // Some(i.to_rust(&context)) + // } else { + // None + // } + // }) + // .collect(); + // let input_object_definitions = input_object_definitions?; + + // let scalar_definitions: Vec = schema + // .scalars + // .values() + // .filter_map(|s| { + // if s.is_required.get() { + // Some(s.to_rust(context.normalization)) + // } else { + // None + // } + // }) + // .collect(); + + // let response_derives = context.response_derives(); + + // Ok(quote! { + // use serde::{Serialize, Deserialize}; + + // #[allow(dead_code)] + // type Boolean = bool; + // #[allow(dead_code)] + // type Float = f64; + // #[allow(dead_code)] + // type Int = i64; + // #[allow(dead_code)] + // type ID = String; + + // #(#scalar_definitions)* + + // #(#input_object_definitions)* + + // #(#enum_definitions)* + + // #(#fragment_definitions)* + + // #(#definitions)* + + // #variables_struct + + // #response_derives + + // pub struct ResponseData { + // #(#response_data_fields,)* + // } + + // }) } diff --git a/graphql_client_codegen/src/enums.rs b/graphql_client_codegen/src/enums.rs index 563166c09..76becea6e 100644 --- a/graphql_client_codegen/src/enums.rs +++ b/graphql_client_codegen/src/enums.rs @@ -26,10 +26,7 @@ impl<'schema> GqlEnum<'schema> { * Generated "variant_names" enum: pub enum AnEnum { where_, self_, Other(String), } * Generated serialize line: "AnEnum::where_ => "where"," */ - pub(crate) fn to_rust( - &self, - query_context: &crate::query::QueryContext<'_, '_>, - ) -> TokenStream { + pub(crate) fn to_rust(&self, query_context: &crate::query::QueryContext<'_>) -> TokenStream { let derives = query_context.response_enum_derives(); let norm = query_context.normalization; let variant_names: Vec = self diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index a2c7a10fb..4ac1480bb 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -5,8 +5,12 @@ use graphql_introspection_query::introspection_response; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; +pub(crate) fn field_type_to_rust() -> TokenStream { + todo!() +} + #[derive(Clone, Debug, PartialEq, Hash)] -enum GraphqlTypeQualifier { +pub(crate) enum GraphqlTypeQualifier { Required, List, } @@ -45,78 +49,79 @@ impl<'a> FieldType<'a> { } /// Takes a field type with its name. - pub(crate) fn to_rust(&self, context: &QueryContext<'_, '_>, prefix: &str) -> TokenStream { - let prefix: &str = if prefix.is_empty() { - self.inner_name_str() - } else { - prefix - }; - - let full_name = { - if context - .schema - .scalars - .get(&self.name) - .map(|s| s.is_required.set(true)) - .is_some() - || DEFAULT_SCALARS.iter().any(|elem| elem == &self.name) - { - self.name.to_string() - } else if context - .schema - .enums - .get(&self.name) - .map(|enm| enm.is_required.set(true)) - .is_some() - { - format!("{}{}", ENUMS_PREFIX, self.name) - } else { - if prefix.is_empty() { - panic!("Empty prefix for {:?}", self); - } - prefix.to_string() - } - }; - - let norm = context.normalization; - let full_name = norm.field_type(crate::shared::keyword_replace(&full_name)); - - let full_name = Ident::new(&full_name, Span::call_site()); - let mut qualified = quote!(#full_name); - - let mut non_null = false; - - // Note: we iterate over qualifiers in reverse because it is more intuitive. This - // means we start from the _inner_ type and make our way to the outside. - for qualifier in self.qualifiers.iter().rev() { - match (non_null, qualifier) { - // We are in non-null context, and we wrap the non-null type into a list. - // We switch back to null context. - (true, GraphqlTypeQualifier::List) => { - qualified = quote!(Vec<#qualified>); - non_null = false; - } - // We are in nullable context, and we wrap the nullable type into a list. - (false, GraphqlTypeQualifier::List) => { - qualified = quote!(Vec>); - } - // We are in non-nullable context, but we can't double require a type - // (!!). - (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), - // We are in nullable context, and we switch to non-nullable context. - (false, GraphqlTypeQualifier::Required) => { - non_null = true; - } - } - } - - // If we are in nullable context at the end of the iteration, we wrap the whole - // type with an Option. - if !non_null { - qualified = quote!(Option<#qualified>); - } - - qualified + pub(crate) fn to_rust(&self, context: &QueryContext<'_>, prefix: &str) -> TokenStream { + todo!() + // let prefix: &str = if prefix.is_empty() { + // self.inner_name_str() + // } else { + // prefix + // }; + + // let full_name = { + // if context + // .schema + // .scalars + // .get(&self.name) + // .map(|s| s.is_required.set(true)) + // .is_some() + // || DEFAULT_SCALARS.iter().any(|elem| elem == &self.name) + // { + // self.name.to_string() + // } else if context + // .schema + // .enums + // .get(&self.name) + // .map(|enm| enm.is_required.set(true)) + // .is_some() + // { + // format!("{}{}", ENUMS_PREFIX, self.name) + // } else { + // if prefix.is_empty() { + // panic!("Empty prefix for {:?}", self); + // } + // prefix.to_string() + // } + // }; + + // let norm = context.normalization; + // let full_name = norm.field_type(crate::shared::keyword_replace(&full_name)); + + // let full_name = Ident::new(&full_name, Span::call_site()); + // let mut qualified = quote!(#full_name); + + // let mut non_null = false; + + // // Note: we iterate over qualifiers in reverse because it is more intuitive. This + // // means we start from the _inner_ type and make our way to the outside. + // for qualifier in self.qualifiers.iter().rev() { + // match (non_null, qualifier) { + // // We are in non-null context, and we wrap the non-null type into a list. + // // We switch back to null context. + // (true, GraphqlTypeQualifier::List) => { + // qualified = quote!(Vec<#qualified>); + // non_null = false; + // } + // // We are in nullable context, and we wrap the nullable type into a list. + // (false, GraphqlTypeQualifier::List) => { + // qualified = quote!(Vec>); + // } + // // We are in non-nullable context, but we can't double require a type + // // (!!). + // (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), + // // We are in nullable context, and we switch to non-nullable context. + // (false, GraphqlTypeQualifier::Required) => { + // non_null = true; + // } + // } + // } + + // // If we are in nullable context at the end of the iteration, we wrap the whole + // // type with an Option. + // if !non_null { + // qualified = quote!(Option<#qualified>); + // } + + // qualified } /// Return the innermost name - we mostly use this for looking types up in our Schema struct. @@ -147,11 +152,12 @@ impl<'a> FieldType<'a> { impl<'schema> std::convert::From<&'schema graphql_parser::schema::Type> for FieldType<'schema> { fn from(schema_type: &'schema graphql_parser::schema::Type) -> FieldType<'schema> { - from_schema_type_inner(schema_type) + todo!() + // from_schema_type_inner(schema_type) } } -fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { +pub(crate) fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { match schema_type { graphql_parser::schema::Type::ListType(inner) => 1 + graphql_parser_depth(inner), graphql_parser::schema::Type::NonNullType(inner) => 1 + graphql_parser_depth(inner), @@ -159,29 +165,6 @@ fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { } } -fn from_schema_type_inner(inner: &graphql_parser::schema::Type) -> FieldType<'_> { - use graphql_parser::schema::Type::*; - - let qualifiers_depth = graphql_parser_depth(inner); - let mut qualifiers = Vec::with_capacity(qualifiers_depth); - - let mut inner = inner; - - loop { - match inner { - ListType(new_inner) => { - qualifiers.push(GraphqlTypeQualifier::List); - inner = new_inner; - } - NonNullType(new_inner) => { - qualifiers.push(GraphqlTypeQualifier::Required); - inner = new_inner; - } - NamedType(name) => return FieldType { name, qualifiers }, - } - } -} - fn json_type_qualifiers_depth(typeref: &introspection_response::TypeRef) -> usize { use graphql_introspection_query::introspection_response::*; @@ -237,46 +220,46 @@ impl<'a> std::convert::From<&'a introspection_response::InputValueType> for Fiel } } -#[cfg(test)] -mod tests { - use super::*; - use graphql_introspection_query::introspection_response::{ - FullTypeFieldsType, TypeRef, __TypeKind, - }; - use graphql_parser::schema::Type as GqlParserType; - - #[test] - fn field_type_from_graphql_parser_schema_type_works() { - let ty = GqlParserType::NamedType("Cat".to_owned()); - assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); - - let ty = GqlParserType::NonNullType(Box::new(GqlParserType::NamedType("Cat".to_owned()))); - - assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); - } - - #[test] - fn field_type_from_introspection_response_works() { - let ty = FullTypeFieldsType { - type_ref: TypeRef { - kind: Some(__TypeKind::OBJECT), - name: Some("Cat".into()), - of_type: None, - }, - }; - assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); - - let ty = FullTypeFieldsType { - type_ref: TypeRef { - kind: Some(__TypeKind::NON_NULL), - name: None, - of_type: Some(Box::new(TypeRef { - kind: Some(__TypeKind::OBJECT), - name: Some("Cat".into()), - of_type: None, - })), - }, - }; - assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use graphql_introspection_query::introspection_response::{ +// FullTypeFieldsType, TypeRef, __TypeKind, +// }; +// use graphql_parser::schema::Type as GqlParserType; + +// #[test] +// fn field_type_from_graphql_parser_schema_type_works() { +// let ty = GqlParserType::NamedType("Cat".to_owned()); +// assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); + +// let ty = GqlParserType::NonNullType(Box::new(GqlParserType::NamedType("Cat".to_owned()))); + +// assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); +// } + +// #[test] +// fn field_type_from_introspection_response_works() { +// let ty = FullTypeFieldsType { +// type_ref: TypeRef { +// kind: Some(__TypeKind::OBJECT), +// name: Some("Cat".into()), +// of_type: None, +// }, +// }; +// assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); + +// let ty = FullTypeFieldsType { +// type_ref: TypeRef { +// kind: Some(__TypeKind::NON_NULL), +// name: None, +// of_type: Some(Box::new(TypeRef { +// kind: Some(__TypeKind::OBJECT), +// name: Some("Cat".into()), +// of_type: None, +// })), +// }, +// }; +// assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); +// } +// } diff --git a/graphql_client_codegen/src/fragments.rs b/graphql_client_codegen/src/fragments.rs index ac5c8d901..6e86dca58 100644 --- a/graphql_client_codegen/src/fragments.rs +++ b/graphql_client_codegen/src/fragments.rs @@ -27,7 +27,7 @@ pub(crate) struct GqlFragment<'query> { /// The name of the fragment, matching one-to-one with the name in the GraphQL query document. pub name: &'query str, /// The `on` clause of the fragment. - pub on: FragmentTarget<'query>, + pub on: crate::schema::TypeId, /// The selected fields. pub selection: Selection<'query>, /// Whether the fragment is used in the current query @@ -38,26 +38,27 @@ impl<'query> GqlFragment<'query> { /// Generate all the Rust code required by the fragment's object selection. pub(crate) fn to_rust( &self, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, ) -> Result { - match self.on { - FragmentTarget::Object(obj) => { - obj.response_for_selection(context, &self.selection, &self.name) - } - FragmentTarget::Interface(iface) => { - iface.response_for_selection(context, &self.selection, &self.name) - } - FragmentTarget::Union(_) => { - unreachable!("Wrong code path. Fragment on unions are treated differently.") - } - } + todo!() + // match self.on { + // FragmentTarget::Object(obj) => { + // obj.response_for_selection(context, &self.selection, &self.name) + // } + // FragmentTarget::Interface(iface) => { + // iface.response_for_selection(context, &self.selection, &self.name) + // } + // FragmentTarget::Union(_) => { + // unreachable!("Wrong code path. Fragment on unions are treated differently.") + // } + // } } pub(crate) fn is_recursive(&self) -> bool { self.selection.contains_fragment(&self.name) } - pub(crate) fn require<'schema>(&self, context: &QueryContext<'query, 'schema>) { + pub(crate) fn require<'schema>(&self, context: &QueryContext<'query>) { self.is_required.set(true); self.selection.require_items(context); } diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 09e649a6d..52985d9b1 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -8,7 +8,7 @@ pub(crate) struct GeneratedModule<'a> { pub operation: &'a crate::operations::Operation<'a>, pub query_string: &'a str, pub query_document: &'a graphql_parser::query::Document, - pub schema: &'a crate::schema::Schema<'a>, + pub schema: &'a crate::schema::Schema, pub options: &'a crate::GraphQLClientCodegenOptions, } diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index c91cb29a7..fcf8be0c0 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -1,244 +1,230 @@ use crate::deprecation::DeprecationStatus; use crate::objects::GqlObjectField; use crate::query::QueryContext; -use crate::schema::Schema; +use crate::schema; +use crate::schema::{InputObjectId, SchemaRef}; use graphql_introspection_query::introspection_response; -use heck::SnakeCase; +// use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +// use quote::quote; use std::cell::Cell; use std::collections::HashMap; -/// Represents an input object type from a GraphQL schema -#[derive(Debug, Clone, PartialEq)] -pub struct GqlInput<'schema> { - pub description: Option<&'schema str>, - pub name: &'schema str, - pub fields: HashMap<&'schema str, GqlObjectField<'schema>>, - pub is_required: Cell, +pub(crate) fn input_to_rust( + ctx: &mut QueryContext<'_>, + input: crate::schema::InputRef<'_>, +) -> Result { + todo!() } -impl<'schema> GqlInput<'schema> { - pub(crate) fn require(&self, schema: &Schema<'schema>) { - if self.is_required.get() { - return; - } - self.is_required.set(true); - self.fields.values().for_each(|field| { - schema.require(&field.type_.inner_name_str()); - }) - } - - fn contains_type_without_indirection( - &self, - context: &QueryContext<'_, '_>, - type_name: &str, - ) -> bool { - // the input type is recursive if any of its members contains it, without indirection - self.fields.values().any(|field| { - // the field is indirected, so no boxing is needed - if field.type_.is_indirected() { - return false; - } - - let field_type_name = field.type_.inner_name_str(); - let input = context.schema.inputs.get(field_type_name); - - if let Some(input) = input { - // the input contains itself, not indirected - if input.name == type_name { - return true; - } - - // we check if the other input contains this one (without indirection) - input.contains_type_without_indirection(context, type_name) - } else { - // the field is not referring to an input type - false - } - }) - } - - fn is_recursive_without_indirection(&self, context: &QueryContext<'_, '_>) -> bool { - self.contains_type_without_indirection(context, &self.name) - } - - pub(crate) fn to_rust( - &self, - context: &QueryContext<'_, '_>, - ) -> Result { - let norm = context.normalization; - let mut fields: Vec<&GqlObjectField<'_>> = self.fields.values().collect(); - fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); - let fields = fields.iter().map(|field| { - let ty = field.type_.to_rust(&context, ""); - - // If the type is recursive, we have to box it - let ty = if let Some(input) = context.schema.inputs.get(field.type_.inner_name_str()) { - if input.is_recursive_without_indirection(context) { - quote! { Box<#ty> } - } else { - quote!(#ty) - } - } else { - quote!(#ty) - }; - - context.schema.require(&field.type_.inner_name_str()); - let name = crate::shared::keyword_replace(&field.name.to_snake_case()); - let rename = crate::shared::field_rename_annotation(&field.name, &name); - let name = norm.field_name(name); - let name = Ident::new(&name, Span::call_site()); - - quote!(#rename pub #name: #ty) - }); - let variables_derives = context.variables_derives(); - - // Prevent generated code like "pub struct crate" for a schema input like "input crate { ... }" - // This works in tandem with renamed struct Variables field types, eg: pub struct Variables { pub criteria : crate_ , } - let name = crate::shared::keyword_replace(&self.name); - let name = norm.input_name(name); - let name = Ident::new(&name, Span::call_site()); - Ok(quote! { - #variables_derives - pub struct #name { - #(#fields,)* - } - }) - } -} - -impl<'schema> std::convert::From<&'schema graphql_parser::schema::InputObjectType> - for GqlInput<'schema> -{ - fn from(schema_input: &'schema graphql_parser::schema::InputObjectType) -> GqlInput<'schema> { - GqlInput { - description: schema_input.description.as_deref(), - name: &schema_input.name, - fields: schema_input - .fields - .iter() - .map(|field| { - let name = field.name.as_str(); - let field = GqlObjectField { - description: None, - name: &field.name, - type_: crate::field_type::FieldType::from(&field.value_type), - deprecation: DeprecationStatus::Current, - }; - (name, field) - }) - .collect(), - is_required: false.into(), - } - } -} - -impl<'schema> std::convert::From<&'schema introspection_response::FullType> for GqlInput<'schema> { - fn from(schema_input: &'schema introspection_response::FullType) -> GqlInput<'schema> { - GqlInput { - description: schema_input.description.as_deref(), - name: schema_input.name.as_deref().expect("unnamed input object"), - fields: schema_input - .input_fields - .as_ref() - .expect("fields on input object") - .iter() - .filter_map(Option::as_ref) - .map(|f| { - let name = f - .input_value - .name - .as_ref() - .expect("unnamed input object field") - .as_str(); - let field = GqlObjectField { - description: None, - name: &name, - type_: f - .input_value - .type_ - .as_ref() - .map(|s| s.into()) - .expect("type on input object field"), - deprecation: DeprecationStatus::Current, - }; - (name, field) - }) - .collect(), - is_required: false.into(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::constants::*; - use crate::field_type::FieldType; - - #[test] - fn gql_input_to_rust() { - let cat = GqlInput { - description: None, - name: "Cat", - fields: vec![ - ( - "pawsCount", - GqlObjectField { - description: None, - name: "pawsCount", - type_: FieldType::new(float_type()).nonnull(), - deprecation: DeprecationStatus::Current, - }, - ), - ( - "offsprings", - GqlObjectField { - description: None, - name: "offsprings", - type_: FieldType::new("Cat").nonnull().list().nonnull(), - deprecation: DeprecationStatus::Current, - }, - ), - ( - "requirements", - GqlObjectField { - description: None, - name: "requirements", - type_: FieldType::new("CatRequirements"), - deprecation: DeprecationStatus::Current, - }, - ), - ] - .into_iter() - .collect(), - is_required: false.into(), - }; - - let expected: String = vec![ - "# [ derive ( Clone , Serialize ) ] ", - "pub struct Cat { ", - "pub offsprings : Vec < Cat > , ", - "# [ serde ( rename = \"pawsCount\" ) ] ", - "pub paws_count : Float , ", - "pub requirements : Option < CatRequirements > , ", - "}", - ] - .into_iter() - .collect(); - - let mut schema = crate::schema::Schema::new(); - schema.inputs.insert(cat.name, cat); - let mut context = QueryContext::new_empty(&schema); - context.ingest_variables_derives("Clone").unwrap(); - - assert_eq!( - format!( - "{}", - context.schema.inputs["Cat"].to_rust(&context).unwrap() - ), - expected - ); - } -} +// /// Represents an input object type from a GraphQL schema +// #[derive(Debug, Clone, PartialEq)] +// pub struct InputRef<'a> { +// schema: SchemaRef<'a>, +// input_id: InputObjectId, +// // pub description: Option<&'schema str>, +// // pub name: &'schema str, +// // pub fields: HashMap<&'schema str, GqlObjectField<'schema>>, +// // pub is_required: Cell, +// } + +// impl InputRef<'_> { +// // pub(crate) fn require(&self, schema: &Schema) { +// // if self.is_required.get() { +// // return; +// // } +// // self.is_required.set(true); +// // self.fields.values().for_each(|field| { +// // schema.require(&field.type_.inner_name_str()); +// // }) +// // } + +// fn is_recursive_without_indirection(&self, context: &QueryContext<'_>) -> bool { +// self.contains_type_without_indirection(context, &self.name) +// } + +// pub(crate) fn to_rust( +// &self, +// context: &QueryContext<'_>, +// ) -> Result { +// let norm = context.normalization; +// let mut fields: Vec<&GqlObjectField<'_>> = self.fields.values().collect(); +// fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); +// let fields = fields.iter().map(|field| { +// let ty = field.type_.to_rust(&context, ""); + +// // If the type is recursive, we have to box it +// let ty = if let Some(input) = context.schema.inputs.get(field.type_.inner_name_str()) { +// if input.is_recursive_without_indirection(context) { +// quote! { Box<#ty> } +// } else { +// quote!(#ty) +// } +// } else { +// quote!(#ty) +// }; + +// // context.schema.require(&field.type_.inner_name_str()); +// let name = crate::shared::keyword_replace(&field.name.to_snake_case()); +// let rename = crate::shared::field_rename_annotation(&field.name, &name); +// let name = norm.field_name(name); +// let name = Ident::new(&name, Span::call_site()); + +// quote!(#rename pub #name: #ty) +// }); +// let variables_derives = context.variables_derives(); + +// // Prevent generated code like "pub struct crate" for a schema input like "input crate { ... }" +// // This works in tandem with renamed struct Variables field types, eg: pub struct Variables { pub criteria : crate_ , } +// let name = crate::shared::keyword_replace(&self.name); +// let name = norm.input_name(name); +// let name = Ident::new(&name, Span::call_site()); +// Ok(quote! { +// #variables_derives +// pub struct #name { +// #(#fields,)* +// } +// }) +// } +// } + +// // impl<'schema> std::convert::From<&'schema mut graphql_parser::schema::InputObjectType> +// // for InputRef<'schema> +// // { +// // fn from( +// // schema_input: &'schema mut graphql_parser::schema::InputObjectType, +// // ) -> InputRef<'schema> { +// // InputRef { +// // description: schema_input.description.as_ref().map(String::as_str), +// // name: &schema_input.name, +// // fields: schema_input +// // .fields +// // .iter() +// // .map(|field| { +// // let name = field.name.as_str(); +// // let field = GqlObjectField { +// // description: None, +// // name: &field.name, +// // type_: crate::field_type::FieldType::from(&field.value_type), +// // deprecation: DeprecationStatus::Current, +// // }; +// // (name, field) +// // }) +// // .collect(), +// // is_required: false.into(), +// // } +// // } +// // } + +// // impl<'schema> std::convert::From<&'schema introspection_response::FullType> for InputRef<'schema> { +// // fn from(schema_input: &'schema introspection_response::FullType) -> InputRef<'schema> { +// // InputRef { +// // description: schema_input.description.as_ref().map(String::as_str), +// // name: schema_input +// // .name +// // .as_ref() +// // .map(String::as_str) +// // .expect("unnamed input object"), +// // fields: schema_input +// // .input_fields +// // .as_ref() +// // .expect("fields on input object") +// // .iter() +// // .filter_map(Option::as_ref) +// // .map(|f| { +// // let name = f +// // .input_value +// // .name +// // .as_ref() +// // .expect("unnamed input object field") +// // .as_str(); +// // let field = GqlObjectField { +// // description: None, +// // name: &name, +// // type_: f +// // .input_value +// // .type_ +// // .as_ref() +// // .map(|s| s.into()) +// // .expect("type on input object field"), +// // deprecation: DeprecationStatus::Current, +// // }; +// // (name, field) +// // }) +// // .collect(), +// // is_required: false.into(), +// // } +// // } +// // } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::constants::*; +// use crate::field_type::FieldType; + +// #[test] +// fn gql_input_to_rust() { +// let cat = InputRef { +// description: None, +// name: "Cat", +// fields: vec![ +// ( +// "pawsCount", +// GqlObjectField { +// description: None, +// name: "pawsCount", +// type_: FieldType::new(float_type()).nonnull(), +// deprecation: DeprecationStatus::Current, +// }, +// ), +// ( +// "offsprings", +// GqlObjectField { +// description: None, +// name: "offsprings", +// type_: FieldType::new("Cat").nonnull().list().nonnull(), +// deprecation: DeprecationStatus::Current, +// }, +// ), +// ( +// "requirements", +// GqlObjectField { +// description: None, +// name: "requirements", +// type_: FieldType::new("CatRequirements"), +// deprecation: DeprecationStatus::Current, +// }, +// ), +// ] +// .into_iter() +// .collect(), +// is_required: false.into(), +// }; + +// let expected: String = vec![ +// "# [ derive ( Clone , Serialize ) ] ", +// "pub struct Cat { ", +// "pub offsprings : Vec < Cat > , ", +// "# [ serde ( rename = \"pawsCount\" ) ] ", +// "pub paws_count : Float , ", +// "pub requirements : Option < CatRequirements > , ", +// "}", +// ] +// .into_iter() +// .collect(); + +// let mut schema = crate::schema::Schema::new(); +// schema.inputs.insert(cat.name, cat); +// let mut context = QueryContext::new_empty(&schema); +// context.ingest_variables_derives("Clone").unwrap(); + +// assert_eq!( +// format!( +// "{}", +// context.schema.inputs["Cat"].to_rust(&context).unwrap() +// ), +// expected +// ); +// } +// } diff --git a/graphql_client_codegen/src/interfaces.rs b/graphql_client_codegen/src/interfaces.rs index 1ae87ff5a..5df0ae870 100644 --- a/graphql_client_codegen/src/interfaces.rs +++ b/graphql_client_codegen/src/interfaces.rs @@ -33,55 +33,57 @@ impl<'schema> GqlInterface<'schema> { fn object_selection<'query>( &self, selection: &'query Selection<'query>, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, ) -> Selection<'query> { - (&selection) - .into_iter() - // Only keep what we can handle - .filter(|f| match f { - SelectionItem::Field(f) => f.name != TYPENAME_FIELD, - SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - // only if the fragment refers to the interface’s own fields (to take into account type-refining fragments) - let fragment = query_context - .fragments - .get(fragment_name) - .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) - // TODO: fix this - .unwrap(); + todo!() + // (&selection) + // .into_iter() + // // Only keep what we can handle + // .filter(|f| match f { + // SelectionItem::Field(f) => f.name != TYPENAME_FIELD, + // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { + // // only if the fragment refers to the interface’s own fields (to take into account type-refining fragments) + // let fragment = query_context + // .fragments + // .get(fragment_name) + // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) + // // TODO: fix this + // .unwrap(); - fragment.on.name() == self.name - } - SelectionItem::InlineFragment(_) => false, - }) - .map(|a| (*a).clone()) - .collect() + // fragment.on.name() == self.name + // } + // SelectionItem::InlineFragment(_) => false, + // }) + // .map(|a| (*a).clone()) + // .collect() } fn union_selection<'query>( &self, selection: &'query Selection<'_>, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, ) -> Selection<'query> { - (&selection) - .into_iter() - // Only keep what we can handle - .filter(|f| match f { - SelectionItem::InlineFragment(_) => true, - SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - let fragment = query_context - .fragments - .get(fragment_name) - .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) - // TODO: fix this - .unwrap(); + todo!() + // (&selection) + // .into_iter() + // // Only keep what we can handle + // .filter(|f| match f { + // SelectionItem::InlineFragment(_) => true, + // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { + // let fragment = query_context + // .fragments + // .get(fragment_name) + // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) + // // TODO: fix this + // .unwrap(); - // only the fragments _not_ on the interface - fragment.on.name() != self.name - } - SelectionItem::Field(SelectionField { name, .. }) => *name == "__typename", - }) - .map(|a| (*a).clone()) - .collect() + // // only the fragments _not_ on the interface + // fragment.on.name() != self.name + // } + // SelectionItem::Field(SelectionField { name, .. }) => *name == "__typename", + // }) + // .map(|a| (*a).clone()) + // .collect() } /// Create an empty interface. This needs to be mutated before it is useful. @@ -101,7 +103,7 @@ impl<'schema> GqlInterface<'schema> { /// The generated code for each of the selected field's types. See [shared::field_impls_for_selection]. pub(crate) fn field_impls_for_selection( &self, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { @@ -116,7 +118,7 @@ impl<'schema> GqlInterface<'schema> { /// The code for the interface's corresponding struct's fields. pub(crate) fn response_fields_for_selection( &self, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { @@ -132,7 +134,7 @@ impl<'schema> GqlInterface<'schema> { /// Generate all the code for the interface. pub(crate) fn response_for_selection( &self, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result { diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index f5fa21e5b..f49c5c4fa 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -108,7 +108,7 @@ pub fn generate_module_token_stream( } }; - let parsed_schema = match schema_extension { + let mut parsed_schema = match schema_extension { "graphql" | "gql" => { let s = graphql_parser::schema::parse_schema(&schema_string)?; schema::ParsedSchema::GraphQLParser(s) @@ -120,7 +120,7 @@ pub fn generate_module_token_stream( extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension) }; - let schema = schema::Schema::from(&parsed_schema); + let schema = schema::Schema::from(parsed_schema); // The generated modules. let mut modules = Vec::with_capacity(operations.len()); diff --git a/graphql_client_codegen/src/objects.rs b/graphql_client_codegen/src/objects.rs index 48f0607e2..0cca2796b 100644 --- a/graphql_client_codegen/src/objects.rs +++ b/graphql_client_codegen/src/objects.rs @@ -2,7 +2,7 @@ use crate::constants::*; use crate::deprecation::DeprecationStatus; use crate::field_type::FieldType; use crate::query::QueryContext; -use crate::schema::Schema; +// use crate::schema::Schema; use crate::selection::*; use crate::shared::{field_impls_for_selection, response_fields_for_selection}; use graphql_parser::schema; @@ -75,67 +75,68 @@ impl<'schema> GqlObject<'schema> { item } - pub fn from_introspected_schema_json( - obj: &'schema graphql_introspection_query::introspection_response::FullType, - ) -> Self { - let description = obj.description.as_deref(); - let mut item = GqlObject::new(obj.name.as_ref().expect("missing object name"), description); - let fields = obj.fields.as_ref().unwrap().iter().filter_map(|t| { - t.as_ref().map(|t| { - let deprecation = if t.is_deprecated.unwrap_or(false) { - DeprecationStatus::Deprecated(t.deprecation_reason.clone()) - } else { - DeprecationStatus::Current - }; - GqlObjectField { - description: t.description.as_deref(), - name: t.name.as_ref().expect("field name"), - type_: FieldType::from(t.type_.as_ref().expect("field type")), - deprecation, - } - }) - }); - - item.fields.extend(fields); - - item - } - - pub(crate) fn require(&self, schema: &Schema<'_>) { - if self.is_required.get() { - return; - } - self.is_required.set(true); - self.fields.iter().for_each(|field| { - schema.require(&field.type_.inner_name_str()); - }) - } + // pub fn from_introspected_schema_json( + // obj: &'schema graphql_introspection_query::introspection_response::FullType, + // ) -> Self { + // let description = obj.description.as_ref().map(String::as_str); + // let mut item = GqlObject::new(obj.name.as_ref().expect("missing object name"), description); + // let fields = obj.fields.as_ref().unwrap().iter().filter_map(|t| { + // t.as_ref().map(|t| { + // let deprecation = if t.is_deprecated.unwrap_or(false) { + // DeprecationStatus::Deprecated(t.deprecation_reason.clone()) + // } else { + // DeprecationStatus::Current + // }; + // GqlObjectField { + // description: t.description.as_ref().map(String::as_str), + // name: t.name.as_ref().expect("field name"), + // type_: FieldType::from(t.type_.as_ref().expect("field type")), + // deprecation, + // } + // }) + // }); + + // item.fields.extend(fields); + + // item + // } + + // pub(crate) fn require(&self, schema: &Schema) { + // if self.is_required.get() { + // return; + // } + // self.is_required.set(true); + // self.fields.iter().for_each(|field| { + // schema.require(&field.type_.inner_name_str()); + // }) + // } pub(crate) fn response_for_selection( &self, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result { - let derives = query_context.response_derives(); - let name = Ident::new(prefix, Span::call_site()); - let fields = self.response_fields_for_selection(query_context, selection, prefix)?; - let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?; - let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc])); - Ok(quote! { - #(#field_impls)* - - #derives - #description - pub struct #name { - #(#fields,)* - } - }) + unimplemented!() + // let derives = query_context.response_derives(); + // let name = Ident::new(prefix, Span::call_site()); + // let fields = self.response_fields_for_selection(query_context, selection, prefix)?; + // let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?; + // let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc])); + // Ok(quote! { + // #(#field_impls)* + + // #derives + // #description + // pub struct #name { + // #(#fields,)* + // } + // }) } pub(crate) fn field_impls_for_selection( &self, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { @@ -144,7 +145,7 @@ impl<'schema> GqlObject<'schema> { pub(crate) fn response_fields_for_selection( &self, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { diff --git a/graphql_client_codegen/src/operations.rs b/graphql_client_codegen/src/operations.rs index 50b22a828..d283bc004 100644 --- a/graphql_client_codegen/src/operations.rs +++ b/graphql_client_codegen/src/operations.rs @@ -26,12 +26,12 @@ pub struct Operation<'query> { impl<'query> Operation<'query> { pub(crate) fn root_name<'schema>( &self, - schema: &'schema crate::schema::Schema<'_>, + schema: &'schema crate::schema::Schema, ) -> &'schema str { match self.operation_type { - OperationType::Query => schema.query_type.unwrap_or("Query"), - OperationType::Mutation => schema.mutation_type.unwrap_or("Mutation"), - OperationType::Subscription => schema.subscription_type.unwrap_or("Subscription"), + OperationType::Query => schema.query_type().name(), + OperationType::Mutation => schema.mutation_type().name(), + OperationType::Subscription => schema.subscription_type().name(), } } @@ -43,42 +43,43 @@ impl<'query> Operation<'query> { } /// Generate the Variables struct and all the necessary supporting code. - pub(crate) fn expand_variables(&self, context: &QueryContext<'_, '_>) -> TokenStream { - let variables = &self.variables; - let variables_derives = context.variables_derives(); + pub(crate) fn expand_variables(&self, context: &QueryContext<'_>) -> TokenStream { + todo!() + // let variables = &self.variables; + // let variables_derives = context.variables_derives(); - if variables.is_empty() { - return quote! { - #variables_derives - pub struct Variables; - }; - } + // if variables.is_empty() { + // return quote! { + // #variables_derives + // pub struct Variables; + // }; + // } - let fields = variables.iter().map(|variable| { - let ty = variable.ty.to_rust(context, ""); - let rust_safe_field_name = - crate::shared::keyword_replace(&variable.name.to_snake_case()); - let rename = - crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); - let name = Ident::new(&rust_safe_field_name, Span::call_site()); + // let fields = variables.iter().map(|variable| { + // let ty = variable.ty.to_rust(context, ""); + // let rust_safe_field_name = + // crate::shared::keyword_replace(&variable.name.to_snake_case()); + // let rename = + // crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); + // let name = Ident::new(&rust_safe_field_name, Span::call_site()); - quote!(#rename pub #name: #ty) - }); + // quote!(#rename pub #name: #ty) + // }); - let default_constructors = variables - .iter() - .map(|variable| variable.generate_default_value_constructor(context)); + // let default_constructors = variables + // .iter() + // .map(|variable| variable.generate_default_value_constructor(context)); - quote! { - #variables_derives - pub struct Variables { - #(#fields,)* - } + // quote! { + // #variables_derives + // pub struct Variables { + // #(#fields,)* + // } - impl Variables { - #(#default_constructors)* - } - } + // impl Variables { + // #(#default_constructors)* + // } + // } } } diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 15aef89d1..ffa4f329b 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -11,25 +11,25 @@ use std::collections::{BTreeMap, BTreeSet}; use syn::Ident; /// This holds all the information we need during the code generation phase. -pub(crate) struct QueryContext<'query, 'schema: 'query> { +pub(crate) struct QueryContext<'query> { pub fragments: BTreeMap<&'query str, GqlFragment<'query>>, - pub schema: &'schema Schema<'schema>, + // pub schema: &'schema Schema, pub deprecation_strategy: DeprecationStrategy, pub normalization: Normalization, variables_derives: Vec, response_derives: Vec, } -impl<'query, 'schema> QueryContext<'query, 'schema> { +impl<'query, 'schema> QueryContext<'query> { /// Create a QueryContext with the given Schema. pub(crate) fn new( - schema: &'schema Schema<'schema>, + schema: &'schema Schema, deprecation_strategy: DeprecationStrategy, normalization: Normalization, - ) -> QueryContext<'query, 'schema> { + ) -> QueryContext<'query> { QueryContext { fragments: BTreeMap::new(), - schema, + // schema, deprecation_strategy, normalization, variables_derives: vec![Ident::new("Serialize", Span::call_site())], @@ -44,12 +44,12 @@ impl<'query, 'schema> QueryContext<'query, 'schema> { } } - /// For testing only. creates an empty QueryContext with an empty Schema. + /// For testing only. creates an empty QueryContext. #[cfg(test)] - pub(crate) fn new_empty(schema: &'schema Schema<'_>) -> QueryContext<'query, 'schema> { + pub(crate) fn new_empty() -> QueryContext<'query> { QueryContext { fragments: BTreeMap::new(), - schema, + // schema, deprecation_strategy: DeprecationStrategy::Allow, normalization: Normalization::None, variables_derives: vec![Ident::new("Serialize", Span::call_site())], @@ -64,27 +64,28 @@ impl<'query, 'schema> QueryContext<'query, 'schema> { selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { - if self.schema.contains_scalar(ty) { - Ok(None) - } else if let Some(enm) = self.schema.enums.get(ty) { - enm.is_required.set(true); - Ok(None) // we already expand enums separately - } else if let Some(obj) = self.schema.objects.get(ty) { - obj.is_required.set(true); - obj.response_for_selection(self, &selection, prefix) - .map(Some) - } else if let Some(iface) = self.schema.interfaces.get(ty) { - iface.is_required.set(true); - iface - .response_for_selection(self, &selection, prefix) - .map(Some) - } else if let Some(unn) = self.schema.unions.get(ty) { - unn.is_required.set(true); - unn.response_for_selection(self, &selection, prefix) - .map(Some) - } else { - Err(format_err!("Unknown type: {}", ty)) - } + unimplemented!() + // if self.schema.contains_scalar(ty) { + // Ok(None) + // } else if let Some(enm) = self.schema.enums.get(ty) { + // enm.is_required.set(true); + // Ok(None) // we already expand enums separately + // } else if let Some(obj) = self.schema.objects.get(ty) { + // obj.is_required.set(true); + // obj.response_for_selection(self, &selection, prefix) + // .map(Some) + // } else if let Some(iface) = self.schema.interfaces.get(ty) { + // iface.is_required.set(true); + // iface + // .response_for_selection(self, &selection, prefix) + // .map(Some) + // } else if let Some(unn) = self.schema.unions.get(ty) { + // unn.is_required.set(true); + // unn.response_for_selection(self, &selection, prefix) + // .map(Some) + // } else { + // Err(format_err!("Unknown type: {}", ty)) + // } } pub(crate) fn ingest_response_derives( diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 5b67a6ed4..6dcee4bed 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -1,357 +1,503 @@ -use crate::deprecation::DeprecationStatus; -use crate::enums::{EnumVariant, GqlEnum}; -use crate::field_type::FieldType; -use crate::inputs::GqlInput; -use crate::interfaces::GqlInterface; -use crate::objects::{GqlObject, GqlObjectField}; -use crate::scalars::Scalar; -use crate::unions::GqlUnion; -use failure::*; -use graphql_parser::{self, schema}; -use std::collections::{BTreeMap, BTreeSet}; +mod graphql_parser_conversion; +mod json_conversion; +use std::collections::HashMap; + +// use crate::deprecation::DeprecationStatus; +// use crate::enums::{EnumVariant, GqlEnum}; +// use crate::field_type::FieldType; +// use crate::inputs::GqlInput; +// use crate::interfaces::GqlInterface; +// use crate::objects::{GqlObject, GqlObjectField}; +// use crate::scalars::Scalar; +// use crate::unions::GqlUnion; +// use failure::*; pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"]; +pub(crate) type SchemaRef<'a> = &'a Schema; + +#[derive(Debug, PartialEq, Clone)] +struct StoredObjectField { + name: String, + object: ObjectId, +} + +#[derive(Debug, PartialEq, Clone)] +struct StoredObject { + name: String, + // fields: Vec, + implements_interfaces: Vec, + fields: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +struct StoredField { + name: String, + r#type: StoredFieldType, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct ObjectId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct ObjectFieldId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct InterfaceFieldId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct InterfaceId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct ScalarId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct UnionId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct EnumId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct InputObjectId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +struct InputFieldId(usize); + +#[derive(Debug, Clone, PartialEq)] +struct StoredInterface { + name: String, + // fields: Vec, + fields: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredInterfaceField { + name: String, + interface: InterfaceId, + r#type: StoredFieldType, +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredFieldType { + id: TypeId, + qualifiers: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredUnion { + name: String, + variants: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredScalar { + name: String, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum TypeId { + Object(ObjectId), + Scalar(ScalarId), + Interface(InterfaceId), + Union(UnionId), + Enum(EnumId), + Input(InputObjectId), +} + +impl TypeId { + fn scalar(id: usize) -> Self { + TypeId::Scalar(ScalarId(id)) + } + + fn r#enum(id: usize) -> Self { + TypeId::Enum(EnumId(id)) + } + + fn interface(id: usize) -> Self { + TypeId::Interface(InterfaceId(id)) + } + + fn union(id: usize) -> Self { + TypeId::Union(UnionId(id)) + } + + fn object(id: usize) -> Self { + TypeId::Object(ObjectId(id)) + } + + fn input(id: usize) -> Self { + TypeId::Input(InputObjectId(id)) + } + + fn as_interface_id(&self) -> Option { + match self { + TypeId::Interface(id) => Some(*id), + _ => None, + } + } + + fn as_object_id(&self) -> Option { + match self { + TypeId::Object(id) => Some(*id), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredEnum { + name: String, + variants: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredInputFieldType { + id: TypeId, + qualifiers: Vec, +} + +impl StoredInputFieldType { + /// A type is indirected if it is a (flat or nested) list type, optional or not. + /// + /// We use this to determine whether a type needs to be boxed for recursion. + pub fn is_indirected(&self) -> bool { + self.qualifiers + .iter() + .any(|qualifier| qualifier == &crate::field_type::GraphqlTypeQualifier::List) + } +} + +#[derive(Debug, Clone, PartialEq)] +struct StoredInputType { + name: String, + fields: Vec<(String, StoredInputFieldType)>, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum InputFieldTypeId { + Scalar(ScalarId), + InputObject(InputObjectId), +} + /// Intermediate representation for a parsed GraphQL schema used during code generation. #[derive(Debug, Clone, PartialEq)] -pub(crate) struct Schema<'schema> { - pub(crate) enums: BTreeMap<&'schema str, GqlEnum<'schema>>, - pub(crate) inputs: BTreeMap<&'schema str, GqlInput<'schema>>, - pub(crate) interfaces: BTreeMap<&'schema str, GqlInterface<'schema>>, - pub(crate) objects: BTreeMap<&'schema str, GqlObject<'schema>>, - pub(crate) scalars: BTreeMap<&'schema str, Scalar<'schema>>, - pub(crate) unions: BTreeMap<&'schema str, GqlUnion<'schema>>, - pub(crate) query_type: Option<&'schema str>, - pub(crate) mutation_type: Option<&'schema str>, - pub(crate) subscription_type: Option<&'schema str>, +pub(crate) struct Schema { + stored_objects: Vec, + // stored_object_fields: Vec, + stored_interfaces: Vec, + // stored_interface_fields: Vec, + stored_unions: Vec, + stored_scalars: Vec, + stored_enums: Vec, + stored_inputs: Vec, + names: HashMap, + + pub(crate) query_type: Option, + pub(crate) mutation_type: Option, + pub(crate) subscription_type: Option, } -impl<'schema> Schema<'schema> { - pub(crate) fn new() -> Schema<'schema> { - Schema { - enums: BTreeMap::new(), - inputs: BTreeMap::new(), - interfaces: BTreeMap::new(), - objects: BTreeMap::new(), - scalars: BTreeMap::new(), - unions: BTreeMap::new(), +impl Schema { + pub(crate) fn new() -> Schema { + let mut schema = Schema { + stored_objects: Vec::new(), + // stored_object_fields: Vec::new(), + stored_interfaces: Vec::new(), + // stored_interface_fields: Vec::new(), + stored_unions: Vec::new(), + stored_scalars: Vec::with_capacity(DEFAULT_SCALARS.len()), + stored_enums: Vec::new(), + stored_inputs: Vec::new(), + names: HashMap::new(), query_type: None, mutation_type: None, subscription_type: None, + }; + + schema.push_default_scalars(); + + schema + } + + fn push_default_scalars(&mut self) { + for scalar in DEFAULT_SCALARS { + let id = self.push_scalar(StoredScalar { + name: (*scalar).to_owned(), + }); + + self.names.insert(scalar.to_string(), TypeId::Scalar(id)); } } - pub(crate) fn ingest_interface_implementations( + // pub(crate) fn ingest_interface_implementations( + // &mut self, + // impls: BTreeMap<&'schema str, Vec<&'schema str>>, + // ) -> Result<(), failure::Error> { + // impls + // .into_iter() + // .map(|(iface_name, implementors)| { + // let iface = self + // .interfaces + // .get_mut(&iface_name) + // .ok_or_else(|| format_err!("interface not found: {}", iface_name))?; + // iface.implemented_by = implementors.iter().cloned().collect(); + // Ok(()) + // }) + // .collect() + // } + + // pub(crate) fn require(&self, typename_: &str) { + // DEFAULT_SCALARS + // .iter() + // .find(|&&s| s == typename_) + // .map(|_| ()) + // .or_else(|| { + // self.enums + // .get(typename_) + // .map(|enm| enm.is_required.set(true)) + // }) + // .or_else(|| self.inputs.get(typename_).map(|input| input.require(self))) + // .or_else(|| { + // self.objects + // .get(typename_) + // .map(|object| object.require(self)) + // }) + // .or_else(|| { + // self.scalars + // .get(typename_) + // .map(|scalar| scalar.is_required.set(true)) + // }); + // } + + // pub(crate) fn contains_scalar(&self, type_name: &str) -> bool { + // DEFAULT_SCALARS.iter().any(|s| s == &type_name) || self.scalars.contains_key(type_name) + // } + + // pub(crate) fn fragment_target( + // &self, + // target_name: &str, + // ) -> Option> { + // self.objects + // .get(target_name) + // .map(crate::fragments::FragmentTarget::Object) + // .or_else(|| { + // self.interfaces + // .get(target_name) + // .map(crate::fragments::FragmentTarget::Interface) + // }) + // .or_else(|| { + // self.unions + // .get(target_name) + // .map(crate::fragments::FragmentTarget::Union) + // }) + // } + + fn get_object_mut(&mut self, object_id: ObjectId) -> &mut StoredObject { + self.stored_objects.get_mut(object_id.0).unwrap() + } + + fn get_interface_mut(&mut self, id: InterfaceId) -> &mut StoredInterface { + self.stored_interfaces.get_mut(id.0).unwrap() + } + + fn get_interface_by_name_mut( &mut self, - impls: BTreeMap<&'schema str, Vec<&'schema str>>, - ) -> Result<(), failure::Error> { - impls - .into_iter() - .map(|(iface_name, implementors)| { - let iface = self - .interfaces - .get_mut(&iface_name) - .ok_or_else(|| format_err!("interface not found: {}", iface_name))?; - iface.implemented_by = implementors.iter().cloned().collect(); - Ok(()) - }) - .collect() + interface_name: &str, + ) -> Option<(InterfaceId, &mut StoredInterface)> { + self.stored_interfaces + .iter_mut() + .enumerate() + .find(|(idx, iface)| iface.name == interface_name) + .map(|(idx, iface)| (InterfaceId(idx), iface)) } - pub(crate) fn require(&self, typename_: &str) { - DEFAULT_SCALARS - .iter() - .find(|&&s| s == typename_) - .map(|_| ()) - .or_else(|| { - self.enums - .get(typename_) - .map(|enm| enm.is_required.set(true)) - }) - .or_else(|| self.inputs.get(typename_).map(|input| input.require(self))) - .or_else(|| { - self.objects - .get(typename_) - .map(|object| object.require(self)) - }) - .or_else(|| { - self.scalars - .get(typename_) - .map(|scalar| scalar.is_required.set(true)) - }); + fn push_object(&mut self, object: StoredObject) -> ObjectId { + let id = ObjectId(self.stored_objects.len()); + self.stored_objects.push(object); + + id } - pub(crate) fn contains_scalar(&self, type_name: &str) -> bool { - DEFAULT_SCALARS.iter().any(|s| s == &type_name) || self.scalars.contains_key(type_name) + // fn push_object_field(&mut self, object_field: StoredObjectField) -> ObjectFieldId { + // let id = ObjectFieldId(self.stored_object_fields.len()); + + // self.stored_object_fields.push(object_field); + + // id + // } + + fn push_interface(&mut self, interface: StoredInterface) -> InterfaceId { + let id = InterfaceId(self.stored_interfaces.len()); + + self.stored_interfaces.push(interface); + + id } - pub(crate) fn fragment_target( - &self, - target_name: &str, - ) -> Option> { - self.objects - .get(target_name) - .map(crate::fragments::FragmentTarget::Object) - .or_else(|| { - self.interfaces - .get(target_name) - .map(crate::fragments::FragmentTarget::Interface) - }) - .or_else(|| { - self.unions - .get(target_name) - .map(crate::fragments::FragmentTarget::Union) - }) + // fn push_interface_field(&mut self, interface_field: StoredInterfaceField) -> InterfaceFieldId { + // let id = InterfaceFieldId(self.stored_interface_fields.len()); + + // self.stored_interface_fields.push(interface_field); + + // id + // } + + fn push_scalar(&mut self, scalar: StoredScalar) -> ScalarId { + let id = ScalarId(self.stored_scalars.len()); + + self.stored_scalars.push(scalar); + + id + } + + fn push_enum(&mut self, enm: StoredEnum) -> EnumId { + let id = EnumId(self.stored_enums.len()); + + self.stored_enums.push(enm); + + id + } + + // pub(crate) fn get_input_type_by_name(&self, name: &str) -> Option> { + // self.stored_inputs + // .iter() + // .position(|input| input.name == name) + // .map(InputObjectId) + // .map(|idx| InputRef { + // schema: self, + // input_id: idx, + // }) + // } + + // pub(crate) fn get_object_by_name(&self, name: &str) -> Option<()> { + // Some(()) + // } + + // pub(crate) fn lookup_type(&self, name: &str) -> Option { + // todo!() + // } + + pub(crate) fn query_type(&self) -> ObjectRef<'_> { + ObjectRef { + object_id: self + .query_type + .expect("Query operation type must be defined"), + schema: self, + } + } + + pub(crate) fn mutation_type(&self) -> ObjectRef<'_> { + ObjectRef { + object_id: self + .mutation_type + .expect("Mutation operation type must be defined"), + schema: self, + } + } + + pub(crate) fn subscription_type(&self) -> ObjectRef<'_> { + ObjectRef { + object_id: self + .mutation_type + // TODO: make this return an option + .expect("Subscription operation type must be defined"), + schema: self, + } + } + + fn get_stored_input(&self, input_id: InputObjectId) -> &StoredInputType { + self.stored_inputs.get(input_id.0).unwrap() + } + + fn get_object(&self, object_id: ObjectId) -> &StoredObject { + self.stored_objects.get(object_id.0).unwrap() + } + + fn find_interface(&self, interface_name: &str) -> InterfaceId { + self.find_type_id(interface_name).as_interface_id().unwrap() } -} -impl<'schema> std::convert::From<&'schema graphql_parser::schema::Document> for Schema<'schema> { - fn from(ast: &'schema graphql_parser::schema::Document) -> Schema<'schema> { - let mut schema = Schema::new(); - - // Holds which objects implement which interfaces so we can populate GqlInterface#implemented_by later. - // It maps interface names to a vec of implementation names. - let mut interface_implementations: BTreeMap<&str, Vec<&str>> = BTreeMap::new(); - - for definition in &ast.definitions { - match definition { - schema::Definition::TypeDefinition(ty_definition) => match ty_definition { - schema::TypeDefinition::Object(obj) => { - for implementing in &obj.implements_interfaces { - let name = &obj.name; - interface_implementations - .entry(implementing) - .and_modify(|objects| objects.push(name)) - .or_insert_with(|| vec![name]); - } - - schema - .objects - .insert(&obj.name, GqlObject::from_graphql_parser_object(&obj)); - } - schema::TypeDefinition::Enum(enm) => { - schema.enums.insert( - &enm.name, - GqlEnum { - name: &enm.name, - description: enm.description.as_deref(), - variants: enm - .values - .iter() - .map(|v| EnumVariant { - description: v.description.as_deref(), - name: &v.name, - }) - .collect(), - is_required: false.into(), - }, - ); - } - schema::TypeDefinition::Scalar(scalar) => { - schema.scalars.insert( - &scalar.name, - Scalar { - name: &scalar.name, - description: scalar.description.as_deref(), - is_required: false.into(), - }, - ); - } - schema::TypeDefinition::Union(union) => { - let variants: BTreeSet<&str> = - union.types.iter().map(String::as_str).collect(); - schema.unions.insert( - &union.name, - GqlUnion { - name: &union.name, - variants, - description: union.description.as_deref(), - is_required: false.into(), - }, - ); - } - schema::TypeDefinition::Interface(interface) => { - let mut iface = - GqlInterface::new(&interface.name, interface.description.as_deref()); - iface - .fields - .extend(interface.fields.iter().map(|f| GqlObjectField { - description: f.description.as_deref(), - name: f.name.as_str(), - type_: FieldType::from(&f.field_type), - deprecation: DeprecationStatus::Current, - })); - schema.interfaces.insert(&interface.name, iface); - } - schema::TypeDefinition::InputObject(input) => { - schema.inputs.insert(&input.name, GqlInput::from(input)); - } - }, - schema::Definition::DirectiveDefinition(_) => (), - schema::Definition::TypeExtension(_extension) => (), - schema::Definition::SchemaDefinition(definition) => { - schema.query_type = definition.query.as_deref(); - schema.mutation_type = definition.mutation.as_deref(); - schema.subscription_type = definition.subscription.as_deref(); - } + fn find_type_id(&self, type_name: &str) -> TypeId { + match self.names.get(type_name) { + Some(id) => *id, + None => { + panic!( + "graphql-client-codegen internal error: failed to resolve TypeId for `{}°.", + type_name + ); } } + } +} - schema - .ingest_interface_implementations(interface_implementations) - .expect("schema ingestion"); +pub(crate) struct ObjectRef<'a> { + schema: SchemaRef<'a>, + object_id: ObjectId, +} - schema +impl<'a> ObjectRef<'a> { + fn get(&self) -> &'a StoredObject { + self.schema.get_object(self.object_id) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } +} + +pub(crate) struct InputRef<'a> { + schema: SchemaRef<'a>, + input_id: InputObjectId, +} + +impl<'a> InputRef<'a> { + fn get(&self) -> &StoredInputType { + self.schema.get_stored_input(self.input_id) + } + + pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { + todo!() + // let input = self.get(); + + // // the input type is recursive if any of its members contains it, without indirection + // input.fields.iter().any(|(name, r#type)| { + // // the field is indirected, so no boxing is needed + // if r#type.is_indirected() { + // return false; + // } + + // let field_type_name = field.type_.inner_name_str(); + // let input = self.schema.inputs.get(field_type_name); + + // if let Some(input) = input { + // // the input contains itself, not indirected + // if input.name == type_name { + // return true; + // } + + // // we check if the other input contains this one (without indirection) + // input.contains_type_without_indirection(context, type_name) + // } else { + // // the field is not referring to an input type + // false + // } + // }) } } -impl<'schema> - std::convert::From< - &'schema graphql_introspection_query::introspection_response::IntrospectionResponse, - > for Schema<'schema> +impl std::convert::From for Schema { + fn from(ast: graphql_parser::schema::Document) -> Schema { + graphql_parser_conversion::build_schema(ast) + } +} + +impl std::convert::From + for Schema { fn from( - src: &'schema graphql_introspection_query::introspection_response::IntrospectionResponse, + src: graphql_introspection_query::introspection_response::IntrospectionResponse, ) -> Self { - use graphql_introspection_query::introspection_response::__TypeKind; - - let mut schema = Schema::new(); - let root = src - .as_schema() - .schema - .as_ref() - .expect("__schema is not null"); - - schema.query_type = root - .query_type - .as_ref() - .and_then(|ty| ty.name.as_ref()) - .map(String::as_str); - schema.mutation_type = root - .mutation_type - .as_ref() - .and_then(|ty| ty.name.as_ref()) - .map(String::as_str); - schema.subscription_type = root - .subscription_type - .as_ref() - .and_then(|ty| ty.name.as_ref()) - .map(String::as_str); - - // Holds which objects implement which interfaces so we can populate GqlInterface#implemented_by later. - // It maps interface names to a vec of implementation names. - let mut interface_implementations: BTreeMap<&str, Vec<&str>> = BTreeMap::new(); - - for ty in root - .types - .as_ref() - .expect("types in schema") - .iter() - .filter_map(|t| t.as_ref().map(|t| &t.full_type)) - { - let name: &str = ty.name.as_deref().expect("type definition name"); - - match ty.kind { - Some(__TypeKind::ENUM) => { - let variants: Vec> = ty - .enum_values - .as_ref() - .expect("enum variants") - .iter() - .map(|t| { - t.as_ref().map(|t| EnumVariant { - description: t.description.as_deref(), - name: t.name.as_deref().expect("enum variant name"), - }) - }) - .filter_map(|t| t) - .collect(); - let enm = GqlEnum { - name, - description: ty.description.as_deref(), - variants, - is_required: false.into(), - }; - schema.enums.insert(name, enm); - } - Some(__TypeKind::SCALAR) => { - if DEFAULT_SCALARS.iter().find(|s| s == &&name).is_none() { - schema.scalars.insert( - name, - Scalar { - name, - description: ty.description.as_deref(), - is_required: false.into(), - }, - ); - } - } - Some(__TypeKind::UNION) => { - let variants: BTreeSet<&str> = ty - .possible_types - .as_ref() - .unwrap() - .iter() - .filter_map(|t| t.as_ref().and_then(|t| t.type_ref.name.as_deref())) - .collect(); - schema.unions.insert( - name, - GqlUnion { - name: ty.name.as_deref().expect("unnamed union"), - description: ty.description.as_deref(), - variants, - is_required: false.into(), - }, - ); - } - Some(__TypeKind::OBJECT) => { - for implementing in ty - .interfaces - .as_deref() - .unwrap_or_else(|| &[]) - .iter() - .filter_map(Option::as_ref) - .map(|t| &t.type_ref.name) - { - interface_implementations - .entry(implementing.as_deref().expect("interface name")) - .and_modify(|objects| objects.push(name)) - .or_insert_with(|| vec![name]); - } - - schema - .objects - .insert(name, GqlObject::from_introspected_schema_json(ty)); - } - Some(__TypeKind::INTERFACE) => { - let mut iface = GqlInterface::new(name, ty.description.as_deref()); - iface.fields.extend( - ty.fields - .as_ref() - .expect("interface fields") - .iter() - .filter_map(Option::as_ref) - .map(|f| GqlObjectField { - description: f.description.as_deref(), - name: f.name.as_ref().expect("field name").as_str(), - type_: FieldType::from(f.type_.as_ref().expect("field type")), - deprecation: DeprecationStatus::Current, - }), - ); - schema.interfaces.insert(name, iface); - } - Some(__TypeKind::INPUT_OBJECT) => { - schema.inputs.insert(name, GqlInput::from(ty)); - } - _ => unimplemented!("unimplemented definition"), - } - } - - schema - .ingest_interface_implementations(interface_implementations) - .expect("schema ingestion"); - - schema + json_conversion::build_schema(src) } } @@ -360,8 +506,8 @@ pub(crate) enum ParsedSchema { Json(graphql_introspection_query::introspection_response::IntrospectionResponse), } -impl<'schema> From<&'schema ParsedSchema> for Schema<'schema> { - fn from(parsed_schema: &'schema ParsedSchema) -> Schema<'schema> { +impl From for Schema { + fn from(parsed_schema: ParsedSchema) -> Schema { match parsed_schema { ParsedSchema::GraphQLParser(s) => s.into(), ParsedSchema::Json(s) => s.into(), diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs new file mode 100644 index 000000000..4ea2e20a7 --- /dev/null +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -0,0 +1,268 @@ +use super::{EnumId, InputObjectId, InterfaceId, ObjectId, ScalarId, Schema, TypeId, UnionId}; +use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; + +pub(super) fn build_schema(mut src: graphql_parser::schema::Document) -> super::Schema { + let mut schema = Schema::new(); + convert(&mut src, &mut schema); + schema +} + +fn convert(src: &mut graphql_parser::schema::Document, schema: &mut Schema) { + populate_names_map(schema, &src.definitions); + + src.definitions + .iter_mut() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Scalar(scalar)) => Some(scalar), + _ => None, + }) + .for_each(|scalar| ingest_scalar(schema, scalar)); + + enums_mut(src).for_each(|enm| ingest_enum(schema, enm)); + + unions_mut(src).for_each(|union| ingest_union(schema, union)); + + interfaces_mut(src).for_each(|iface| ingest_interface(schema, iface)); + + objects_mut(src).for_each(|obj| ingest_object(schema, obj)); + + inputs_mut(src).for_each(|input| ingest_input(schema, input)); + + let schema_definition = src.definitions.iter_mut().find_map(|def| match def { + Definition::SchemaDefinition(definition) => Some(definition), + _ => None, + }); + + if let Some(schema_definition) = schema_definition { + schema.query_type = schema_definition + .query + .as_mut() + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.mutation_type = schema_definition + .mutation + .as_mut() + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.subscription_type = schema_definition + .subscription + .as_mut() + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + }; +} + +fn populate_names_map(schema: &mut Schema, definitions: &[Definition]) { + definitions + .iter() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Enum(enm)) => Some(enm.name.as_str()), + _ => None, + }) + .enumerate() + .for_each(|(idx, enum_name)| { + schema.names.insert(enum_name.into(), TypeId::r#enum(idx)); + }); + + definitions + .iter() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Object(object)) => { + Some(object.name.as_str()) + } + _ => None, + }) + .enumerate() + .for_each(|(idx, object_name)| { + schema + .names + .insert(object_name.into(), TypeId::r#object(idx)); + }); + + definitions + .iter() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Interface(interface)) => { + Some(interface.name.as_str()) + } + _ => None, + }) + .enumerate() + .for_each(|(idx, interface_name)| { + schema + .names + .insert(interface_name.into(), TypeId::interface(idx)); + }); + + definitions + .iter() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Union(union)) => Some(union.name.as_str()), + _ => None, + }) + .enumerate() + .for_each(|(idx, union_name)| { + schema.names.insert(union_name.into(), TypeId::union(idx)); + }); + + definitions + .iter() + .filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::InputObject(input)) => { + Some(input.name.as_str()) + } + _ => None, + }) + .enumerate() + .for_each(|(idx, input_name)| { + schema.names.insert(input_name.into(), TypeId::input(idx)); + }); +} + +fn ingest_union(schema: &mut Schema, union: &mut graphql_parser::schema::UnionType) { + let stored_union = super::StoredUnion { + name: std::mem::replace(&mut union.name, String::new()), + variants: union + .types + .iter() + .map(|name| schema.find_type_id(name)) + .collect(), + }; + + schema.stored_unions.push(stored_union); +} + +fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectType) { + // Ingest the object itself + let object = super::StoredObject { + name: std::mem::replace(&mut obj.name, String::new()), + fields: obj + .fields + .iter_mut() + .map(|graphql_field| super::StoredField { + name: std::mem::replace(&mut graphql_field.name, String::new()), + r#type: resolve_field_type(schema, &graphql_field.field_type), + }) + .collect(), + implements_interfaces: obj + .implements_interfaces + .iter() + .map(|iface_name| schema.find_interface(iface_name)) + .collect(), + }; + + schema.push_object(object); +} + +fn resolve_field_type( + schema: &mut Schema, + inner: &graphql_parser::schema::Type, +) -> super::StoredFieldType { + use crate::field_type::{graphql_parser_depth, GraphqlTypeQualifier}; + use graphql_parser::schema::Type::*; + + let qualifiers_depth = graphql_parser_depth(inner); + let mut qualifiers = Vec::with_capacity(qualifiers_depth); + + let mut inner = inner; + + loop { + match inner { + ListType(new_inner) => { + qualifiers.push(GraphqlTypeQualifier::List); + inner = new_inner; + } + NonNullType(new_inner) => { + qualifiers.push(GraphqlTypeQualifier::Required); + inner = new_inner; + } + NamedType(name) => { + return super::StoredFieldType { + id: schema.find_type_id(name), + qualifiers, + } + } + } + } +} + +fn ingest_scalar(schema: &mut Schema, scalar: &mut graphql_parser::schema::ScalarType) { + let name = std::mem::replace(&mut scalar.name, String::new()); + let name_for_names = name.clone(); + + let scalar = super::StoredScalar { name }; + + let scalar_id = schema.push_scalar(scalar); + + schema + .names + .insert(name_for_names, TypeId::Scalar(scalar_id)); +} + +fn ingest_enum(schema: &mut Schema, enm: &mut graphql_parser::schema::EnumType) { + let enm = super::StoredEnum { + name: std::mem::replace(&mut enm.name, String::new()), + variants: enm + .values + .iter_mut() + .map(|value| std::mem::replace(&mut value.name, String::new())) + .collect(), + }; + + schema.push_enum(enm); +} + +fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema::InterfaceType) { + let new_interface = super::StoredInterface { + name: std::mem::replace(&mut interface.name, String::new()), + fields: interface + .fields + .iter_mut() + .map(|graphql_field| super::StoredField { + name: std::mem::replace(&mut graphql_field.name, String::new()), + r#type: resolve_field_type(schema, &graphql_field.field_type), + }) + .collect(), + }; + + schema.push_interface(new_interface); +} + +fn ingest_input(schema: &mut Schema, input: &mut parser::InputObjectType) { + unimplemented!() +} + +fn objects_mut(doc: &mut Document) -> impl Iterator { + doc.definitions.iter_mut().filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Object(obj)) => Some(obj), + _ => None, + }) +} + +fn interfaces_mut(doc: &mut Document) -> impl Iterator { + doc.definitions.iter_mut().filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Interface(interface)) => Some(interface), + _ => None, + }) +} + +fn unions_mut(doc: &mut Document) -> impl Iterator { + doc.definitions.iter_mut().filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Union(union)) => Some(union), + _ => None, + }) +} + +fn enums_mut(doc: &mut Document) -> impl Iterator { + doc.definitions.iter_mut().filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::Enum(r#enum)) => Some(r#enum), + _ => None, + }) +} + +fn inputs_mut(doc: &mut Document) -> impl Iterator { + doc.definitions.iter_mut().filter_map(|def| match def { + Definition::TypeDefinition(TypeDefinition::InputObject(input)) => Some(input), + _ => None, + }) +} diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs new file mode 100644 index 000000000..091ff02ac --- /dev/null +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -0,0 +1,392 @@ +use super::{Schema, TypeId}; +use graphql_introspection_query::introspection_response::{ + FullType, IntrospectionResponse, Schema as JsonSchema, TypeRef, __TypeKind, +}; + +pub(super) fn build_schema(src: IntrospectionResponse) -> super::Schema { + let converter = JsonSchemaConverter { + src: src.into_schema().schema.unwrap(), + schema: Schema::new(), + }; + + converter.convert() +} + +struct JsonSchemaConverter { + src: JsonSchema, + schema: Schema, +} + +impl JsonSchemaConverter { + fn build_names_map(&mut self) { + self.schema.names.reserve(types_mut(&mut self.src).count()); + let names = &mut self.schema.names; + + unions_mut(&mut self.src) + .map(|u| u.name.as_ref().unwrap()) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::union(idx)); + }); + + interfaces_mut(&mut self.src) + .map(|iface| iface.name.as_ref().unwrap()) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::interface(idx)); + }); + + objects_mut(&mut self.src) + .map(|obj| obj.name.as_ref().unwrap()) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::object(idx)); + }); + + inputs_mut(&mut self.src) + .map(|obj| obj.name.as_ref().unwrap()) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::input(idx)); + }); + } + + fn convert(mut self) -> Schema { + self.build_names_map(); + let JsonSchemaConverter { + mut src, + mut schema, + } = self; + + for scalar in scalars_mut(&mut src) { + ingest_scalar(&mut schema, scalar); + } + + for enm in enums_mut(&mut src) { + ingest_enum(&mut schema, enm) + } + + for interface in interfaces_mut(&mut src) { + ingest_interface(&mut schema, interface); + } + + // for ty in src + // .types + // .as_ref() + // .expect("types in schema") + // .iter() + // .filter_map(|t| t.as_ref().map(|t| &t.full_type)) + // { + // let name: &str = ty + // .name + // .as_ref() + // .map(String::as_str) + // .expect("type definition name"); + + // match ty.kind { + // Some(__TypeKind::ENUM) => { + // // let variants: Vec> = ty + // // .enum_values + // // .as_ref() + // // .expect("enum variants") + // // .iter() + // // .map(|t| { + // // t.as_ref().map(|t| EnumVariant { + // // description: t.description.as_ref().map(String::as_str), + // // name: t + // // .name + // // .as_ref() + // // .map(String::as_str) + // // .expect("enum variant name"), + // // }) + // // }) + // // .filter_map(|t| t) + // // .collect(); + // // let enm = GqlEnum { + // // name, + // // description: ty.description.as_ref().map(String::as_str), + // // variants, + // // is_required: false.into(), + // // }; + // // schema.enums.insert(name, enm); + // } + // Some(__TypeKind::SCALAR) => { + // // if DEFAULT_SCALARS.iter().find(|s| s == &&name).is_none() { + // // schema.scalars.insert( + // // name, + // // Scalar { + // // name, + // // description: ty.description.as_ref().map(String::as_str), + // // is_required: false.into(), + // // }, + // // ); + // // } + // } + // Some(__TypeKind::UNION) => { + // // let variants: BTreeSet<&str> = ty + // // .possible_types + // // .as_ref() + // // .unwrap() + // // .iter() + // // .filter_map(|t| { + // // t.as_ref() + // // .and_then(|t| t.type_ref.name.as_ref().map(String::as_str)) + // // }) + // // .collect(); + // // schema.unions.insert( + // // name, + // // GqlUnion { + // // name: ty.name.as_ref().map(String::as_str).expect("unnamed union"), + // // description: ty.description.as_ref().map(String::as_str), + // // variants, + // // is_required: false.into(), + // // }, + // // ); + // } + // Some(__TypeKind::OBJECT) => { + // // for implementing in ty + // // .interfaces + // // .as_ref() + // // .map(Vec::as_slice) + // // .unwrap_or_else(|| &[]) + // // .iter() + // // .filter_map(Option::as_ref) + // // .map(|t| &t.type_ref.name) + // // { + // // interface_implementations + // // .entry( + // // implementing + // // .as_ref() + // // .map(String::as_str) + // // .expect("interface name"), + // // ) + // // .and_modify(|objects| objects.push(name)) + // // .or_insert_with(|| vec![name]); + // // } + + // // schema + // // .objects + // // .insert(name, GqlObject::from_introspected_schema_json(ty)); + // } + // Some(__TypeKind::INTERFACE) => { + // // let mut iface = + // // GqlInterface::new(name, ty.description.as_ref().map(String::as_str)); + // // iface.fields.extend( + // // ty.fields + // // .as_ref() + // // .expect("interface fields") + // // .iter() + // // .filter_map(Option::as_ref) + // // .map(|f| GqlObjectField { + // // description: f.description.as_ref().map(String::as_str), + // // name: f.name.as_ref().expect("field name").as_str(), + // // type_: FieldType::from(f.type_.as_ref().expect("field type")), + // // deprecation: DeprecationStatus::Current, + // // }), + // // ); + // // schema.interfaces.insert(name, iface); + // } + // Some(__TypeKind::INPUT_OBJECT) => { + // // schema.inputs.insert(name, GqlInput::from(ty)); + // } + // _ => unimplemented!("unimplemented definition"), + // } + // } + + // Define the root operations. + { + schema.query_type = src + .query_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.mutation_type = src + .mutation_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.subscription_type = src + .mutation_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + } + + schema + } +} + +fn types_mut(schema: &mut JsonSchema) -> impl Iterator { + schema + .types + .as_mut() + .unwrap() + .iter_mut() + .filter_map(|t| -> Option<&mut FullType> { t.as_mut().map(|f| &mut f.full_type) }) +} + +fn objects_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| t.kind == Some(__TypeKind::OBJECT)) +} + +fn enums_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| t.kind == Some(__TypeKind::ENUM)) +} + +fn interfaces_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| t.kind == Some(__TypeKind::INTERFACE)) +} + +fn unions_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| t.kind == Some(__TypeKind::UNION)) +} + +fn inputs_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| t.kind == Some(__TypeKind::INPUT_OBJECT)) +} + +fn scalars_mut(schema: &mut JsonSchema) -> impl Iterator { + types_mut(schema).filter(|t| { + t.kind == Some(__TypeKind::SCALAR) + && !super::DEFAULT_SCALARS.contains(&t.name.as_ref().map(String::as_str).unwrap()) + }) +} + +fn ingest_scalar(schema: &mut Schema, scalar: &mut FullType) { + let name: String = scalar.name.take().unwrap(); + let names_name = name.clone(); + + let id = schema.push_scalar(super::StoredScalar { name }); + + schema.names.insert(names_name, TypeId::Scalar(id)); +} + +fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { + let name = enm.name.take().unwrap(); + let names_name = name.clone(); + + let variants = enm + .enum_values + .as_mut() + .unwrap() + .into_iter() + .map(|v| std::mem::replace(v.name.as_mut().take().unwrap(), String::new())) + .collect(); + + let enm = super::StoredEnum { name, variants }; + + let id = schema.push_enum(enm); + + schema.names.insert(names_name, TypeId::Enum(id)); +} + +fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { + let interface = super::StoredInterface { + name: std::mem::replace(iface.name.as_mut().unwrap(), String::new()), + fields: iface + .fields + .as_mut() + .unwrap() + .iter_mut() + .map(|json_field| super::StoredField { + name: json_field.name.take().unwrap(), + r#type: resolve_field_type( + schema, + &mut json_field.type_.as_mut().unwrap().type_ref, + ), + }) + .collect(), + }; + + schema.push_interface(interface); +} + +fn ingest_object(schema: &mut Schema, object: &mut FullType) { + let object = super::StoredObject { + name: object.name.take().unwrap(), + implements_interfaces: Vec::new(), + fields: object + .fields + .as_mut() + .unwrap() + .iter_mut() + .map(|json_field| super::StoredField { + name: json_field.name.take().unwrap(), + r#type: resolve_field_type( + schema, + &mut json_field.type_.as_mut().unwrap().type_ref, + ), + }) + .collect(), + }; + + schema.push_object(object); +} + +fn ingest_union(schema: &mut Schema, union: &mut FullType) { + let variants = union + .possible_types + .as_ref() + .unwrap() + .iter() + .map(|variant| schema.find_type_id(variant.type_ref.name.as_ref().unwrap())) + .collect(); + let un = super::StoredUnion { + name: union.name.take().unwrap(), + variants, + }; + + schema.stored_unions.push(un); +} + +fn resolve_field_type(schema: &mut Schema, typeref: &mut TypeRef) -> super::StoredFieldType { + from_json_type_inner(schema, typeref) +} + +fn json_type_qualifiers_depth(typeref: &mut TypeRef) -> usize { + use graphql_introspection_query::introspection_response::*; + + match (typeref.kind.as_mut(), typeref.of_type.as_mut()) { + (Some(__TypeKind::NON_NULL), Some(inner)) => 1 + json_type_qualifiers_depth(inner), + (Some(__TypeKind::LIST), Some(inner)) => 1 + json_type_qualifiers_depth(inner), + (Some(_), None) => 0, + _ => panic!("Non-convertible type in JSON schema: {:?}", typeref), + } +} + +fn from_json_type_inner(schema: &mut Schema, inner: &mut TypeRef) -> super::StoredFieldType { + use crate::field_type::GraphqlTypeQualifier; + use graphql_introspection_query::introspection_response::*; + + let qualifiers_depth = json_type_qualifiers_depth(inner); + let mut qualifiers = Vec::with_capacity(qualifiers_depth); + + let mut inner = inner; + + loop { + match ( + inner.kind.as_mut(), + inner.of_type.as_mut(), + inner.name.as_mut(), + ) { + (Some(__TypeKind::NON_NULL), Some(new_inner), _) => { + qualifiers.push(GraphqlTypeQualifier::Required); + inner = new_inner.as_mut(); + } + (Some(__TypeKind::LIST), Some(new_inner), _) => { + qualifiers.push(GraphqlTypeQualifier::List); + inner = new_inner.as_mut(); + } + (Some(_), None, Some(name)) => { + return super::StoredFieldType { + id: *schema.names.get(name).unwrap(), + qualifiers, + } + } + _ => panic!("Non-convertible type in JSON schema"), + } + } +} diff --git a/graphql_client_codegen/src/selection.rs b/graphql_client_codegen/src/selection.rs index 6a8dc88e6..3139750f4 100644 --- a/graphql_client_codegen/src/selection.rs +++ b/graphql_client_codegen/src/selection.rs @@ -1,5 +1,5 @@ use crate::constants::*; -use failure::*; +// use failure::*; use graphql_parser::query::SelectionSet; use std::collections::BTreeMap; @@ -49,7 +49,7 @@ pub struct Selection<'query>(Vec>); impl<'query> Selection<'query> { pub(crate) fn extract_typename<'s, 'context: 's>( &'s self, - context: &'context crate::query::QueryContext<'_, '_>, + context: &'context crate::query::QueryContext<'_>, ) -> Option<&SelectionField<'_>> { // __typename is selected directly if let Some(field) = self.0.iter().filter_map(SelectionItem::as_typename).next() { @@ -76,54 +76,55 @@ impl<'query> Selection<'query> { // Implementation helper for `selected_variants_on_union`. fn selected_variants_on_union_inner<'s>( &'s self, - context: &'s crate::query::QueryContext<'_, '_>, + context: &'s crate::query::QueryContext<'_>, selected_variants: &mut BTreeMap<&'s str, Selection<'s>>, // the name of the type the selection applies to selection_on: &str, ) -> Result<(), failure::Error> { - for item in self.0.iter() { - match item { - SelectionItem::Field(_) => (), - SelectionItem::InlineFragment(inline_fragment) => { - selected_variants - .entry(inline_fragment.on) - .and_modify(|entry| entry.0.extend(inline_fragment.fields.0.clone())) - .or_insert_with(|| { - let mut items = Vec::with_capacity(inline_fragment.fields.0.len()); - items.extend(inline_fragment.fields.0.clone()); - Selection(items) - }); - } - SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - let fragment = context - .fragments - .get(fragment_name) - .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?; - - // The fragment can either be on the union/interface itself, or on one of its variants (type-refining fragment). - if fragment.on.name() == selection_on { - // The fragment is on the union/interface itself. - fragment.selection.selected_variants_on_union_inner( - context, - selected_variants, - selection_on, - )?; - } else { - // Type-refining fragment - selected_variants - .entry(fragment.on.name()) - .and_modify(|entry| entry.0.extend(fragment.selection.0.clone())) - .or_insert_with(|| { - let mut items = Vec::with_capacity(fragment.selection.0.len()); - items.extend(fragment.selection.0.clone()); - Selection(items) - }); - } - } - } - } - - Ok(()) + unimplemented!() + // for item in self.0.iter() { + // match item { + // SelectionItem::Field(_) => (), + // SelectionItem::InlineFragment(inline_fragment) => { + // selected_variants + // .entry(inline_fragment.on) + // .and_modify(|entry| entry.0.extend(inline_fragment.fields.0.clone())) + // .or_insert_with(|| { + // let mut items = Vec::with_capacity(inline_fragment.fields.0.len()); + // items.extend(inline_fragment.fields.0.clone()); + // Selection(items) + // }); + // } + // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { + // let fragment = context + // .fragments + // .get(fragment_name) + // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?; + + // // The fragment can either be on the union/interface itself, or on one of its variants (type-refining fragment). + // if fragment.on.name() == selection_on { + // // The fragment is on the union/interface itself. + // fragment.selection.selected_variants_on_union_inner( + // context, + // selected_variants, + // selection_on, + // )?; + // } else { + // // Type-refining fragment + // selected_variants + // .entry(fragment.on.name()) + // .and_modify(|entry| entry.0.extend(fragment.selection.0.clone())) + // .or_insert_with(|| { + // let mut items = Vec::with_capacity(fragment.selection.0.len()); + // items.extend(fragment.selection.0.clone()); + // Selection(items) + // }); + // } + // } + // } + // } + + // Ok(()) } /// This method should only be invoked on selections on union and interface fields. It returns a map from the name of the selected variants to the corresponding selections. @@ -133,7 +134,7 @@ impl<'query> Selection<'query> { /// The `context` argument is required so we can expand the fragments. pub(crate) fn selected_variants_on_union<'s>( &'s self, - context: &'s crate::query::QueryContext<'_, '_>, + context: &'s crate::query::QueryContext<'_>, // the name of the type the selection applies to selection_on: &str, ) -> Result>, failure::Error> { @@ -168,7 +169,7 @@ impl<'query> Selection<'query> { self.0.len() } - pub(crate) fn require_items<'s>(&self, context: &crate::query::QueryContext<'query, 's>) { + pub(crate) fn require_items(&self, context: &crate::query::QueryContext<'query>) { self.0.iter().for_each(|item| { if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { context.require_fragment(fragment_name); diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 62c940398..44bf14575 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -114,7 +114,7 @@ pub(crate) fn render_object_field( pub(crate) fn field_impls_for_selection( fields: &[GqlObjectField<'_>], - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { @@ -144,7 +144,7 @@ pub(crate) fn field_impls_for_selection( pub(crate) fn response_fields_for_selection( type_name: &str, schema_fields: &[GqlObjectField<'_>], - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index 784a8551b..3e9e50d79 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -6,6 +6,13 @@ use quote::quote; use std::cell::Cell; use std::collections::BTreeSet; +pub(crate) fn union_type_to_rust( + ctx: &mut QueryContext<'_>, + union: (), +) -> Result { + todo!() +} + /// A GraphQL union (simplified schema representation). /// /// For code generation purposes, unions will "flatten" fragment spreads, so there is only one enum for the selection. See the tests in the graphql_client crate for examples. @@ -38,64 +45,65 @@ type UnionVariantResult<'selection> = /// - The last one contains which fields have been selected on the union, so we can make the enum exhaustive by complementing with those missing. pub(crate) fn union_variants<'selection>( selection: &'selection Selection<'_>, - context: &'selection QueryContext<'selection, 'selection>, + context: &'selection QueryContext<'selection>, prefix: &str, selection_on: &str, ) -> UnionVariantResult<'selection> { - let selection = selection.selected_variants_on_union(context, selection_on)?; - let mut used_variants: Vec<&str> = selection.keys().cloned().collect(); - let mut children_definitions = Vec::with_capacity(selection.len()); - let mut variants = Vec::with_capacity(selection.len()); - - for (on, fields) in selection.iter() { - let variant_name = Ident::new(&on, Span::call_site()); - used_variants.push(on); - - let new_prefix = format!("{}On{}", prefix, on); - - let variant_type = Ident::new(&new_prefix, Span::call_site()); - - let field_object_type = context - .schema - .objects - .get(on) - .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - let field_interface = context - .schema - .interfaces - .get(on) - .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - let field_union_type = context - .schema - .unions - .get(on) - .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - - match field_object_type.or(field_interface).or(field_union_type) { - Some(Ok(Some(tokens))) => children_definitions.push(tokens), - Some(Err(err)) => return Err(err), - Some(Ok(None)) => (), - None => { - return Err(UnionError::UnknownType { - ty: (*on).to_string(), - } - .into()) - } - }; - - variants.push(quote! { - #variant_name(#variant_type) - }) - } - - Ok((variants, children_definitions, used_variants)) + todo!() + // let selection = selection.selected_variants_on_union(context, selection_on)?; + // let mut used_variants: Vec<&str> = selection.keys().cloned().collect(); + // let mut children_definitions = Vec::with_capacity(selection.len()); + // let mut variants = Vec::with_capacity(selection.len()); + + // for (on, fields) in selection.iter() { + // let variant_name = Ident::new(&on, Span::call_site()); + // used_variants.push(on); + + // let new_prefix = format!("{}On{}", prefix, on); + + // let variant_type = Ident::new(&new_prefix, Span::call_site()); + + // let field_object_type = context + // .schema + // .objects + // .get(on) + // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); + // let field_interface = context + // .schema + // .interfaces + // .get(on) + // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); + // let field_union_type = context + // .schema + // .unions + // .get(on) + // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); + + // match field_object_type.or(field_interface).or(field_union_type) { + // Some(Ok(Some(tokens))) => children_definitions.push(tokens), + // Some(Err(err)) => return Err(err), + // Some(Ok(None)) => (), + // None => { + // return Err(UnionError::UnknownType { + // ty: (*on).to_string(), + // } + // .into()) + // } + // }; + + // variants.push(quote! { + // #variant_name(#variant_type) + // }) + // } + + // Ok((variants, children_definitions, used_variants)) } impl<'schema> GqlUnion<'schema> { /// Returns the code to deserialize this union in the response given the query selection. pub(crate) fn response_for_selection( &self, - query_context: &QueryContext<'_, '_>, + query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, ) -> Result { diff --git a/graphql_client_codegen/src/variables.rs b/graphql_client_codegen/src/variables.rs index 7865f6e28..b1f764669 100644 --- a/graphql_client_codegen/src/variables.rs +++ b/graphql_client_codegen/src/variables.rs @@ -14,28 +14,31 @@ pub struct Variable<'query> { impl<'query> Variable<'query> { pub(crate) fn generate_default_value_constructor( &self, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, + schema: &crate::schema::Schema, ) -> Option { - context.schema.require(&self.ty.inner_name_str()); - match &self.default { - Some(default) => { - let fn_name = Ident::new(&format!("default_{}", self.name), Span::call_site()); - let ty = self.ty.to_rust(context, ""); - let value = graphql_parser_value_to_literal( - default, - context, - &self.ty, - self.ty.is_optional(), - ); - Some(quote! { - pub fn #fn_name() -> #ty { - #value - } + todo!() + // // TODO + // // context.schema.require(&self.ty.inner_name_str()); + // match &self.default { + // Some(default) => { + // let fn_name = Ident::new(&format!("default_{}", self.name), Span::call_site()); + // let ty = self.ty.to_rust(context, ""); + // let value = graphql_parser_value_to_literal( + // default, + // context, + // &self.ty, + // self.ty.is_optional(), + // ); + // Some(quote! { + // pub fn #fn_name() -> #ty { + // #value + // } - }) - } - None => None, - } + // }) + // } + // None => None, + // } } } @@ -53,7 +56,7 @@ impl<'query> std::convert::From<&'query graphql_parser::query::VariableDefinitio fn graphql_parser_value_to_literal( value: &graphql_parser::query::Value, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, ty: &FieldType<'_>, is_optional: bool, ) -> TokenStream { @@ -99,37 +102,38 @@ fn graphql_parser_value_to_literal( fn render_object_literal( object: &BTreeMap, ty: &FieldType<'_>, - context: &QueryContext<'_, '_>, + context: &QueryContext<'_>, ) -> TokenStream { - let type_name = ty.inner_name_str(); - let constructor = Ident::new(&type_name, Span::call_site()); - let schema_type = context - .schema - .inputs - .get(type_name) - .expect("unknown input type"); - let fields: Vec = schema_type - .fields - .iter() - .map(|(name, field)| { - let field_name = Ident::new(&name, Span::call_site()); - let provided_value = object.get(name.to_owned()); - match provided_value { - Some(default_value) => { - let value = graphql_parser_value_to_literal( - default_value, - context, - &field.type_, - field.type_.is_optional(), - ); - quote!(#field_name: #value) - } - None => quote!(#field_name: None), - } - }) - .collect(); + unimplemented!() + // let type_name = ty.inner_name_str(); + // let constructor = Ident::new(&type_name, Span::call_site()); + // let schema_type = context + // .schema + // .inputs + // .get(type_name) + // .expect("unknown input type"); + // let fields: Vec = schema_type + // .fields + // .iter() + // .map(|(name, field)| { + // let field_name = Ident::new(&name, Span::call_site()); + // let provided_value = object.get(name.to_owned()); + // match provided_value { + // Some(default_value) => { + // let value = graphql_parser_value_to_literal( + // default_value, + // context, + // &field.type_, + // field.type_.is_optional(), + // ); + // quote!(#field_name: #value) + // } + // None => quote!(#field_name: None), + // } + // }) + // .collect(); - quote!(#constructor { - #(#fields,)* - }) + // quote!(#constructor { + // #(#fields,)* + // }) } From 9f83c6dd3631e52e6b4f4df9475306d80ad21cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Mon, 27 Jan 2020 23:52:51 +0100 Subject: [PATCH 02/93] Normalize fields --- graphql_client_codegen/src/codegen.rs | 26 ++-- graphql_client_codegen/src/inputs.rs | 18 +-- graphql_client_codegen/src/query.rs | 36 +++-- graphql_client_codegen/src/schema.rs | 44 ++++-- .../src/schema/graphql_parser_conversion.rs | 51 ++++--- .../src/schema/json_conversion.rs | 58 ++++---- graphql_client_codegen/src/selection.rs | 11 +- graphql_client_codegen/src/shared.rs | 135 +++++++++--------- 8 files changed, 217 insertions(+), 162 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index d457f80fd..b257ae30b 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -41,21 +41,21 @@ pub(crate) fn response_for_query( operation: &Operation<'_>, options: &crate::GraphQLClientCodegenOptions, ) -> Result { - todo!() - - // let mut context = QueryContext::new( - // schema, - // options.deprecation_strategy(), - // options.normalization(), - // ); + let mut context = QueryContext::new( + schema, + options.deprecation_strategy(), + options.normalization(), + ); + + if let Some(derives) = options.variables_derives() { + context.ingest_variables_derives(&derives)?; + } - // if let Some(derives) = options.variables_derives() { - // context.ingest_variables_derives(&derives)?; - // } + if let Some(derives) = options.response_derives() { + context.ingest_response_derives(&derives)?; + } - // if let Some(derives) = options.response_derives() { - // context.ingest_response_derives(&derives)?; - // } + todo!("response for query"); // let mut definitions = Vec::new(); diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index fcf8be0c0..71ea32686 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -2,7 +2,7 @@ use crate::deprecation::DeprecationStatus; use crate::objects::GqlObjectField; use crate::query::QueryContext; use crate::schema; -use crate::schema::{InputObjectId, SchemaRef}; +use crate::schema::{InputId, SchemaRef}; use graphql_introspection_query::introspection_response; // use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; @@ -17,16 +17,12 @@ pub(crate) fn input_to_rust( todo!() } -// /// Represents an input object type from a GraphQL schema -// #[derive(Debug, Clone, PartialEq)] -// pub struct InputRef<'a> { -// schema: SchemaRef<'a>, -// input_id: InputObjectId, -// // pub description: Option<&'schema str>, -// // pub name: &'schema str, -// // pub fields: HashMap<&'schema str, GqlObjectField<'schema>>, -// // pub is_required: Cell, -// } +/// Represents an input object type from a GraphQL schema +#[derive(Debug, Clone, PartialEq)] +pub struct InputRef<'a> { + schema: SchemaRef<'a>, + input_id: InputId, +} // impl InputRef<'_> { // // pub(crate) fn require(&self, schema: &Schema) { diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index ffa4f329b..f6be9064c 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -1,47 +1,67 @@ use crate::deprecation::DeprecationStrategy; use crate::fragments::GqlFragment; use crate::normalization::Normalization; -use crate::schema::Schema; +use crate::schema::{StoredFieldId, TypeId, EnumId, InputId, Schema}; use crate::selection::Selection; use failure::*; use proc_macro2::Span; use proc_macro2::TokenStream; use quote::quote; use std::collections::{BTreeMap, BTreeSet}; + use syn::Ident; +#[derive(Debug, Clone, Copy)] +pub(crate) struct FragmentId(usize); + +struct Q { + operation: &'static str, + selection: IdSelection, +} + +#[derive(Debug, Clone)] +enum IdSelection { + Field(StoredFieldId), + FragmentSpread(FragmentId), + InlineFragment(TypeId, Vec), +} + /// This holds all the information we need during the code generation phase. pub(crate) struct QueryContext<'query> { pub fragments: BTreeMap<&'query str, GqlFragment<'query>>, - // pub schema: &'schema Schema, + pub schema: &'query Schema, pub deprecation_strategy: DeprecationStrategy, pub normalization: Normalization, variables_derives: Vec, response_derives: Vec, + used_enums: Vec, + used_input_objects: Vec, + used_fragments: Vec, } impl<'query, 'schema> QueryContext<'query> { /// Create a QueryContext with the given Schema. pub(crate) fn new( - schema: &'schema Schema, + schema: &'query Schema, deprecation_strategy: DeprecationStrategy, normalization: Normalization, ) -> QueryContext<'query> { QueryContext { fragments: BTreeMap::new(), - // schema, + schema, deprecation_strategy, normalization, variables_derives: vec![Ident::new("Serialize", Span::call_site())], response_derives: vec![Ident::new("Deserialize", Span::call_site())], + used_enums: Vec::new(), + used_input_objects: Vec::new(), + used_fragments: Vec::new(), } } /// Mark a fragment as required, so code is actually generated for it. - pub(crate) fn require_fragment(&self, typename_: &str) { - if let Some(fragment) = self.fragments.get(typename_) { - fragment.require(&self); - } + pub(crate) fn require_fragment(&mut self, id: FragmentId) { + self.used_fragments.push(id); } /// For testing only. creates an empty QueryContext. diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 6dcee4bed..71788ad9f 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -25,15 +25,21 @@ struct StoredObjectField { #[derive(Debug, PartialEq, Clone)] struct StoredObject { name: String, - // fields: Vec, + fields: Vec, implements_interfaces: Vec, - fields: Vec, } #[derive(Debug, PartialEq, Clone)] struct StoredField { name: String, r#type: StoredFieldType, + parent: StoredFieldParent, +} + +#[derive(Debug, PartialEq, Clone)] +enum StoredFieldParent { + Object(ObjectId), + Interface(InterfaceId) } #[derive(Debug, Clone, Copy, PartialEq)] @@ -58,7 +64,10 @@ pub(crate) struct UnionId(usize); pub(crate) struct EnumId(usize); #[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct InputObjectId(usize); +pub(crate) struct InputId(usize); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct StoredFieldId(usize); #[derive(Debug, Clone, Copy, PartialEq)] struct InputFieldId(usize); @@ -66,8 +75,7 @@ struct InputFieldId(usize); #[derive(Debug, Clone, PartialEq)] struct StoredInterface { name: String, - // fields: Vec, - fields: Vec, + fields: Vec, } #[derive(Debug, Clone, PartialEq)] @@ -101,7 +109,7 @@ pub(crate) enum TypeId { Interface(InterfaceId), Union(UnionId), Enum(EnumId), - Input(InputObjectId), + Input(InputId), } impl TypeId { @@ -126,7 +134,7 @@ impl TypeId { } fn input(id: usize) -> Self { - TypeId::Input(InputObjectId(id)) + TypeId::Input(InputId(id)) } fn as_interface_id(&self) -> Option { @@ -176,16 +184,15 @@ struct StoredInputType { #[derive(Debug, Clone, Copy, PartialEq)] enum InputFieldTypeId { Scalar(ScalarId), - InputObject(InputObjectId), + InputObject(InputId), } /// Intermediate representation for a parsed GraphQL schema used during code generation. #[derive(Debug, Clone, PartialEq)] pub(crate) struct Schema { stored_objects: Vec, - // stored_object_fields: Vec, + stored_fields: Vec, stored_interfaces: Vec, - // stored_interface_fields: Vec, stored_unions: Vec, stored_scalars: Vec, stored_enums: Vec, @@ -201,9 +208,8 @@ impl Schema { pub(crate) fn new() -> Schema { let mut schema = Schema { stored_objects: Vec::new(), - // stored_object_fields: Vec::new(), stored_interfaces: Vec::new(), - // stored_interface_fields: Vec::new(), + stored_fields: Vec::new(), stored_unions: Vec::new(), stored_scalars: Vec::with_capacity(DEFAULT_SCALARS.len()), stored_enums: Vec::new(), @@ -358,11 +364,19 @@ impl Schema { id } + fn push_field(&mut self, field: StoredField) -> StoredFieldId { + let id = StoredFieldId(self.stored_fields.len()); + + self.stored_fields.push(field); + + id + } + // pub(crate) fn get_input_type_by_name(&self, name: &str) -> Option> { // self.stored_inputs // .iter() // .position(|input| input.name == name) - // .map(InputObjectId) + // .map(InputId) // .map(|idx| InputRef { // schema: self, // input_id: idx, @@ -405,7 +419,7 @@ impl Schema { } } - fn get_stored_input(&self, input_id: InputObjectId) -> &StoredInputType { + fn get_stored_input(&self, input_id: InputId) -> &StoredInputType { self.stored_inputs.get(input_id.0).unwrap() } @@ -447,7 +461,7 @@ impl<'a> ObjectRef<'a> { pub(crate) struct InputRef<'a> { schema: SchemaRef<'a>, - input_id: InputObjectId, + input_id: InputId, } impl<'a> InputRef<'a> { diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 4ea2e20a7..e9352b577 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,4 @@ -use super::{EnumId, InputObjectId, InterfaceId, ObjectId, ScalarId, Schema, TypeId, UnionId}; +use super::{EnumId, InputId, InterfaceId, ObjectId, ScalarId, Schema, TypeId, UnionId}; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; pub(super) fn build_schema(mut src: graphql_parser::schema::Document) -> super::Schema { @@ -133,17 +133,24 @@ fn ingest_union(schema: &mut Schema, union: &mut graphql_parser::schema::UnionTy } fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectType) { + let object_id = schema.find_type_id(&obj.name).as_object_id().unwrap(); + let mut field_ids = Vec::with_capacity(obj.fields.len()); + + for field in obj.fields.iter_mut() { + let field = super::StoredField { + name: std::mem::replace(&mut field.name, String::new()), + r#type: resolve_field_type(schema, &field.field_type), + parent: super::StoredFieldParent::Object(object_id), + }; + + field_ids.push(schema.push_field(field)); + } + + // Ingest the object itself let object = super::StoredObject { name: std::mem::replace(&mut obj.name, String::new()), - fields: obj - .fields - .iter_mut() - .map(|graphql_field| super::StoredField { - name: std::mem::replace(&mut graphql_field.name, String::new()), - r#type: resolve_field_type(schema, &graphql_field.field_type), - }) - .collect(), + fields: field_ids, implements_interfaces: obj .implements_interfaces .iter() @@ -213,16 +220,26 @@ fn ingest_enum(schema: &mut Schema, enm: &mut graphql_parser::schema::EnumType) } fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema::InterfaceType) { + let interface_id = schema + .find_type_id(&interface.name) + .as_interface_id() + .unwrap(); + + let mut field_ids = Vec::with_capacity(interface.fields.len()); + + for field in interface.fields.iter_mut() { + let field = super::StoredField { + name: std::mem::replace(&mut field.name, String::new()), + r#type: resolve_field_type(schema, &field.field_type), + parent: super::StoredFieldParent::Interface(interface_id), + }; + + field_ids.push(schema.push_field(field)); + } + let new_interface = super::StoredInterface { name: std::mem::replace(&mut interface.name, String::new()), - fields: interface - .fields - .iter_mut() - .map(|graphql_field| super::StoredField { - name: std::mem::replace(&mut graphql_field.name, String::new()), - r#type: resolve_field_type(schema, &graphql_field.field_type), - }) - .collect(), + fields: field_ids, }; schema.push_interface(new_interface); diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 091ff02ac..ca635daff 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -284,43 +284,49 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { } fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { + let interface_id = schema.find_type_id(iface.name.as_ref().unwrap()).as_interface_id().unwrap(); + let fields = iface.fields.as_mut().unwrap(); + let mut field_ids = Vec::with_capacity(fields.len()); + + for field in fields.iter_mut() { + let field = super::StoredField { + parent: super::StoredFieldParent::Interface(interface_id), + name: field.name.take().unwrap(), + r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + }; + + field_ids.push(schema.push_field(field)); + } + let interface = super::StoredInterface { name: std::mem::replace(iface.name.as_mut().unwrap(), String::new()), - fields: iface - .fields - .as_mut() - .unwrap() - .iter_mut() - .map(|json_field| super::StoredField { - name: json_field.name.take().unwrap(), - r#type: resolve_field_type( - schema, - &mut json_field.type_.as_mut().unwrap().type_ref, - ), - }) - .collect(), + fields: field_ids, }; schema.push_interface(interface); } fn ingest_object(schema: &mut Schema, object: &mut FullType) { + let object_id = schema.find_type_id(object.name.as_ref().unwrap()).as_object_id().unwrap(); + + let fields = object.fields.as_mut().unwrap(); + let mut field_ids = Vec::with_capacity(fields.len()); + + for field in fields.iter_mut() { + let field = super::StoredField { + parent: super::StoredFieldParent::Object(object_id), + name: field.name.take().unwrap(), + r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + }; + + field_ids.push(schema.push_field(field)); + } + + let object = super::StoredObject { name: object.name.take().unwrap(), implements_interfaces: Vec::new(), - fields: object - .fields - .as_mut() - .unwrap() - .iter_mut() - .map(|json_field| super::StoredField { - name: json_field.name.take().unwrap(), - r#type: resolve_field_type( - schema, - &mut json_field.type_.as_mut().unwrap().type_ref, - ), - }) - .collect(), + fields: field_ids, }; schema.push_object(object); diff --git a/graphql_client_codegen/src/selection.rs b/graphql_client_codegen/src/selection.rs index 3139750f4..71c0000f6 100644 --- a/graphql_client_codegen/src/selection.rs +++ b/graphql_client_codegen/src/selection.rs @@ -170,11 +170,12 @@ impl<'query> Selection<'query> { } pub(crate) fn require_items(&self, context: &crate::query::QueryContext<'query>) { - self.0.iter().for_each(|item| { - if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { - context.require_fragment(fragment_name); - } - }) + // self.0.iter().for_each(|item| { + // if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { + // context.require_fragment(fragment_name); + // } + // }) + todo!("require_items"); } } diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 44bf14575..4103ff0cc 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -148,75 +148,76 @@ pub(crate) fn response_fields_for_selection( selection: &Selection<'_>, prefix: &str, ) -> Result, failure::Error> { - (&selection) - .into_iter() - .map(|item| match item { - SelectionItem::Field(f) => { - let name = &f.name; - let alias = f.alias.as_ref().unwrap_or(name); + todo!("response fields for selection") + // (&selection) + // .into_iter() + // .map(|item| match item { + // SelectionItem::Field(f) => { + // let name = &f.name; + // let alias = f.alias.as_ref().unwrap_or(name); - let schema_field = &schema_fields - .iter() - .find(|field| &field.name == name) - .ok_or_else(|| { - format_err!( - "Could not find field `{}` on `{}`. Available fields: `{}`.", - *name, - type_name, - schema_fields - .iter() - .map(|ref field| &field.name) - .fold(String::new(), |mut acc, item| { - acc.push_str(item); - acc.push_str(", "); - acc - }) - .trim_end_matches(", ") - ) - })?; - let ty = schema_field.type_.to_rust( - context, - &format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()), - ); + // let schema_field = &schema_fields + // .iter() + // .find(|field| &field.name == name) + // .ok_or_else(|| { + // format_err!( + // "Could not find field `{}` on `{}`. Available fields: `{}`.", + // *name, + // type_name, + // schema_fields + // .iter() + // .map(|ref field| &field.name) + // .fold(String::new(), |mut acc, item| { + // acc.push_str(item); + // acc.push_str(", "); + // acc + // }) + // .trim_end_matches(", ") + // ) + // })?; + // let ty = schema_field.type_.to_rust( + // context, + // &format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()), + // ); - Ok(render_object_field( - alias, - &ty, - schema_field.description.as_ref().cloned(), - &schema_field.deprecation, - &context.deprecation_strategy, - )) - } - SelectionItem::FragmentSpread(fragment) => { - let field_name = - Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site()); - context.require_fragment(&fragment.fragment_name); - let fragment_from_context = context - .fragments - .get(&fragment.fragment_name) - .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment.fragment_name))?; - let type_name = Ident::new(&fragment.fragment_name, Span::call_site()); - let type_name = if fragment_from_context.is_recursive() { - quote!(Box<#type_name>) - } else { - quote!(#type_name) - }; - Ok(Some(quote! { - #[serde(flatten)] - pub #field_name: #type_name - })) - } - SelectionItem::InlineFragment(_) => Err(format_err!( - "unimplemented: inline fragment on object field" - )), - }) - .filter_map(|x| match x { - // Remove empty fields so callers always know a field has some - // tokens. - Ok(f) => f.map(Ok), - Err(err) => Some(Err(err)), - }) - .collect() + // Ok(render_object_field( + // alias, + // &ty, + // schema_field.description.as_ref().cloned(), + // &schema_field.deprecation, + // &context.deprecation_strategy, + // )) + // } + // SelectionItem::FragmentSpread(fragment) => { + // let field_name = + // Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site()); + // context.require_fragment(&fragment.fragment_name); + // let fragment_from_context = context + // .fragments + // .get(&fragment.fragment_name) + // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment.fragment_name))?; + // let type_name = Ident::new(&fragment.fragment_name, Span::call_site()); + // let type_name = if fragment_from_context.is_recursive() { + // quote!(Box<#type_name>) + // } else { + // quote!(#type_name) + // }; + // Ok(Some(quote! { + // #[serde(flatten)] + // pub #field_name: #type_name + // })) + // } + // SelectionItem::InlineFragment(_) => Err(format_err!( + // "unimplemented: inline fragment on object field" + // )), + // }) + // .filter_map(|x| match x { + // // Remove empty fields so callers always know a field has some + // // tokens. + // Ok(f) => f.map(Ok), + // Err(err) => Some(Err(err)), + // }) + // .collect() } /// Given the GraphQL schema name for an object/interface/input object field and From c49ad8469c92546214748459dd1a50199b564e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Tue, 28 Jan 2020 19:56:56 +0100 Subject: [PATCH 03/93] Decide general workflow --- graphql_client_codegen/Cargo.toml | 2 +- graphql_client_codegen/src/codegen.rs | 100 ++++------ graphql_client_codegen/src/constants.rs | 24 +-- graphql_client_codegen/src/field_type.rs | 170 ++++++++--------- graphql_client_codegen/src/fragments.rs | 4 +- .../src/generated_module.rs | 22 +-- graphql_client_codegen/src/lib.rs | 137 +++++++------- graphql_client_codegen/src/operations.rs | 172 +++++++++--------- graphql_client_codegen/src/query.rs | 35 ++-- graphql_client_codegen/src/rendering.rs | 11 ++ graphql_client_codegen/src/resolution.rs | 62 +++++++ graphql_client_codegen/src/schema.rs | 31 ++-- .../src/schema/graphql_parser_conversion.rs | 3 +- graphql_client_codegen/src/shared.rs | 45 ++--- graphql_client_codegen/src/unions.rs | 8 +- graphql_query_derive/src/lib.rs | 3 +- 16 files changed, 423 insertions(+), 406 deletions(-) create mode 100644 graphql_client_codegen/src/rendering.rs create mode 100644 graphql_client_codegen/src/resolution.rs diff --git a/graphql_client_codegen/Cargo.toml b/graphql_client_codegen/Cargo.toml index 954816c5b..d41aacb76 100644 --- a/graphql_client_codegen/Cargo.toml +++ b/graphql_client_codegen/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/graphql-rust/graphql-client" edition = "2018" [dependencies] -failure = "0.1" +anyhow = "1.0" graphql-introspection-query = { version = "0.1.0", path = "../graphql-introspection-query" } graphql-parser = "^0.2" heck = "0.3" diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index b257ae30b..e7aad41db 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,89 +1,55 @@ -use crate::fragments::GqlFragment; +// use crate::fragments::GqlFragment; use crate::normalization::Normalization; -use crate::operations::Operation; -use crate::query::QueryContext; +// use crate::operations::Operation; +// use crate::query::QueryContext; use crate::schema; // use crate::selection::Selection; -use failure::*; +use crate::resolution::{ResolvedOperation, ResolvedQuery}; use graphql_parser::query; use proc_macro2::TokenStream; use quote::*; /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. -pub(crate) fn select_operation<'query>( - query: &'query query::Document, +pub(crate) fn select_operation<'a>( + query: &'a ResolvedQuery, struct_name: &str, norm: Normalization, -) -> Option> { - let operations = all_operations(query); - - operations +) -> Option<&'a ResolvedOperation> { + query + .operations .iter() - .find(|op| norm.operation(&op.name) == struct_name) - .map(ToOwned::to_owned) -} - -pub(crate) fn all_operations(query: &query::Document) -> Vec> { - let mut operations: Vec> = Vec::new(); - - for definition in &query.definitions { - if let query::Definition::Operation(op) = definition { - operations.push(op.into()); - } - } - operations + .find(|op| norm.operation(op.name()) == struct_name) } /// The main code generation function. pub(crate) fn response_for_query( schema: &schema::Schema, - query: &query::Document, - operation: &Operation<'_>, + query: &crate::resolution::ResolvedQuery, + operation: &str, options: &crate::GraphQLClientCodegenOptions, -) -> Result { - let mut context = QueryContext::new( - schema, - options.deprecation_strategy(), - options.normalization(), - ); - - if let Some(derives) = options.variables_derives() { - context.ingest_variables_derives(&derives)?; - } - - if let Some(derives) = options.response_derives() { - context.ingest_response_derives(&derives)?; - } - - todo!("response for query"); - - // let mut definitions = Vec::new(); - - // for definition in &query.definitions { - // match definition { - // query::Definition::Operation(_op) => (), - // query::Definition::Fragment(fragment) => { - // let &query::TypeCondition::On(ref on) = &fragment.type_condition; - // let on = schema.lookup_type(on).ok_or_else(|| { - // format_err!( - // "Fragment {} is defined on unknown type: {}", - // &fragment.name, - // on, - // ) - // })?; - // context.fragments.insert( - // &fragment.name, - // GqlFragment { - // name: &fragment.name, - // selection: Selection::from(&fragment.selection_set), - // on, - // is_required: false.into(), - // }, - // ); - // } - // } +) -> anyhow::Result { + todo!() + // let mut context = QueryContext::new( + // schema, + // options.deprecation_strategy(), + // options.normalization(), + // ); + + // if let Some(derives) = options.variables_derives() { + // context.ingest_variables_derives(&derives)?; // } + // if let Some(derives) = options.response_derives() { + // context.ingest_response_derives(&derives)?; + // } + + // let resolved_query = crate::resolution::resolve(schema, query)?; + // crate::rendering::render(schema, &resolved_query) + + // context.resolve_fragments(&query.definitions); + + // let module = context.types_for_operation(operation); + // let response_data_fields = { // let root_name = operation.root_name(&schema); // let opt_definition = schema.get_object_by_name(&root_name); diff --git a/graphql_client_codegen/src/constants.rs b/graphql_client_codegen/src/constants.rs index 5fc1ac1da..b2b97e8a4 100644 --- a/graphql_client_codegen/src/constants.rs +++ b/graphql_client_codegen/src/constants.rs @@ -1,6 +1,6 @@ use crate::deprecation::DeprecationStatus; -use crate::field_type::FieldType; -use crate::objects::GqlObjectField; +// use crate::field_type::FieldType; +// use crate::objects::GqlObjectField; pub(crate) const TYPENAME_FIELD: &str = "__typename"; @@ -13,16 +13,16 @@ pub(crate) fn float_type() -> &'static str { "Float" } -pub(crate) fn typename_field() -> GqlObjectField<'static> { - GqlObjectField { - description: None, - name: TYPENAME_FIELD, - /// Non-nullable, see spec: - /// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md - type_: FieldType::new(string_type()), - deprecation: DeprecationStatus::Current, - } -} +// pub(crate) fn typename_field() -> GqlObjectField<'static> { +// GqlObjectField { +// description: None, +// name: TYPENAME_FIELD, +// /// Non-nullable, see spec: +// /// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md +// type_: FieldType::new(string_type()), +// deprecation: DeprecationStatus::Current, +// } +// } pub(crate) const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##" Multiple-field queries on the root subscription field are forbidden by the spec. diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index 4ac1480bb..a690df3fa 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -1,13 +1,13 @@ -use crate::enums::ENUMS_PREFIX; -use crate::query::QueryContext; +// use crate::enums::ENUMS_PREFIX; +// use crate::query::QueryContext; use crate::schema::DEFAULT_SCALARS; use graphql_introspection_query::introspection_response; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -pub(crate) fn field_type_to_rust() -> TokenStream { - todo!() -} +// pub(crate) fn field_type_to_rust() -> TokenStream { +// todo!() +// } #[derive(Clone, Debug, PartialEq, Hash)] pub(crate) enum GraphqlTypeQualifier { @@ -49,80 +49,80 @@ impl<'a> FieldType<'a> { } /// Takes a field type with its name. - pub(crate) fn to_rust(&self, context: &QueryContext<'_>, prefix: &str) -> TokenStream { - todo!() - // let prefix: &str = if prefix.is_empty() { - // self.inner_name_str() - // } else { - // prefix - // }; - - // let full_name = { - // if context - // .schema - // .scalars - // .get(&self.name) - // .map(|s| s.is_required.set(true)) - // .is_some() - // || DEFAULT_SCALARS.iter().any(|elem| elem == &self.name) - // { - // self.name.to_string() - // } else if context - // .schema - // .enums - // .get(&self.name) - // .map(|enm| enm.is_required.set(true)) - // .is_some() - // { - // format!("{}{}", ENUMS_PREFIX, self.name) - // } else { - // if prefix.is_empty() { - // panic!("Empty prefix for {:?}", self); - // } - // prefix.to_string() - // } - // }; - - // let norm = context.normalization; - // let full_name = norm.field_type(crate::shared::keyword_replace(&full_name)); - - // let full_name = Ident::new(&full_name, Span::call_site()); - // let mut qualified = quote!(#full_name); - - // let mut non_null = false; - - // // Note: we iterate over qualifiers in reverse because it is more intuitive. This - // // means we start from the _inner_ type and make our way to the outside. - // for qualifier in self.qualifiers.iter().rev() { - // match (non_null, qualifier) { - // // We are in non-null context, and we wrap the non-null type into a list. - // // We switch back to null context. - // (true, GraphqlTypeQualifier::List) => { - // qualified = quote!(Vec<#qualified>); - // non_null = false; - // } - // // We are in nullable context, and we wrap the nullable type into a list. - // (false, GraphqlTypeQualifier::List) => { - // qualified = quote!(Vec>); - // } - // // We are in non-nullable context, but we can't double require a type - // // (!!). - // (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), - // // We are in nullable context, and we switch to non-nullable context. - // (false, GraphqlTypeQualifier::Required) => { - // non_null = true; - // } - // } - // } - - // // If we are in nullable context at the end of the iteration, we wrap the whole - // // type with an Option. - // if !non_null { - // qualified = quote!(Option<#qualified>); - // } - - // qualified - } + // pub(crate) fn to_rust(&self, context: &QueryContext<'_>, prefix: &str) -> TokenStream { + // todo!() + // // let prefix: &str = if prefix.is_empty() { + // // self.inner_name_str() + // // } else { + // // prefix + // // }; + + // // let full_name = { + // // if context + // // .schema + // // .scalars + // // .get(&self.name) + // // .map(|s| s.is_required.set(true)) + // // .is_some() + // // || DEFAULT_SCALARS.iter().any(|elem| elem == &self.name) + // // { + // // self.name.to_string() + // // } else if context + // // .schema + // // .enums + // // .get(&self.name) + // // .map(|enm| enm.is_required.set(true)) + // // .is_some() + // // { + // // format!("{}{}", ENUMS_PREFIX, self.name) + // // } else { + // // if prefix.is_empty() { + // // panic!("Empty prefix for {:?}", self); + // // } + // // prefix.to_string() + // // } + // // }; + + // // let norm = context.normalization; + // // let full_name = norm.field_type(crate::shared::keyword_replace(&full_name)); + + // // let full_name = Ident::new(&full_name, Span::call_site()); + // // let mut qualified = quote!(#full_name); + + // // let mut non_null = false; + + // // // Note: we iterate over qualifiers in reverse because it is more intuitive. This + // // // means we start from the _inner_ type and make our way to the outside. + // // for qualifier in self.qualifiers.iter().rev() { + // // match (non_null, qualifier) { + // // // We are in non-null context, and we wrap the non-null type into a list. + // // // We switch back to null context. + // // (true, GraphqlTypeQualifier::List) => { + // // qualified = quote!(Vec<#qualified>); + // // non_null = false; + // // } + // // // We are in nullable context, and we wrap the nullable type into a list. + // // (false, GraphqlTypeQualifier::List) => { + // // qualified = quote!(Vec>); + // // } + // // // We are in non-nullable context, but we can't double require a type + // // // (!!). + // // (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), + // // // We are in nullable context, and we switch to non-nullable context. + // // (false, GraphqlTypeQualifier::Required) => { + // // non_null = true; + // // } + // // } + // // } + + // // // If we are in nullable context at the end of the iteration, we wrap the whole + // // // type with an Option. + // // if !non_null { + // // qualified = quote!(Option<#qualified>); + // // } + + // // qualified + // } /// Return the innermost name - we mostly use this for looking types up in our Schema struct. pub fn inner_name_str(&self) -> &str { @@ -150,12 +150,12 @@ impl<'a> FieldType<'a> { } } -impl<'schema> std::convert::From<&'schema graphql_parser::schema::Type> for FieldType<'schema> { - fn from(schema_type: &'schema graphql_parser::schema::Type) -> FieldType<'schema> { - todo!() - // from_schema_type_inner(schema_type) - } -} +// impl<'schema> std::convert::From<&'schema graphql_parser::schema::Type> for FieldType<'schema> { +// fn from(schema_type: &'schema graphql_parser::schema::Type) -> FieldType<'schema> { +// todo!() +// // from_schema_type_inner(schema_type) +// } +// } pub(crate) fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { match schema_type { diff --git a/graphql_client_codegen/src/fragments.rs b/graphql_client_codegen/src/fragments.rs index 6e86dca58..d60e4fdcd 100644 --- a/graphql_client_codegen/src/fragments.rs +++ b/graphql_client_codegen/src/fragments.rs @@ -1,5 +1,5 @@ -use crate::query::QueryContext; -use crate::selection::Selection; +// use crate::query::QueryContext; +// use crate::selection::Selection; use proc_macro2::TokenStream; use std::cell::Cell; diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 52985d9b1..4087d9407 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -5,33 +5,31 @@ use quote::quote; /// This struct contains the parameters necessary to generate code for a given operation. pub(crate) struct GeneratedModule<'a> { - pub operation: &'a crate::operations::Operation<'a>, + pub operation: &'a str, pub query_string: &'a str, - pub query_document: &'a graphql_parser::query::Document, + pub resolved_query: &'a crate::resolution::ResolvedQuery, + // pub query_document: &'a graphql_parser::query::Document, pub schema: &'a crate::schema::Schema, pub options: &'a crate::GraphQLClientCodegenOptions, } impl<'a> GeneratedModule<'a> { /// Generate the items for the variables and the response that will go inside the module. - fn build_impls(&self) -> Result { + fn build_impls(&self) -> anyhow::Result { Ok(crate::codegen::response_for_query( &self.schema, - &self.query_document, + &self.resolved_query, &self.operation, &self.options, )?) } /// Generate the module and all the code inside. - pub(crate) fn to_token_stream(&self) -> Result { - let module_name = Ident::new(&self.operation.name.to_snake_case(), Span::call_site()); + pub(crate) fn to_token_stream(&self) -> anyhow::Result { + let module_name = Ident::new(&self.operation.to_snake_case(), Span::call_site()); let module_visibility = &self.options.module_visibility(); - let operation_name_literal = &self.operation.name; - let operation_name_ident = self - .options - .normalization() - .operation(operation_name_literal); + let operation_name = self.operation; + let operation_name_ident = self.options.normalization().operation(self.operation); let operation_name_ident = Ident::new(&operation_name_ident, Span::call_site()); // Force cargo to refresh the generated code when the query file changes. @@ -61,7 +59,7 @@ impl<'a> GeneratedModule<'a> { #module_visibility mod #module_name { #![allow(dead_code)] - pub const OPERATION_NAME: &'static str = #operation_name_literal; + pub const OPERATION_NAME: &'static str = #operation_name; pub const QUERY: &'static str = #query_string; #query_include diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index f49c5c4fa..91b3e2ef8 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -6,7 +6,7 @@ //! //! It is not meant to be used directly by users of the library. -use failure::*; +use anyhow::format_err; use lazy_static::*; use proc_macro2::TokenStream; use quote::*; @@ -15,29 +15,31 @@ mod codegen; mod codegen_options; /// Deprecation-related code pub mod deprecation; -mod query; +// mod query; /// Contains the [Schema] type and its implementation. pub mod schema; mod constants; -mod enums; +// mod enums; mod field_type; -mod fragments; +// mod fragments; mod generated_module; -mod inputs; -mod interfaces; +// mod inputs; +// mod interfaces; /// Normalization-related code pub mod normalization; -mod objects; +// mod objects; mod operations; +mod rendering; +mod resolution; mod scalars; -mod selection; -mod shared; -mod unions; -mod variables; +// mod selection; +// mod shared; +// mod unions; +// mod variables; -#[cfg(test)] -mod tests; +// #[cfg(test)] +// mod tests; pub use crate::codegen_options::{CodegenMode, GraphQLClientCodegenOptions}; @@ -56,8 +58,40 @@ pub fn generate_module_token_stream( query_path: std::path::PathBuf, schema_path: &std::path::Path, options: GraphQLClientCodegenOptions, -) -> Result { +) -> anyhow::Result { use std::collections::hash_map; + + let schema_extension = schema_path + .extension() + .and_then(std::ffi::OsStr::to_str) + .unwrap_or("INVALID"); + + // Check the schema cache. + let schema_string: String = { + let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned"); + match lock.entry(schema_path.to_path_buf()) { + hash_map::Entry::Occupied(o) => o.get().clone(), + hash_map::Entry::Vacant(v) => { + let schema_string = read_file(v.key())?; + (*v.insert(schema_string)).to_string() + } + } + }; + + let mut parsed_schema = match schema_extension { + "graphql" | "gql" => { + let s = graphql_parser::schema::parse_schema(&schema_string).expect("TODO: error conversion"); + schema::ParsedSchema::GraphQLParser(s) + } + "json" => { + let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string)?; + schema::ParsedSchema::Json(parsed) + } + extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension) + }; + + let schema = schema::Schema::from(parsed_schema); + // We need to qualify the query with the path to the crate it is part of let (query_string, query) = { let mut lock = QUERY_CACHE.lock().expect("query cache is poisoned"); @@ -65,12 +99,15 @@ pub fn generate_module_token_stream( hash_map::Entry::Occupied(o) => o.get().clone(), hash_map::Entry::Vacant(v) => { let query_string = read_file(v.key())?; - let query = graphql_parser::parse_query(&query_string)?; + let query = + graphql_parser::parse_query(&query_string).expect("TODO: error conversion"); v.insert((query_string, query)).clone() } } }; + let query = resolution::resolve(&schema, &query)?; + // Determine which operation we are generating code for. This will be used in operationName. let operations = options .operation_name @@ -82,7 +119,7 @@ pub fn generate_module_token_stream( let operations = match (operations, &options.mode) { (Some(ops), _) => ops, - (None, &CodegenMode::Cli) => codegen::all_operations(&query), + (None, &CodegenMode::Cli) => query.operations.iter().collect(), (None, &CodegenMode::Derive) => { return Err(derive_operation_not_found_error( options.struct_ident(), @@ -91,37 +128,6 @@ pub fn generate_module_token_stream( } }; - let schema_extension = schema_path - .extension() - .and_then(std::ffi::OsStr::to_str) - .unwrap_or("INVALID"); - - // Check the schema cache. - let schema_string: String = { - let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned"); - match lock.entry(schema_path.to_path_buf()) { - hash_map::Entry::Occupied(o) => o.get().clone(), - hash_map::Entry::Vacant(v) => { - let schema_string = read_file(v.key())?; - (*v.insert(schema_string)).to_string() - } - } - }; - - let mut parsed_schema = match schema_extension { - "graphql" | "gql" => { - let s = graphql_parser::schema::parse_schema(&schema_string)?; - schema::ParsedSchema::GraphQLParser(s) - } - "json" => { - let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string)?; - schema::ParsedSchema::Json(parsed) - } - extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension) - }; - - let schema = schema::Schema::from(parsed_schema); - // The generated modules. let mut modules = Vec::with_capacity(operations.len()); @@ -129,8 +135,8 @@ pub fn generate_module_token_stream( let generated = generated_module::GeneratedModule { query_string: query_string.as_str(), schema: &schema, - query_document: &query, - operation, + resolved_query: &query, + operation: operation.name(), options: &options, } .to_token_stream()?; @@ -142,13 +148,13 @@ pub fn generate_module_token_stream( Ok(modules) } -fn read_file(path: &std::path::Path) -> Result { +fn read_file(path: &std::path::Path) -> anyhow::Result { use std::fs; use std::io::prelude::*; let mut out = String::new(); let mut file = fs::File::open(path).map_err(|io_err| { - let err: failure::Error = io_err.into(); + let err: anyhow::Error = io_err.into(); err.context(format!( r#" Could not find file with path: {} @@ -164,34 +170,13 @@ fn read_file(path: &std::path::Path) -> Result { /// In derive mode, build an error when the operation with the same name as the struct is not found. fn derive_operation_not_found_error( ident: Option<&proc_macro2::Ident>, - query: &graphql_parser::query::Document, -) -> failure::Error { - use graphql_parser::query::*; - + query: &crate::resolution::ResolvedQuery, +) -> anyhow::Error { let operation_name = ident.map(ToString::to_string); let struct_ident = operation_name.as_deref().unwrap_or(""); - let available_operations = query - .definitions - .iter() - .filter_map(|definition| match definition { - Definition::Operation(op) => match op { - OperationDefinition::Mutation(m) => Some(m.name.as_ref().unwrap()), - OperationDefinition::Query(m) => Some(m.name.as_ref().unwrap()), - OperationDefinition::Subscription(m) => Some(m.name.as_ref().unwrap()), - OperationDefinition::SelectionSet(_) => { - unreachable!("Bare selection sets are not supported.") - } - }, - _ => None, - }) - .fold(String::new(), |mut acc, item| { - acc.push_str(&item); - acc.push_str(", "); - acc - }); - - let available_operations = available_operations.trim_end_matches(", "); + let available_operations: Vec<&str> = query.operations.iter().map(|op| op.name()).collect(); + let available_operations: String = available_operations.join(", "); return format_err!( "The struct name does not match any defined operation in the query file.\nStruct name: {}\nDefined operations: {}", diff --git a/graphql_client_codegen/src/operations.rs b/graphql_client_codegen/src/operations.rs index d283bc004..3be52ae4d 100644 --- a/graphql_client_codegen/src/operations.rs +++ b/graphql_client_codegen/src/operations.rs @@ -1,7 +1,7 @@ use crate::constants::*; -use crate::query::QueryContext; -use crate::selection::Selection; -use crate::variables::Variable; +// use crate::query::QueryContext; +// use crate::selection::Selection; +// use crate::variables::Variable; use graphql_parser::query::OperationDefinition; use heck::SnakeCase; use proc_macro2::{Span, TokenStream}; @@ -15,96 +15,96 @@ pub enum OperationType { Subscription, } -#[derive(Debug, Clone)] -pub struct Operation<'query> { - pub name: String, - pub operation_type: OperationType, - pub variables: Vec>, - pub selection: Selection<'query>, -} +// #[derive(Debug, Clone)] +// pub struct Operation<'query> { +// pub name: String, +// pub operation_type: OperationType, +// pub variables: Vec>, +// pub selection: Selection<'query>, +// } -impl<'query> Operation<'query> { - pub(crate) fn root_name<'schema>( - &self, - schema: &'schema crate::schema::Schema, - ) -> &'schema str { - match self.operation_type { - OperationType::Query => schema.query_type().name(), - OperationType::Mutation => schema.mutation_type().name(), - OperationType::Subscription => schema.subscription_type().name(), - } - } +// impl<'query> Operation<'query> { +// pub(crate) fn root_name<'schema>( +// &self, +// schema: &'schema crate::schema::Schema, +// ) -> &'schema str { +// match self.operation_type { +// OperationType::Query => schema.query_type().name(), +// OperationType::Mutation => schema.mutation_type().name(), +// OperationType::Subscription => schema.subscription_type().name(), +// } +// } - pub(crate) fn is_subscription(&self) -> bool { - match self.operation_type { - OperationType::Subscription => true, - _ => false, - } - } +// pub(crate) fn is_subscription(&self) -> bool { +// match self.operation_type { +// OperationType::Subscription => true, +// _ => false, +// } +// } - /// Generate the Variables struct and all the necessary supporting code. - pub(crate) fn expand_variables(&self, context: &QueryContext<'_>) -> TokenStream { - todo!() - // let variables = &self.variables; - // let variables_derives = context.variables_derives(); +// /// Generate the Variables struct and all the necessary supporting code. +// pub(crate) fn expand_variables(&self, context: &QueryContext<'_>) -> TokenStream { +// todo!() +// // let variables = &self.variables; +// // let variables_derives = context.variables_derives(); - // if variables.is_empty() { - // return quote! { - // #variables_derives - // pub struct Variables; - // }; - // } +// // if variables.is_empty() { +// // return quote! { +// // #variables_derives +// // pub struct Variables; +// // }; +// // } - // let fields = variables.iter().map(|variable| { - // let ty = variable.ty.to_rust(context, ""); - // let rust_safe_field_name = - // crate::shared::keyword_replace(&variable.name.to_snake_case()); - // let rename = - // crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); - // let name = Ident::new(&rust_safe_field_name, Span::call_site()); +// // let fields = variables.iter().map(|variable| { +// // let ty = variable.ty.to_rust(context, ""); +// // let rust_safe_field_name = +// // crate::shared::keyword_replace(&variable.name.to_snake_case()); +// // let rename = +// // crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); +// // let name = Ident::new(&rust_safe_field_name, Span::call_site()); - // quote!(#rename pub #name: #ty) - // }); +// // quote!(#rename pub #name: #ty) +// // }); - // let default_constructors = variables - // .iter() - // .map(|variable| variable.generate_default_value_constructor(context)); +// // let default_constructors = variables +// // .iter() +// // .map(|variable| variable.generate_default_value_constructor(context)); - // quote! { - // #variables_derives - // pub struct Variables { - // #(#fields,)* - // } +// // quote! { +// // #variables_derives +// // pub struct Variables { +// // #(#fields,)* +// // } - // impl Variables { - // #(#default_constructors)* - // } - // } - } -} +// // impl Variables { +// // #(#default_constructors)* +// // } +// // } +// } +// } -impl<'query> std::convert::From<&'query OperationDefinition> for Operation<'query> { - fn from(definition: &'query OperationDefinition) -> Operation<'query> { - match *definition { - OperationDefinition::Query(ref q) => Operation { - name: q.name.clone().expect("unnamed operation"), - operation_type: OperationType::Query, - variables: q.variable_definitions.iter().map(|v| v.into()).collect(), - selection: (&q.selection_set).into(), - }, - OperationDefinition::Mutation(ref m) => Operation { - name: m.name.clone().expect("unnamed operation"), - operation_type: OperationType::Mutation, - variables: m.variable_definitions.iter().map(|v| v.into()).collect(), - selection: (&m.selection_set).into(), - }, - OperationDefinition::Subscription(ref s) => Operation { - name: s.name.clone().expect("unnamed operation"), - operation_type: OperationType::Subscription, - variables: s.variable_definitions.iter().map(|v| v.into()).collect(), - selection: (&s.selection_set).into(), - }, - OperationDefinition::SelectionSet(_) => panic!(SELECTION_SET_AT_ROOT), - } - } -} +// impl<'query> std::convert::From<&'query OperationDefinition> for Operation<'query> { +// fn from(definition: &'query OperationDefinition) -> Operation<'query> { +// match *definition { +// OperationDefinition::Query(ref q) => Operation { +// name: q.name.clone().expect("unnamed operation"), +// operation_type: OperationType::Query, +// variables: q.variable_definitions.iter().map(|v| v.into()).collect(), +// selection: (&q.selection_set).into(), +// }, +// OperationDefinition::Mutation(ref m) => Operation { +// name: m.name.clone().expect("unnamed operation"), +// operation_type: OperationType::Mutation, +// variables: m.variable_definitions.iter().map(|v| v.into()).collect(), +// selection: (&m.selection_set).into(), +// }, +// OperationDefinition::Subscription(ref s) => Operation { +// name: s.name.clone().expect("unnamed operation"), +// operation_type: OperationType::Subscription, +// variables: s.variable_definitions.iter().map(|v| v.into()).collect(), +// selection: (&s.selection_set).into(), +// }, +// OperationDefinition::SelectionSet(_) => panic!(SELECTION_SET_AT_ROOT), +// } +// } +// } diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index f6be9064c..e6430925f 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -1,7 +1,7 @@ use crate::deprecation::DeprecationStrategy; use crate::fragments::GqlFragment; use crate::normalization::Normalization; -use crate::schema::{StoredFieldId, TypeId, EnumId, InputId, Schema}; +use crate::schema::{EnumId, InputId, ScalarId, Schema, StoredFieldId, TypeId}; use crate::selection::Selection; use failure::*; use proc_macro2::Span; @@ -14,17 +14,10 @@ use syn::Ident; #[derive(Debug, Clone, Copy)] pub(crate) struct FragmentId(usize); -struct Q { - operation: &'static str, - selection: IdSelection, -} - -#[derive(Debug, Clone)] -enum IdSelection { - Field(StoredFieldId), - FragmentSpread(FragmentId), - InlineFragment(TypeId, Vec), -} +// struct Q { +// operation: &'static str, +// selection: IdSelection, +// } /// This holds all the information we need during the code generation phase. pub(crate) struct QueryContext<'query> { @@ -37,6 +30,7 @@ pub(crate) struct QueryContext<'query> { used_enums: Vec, used_input_objects: Vec, used_fragments: Vec, + used_scalars: Vec, } impl<'query, 'schema> QueryContext<'query> { @@ -56,9 +50,14 @@ impl<'query, 'schema> QueryContext<'query> { used_enums: Vec::new(), used_input_objects: Vec::new(), used_fragments: Vec::new(), + used_scalars: Vec::new(), } } + pub(crate) fn resolve_query(query: &graphql_parser::query::Document) -> ResolvedQuery { + todo!("resolve query") + } + /// Mark a fragment as required, so code is actually generated for it. pub(crate) fn require_fragment(&mut self, id: FragmentId) { self.used_fragments.push(id); @@ -83,7 +82,7 @@ impl<'query, 'schema> QueryContext<'query> { ty: &str, selection: &Selection<'_>, prefix: &str, - ) -> Result, failure::Error> { + ) -> anyhow::Result> { unimplemented!() // if self.schema.contains_scalar(ty) { // Ok(None) @@ -108,10 +107,7 @@ impl<'query, 'schema> QueryContext<'query> { // } } - pub(crate) fn ingest_response_derives( - &mut self, - attribute_value: &str, - ) -> Result<(), failure::Error> { + pub(crate) fn ingest_response_derives(&mut self, attribute_value: &str) -> anyhow::Result<()> { if self.response_derives.len() > 1 { return Err(format_err!( "ingest_response_derives should only be called once" @@ -127,10 +123,7 @@ impl<'query, 'schema> QueryContext<'query> { Ok(()) } - pub(crate) fn ingest_variables_derives( - &mut self, - attribute_value: &str, - ) -> Result<(), failure::Error> { + pub(crate) fn ingest_variables_derives(&mut self, attribute_value: &str) -> anyhow::Result<()> { if self.variables_derives.len() > 1 { return Err(format_err!( "ingest_variables_derives should only be called once" diff --git a/graphql_client_codegen/src/rendering.rs b/graphql_client_codegen/src/rendering.rs new file mode 100644 index 000000000..f3a052398 --- /dev/null +++ b/graphql_client_codegen/src/rendering.rs @@ -0,0 +1,11 @@ +//! The rendering path. Goes from a fully resolved query to Rust code. + +use crate::resolution::ResolvedQuery; +use quote::quote; + +pub(crate) fn render( + schema: &crate::schema::Schema, + query: &ResolvedQuery, +) -> anyhow::Result { + Ok(quote!()) +} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs new file mode 100644 index 000000000..a4fee2ee1 --- /dev/null +++ b/graphql_client_codegen/src/resolution.rs @@ -0,0 +1,62 @@ +//! The responsibility of this module is to resolve and validate a query against a given schema. + +use crate::schema::{Schema, StoredFieldId, TypeId}; + +pub(crate) fn resolve( + schema: &crate::schema::Schema, + query: &graphql_parser::query::Document, +) -> anyhow::Result { + let mut resolved_query: ResolvedQuery = Default::default(); + + for definition in &query.definitions { + match definition { + graphql_parser::query::Definition::Fragment(fragment) => (), + graphql_parser::query::Definition::Operation(operation) => (), + } + } + + todo!("resolve") +} + +#[derive(Debug, Clone, Copy)] +struct ResolvedFragmentId(usize); + +#[derive(Debug, Default)] +pub(crate) struct ResolvedQuery { + pub(crate) operations: Vec, + fragments: Vec, +} + +#[derive(Debug)] +struct ResolvedFragment { + name: String, + on: crate::schema::TypeId, + selection: IdSelection, +} + +#[derive(Debug)] +pub(crate) struct ResolvedOperation { + name: String, + operation_type: crate::operations::OperationType, + variables: Vec, +} + +impl ResolvedOperation { + pub(crate) fn name(&self) -> &str { + &self.name + } +} + +#[derive(Debug)] +struct ResolvedVariable { + name: String, + default: Option, + r#type: crate::schema::StoredInputFieldType, +} + +#[derive(Debug, Clone)] +enum IdSelection { + Field(StoredFieldId), + FragmentSpread(ResolvedFragmentId), + InlineFragment(TypeId, Vec), +} diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 71788ad9f..c839ae640 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -1,5 +1,6 @@ mod graphql_parser_conversion; mod json_conversion; +use crate::field_type::GraphqlTypeQualifier; use std::collections::HashMap; // use crate::deprecation::DeprecationStatus; @@ -39,7 +40,7 @@ struct StoredField { #[derive(Debug, PartialEq, Clone)] enum StoredFieldParent { Object(ObjectId), - Interface(InterfaceId) + Interface(InterfaceId), } #[derive(Debug, Clone, Copy, PartialEq)] @@ -88,7 +89,7 @@ struct StoredInterfaceField { #[derive(Debug, Clone, PartialEq)] struct StoredFieldType { id: TypeId, - qualifiers: Vec, + qualifiers: Vec, } #[derive(Debug, Clone, PartialEq)] @@ -159,9 +160,9 @@ struct StoredEnum { } #[derive(Debug, Clone, PartialEq)] -struct StoredInputFieldType { +pub(crate) struct StoredInputFieldType { id: TypeId, - qualifiers: Vec, + qualifiers: Vec, } impl StoredInputFieldType { @@ -171,7 +172,7 @@ impl StoredInputFieldType { pub fn is_indirected(&self) -> bool { self.qualifiers .iter() - .any(|qualifier| qualifier == &crate::field_type::GraphqlTypeQualifier::List) + .any(|qualifier| qualifier == &GraphqlTypeQualifier::List) } } @@ -306,16 +307,16 @@ impl Schema { self.stored_interfaces.get_mut(id.0).unwrap() } - fn get_interface_by_name_mut( - &mut self, - interface_name: &str, - ) -> Option<(InterfaceId, &mut StoredInterface)> { - self.stored_interfaces - .iter_mut() - .enumerate() - .find(|(idx, iface)| iface.name == interface_name) - .map(|(idx, iface)| (InterfaceId(idx), iface)) - } + // fn get_interface_by_name_mut( + // &mut self, + // interface_name: &str, + // ) -> Option<(InterfaceId, &mut StoredInterface)> { + // self.stored_interfaces + // .iter_mut() + // .enumerate() + // .find(|(idx, iface)| iface.name == interface_name) + // .map(|(idx, iface)| (InterfaceId(idx), iface)) + // } fn push_object(&mut self, object: StoredObject) -> ObjectId { let id = ObjectId(self.stored_objects.len()); diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index e9352b577..60abcfd45 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -146,7 +146,6 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy field_ids.push(schema.push_field(field)); } - // Ingest the object itself let object = super::StoredObject { name: std::mem::replace(&mut obj.name, String::new()), @@ -225,7 +224,7 @@ fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema: .as_interface_id() .unwrap(); - let mut field_ids = Vec::with_capacity(interface.fields.len()); + let mut field_ids = Vec::with_capacity(interface.fields.len()); for field in interface.fields.iter_mut() { let field = super::StoredField { diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 4103ff0cc..6faa9e52b 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -117,28 +117,29 @@ pub(crate) fn field_impls_for_selection( context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, -) -> Result, failure::Error> { - (&selection) - .into_iter() - .map(|selected| { - if let SelectionItem::Field(selected) = selected { - let name = &selected.name; - let alias = selected.alias.as_ref().unwrap_or(name); +) -> anyhow::Result> { + todo!("field_impls_for_selection") + // (&selection) + // .into_iter() + // .map(|selected| { + // if let SelectionItem::Field(selected) = selected { + // let name = &selected.name; + // let alias = selected.alias.as_ref().unwrap_or(name); - let ty = fields - .iter() - .find(|f| &f.name == name) - .ok_or_else(|| format_err!("could not find field `{}`", name))? - .type_ - .inner_name_str(); - let prefix = format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()); - context.maybe_expand_field(&ty, &selected.fields, &prefix) - } else { - Ok(None) - } - }) - .filter_map(|i| i.transpose()) - .collect() + // let ty = fields + // .iter() + // .find(|f| &f.name == name) + // .ok_or_else(|| format_err!("could not find field `{}`", name))? + // .type_ + // .inner_name_str(); + // let prefix = format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()); + // context.maybe_expand_field(&ty, &selected.fields, &prefix) + // } else { + // Ok(None) + // } + // }) + // .filter_map(|i| i.transpose()) + // .collect() } pub(crate) fn response_fields_for_selection( @@ -147,7 +148,7 @@ pub(crate) fn response_fields_for_selection( context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, -) -> Result, failure::Error> { +) -> anyhow::Result> { todo!("response fields for selection") // (&selection) // .into_iter() diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index 3e9e50d79..b8e48b904 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -1,6 +1,6 @@ use crate::query::QueryContext; use crate::selection::Selection; -use failure::*; +use anyhow::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::cell::Cell; @@ -9,7 +9,7 @@ use std::collections::BTreeSet; pub(crate) fn union_type_to_rust( ctx: &mut QueryContext<'_>, union: (), -) -> Result { +) -> Result { todo!() } @@ -36,7 +36,7 @@ enum UnionError { } type UnionVariantResult<'selection> = - Result<(Vec, Vec, Vec<&'selection str>), failure::Error>; + Result<(Vec, Vec, Vec<&'selection str>), anyhow::Error>; /// Returns a triple. /// @@ -106,7 +106,7 @@ impl<'schema> GqlUnion<'schema> { query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result { + ) -> Result { let typename_field = selection.extract_typename(query_context); if typename_field.is_none() { diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index 2d3bdae01..89a62173e 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -23,7 +23,8 @@ fn graphql_query_derive_inner( input: proc_macro::TokenStream, ) -> Result { let input = TokenStream::from(input); - let ast = syn::parse2(input).context("Derive input parsing.")?; + let ast = syn::parse2(input).expect("derive input parsing"); + // .context("Derive input parsing.")?; let (query_path, schema_path) = build_query_and_schema_path(&ast)?; let options = build_graphql_client_derive_options(&ast, query_path.to_path_buf())?; Ok( From 6db9d192b55b6f3abf6188c772ca29d3d43cddb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 29 Jan 2020 00:25:14 +0100 Subject: [PATCH 04/93] Make progress on query resolution --- examples/github/Cargo.toml | 2 +- examples/hasura/Cargo.toml | 2 +- graphql_client/Cargo.toml | 1 + graphql_client_cli/src/introspect_schema.rs | 3 +- graphql_client_codegen/Cargo.toml | 1 + graphql_client_codegen/src/codegen.rs | 191 ++++++++-- graphql_client_codegen/src/constants.rs | 14 +- graphql_client_codegen/src/enums.rs | 7 - graphql_client_codegen/src/fragments.rs | 7 +- .../src/generated_module.rs | 13 +- graphql_client_codegen/src/inputs.rs | 4 +- graphql_client_codegen/src/interfaces.rs | 6 +- graphql_client_codegen/src/lib.rs | 5 +- graphql_client_codegen/src/objects.rs | 6 +- graphql_client_codegen/src/query.rs | 6 +- graphql_client_codegen/src/rendering.rs | 11 - graphql_client_codegen/src/resolution.rs | 352 +++++++++++++++++- graphql_client_codegen/src/scalars.rs | 23 -- graphql_client_codegen/src/schema.rs | 333 +++++++++++++---- .../src/schema/graphql_parser_conversion.rs | 33 +- graphql_client_codegen/src/selection.rs | 16 +- graphql_client_codegen/src/shared.rs | 286 +++++++------- graphql_query_derive/src/attributes.rs | 8 +- 23 files changed, 948 insertions(+), 382 deletions(-) delete mode 100644 graphql_client_codegen/src/rendering.rs delete mode 100644 graphql_client_codegen/src/scalars.rs diff --git a/examples/github/Cargo.toml b/examples/github/Cargo.toml index ca95d2604..8f19481ba 100644 --- a/examples/github/Cargo.toml +++ b/examples/github/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Tom Houlé "] edition = "2018" [dev-dependencies] -anyhow = "*" +anyhow = "1.0" graphql_client = { path = "../../graphql_client" } serde = "^1.0" reqwest = "^0.9" diff --git a/examples/hasura/Cargo.toml b/examples/hasura/Cargo.toml index fd88c28b1..7c4bf245e 100644 --- a/examples/hasura/Cargo.toml +++ b/examples/hasura/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Mark Catley "] edition = "2018" [dev-dependencies] -anyhow = "*" +anyhow = "1.0" graphql_client = { path = "../../graphql_client" } serde = "1.0" serde_derive = "1.0" diff --git a/graphql_client/Cargo.toml b/graphql_client/Cargo.toml index 282c6db63..d42d468b9 100644 --- a/graphql_client/Cargo.toml +++ b/graphql_client/Cargo.toml @@ -16,6 +16,7 @@ thiserror = { version = "1.0", optional = true } graphql_query_derive = { path = "../graphql_query_derive", version = "0.9.0" } serde_json = "1.0" serde = { version = "^1.0.78", features = ["derive"] } +thiserror = "1.0.10" [dependencies.futures] version = "^0.1" diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index 4650e05f1..0d2f7d8fb 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -8,7 +8,8 @@ use std::str::FromStr; #[graphql( schema_path = "src/graphql/introspection_schema.graphql", query_path = "src/graphql/introspection_query.graphql", - response_derives = "Serialize" + response_derives = "Serialize,Deserialize", + variable_derives = "Serialize" )] #[allow(dead_code)] struct IntrospectionQuery; diff --git a/graphql_client_codegen/Cargo.toml b/graphql_client_codegen/Cargo.toml index d41aacb76..1906e1c1b 100644 --- a/graphql_client_codegen/Cargo.toml +++ b/graphql_client_codegen/Cargo.toml @@ -18,3 +18,4 @@ quote = "^1.0" serde_json = "1.0" serde = { version = "^1.0", features = ["derive"] } syn = "^1.0" +thiserror = "1.0.10" diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index e7aad41db..2a586b730 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,34 +1,46 @@ -// use crate::fragments::GqlFragment; -use crate::normalization::Normalization; -// use crate::operations::Operation; -// use crate::query::QueryContext; -use crate::schema; -// use crate::selection::Selection; -use crate::resolution::{ResolvedOperation, ResolvedQuery}; -use graphql_parser::query; -use proc_macro2::TokenStream; -use quote::*; +use crate::{normalization::Normalization, resolution::*, GraphQLClientCodegenOptions}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. pub(crate) fn select_operation<'a>( query: &'a ResolvedQuery, struct_name: &str, norm: Normalization, -) -> Option<&'a ResolvedOperation> { +) -> Option { query .operations .iter() - .find(|op| norm.operation(op.name()) == struct_name) + .position(|op| norm.operation(op.name()) == struct_name) } /// The main code generation function. pub(crate) fn response_for_query( - schema: &schema::Schema, - query: &crate::resolution::ResolvedQuery, - operation: &str, - options: &crate::GraphQLClientCodegenOptions, + operation: Operation<'_>, + options: &GraphQLClientCodegenOptions, ) -> anyhow::Result { - todo!() + let all_used_types = operation.all_used_types(); + let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); + let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); + let fragment_definitions: Vec<&'static str> = Vec::new(); + let definitions: Vec<&'static str> = Vec::new(); + let input_object_definitions: Vec<&'static str> = Vec::new(); + let variable_derives = options + .variables_derives() + .unwrap_or("Serialize") + .split(","); + let variable_derives = render_derives(variable_derives); + + let variables_struct = quote!( + #variable_derives + pub struct Variables; + ); + let response_derives = options + .response_derives() + .map(|derives| derives.split(",")) + .map(render_derives); + let response_data_fields: Vec<&'static str> = Vec::new(); + // let mut context = QueryContext::new( // schema, // options.deprecation_strategy(), @@ -123,35 +135,138 @@ pub(crate) fn response_for_query( // let response_derives = context.response_derives(); - // Ok(quote! { - // use serde::{Serialize, Deserialize}; + let q = quote! { + use serde::{Serialize, Deserialize}; - // #[allow(dead_code)] - // type Boolean = bool; - // #[allow(dead_code)] - // type Float = f64; - // #[allow(dead_code)] - // type Int = i64; - // #[allow(dead_code)] - // type ID = String; + #[allow(dead_code)] + type Boolean = bool; + #[allow(dead_code)] + type Float = f64; + #[allow(dead_code)] + type Int = i64; + #[allow(dead_code)] + type ID = String; - // #(#scalar_definitions)* + #(#scalar_definitions)* - // #(#input_object_definitions)* + #(#enum_definitions)* - // #(#enum_definitions)* + #(#fragment_definitions)* - // #(#fragment_definitions)* + #(#input_object_definitions)* - // #(#definitions)* + #(#definitions)* - // #variables_struct + #response_derives + pub struct ResponseData { + #(#response_data_fields,)* + } - // #response_derives + #variables_struct + }; - // pub struct ResponseData { - // #(#response_data_fields,)* - // } + Ok(q) +} + +fn generate_scalar_definitions<'a, 'schema: 'a>( + operation: Operation<'schema>, + all_used_types: &'a crate::resolution::UsedTypes, +) -> impl Iterator + 'a { + all_used_types.scalars(operation.schema()).map(|scalar| { + let ident = syn::Ident::new(scalar.name(), proc_macro2::Span::call_site()); + quote!(type #ident = super::#ident;) + }) +} + +/** + * About rust keyword escaping: variant_names and constructors must be escaped, + * variant_str not. + * Example schema: enum AnEnum { where \n self } + * Generated "variant_names" enum: pub enum AnEnum { where_, self_, Other(String), } + * Generated serialize line: "AnEnum::where_ => "where"," + */ +fn generate_enum_definitions<'a, 'schema: 'a>( + operation: Operation<'schema>, + all_used_types: &'a crate::resolution::UsedTypes, + options: &'a GraphQLClientCodegenOptions, +) -> impl Iterator + 'a { + let derives = options + .response_derives() + .map(|derives| { + derives + .split(',') + .filter(|d| *d != "Serialize" && *d != "Deserialize") + }) + .map(render_derives); + let normalization = options.normalization(); + + all_used_types.enums(operation.schema()).map(move |r#enum| { + let ident = syn::Ident::new(r#enum.name(), proc_macro2::Span::call_site()); + + let variant_names: Vec = r#enum + .variants() + .iter() + .map(|v| { + let name = normalization.enum_variant(crate::shared::keyword_replace(&v)); + let name = Ident::new(&name, Span::call_site()); + + // let description = &v.description; + // let description = description.as_ref().map(|d| quote!(#[doc = #d])); + + // quote!(#description #name) + quote!(#name) + }) + .collect(); + let variant_names = &variant_names; + let name_ident = normalization.enum_name(r#enum.name()); + let name_ident = Ident::new(&name_ident, Span::call_site()); + let constructors: Vec<_> = r#enum + .variants() + .iter() + .map(|v| { + let name = normalization.enum_variant(crate::shared::keyword_replace(v)); + let v = Ident::new(&name, Span::call_site()); + + quote!(#name_ident::#v) + }) + .collect(); + let constructors = &constructors; + let variant_str: Vec<&str> = r#enum.variants().iter().map(|s| s.as_str()).collect(); + let variant_str = &variant_str; + + let name = name_ident; + + quote! { + #derives + pub enum #name { + #(#variant_names,)* + Other(String), + } + + impl ::serde::Serialize for #name { + fn serialize(&self, ser: S) -> Result { + ser.serialize_str(match *self { + #(#constructors => #variant_str,)* + #name::Other(ref s) => &s, + }) + } + } + + impl<'de> ::serde::Deserialize<'de> for #name { + fn deserialize>(deserializer: D) -> Result { + let s = ::deserialize(deserializer)?; + + match s.as_str() { + #(#variant_str => Ok(#constructors),)* + _ => Ok(#name::Other(s)), + } + } + } + }}) +} + +fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToTokens { + let idents = derives.map(|s| Ident::new(s, Span::call_site())); - // }) + quote!(#[derive(#(#idents),*)]) } diff --git a/graphql_client_codegen/src/constants.rs b/graphql_client_codegen/src/constants.rs index b2b97e8a4..095ecd64c 100644 --- a/graphql_client_codegen/src/constants.rs +++ b/graphql_client_codegen/src/constants.rs @@ -4,14 +4,14 @@ use crate::deprecation::DeprecationStatus; pub(crate) const TYPENAME_FIELD: &str = "__typename"; -pub(crate) fn string_type() -> &'static str { - "String" -} +// pub(crate) fn string_type() -> &'static str { +// "String" +// } -#[cfg(test)] -pub(crate) fn float_type() -> &'static str { - "Float" -} +// #[cfg(test)] +// pub(crate) fn float_type() -> &'static str { +// "Float" +// } // pub(crate) fn typename_field() -> GqlObjectField<'static> { // GqlObjectField { diff --git a/graphql_client_codegen/src/enums.rs b/graphql_client_codegen/src/enums.rs index 76becea6e..0f0e939da 100644 --- a/graphql_client_codegen/src/enums.rs +++ b/graphql_client_codegen/src/enums.rs @@ -19,13 +19,6 @@ pub struct GqlEnum<'schema> { } impl<'schema> GqlEnum<'schema> { - /** - * About rust keyword escaping: variant_names and constructors must be escaped, - * variant_str not. - * Example schema: enum AnEnum { where \n self } - * Generated "variant_names" enum: pub enum AnEnum { where_, self_, Other(String), } - * Generated serialize line: "AnEnum::where_ => "where"," - */ pub(crate) fn to_rust(&self, query_context: &crate::query::QueryContext<'_>) -> TokenStream { let derives = query_context.response_enum_derives(); let norm = query_context.normalization; diff --git a/graphql_client_codegen/src/fragments.rs b/graphql_client_codegen/src/fragments.rs index d60e4fdcd..202b04f60 100644 --- a/graphql_client_codegen/src/fragments.rs +++ b/graphql_client_codegen/src/fragments.rs @@ -36,11 +36,8 @@ pub(crate) struct GqlFragment<'query> { impl<'query> GqlFragment<'query> { /// Generate all the Rust code required by the fragment's object selection. - pub(crate) fn to_rust( - &self, - context: &QueryContext<'_>, - ) -> Result { - todo!() + pub(crate) fn to_rust(&self, context: &QueryContext<'_>) -> Result { + todo!("fragment to rust") // match self.on { // FragmentTarget::Object(obj) => { // obj.response_for_selection(context, &self.selection, &self.name) diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 4087d9407..2273dc1b9 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,4 +1,4 @@ -use crate::codegen_options::*; +use crate::{codegen_options::*, resolution::Operation}; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -16,10 +16,17 @@ pub(crate) struct GeneratedModule<'a> { impl<'a> GeneratedModule<'a> { /// Generate the items for the variables and the response that will go inside the module. fn build_impls(&self) -> anyhow::Result { - Ok(crate::codegen::response_for_query( - &self.schema, + let root = crate::codegen::select_operation( &self.resolved_query, &self.operation, + self.options.normalization(), + ) + .expect("TODO: handle operation not found"); + + let operation = Operation::new(root, self.schema, self.resolved_query); + + Ok(crate::codegen::response_for_query( + operation, &self.options, )?) } diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index 71ea32686..a09c5f161 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; pub(crate) fn input_to_rust( ctx: &mut QueryContext<'_>, input: crate::schema::InputRef<'_>, -) -> Result { +) -> Result { todo!() } @@ -42,7 +42,7 @@ pub struct InputRef<'a> { // pub(crate) fn to_rust( // &self, // context: &QueryContext<'_>, -// ) -> Result { +// ) -> Result { // let norm = context.normalization; // let mut fields: Vec<&GqlObjectField<'_>> = self.fields.values().collect(); // fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); diff --git a/graphql_client_codegen/src/interfaces.rs b/graphql_client_codegen/src/interfaces.rs index 5df0ae870..8b85a2607 100644 --- a/graphql_client_codegen/src/interfaces.rs +++ b/graphql_client_codegen/src/interfaces.rs @@ -106,7 +106,7 @@ impl<'schema> GqlInterface<'schema> { context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result, failure::Error> { + ) -> Result, anyhow::Error> { crate::shared::field_impls_for_selection( &self.fields, context, @@ -121,7 +121,7 @@ impl<'schema> GqlInterface<'schema> { context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result, failure::Error> { + ) -> Result, anyhow::Error> { response_fields_for_selection( &self.name, &self.fields, @@ -137,7 +137,7 @@ impl<'schema> GqlInterface<'schema> { query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result { + ) -> Result { let name = Ident::new(&prefix, Span::call_site()); let derives = query_context.response_derives(); diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 91b3e2ef8..a072e7d10 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -30,11 +30,9 @@ mod generated_module; pub mod normalization; // mod objects; mod operations; -mod rendering; mod resolution; -mod scalars; // mod selection; -// mod shared; +mod shared; // mod unions; // mod variables; @@ -114,6 +112,7 @@ pub fn generate_module_token_stream( .as_ref() .and_then(|operation_name| { codegen::select_operation(&query, &operation_name, options.normalization()) + .and_then(|idx| query.operations.get(idx)) }) .map(|op| vec![op]); diff --git a/graphql_client_codegen/src/objects.rs b/graphql_client_codegen/src/objects.rs index 0cca2796b..6c1112c45 100644 --- a/graphql_client_codegen/src/objects.rs +++ b/graphql_client_codegen/src/objects.rs @@ -116,7 +116,7 @@ impl<'schema> GqlObject<'schema> { query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result { + ) -> Result { unimplemented!() // let derives = query_context.response_derives(); // let name = Ident::new(prefix, Span::call_site()); @@ -139,7 +139,7 @@ impl<'schema> GqlObject<'schema> { query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result, failure::Error> { + ) -> Result, anyhow::Error> { field_impls_for_selection(&self.fields, query_context, selection, prefix) } @@ -148,7 +148,7 @@ impl<'schema> GqlObject<'schema> { query_context: &QueryContext<'_>, selection: &Selection<'_>, prefix: &str, - ) -> Result, failure::Error> { + ) -> Result, anyhow::Error> { response_fields_for_selection(&self.name, &self.fields, query_context, selection, prefix) } } diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index e6430925f..5c6aef472 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -54,9 +54,9 @@ impl<'query, 'schema> QueryContext<'query> { } } - pub(crate) fn resolve_query(query: &graphql_parser::query::Document) -> ResolvedQuery { - todo!("resolve query") - } + // pub(crate) fn resolve_query(query: &graphql_parser::query::Document) -> ResolvedQuery { + // todo!("resolve query") + // } /// Mark a fragment as required, so code is actually generated for it. pub(crate) fn require_fragment(&mut self, id: FragmentId) { diff --git a/graphql_client_codegen/src/rendering.rs b/graphql_client_codegen/src/rendering.rs deleted file mode 100644 index f3a052398..000000000 --- a/graphql_client_codegen/src/rendering.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! The rendering path. Goes from a fully resolved query to Rust code. - -use crate::resolution::ResolvedQuery; -use quote::quote; - -pub(crate) fn render( - schema: &crate::schema::Schema, - query: &ResolvedQuery, -) -> anyhow::Result { - Ok(quote!()) -} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index a4fee2ee1..323aaf052 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,24 +1,170 @@ //! The responsibility of this module is to resolve and validate a query against a given schema. -use crate::schema::{Schema, StoredFieldId, TypeId}; +use crate::schema::resolve_field_type; +use crate::schema::EnumRef; +use crate::schema::FieldRef; +use crate::schema::ScalarRef; +use crate::schema::StoredFieldType; +use crate::schema::TypeRef; +use crate::schema::{ObjectRef, Schema, StoredFieldId, TypeId}; +use std::collections::HashSet; pub(crate) fn resolve( - schema: &crate::schema::Schema, + schema: &Schema, query: &graphql_parser::query::Document, ) -> anyhow::Result { let mut resolved_query: ResolvedQuery = Default::default(); for definition in &query.definitions { match definition { - graphql_parser::query::Definition::Fragment(fragment) => (), - graphql_parser::query::Definition::Operation(operation) => (), + graphql_parser::query::Definition::Fragment(fragment) => { + resolve_fragment(&mut resolved_query, schema, fragment)? + } + graphql_parser::query::Definition::Operation(operation) => { + resolve_operation(&mut resolved_query, schema, operation)? + } } } - todo!("resolve") + Ok(resolved_query) } -#[derive(Debug, Clone, Copy)] +fn resolve_fragment( + query: &mut ResolvedQuery, + schema: &Schema, + fragment: &graphql_parser::query::FragmentDefinition, +) -> anyhow::Result<()> { + let graphql_parser::query::TypeCondition::On(on) = &fragment.type_condition; + let on = schema.find_type(on).expect("TODO: proper error message"); + let resolved_fragment = ResolvedFragment { + name: fragment.name.clone(), + on, + selection: resolve_selection(schema, on, &fragment.selection_set)?, + }; + + query.fragments.push(resolved_fragment); + + Ok(()) +} + +fn resolve_object_selection( + object: ObjectRef<'_>, + selection_set: &graphql_parser::query::SelectionSet, +) -> anyhow::Result> { + let id_selection: Vec = selection_set + .items + .iter() + .map(|item| -> anyhow::Result<_> { + match item { + graphql_parser::query::Selection::Field(field) => { + let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { + anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) + })?; + Ok(IdSelection::Field( + field_ref.id(), + resolve_selection( + object.schema(), + field_ref.type_id(), + &field.selection_set, + )?, + )) + } + graphql_parser::query::Selection::InlineFragment(inline) => { + resolve_inline_fragment(object.schema(), inline) + } + graphql_parser::query::Selection::FragmentSpread(fragment_spread) => Ok( + IdSelection::FragmentSpread(fragment_spread.fragment_name.clone()), + ), + } + }) + .collect::>()?; + + Ok(id_selection) +} + +fn resolve_selection( + schema: &Schema, + on: TypeId, + selection_set: &graphql_parser::query::SelectionSet, +) -> anyhow::Result> { + match on { + TypeId::Object(oid) => { + let object = schema.object(oid); + resolve_object_selection(object, selection_set) + } + TypeId::Interface(interface_id) => { + let interface = schema.interface(interface_id); + todo!("interface thing") + } + other => { + anyhow::ensure!( + selection_set.items.is_empty(), + "Selection set on non-object, non-interface type. ({:?})", + other + ); + Ok(Vec::new()) + } + } +} + +fn resolve_inline_fragment( + schema: &Schema, + inline_fragment: &graphql_parser::query::InlineFragment, +) -> anyhow::Result { + let graphql_parser::query::TypeCondition::On(on) = inline_fragment + .type_condition + .as_ref() + .expect("missing type condition"); + let type_id = schema + .find_type(on) + .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; + Ok(IdSelection::InlineFragment( + type_id, + resolve_selection(schema, type_id, &inline_fragment.selection_set)?, + )) +} + +fn resolve_operation( + query: &mut ResolvedQuery, + schema: &Schema, + operation: &graphql_parser::query::OperationDefinition, +) -> anyhow::Result<()> { + match operation { + graphql_parser::query::OperationDefinition::Mutation(m) => { + let on = schema.mutation_type(); + let resolved_operation: ResolvedOperation = ResolvedOperation { + name: m.name.as_ref().expect("mutation without name").to_owned(), + operation_type: crate::operations::OperationType::Mutation, + variables: resolve_variables(&m.variable_definitions, schema)?, + selection: resolve_object_selection(on, &m.selection_set)?, + }; + + query.operations.push(resolved_operation); + } + graphql_parser::query::OperationDefinition::Query(q) => { + let on = schema.query_type(); + + let resolved_operation: ResolvedOperation = ResolvedOperation { + name: q.name.as_ref().expect("query without name").to_owned(), + operation_type: crate::operations::OperationType::Query, + variables: resolve_variables(&q.variable_definitions, schema)?, + selection: resolve_object_selection(on, &q.selection_set)?, + }; + + query.operations.push(resolved_operation); + } + graphql_parser::query::OperationDefinition::Subscription(_) => { + todo!("resolve subscription") + } + graphql_parser::query::OperationDefinition::SelectionSet(_) => { + unreachable!("unnamed queries are not supported") + } + } + + Ok(()) +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] struct ResolvedFragmentId(usize); #[derive(Debug, Default)] @@ -31,7 +177,61 @@ pub(crate) struct ResolvedQuery { struct ResolvedFragment { name: String, on: crate::schema::TypeId, - selection: IdSelection, + selection: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Operation<'a> { + operation_id: usize, + schema: &'a Schema, + query: &'a ResolvedQuery, +} + +impl<'a> Operation<'a> { + pub(crate) fn new( + operation_id: usize, + schema: &'a Schema, + query: &'a ResolvedQuery, + ) -> Operation<'a> { + Operation { + operation_id, + schema, + query, + } + } + + fn get(&self) -> &'a ResolvedOperation { + self.query.operations.get(self.operation_id).unwrap() + } + + fn name(&self) -> &'a str { + self.get().name() + } + + fn selection(&self) -> impl Iterator> { + self.get() + .selection + .iter() + .map(move |id_selection| id_selection.upgrade(&self.schema, &self.query)) + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.schema + } + + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.query + } + + pub(crate) fn all_used_types(&self) -> UsedTypes { + let mut all_used_types = UsedTypes::default(); + + for selection in self.selection() { + selection.collect_used_types(&mut all_used_types); + } + + all_used_types + } } #[derive(Debug)] @@ -39,6 +239,7 @@ pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, variables: Vec, + selection: Vec, } impl ResolvedOperation { @@ -51,12 +252,143 @@ impl ResolvedOperation { struct ResolvedVariable { name: String, default: Option, - r#type: crate::schema::StoredInputFieldType, + r#type: StoredFieldType, } #[derive(Debug, Clone)] enum IdSelection { - Field(StoredFieldId), - FragmentSpread(ResolvedFragmentId), + Field(StoredFieldId, Vec), + FragmentSpread(String), InlineFragment(TypeId, Vec), } + +impl IdSelection { + fn upgrade<'a>(&self, schema: &'a Schema, query: &'a ResolvedQuery) -> Selection<'a> { + match self { + IdSelection::Field(id, selection) => Selection::Field( + schema.field(*id), + selection + .iter() + .map(|selection| selection.upgrade(schema, query)) + .collect(), + ), + IdSelection::FragmentSpread(name) => Selection::FragmentSpread(Fragment { + fragment_id: ResolvedFragmentId( + query + .fragments + .iter() + .position(|frag| frag.name.as_str() == name.as_str()) + .unwrap(), + ), + query, + schema, + }), + IdSelection::InlineFragment(typeid, selection) => Selection::InlineFragment( + typeid.upgrade(schema), + selection + .iter() + .map(|sel| sel.upgrade(schema, query)) + .collect(), + ), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Selection<'a> { + Field(FieldRef<'a>, Vec>), + FragmentSpread(Fragment<'a>), + InlineFragment(TypeRef<'a>, Vec>), +} + +impl Selection<'_> { + fn collect_used_types(&self, used_types: &mut UsedTypes) { + match self { + Selection::Field(field, selection) => { + used_types.types.insert(field.type_id()); + + selection + .iter() + .for_each(|selection| selection.collect_used_types(used_types)); + } + Selection::FragmentSpread(fragment) => { + used_types.fragments.insert(fragment.fragment_id); + fragment + .selection() + .for_each(|selection| selection.collect_used_types(used_types)) + } + Selection::InlineFragment(on, selection) => { + used_types.types.insert(on.type_id()); + + selection + .iter() + .for_each(|selection| selection.collect_used_types(used_types)) + } + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Fragment<'a> { + fragment_id: ResolvedFragmentId, + query: &'a ResolvedQuery, + schema: &'a Schema, +} + +impl Fragment<'_> { + fn get(&self) -> &ResolvedFragment { + self.query.fragments.get(self.fragment_id.0).unwrap() + } + + pub(crate) fn selection(&self) -> impl Iterator> { + self.get() + .selection + .iter() + .map(move |selection| selection.upgrade(&self.schema, &self.query)) + } +} + +#[derive(Debug, Default)] +pub(crate) struct UsedTypes { + types: HashSet, + fragments: HashSet, +} + +impl UsedTypes { + pub(crate) fn scalars<'s, 'a: 's>( + &'s self, + schema: &'a Schema, + ) -> impl Iterator> + 's { + self.types + .iter() + .filter_map(TypeId::as_scalar_id) + .map(move |scalar_id| schema.scalar(scalar_id)) + .filter(|scalar| !crate::schema::DEFAULT_SCALARS.contains(&scalar.name())) + } + + pub(crate) fn enums<'a, 'schema: 'a>( + &'a self, + schema: &'schema Schema, + ) -> impl Iterator> + 'a { + self.types + .iter() + .filter_map(TypeId::as_enum_id) + .map(move |enum_id| schema.r#enum(enum_id)) + } +} + +fn resolve_variables( + variables: &[graphql_parser::query::VariableDefinition], + schema: &Schema, +) -> Result, anyhow::Error> { + variables + .iter() + .map(|var| { + Ok(ResolvedVariable { + name: var.name.clone(), + default: var.default_value.clone(), + r#type: resolve_field_type(schema, &var.var_type), + }) + }) + .collect() +} diff --git a/graphql_client_codegen/src/scalars.rs b/graphql_client_codegen/src/scalars.rs deleted file mode 100644 index 85979ea94..000000000 --- a/graphql_client_codegen/src/scalars.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::normalization::Normalization; -use quote::quote; -use std::cell::Cell; - -#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] -pub struct Scalar<'schema> { - pub name: &'schema str, - pub description: Option<&'schema str>, - pub is_required: Cell, -} - -impl<'schema> Scalar<'schema> { - // TODO: do something smarter here - pub fn to_rust(&self, norm: Normalization) -> proc_macro2::TokenStream { - use proc_macro2::{Ident, Span}; - - let name = norm.scalar_name(self.name); - let ident = Ident::new(&name, Span::call_site()); - let description = &self.description.map(|d| quote!(#[doc = #d])); - - quote!(#description type #ident = super::#ident;) - } -} diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index c839ae640..dc64b006b 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -43,28 +43,28 @@ enum StoredFieldParent { Interface(InterfaceId), } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct ObjectId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct ObjectFieldId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct InterfaceFieldId(usize); +// #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] +// pub(crate) struct InterfaceFieldId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct InterfaceId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct ScalarId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct UnionId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct EnumId(usize); -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct InputId(usize); #[derive(Debug, Clone, Copy, PartialEq)] @@ -87,7 +87,7 @@ struct StoredInterfaceField { } #[derive(Debug, Clone, PartialEq)] -struct StoredFieldType { +pub(crate) struct StoredFieldType { id: TypeId, qualifiers: Vec, } @@ -103,7 +103,7 @@ struct StoredScalar { name: String, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) enum TypeId { Object(ObjectId), Scalar(ScalarId), @@ -113,6 +113,65 @@ pub(crate) enum TypeId { Input(InputId), } +#[derive(Debug, Clone, Copy)] +pub(crate) enum TypeRef<'a> { + Object(ObjectRef<'a>), + Scalar(ScalarRef<'a>), + Interface(InterfaceRef<'a>), + Union(UnionRef<'a>), + Enum(EnumRef<'a>), + Input(InputRef<'a>), +} + +impl TypeRef<'_> { + pub(crate) fn type_id(&self) -> TypeId { + match self { + _ => todo!("TypeRef::type_id"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct ScalarRef<'a> { + scalar_id: ScalarId, + schema: &'a Schema, +} + +impl<'a> ScalarRef<'a> { + fn get(&self) -> &StoredScalar { + self.schema.get_scalar(self.scalar_id) + } + + pub(crate) fn name(&self) -> &str { + &self.get().name + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct UnionRef<'a> { + union_id: UnionId, + schema: &'a Schema, +} +#[derive(Debug, Clone, Copy)] +pub(crate) struct EnumRef<'a> { + enum_id: EnumId, + schema: &'a Schema, +} + +impl<'a> EnumRef<'a> { + fn get(&self) -> &'a StoredEnum { + self.schema.get_enum(self.enum_id) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn variants(&self) -> &'a [String] { + &self.get().variants + } +} + impl TypeId { fn scalar(id: usize) -> Self { TypeId::Scalar(ScalarId(id)) @@ -151,6 +210,49 @@ impl TypeId { _ => None, } } + + pub(crate) fn as_scalar_id(&self) -> Option { + match self { + TypeId::Scalar(id) => Some(*id), + _ => None, + } + } + + pub(crate) fn as_enum_id(&self) -> Option { + match self { + TypeId::Enum(id) => Some(*id), + _ => None, + } + } + + pub(crate) fn upgrade(self, schema: &Schema) -> TypeRef<'_> { + match self { + TypeId::Enum(id) => TypeRef::Enum(EnumRef { + enum_id: id, + schema, + }), + TypeId::Interface(id) => TypeRef::Interface(InterfaceRef { + interface_id: id, + schema, + }), + TypeId::Object(id) => TypeRef::Object(ObjectRef { + object_id: id, + schema, + }), + TypeId::Scalar(id) => TypeRef::Scalar(ScalarRef { + scalar_id: id, + schema, + }), + TypeId::Union(id) => TypeRef::Union(UnionRef { + union_id: id, + schema, + }), + TypeId::Input(id) => TypeRef::Input(InputRef { + input_id: id, + schema, + }), + } + } } #[derive(Debug, Clone, PartialEq)] @@ -239,7 +341,7 @@ impl Schema { // pub(crate) fn ingest_interface_implementations( // &mut self, // impls: BTreeMap<&'schema str, Vec<&'schema str>>, - // ) -> Result<(), failure::Error> { + // ) -> Result<(), anyhow::Error> { // impls // .into_iter() // .map(|(iface_name, implementors)| { @@ -420,6 +522,10 @@ impl Schema { } } + fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface { + self.stored_interfaces.get(interface_id.0).unwrap() + } + fn get_stored_input(&self, input_id: InputId) -> &StoredInputType { self.stored_inputs.get(input_id.0).unwrap() } @@ -428,10 +534,61 @@ impl Schema { self.stored_objects.get(object_id.0).unwrap() } + fn get_field(&self, field_id: StoredFieldId) -> &StoredField { + self.stored_fields.get(field_id.0).unwrap() + } + + fn get_enum(&self, enum_id: EnumId) -> &StoredEnum { + self.stored_enums.get(enum_id.0).unwrap() + } + + fn get_scalar(&self, scalar_id: ScalarId) -> &StoredScalar { + self.stored_scalars.get(scalar_id.0).unwrap() + } + + pub(crate) fn object(&self, id: ObjectId) -> ObjectRef<'_> { + ObjectRef { + object_id: id, + schema: self, + } + } + + pub(crate) fn interface(&self, interface_id: InterfaceId) -> InterfaceRef<'_> { + InterfaceRef { + interface_id, + schema: self, + } + } + + pub(crate) fn field(&self, field_id: StoredFieldId) -> FieldRef<'_> { + FieldRef { + field_id, + schema: self, + } + } + + pub(crate) fn scalar(&self, scalar_id: ScalarId) -> ScalarRef<'_> { + ScalarRef { + scalar_id, + schema: self, + } + } + + pub(crate) fn r#enum(&self, enum_id: EnumId) -> EnumRef<'_> { + EnumRef { + enum_id, + schema: self, + } + } + fn find_interface(&self, interface_name: &str) -> InterfaceId { self.find_type_id(interface_name).as_interface_id().unwrap() } + pub(crate) fn find_type(&self, type_name: &str) -> Option { + self.names.get(type_name).map(|id| *id) + } + fn find_type_id(&self, type_name: &str) -> TypeId { match self.names.get(type_name) { Some(id) => *id, @@ -445,6 +602,25 @@ impl Schema { } } +pub(crate) struct FieldsRef<'a> { + parent_type: StoredFieldParent, + schema: SchemaRef<'a>, + fields: &'a [StoredFieldId], +} + +#[derive(Clone, Debug, Copy, PartialEq)] +pub(crate) struct InterfaceRef<'a> { + schema: SchemaRef<'a>, + interface_id: InterfaceId, +} + +impl<'a> InterfaceRef<'a> { + fn get(&self) -> &'a StoredInterface { + self.schema.get_interface(self.interface_id) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct ObjectRef<'a> { schema: SchemaRef<'a>, object_id: ObjectId, @@ -455,11 +631,51 @@ impl<'a> ObjectRef<'a> { self.schema.get_object(self.object_id) } + fn fields<'b>(&'b self) -> impl Iterator> + 'b { + self.get().fields.iter().map(move |field| FieldRef { + schema: self.schema, + field_id: *field, + }) + } + pub(crate) fn name(&self) -> &'a str { &self.get().name } + + pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { + self.fields().find(|field| field.name() == name) + } + + pub(crate) fn schema(&self) -> SchemaRef<'a> { + self.schema + } } +#[derive(Debug, Clone)] +pub(crate) struct FieldRef<'a> { + schema: SchemaRef<'a>, + field_id: StoredFieldId, +} + +impl<'a> FieldRef<'a> { + fn get(&self) -> &'a StoredField { + self.schema.get_field(self.field_id) + } + + pub(crate) fn id(&self) -> StoredFieldId { + self.field_id + } + + pub(crate) fn name(&self) -> &str { + &self.get().name + } + + pub(crate) fn type_id(&self) -> TypeId { + self.get().r#type.id + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct InputRef<'a> { schema: SchemaRef<'a>, input_id: InputId, @@ -471,7 +687,7 @@ impl<'a> InputRef<'a> { } pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { - todo!() + todo!("contains type without indirection") // let input = self.get(); // // the input type is recursive if any of its members contains it, without indirection @@ -530,67 +746,34 @@ impl From for Schema { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::constants::*; - - #[test] - fn build_schema_works() { - let gql_schema = include_str!("tests/star_wars_schema.graphql"); - let gql_schema = graphql_parser::parse_schema(gql_schema).unwrap(); - let built = Schema::from(&gql_schema); - assert_eq!( - built.objects.get("Droid"), - Some(&GqlObject { - description: None, - name: "Droid", - fields: vec![ - GqlObjectField { - description: None, - name: TYPENAME_FIELD, - type_: FieldType::new(string_type()), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "id", - type_: FieldType::new("ID").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "name", - type_: FieldType::new("String").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "friends", - type_: FieldType::new("Character").list(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "friendsConnection", - type_: FieldType::new("FriendsConnection").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "appearsIn", - type_: FieldType::new("Episode").list().nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "primaryFunction", - type_: FieldType::new("String"), - deprecation: DeprecationStatus::Current, - }, - ], - is_required: false.into(), - }) - ) +pub(crate) fn resolve_field_type( + schema: &Schema, + inner: &graphql_parser::schema::Type, +) -> StoredFieldType { + use crate::field_type::{graphql_parser_depth, GraphqlTypeQualifier}; + use graphql_parser::schema::Type::*; + + let qualifiers_depth = graphql_parser_depth(inner); + let mut qualifiers = Vec::with_capacity(qualifiers_depth); + + let mut inner = inner; + + loop { + match inner { + ListType(new_inner) => { + qualifiers.push(GraphqlTypeQualifier::List); + inner = new_inner; + } + NonNullType(new_inner) => { + qualifiers.push(GraphqlTypeQualifier::Required); + inner = new_inner; + } + NamedType(name) => { + return StoredFieldType { + id: schema.find_type_id(name), + qualifiers, + } + } + } } } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 60abcfd45..fa63a857e 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,5 @@ use super::{EnumId, InputId, InterfaceId, ObjectId, ScalarId, Schema, TypeId, UnionId}; +use crate::schema::resolve_field_type; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; pub(super) fn build_schema(mut src: graphql_parser::schema::Document) -> super::Schema { @@ -160,38 +161,6 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy schema.push_object(object); } -fn resolve_field_type( - schema: &mut Schema, - inner: &graphql_parser::schema::Type, -) -> super::StoredFieldType { - use crate::field_type::{graphql_parser_depth, GraphqlTypeQualifier}; - use graphql_parser::schema::Type::*; - - let qualifiers_depth = graphql_parser_depth(inner); - let mut qualifiers = Vec::with_capacity(qualifiers_depth); - - let mut inner = inner; - - loop { - match inner { - ListType(new_inner) => { - qualifiers.push(GraphqlTypeQualifier::List); - inner = new_inner; - } - NonNullType(new_inner) => { - qualifiers.push(GraphqlTypeQualifier::Required); - inner = new_inner; - } - NamedType(name) => { - return super::StoredFieldType { - id: schema.find_type_id(name), - qualifiers, - } - } - } - } -} - fn ingest_scalar(schema: &mut Schema, scalar: &mut graphql_parser::schema::ScalarType) { let name = std::mem::replace(&mut scalar.name, String::new()); let name_for_names = name.clone(); diff --git a/graphql_client_codegen/src/selection.rs b/graphql_client_codegen/src/selection.rs index 71c0000f6..d514429a7 100644 --- a/graphql_client_codegen/src/selection.rs +++ b/graphql_client_codegen/src/selection.rs @@ -80,7 +80,7 @@ impl<'query> Selection<'query> { selected_variants: &mut BTreeMap<&'s str, Selection<'s>>, // the name of the type the selection applies to selection_on: &str, - ) -> Result<(), failure::Error> { + ) -> Result<(), anyhow::Error> { unimplemented!() // for item in self.0.iter() { // match item { @@ -137,7 +137,7 @@ impl<'query> Selection<'query> { context: &'s crate::query::QueryContext<'_>, // the name of the type the selection applies to selection_on: &str, - ) -> Result>, failure::Error> { + ) -> Result>, anyhow::Error> { let mut selected_variants = BTreeMap::new(); self.selected_variants_on_union_inner(context, &mut selected_variants, selection_on)?; @@ -170,12 +170,12 @@ impl<'query> Selection<'query> { } pub(crate) fn require_items(&self, context: &crate::query::QueryContext<'query>) { - // self.0.iter().for_each(|item| { - // if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { - // context.require_fragment(fragment_name); - // } - // }) - todo!("require_items"); + // self.0.iter().for_each(|item| { + // if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { + // context.require_fragment(fragment_name); + // } + // }) + todo!("require_items"); } } diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 6faa9e52b..130c03701 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -1,10 +1,10 @@ -use crate::deprecation::{DeprecationStatus, DeprecationStrategy}; -use crate::objects::GqlObjectField; -use crate::query::QueryContext; -use crate::selection::*; -use failure::*; -use heck::{CamelCase, SnakeCase}; -use proc_macro2::{Ident, Span, TokenStream}; +// use crate::deprecation::{DeprecationStatus, DeprecationStrategy}; +// use crate::objects::GqlObjectField; +// use crate::query::QueryContext; +// use crate::selection::*; +// use failure::*; +// use heck::{CamelCase, SnakeCase}; +use proc_macro2::TokenStream; use quote::quote; // List of keywords based on https://doc.rust-lang.org/grammar.html#keywords @@ -77,149 +77,149 @@ pub(crate) fn keyword_replace(needle: &str) -> String { } } -pub(crate) fn render_object_field( - field_name: &str, - field_type: &TokenStream, - description: Option<&str>, - status: &DeprecationStatus, - strategy: &DeprecationStrategy, -) -> Option { - #[allow(unused_assignments)] - let mut deprecation = quote!(); - match (status, strategy) { - // If the field is deprecated and we are denying usage, don't generate the - // field in rust at all and short-circuit. - (DeprecationStatus::Deprecated(_), DeprecationStrategy::Deny) => return None, - // Everything is allowed so there is nothing to do. - (_, DeprecationStrategy::Allow) => deprecation = quote!(), - // Current so there is nothing to do. - (DeprecationStatus::Current, _) => deprecation = quote!(), - // A reason was provided, translate it to a note. - (DeprecationStatus::Deprecated(Some(reason)), DeprecationStrategy::Warn) => { - deprecation = quote!(#[deprecated(note = #reason)]) - } - // No reason provided, just mark as deprecated. - (DeprecationStatus::Deprecated(None), DeprecationStrategy::Warn) => { - deprecation = quote!(#[deprecated]) - } - }; +// pub(crate) fn render_object_field( +// field_name: &str, +// field_type: &TokenStream, +// description: Option<&str>, +// status: &DeprecationStatus, +// strategy: &DeprecationStrategy, +// ) -> Option { +// #[allow(unused_assignments)] +// let mut deprecation = quote!(); +// match (status, strategy) { +// // If the field is deprecated and we are denying usage, don't generate the +// // field in rust at all and short-circuit. +// (DeprecationStatus::Deprecated(_), DeprecationStrategy::Deny) => return None, +// // Everything is allowed so there is nothing to do. +// (_, DeprecationStrategy::Allow) => deprecation = quote!(), +// // Current so there is nothing to do. +// (DeprecationStatus::Current, _) => deprecation = quote!(), +// // A reason was provided, translate it to a note. +// (DeprecationStatus::Deprecated(Some(reason)), DeprecationStrategy::Warn) => { +// deprecation = quote!(#[deprecated(note = #reason)]) +// } +// // No reason provided, just mark as deprecated. +// (DeprecationStatus::Deprecated(None), DeprecationStrategy::Warn) => { +// deprecation = quote!(#[deprecated]) +// } +// }; - let description = description.map(|s| quote!(#[doc = #s])); - let rust_safe_field_name = keyword_replace(&field_name.to_snake_case()); - let name_ident = Ident::new(&rust_safe_field_name, Span::call_site()); - let rename = crate::shared::field_rename_annotation(&field_name, &rust_safe_field_name); +// let description = description.map(|s| quote!(#[doc = #s])); +// let rust_safe_field_name = keyword_replace(&field_name.to_snake_case()); +// let name_ident = Ident::new(&rust_safe_field_name, Span::call_site()); +// let rename = crate::shared::field_rename_annotation(&field_name, &rust_safe_field_name); - Some(quote!(#description #deprecation #rename pub #name_ident: #field_type)) -} +// Some(quote!(#description #deprecation #rename pub #name_ident: #field_type)) +// } -pub(crate) fn field_impls_for_selection( - fields: &[GqlObjectField<'_>], - context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, -) -> anyhow::Result> { - todo!("field_impls_for_selection") - // (&selection) - // .into_iter() - // .map(|selected| { - // if let SelectionItem::Field(selected) = selected { - // let name = &selected.name; - // let alias = selected.alias.as_ref().unwrap_or(name); +// pub(crate) fn field_impls_for_selection( +// fields: &[GqlObjectField<'_>], +// context: &QueryContext<'_>, +// selection: &Selection<'_>, +// prefix: &str, +// ) -> anyhow::Result> { +// todo!("field_impls_for_selection") +// // (&selection) +// // .into_iter() +// // .map(|selected| { +// // if let SelectionItem::Field(selected) = selected { +// // let name = &selected.name; +// // let alias = selected.alias.as_ref().unwrap_or(name); - // let ty = fields - // .iter() - // .find(|f| &f.name == name) - // .ok_or_else(|| format_err!("could not find field `{}`", name))? - // .type_ - // .inner_name_str(); - // let prefix = format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()); - // context.maybe_expand_field(&ty, &selected.fields, &prefix) - // } else { - // Ok(None) - // } - // }) - // .filter_map(|i| i.transpose()) - // .collect() -} +// // let ty = fields +// // .iter() +// // .find(|f| &f.name == name) +// // .ok_or_else(|| format_err!("could not find field `{}`", name))? +// // .type_ +// // .inner_name_str(); +// // let prefix = format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()); +// // context.maybe_expand_field(&ty, &selected.fields, &prefix) +// // } else { +// // Ok(None) +// // } +// // }) +// // .filter_map(|i| i.transpose()) +// // .collect() +// } -pub(crate) fn response_fields_for_selection( - type_name: &str, - schema_fields: &[GqlObjectField<'_>], - context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, -) -> anyhow::Result> { - todo!("response fields for selection") - // (&selection) - // .into_iter() - // .map(|item| match item { - // SelectionItem::Field(f) => { - // let name = &f.name; - // let alias = f.alias.as_ref().unwrap_or(name); +// pub(crate) fn response_fields_for_selection( +// type_name: &str, +// schema_fields: &[GqlObjectField<'_>], +// context: &QueryContext<'_>, +// selection: &Selection<'_>, +// prefix: &str, +// ) -> anyhow::Result> { +// todo!("response fields for selection") +// // (&selection) +// // .into_iter() +// // .map(|item| match item { +// // SelectionItem::Field(f) => { +// // let name = &f.name; +// // let alias = f.alias.as_ref().unwrap_or(name); - // let schema_field = &schema_fields - // .iter() - // .find(|field| &field.name == name) - // .ok_or_else(|| { - // format_err!( - // "Could not find field `{}` on `{}`. Available fields: `{}`.", - // *name, - // type_name, - // schema_fields - // .iter() - // .map(|ref field| &field.name) - // .fold(String::new(), |mut acc, item| { - // acc.push_str(item); - // acc.push_str(", "); - // acc - // }) - // .trim_end_matches(", ") - // ) - // })?; - // let ty = schema_field.type_.to_rust( - // context, - // &format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()), - // ); +// // let schema_field = &schema_fields +// // .iter() +// // .find(|field| &field.name == name) +// // .ok_or_else(|| { +// // format_err!( +// // "Could not find field `{}` on `{}`. Available fields: `{}`.", +// // *name, +// // type_name, +// // schema_fields +// // .iter() +// // .map(|ref field| &field.name) +// // .fold(String::new(), |mut acc, item| { +// // acc.push_str(item); +// // acc.push_str(", "); +// // acc +// // }) +// // .trim_end_matches(", ") +// // ) +// // })?; +// // let ty = schema_field.type_.to_rust( +// // context, +// // &format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()), +// // ); - // Ok(render_object_field( - // alias, - // &ty, - // schema_field.description.as_ref().cloned(), - // &schema_field.deprecation, - // &context.deprecation_strategy, - // )) - // } - // SelectionItem::FragmentSpread(fragment) => { - // let field_name = - // Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site()); - // context.require_fragment(&fragment.fragment_name); - // let fragment_from_context = context - // .fragments - // .get(&fragment.fragment_name) - // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment.fragment_name))?; - // let type_name = Ident::new(&fragment.fragment_name, Span::call_site()); - // let type_name = if fragment_from_context.is_recursive() { - // quote!(Box<#type_name>) - // } else { - // quote!(#type_name) - // }; - // Ok(Some(quote! { - // #[serde(flatten)] - // pub #field_name: #type_name - // })) - // } - // SelectionItem::InlineFragment(_) => Err(format_err!( - // "unimplemented: inline fragment on object field" - // )), - // }) - // .filter_map(|x| match x { - // // Remove empty fields so callers always know a field has some - // // tokens. - // Ok(f) => f.map(Ok), - // Err(err) => Some(Err(err)), - // }) - // .collect() -} +// // Ok(render_object_field( +// // alias, +// // &ty, +// // schema_field.description.as_ref().cloned(), +// // &schema_field.deprecation, +// // &context.deprecation_strategy, +// // )) +// // } +// // SelectionItem::FragmentSpread(fragment) => { +// // let field_name = +// // Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site()); +// // context.require_fragment(&fragment.fragment_name); +// // let fragment_from_context = context +// // .fragments +// // .get(&fragment.fragment_name) +// // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment.fragment_name))?; +// // let type_name = Ident::new(&fragment.fragment_name, Span::call_site()); +// // let type_name = if fragment_from_context.is_recursive() { +// // quote!(Box<#type_name>) +// // } else { +// // quote!(#type_name) +// // }; +// // Ok(Some(quote! { +// // #[serde(flatten)] +// // pub #field_name: #type_name +// // })) +// // } +// // SelectionItem::InlineFragment(_) => Err(format_err!( +// // "unimplemented: inline fragment on object field" +// // )), +// // }) +// // .filter_map(|x| match x { +// // // Remove empty fields so callers always know a field has some +// // // tokens. +// // Ok(f) => f.map(Ok), +// // Err(err) => Some(Err(err)), +// // }) +// // .collect() +// } /// Given the GraphQL schema name for an object/interface/input object field and /// the equivalent rust name, produces a serde annotation to map them during diff --git a/graphql_query_derive/src/attributes.rs b/graphql_query_derive/src/attributes.rs index 2084fa45b..634622ab9 100644 --- a/graphql_query_derive/src/attributes.rs +++ b/graphql_query_derive/src/attributes.rs @@ -11,7 +11,7 @@ fn path_to_match() -> syn::Path { } /// Extract an configuration parameter specified in the `graphql` attribute. -pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result { +pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result { let attributes = &ast.attrs; let graphql_path = path_to_match(); let attribute = attributes @@ -37,7 +37,9 @@ pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result { } /// Get the deprecation from a struct attribute in the derive case. -pub fn extract_deprecation_strategy(ast: &syn::DeriveInput) -> Result { +pub fn extract_deprecation_strategy( + ast: &syn::DeriveInput, +) -> Result { extract_attr(&ast, "deprecated")? .to_lowercase() .as_str() @@ -46,7 +48,7 @@ pub fn extract_deprecation_strategy(ast: &syn::DeriveInput) -> Result Result { +pub fn extract_normalization(ast: &syn::DeriveInput) -> Result { extract_attr(&ast, "normalization")? .to_lowercase() .as_str() From a5ca329e8584b5af3a3140846bd9f8a9384156ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 5 Feb 2020 21:09:16 +0100 Subject: [PATCH 05/93] Subscription resolution --- graphql_client_codegen/src/resolution.rs | 16 ++++++++++++++-- graphql_client_codegen/src/schema.rs | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 323aaf052..d79ffc27d 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -153,8 +153,20 @@ fn resolve_operation( query.operations.push(resolved_operation); } - graphql_parser::query::OperationDefinition::Subscription(_) => { - todo!("resolve subscription") + graphql_parser::query::OperationDefinition::Subscription(s) => { + let on = schema.subscription_type(); + let resolved_operation: ResolvedOperation = ResolvedOperation { + name: s + .name + .as_ref() + .expect("subscription without name") + .to_owned(), + operation_type: crate::operations::OperationType::Subscription, + variables: resolve_variables(&s.variable_definitions, schema)?, + selection: resolve_object_selection(on, &s.selection_set)?, + }; + + query.operations.push(resolved_operation); } graphql_parser::query::OperationDefinition::SelectionSet(_) => { unreachable!("unnamed queries are not supported") diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index dc64b006b..4481605a7 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -750,7 +750,8 @@ pub(crate) fn resolve_field_type( schema: &Schema, inner: &graphql_parser::schema::Type, ) -> StoredFieldType { - use crate::field_type::{graphql_parser_depth, GraphqlTypeQualifier}; + use crate::field_type::graphql_parser_depth; + use graphql_parser::schema::Type::*; let qualifiers_depth = graphql_parser_depth(inner); From 20fd6bb59dc1b1df7ec806721416ec1fe66c3bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 5 Feb 2020 22:58:55 +0100 Subject: [PATCH 06/93] Make progress on codegen --- graphql_client_codegen/src/codegen.rs | 55 +++++++++----- graphql_client_codegen/src/codegen_options.rs | 17 ++++- graphql_client_codegen/src/resolution.rs | 73 ++++++++++++++++++- 3 files changed, 119 insertions(+), 26 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 2a586b730..d27667551 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -29,16 +29,10 @@ pub(crate) fn response_for_query( .variables_derives() .unwrap_or("Serialize") .split(","); - let variable_derives = render_derives(variable_derives); - let variables_struct = quote!( - #variable_derives - pub struct Variables; - ); - let response_derives = options - .response_derives() - .map(|derives| derives.split(",")) - .map(render_derives); + let variables_struct = generate_variables_struct(operation, options); + + let response_derives = render_derives(options.all_response_derives()); let response_data_fields: Vec<&'static str> = Vec::new(); // let mut context = QueryContext::new( @@ -168,6 +162,38 @@ pub(crate) fn response_for_query( Ok(q) } +fn generate_variables_struct( + operation: Operation<'_>, + options: &GraphQLClientCodegenOptions, +) -> TokenStream { + let variable_derives = options + .variables_derives() + .unwrap_or("Serialize") + .split(","); + let variable_derives = render_derives(variable_derives); + + if operation.has_no_variables() { + return quote!( + #variable_derives + pub struct Variables; + ); + } + + let variable_fields = operation.variables().map(|v| { + let name = v.name_ident(); + quote!(#name: ()) + }); + + let variables_struct = quote!( + #variable_derives + pub struct Variables { + #(#variable_fields,)* + } + ); + + variables_struct.into() +} + fn generate_scalar_definitions<'a, 'schema: 'a>( operation: Operation<'schema>, all_used_types: &'a crate::resolution::UsedTypes, @@ -190,19 +216,10 @@ fn generate_enum_definitions<'a, 'schema: 'a>( all_used_types: &'a crate::resolution::UsedTypes, options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { - let derives = options - .response_derives() - .map(|derives| { - derives - .split(',') - .filter(|d| *d != "Serialize" && *d != "Deserialize") - }) - .map(render_derives); + let derives = render_derives(options.additional_response_derives()); let normalization = options.normalization(); all_used_types.enums(operation.schema()).map(move |r#enum| { - let ident = syn::Ident::new(r#enum.name(), proc_macro2::Span::call_site()); - let variant_names: Vec = r#enum .variants() .iter() diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 22cb02c6d..a7ad1c96c 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -87,9 +87,20 @@ impl GraphQLClientCodegenOptions { self.variables_derives = Some(variables_derives); } - /// Comma-separated list of additional traits we want to derive for responses. - pub fn response_derives(&self) -> Option<&str> { - self.response_derives.as_deref() + /// Traits we want to derive for responses. + pub fn all_response_derives(&self) -> impl Iterator { + let base_derives = std::iter::once("Deserialize"); + + base_derives.chain(self.additional_response_derives()) + } + + /// Additional traits we want to derive for responses. + pub fn additional_response_derives(&self) -> impl Iterator { + self.response_derives + .as_ref() + .map(String::as_str) + .unwrap_or("") + .split(",") } /// Comma-separated list of additional traits we want to derive for responses. diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index d79ffc27d..b8008de89 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -6,7 +6,11 @@ use crate::schema::FieldRef; use crate::schema::ScalarRef; use crate::schema::StoredFieldType; use crate::schema::TypeRef; -use crate::schema::{ObjectRef, Schema, StoredFieldId, TypeId}; +use crate::{ + constants::TYPENAME_FIELD, + schema::{ObjectRef, Schema, StoredFieldId, TypeId}, +}; +use proc_macro2::{Ident, Span}; use std::collections::HashSet; pub(crate) fn resolve( @@ -57,6 +61,10 @@ fn resolve_object_selection( .map(|item| -> anyhow::Result<_> { match item { graphql_parser::query::Selection::Field(field) => { + if field.name == TYPENAME_FIELD { + return Ok(IdSelection::Typename); + } + let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; @@ -135,7 +143,11 @@ fn resolve_operation( let resolved_operation: ResolvedOperation = ResolvedOperation { name: m.name.as_ref().expect("mutation without name").to_owned(), operation_type: crate::operations::OperationType::Mutation, - variables: resolve_variables(&m.variable_definitions, schema)?, + variables: resolve_variables( + &m.variable_definitions, + schema, + query.operations.len(), + )?, selection: resolve_object_selection(on, &m.selection_set)?, }; @@ -147,7 +159,11 @@ fn resolve_operation( let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), operation_type: crate::operations::OperationType::Query, - variables: resolve_variables(&q.variable_definitions, schema)?, + variables: resolve_variables( + &q.variable_definitions, + schema, + query.operations.len(), + )?, selection: resolve_object_selection(on, &q.selection_set)?, }; @@ -162,7 +178,11 @@ fn resolve_operation( .expect("subscription without name") .to_owned(), operation_type: crate::operations::OperationType::Subscription, - variables: resolve_variables(&s.variable_definitions, schema)?, + variables: resolve_variables( + &s.variable_definitions, + schema, + query.operations.len(), + )?, selection: resolve_object_selection(on, &s.selection_set)?, }; @@ -244,6 +264,21 @@ impl<'a> Operation<'a> { all_used_types } + + pub(crate) fn has_no_variables(&self) -> bool { + self.get().variables.is_empty() + } + + pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { + self.get() + .variables + .iter() + .enumerate() + .map(move |(idx, _)| Variable { + variable_id: idx, + operation: *self, + }) + } } #[derive(Debug)] @@ -262,13 +297,38 @@ impl ResolvedOperation { #[derive(Debug)] struct ResolvedVariable { + operation_id: usize, name: String, default: Option, r#type: StoredFieldType, } +pub(crate) struct Variable<'a> { + operation: Operation<'a>, + variable_id: usize, +} + +impl<'a> Variable<'a> { + fn get(&self) -> &'a ResolvedVariable { + self.operation + .get() + .variables + .get(self.variable_id) + .unwrap() + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn name_ident(&self) -> Ident { + Ident::new(self.name(), Span::call_site()) + } +} + #[derive(Debug, Clone)] enum IdSelection { + Typename, Field(StoredFieldId, Vec), FragmentSpread(String), InlineFragment(TypeId, Vec), @@ -277,6 +337,7 @@ enum IdSelection { impl IdSelection { fn upgrade<'a>(&self, schema: &'a Schema, query: &'a ResolvedQuery) -> Selection<'a> { match self { + IdSelection::Typename => Selection::Typename, IdSelection::Field(id, selection) => Selection::Field( schema.field(*id), selection @@ -308,6 +369,7 @@ impl IdSelection { #[derive(Debug, Clone)] pub(crate) enum Selection<'a> { + Typename, Field(FieldRef<'a>, Vec>), FragmentSpread(Fragment<'a>), InlineFragment(TypeRef<'a>, Vec>), @@ -316,6 +378,7 @@ pub(crate) enum Selection<'a> { impl Selection<'_> { fn collect_used_types(&self, used_types: &mut UsedTypes) { match self { + Selection::Typename => (), Selection::Field(field, selection) => { used_types.types.insert(field.type_id()); @@ -392,11 +455,13 @@ impl UsedTypes { fn resolve_variables( variables: &[graphql_parser::query::VariableDefinition], schema: &Schema, + operation_id: usize, ) -> Result, anyhow::Error> { variables .iter() .map(|var| { Ok(ResolvedVariable { + operation_id, name: var.name.clone(), default: var.default_value.clone(), r#type: resolve_field_type(schema, &var.var_type), From e64bf259323dccd8b398f70d03eaeaa5dfce4eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 5 Feb 2020 23:16:35 +0100 Subject: [PATCH 07/93] Make progress on variables struct generation --- graphql_client_codegen/src/codegen.rs | 17 +++++++++++++---- graphql_client_codegen/src/resolution.rs | 16 ++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index d27667551..62edadc86 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,4 +1,5 @@ use crate::{normalization::Normalization, resolution::*, GraphQLClientCodegenOptions}; +use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -179,10 +180,7 @@ fn generate_variables_struct( ); } - let variable_fields = operation.variables().map(|v| { - let name = v.name_ident(); - quote!(#name: ()) - }); + let variable_fields = operation.variables().map(generate_variable_struct_field); let variables_struct = quote!( #variable_derives @@ -194,6 +192,17 @@ fn generate_variables_struct( variables_struct.into() } +fn generate_variable_struct_field(variable: Variable<'_>) -> TokenStream { + let snake_case_name = variable.name().to_snake_case(); + let ident = Ident::new( + &crate::shared::keyword_replace(&snake_case_name), + Span::call_site(), + ); + let annotation = crate::shared::field_rename_annotation(variable.name(), &snake_case_name); + + quote::quote!(#annotation #ident : ()) +} + fn generate_scalar_definitions<'a, 'schema: 'a>( operation: Operation<'schema>, all_used_types: &'a crate::resolution::UsedTypes, diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index b8008de89..b1acaab96 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,16 +1,12 @@ //! The responsibility of this module is to resolve and validate a query against a given schema. -use crate::schema::resolve_field_type; -use crate::schema::EnumRef; -use crate::schema::FieldRef; -use crate::schema::ScalarRef; -use crate::schema::StoredFieldType; -use crate::schema::TypeRef; use crate::{ constants::TYPENAME_FIELD, - schema::{ObjectRef, Schema, StoredFieldId, TypeId}, + schema::{ + resolve_field_type, EnumRef, FieldRef, ObjectRef, ScalarRef, Schema, StoredFieldId, + StoredFieldType, TypeId, TypeRef, + }, }; -use proc_macro2::{Ident, Span}; use std::collections::HashSet; pub(crate) fn resolve( @@ -320,10 +316,6 @@ impl<'a> Variable<'a> { pub(crate) fn name(&self) -> &'a str { &self.get().name } - - pub(crate) fn name_ident(&self) -> Ident { - Ident::new(self.name(), Span::call_site()) - } } #[derive(Debug, Clone)] From 514f695f71e11434a88135cdd249e1340c7bf3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 5 Feb 2020 23:39:45 +0100 Subject: [PATCH 08/93] Insert parent information in Selection struct --- graphql_client_codegen/src/resolution.rs | 75 ++++++++++++++++-------- graphql_client_codegen/src/schema.rs | 6 +- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index b1acaab96..4fd36b103 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,10 +1,11 @@ -//! The responsibility of this module is to resolve and validate a query against a given schema. +//! The responsibility of this module is to resolve and validate a query +//! against a given schema. use crate::{ constants::TYPENAME_FIELD, schema::{ - resolve_field_type, EnumRef, FieldRef, ObjectRef, ScalarRef, Schema, StoredFieldId, - StoredFieldType, TypeId, TypeRef, + resolve_field_type, EnumRef, FieldRef, InterfaceRef, ObjectId, ObjectRef, ScalarRef, + Schema, StoredFieldId, StoredFieldType, TypeId, TypeRef, UnionRef, }, }; use std::collections::HashSet; @@ -137,6 +138,7 @@ fn resolve_operation( graphql_parser::query::OperationDefinition::Mutation(m) => { let on = schema.mutation_type(); let resolved_operation: ResolvedOperation = ResolvedOperation { + object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), operation_type: crate::operations::OperationType::Mutation, variables: resolve_variables( @@ -160,6 +162,7 @@ fn resolve_operation( schema, query.operations.len(), )?, + object_id: on.id(), selection: resolve_object_selection(on, &q.selection_set)?, }; @@ -179,6 +182,7 @@ fn resolve_operation( schema, query.operations.len(), )?, + object_id: on.id(), selection: resolve_object_selection(on, &s.selection_set)?, }; @@ -237,10 +241,11 @@ impl<'a> Operation<'a> { } fn selection(&self) -> impl Iterator> { - self.get() + let operation = self.get(); + operation .selection .iter() - .map(move |id_selection| id_selection.upgrade(&self.schema, &self.query)) + .map(move |id_selection| id_selection.upgrade(&self.schema, &self.query, None)) } pub(crate) fn schema(&self) -> &'a Schema { @@ -283,6 +288,7 @@ pub(crate) struct ResolvedOperation { operation_type: crate::operations::OperationType, variables: Vec, selection: Vec, + object_id: ObjectId, } impl ResolvedOperation { @@ -327,17 +333,25 @@ enum IdSelection { } impl IdSelection { - fn upgrade<'a>(&self, schema: &'a Schema, query: &'a ResolvedQuery) -> Selection<'a> { - match self { - IdSelection::Typename => Selection::Typename, - IdSelection::Field(id, selection) => Selection::Field( - schema.field(*id), - selection - .iter() - .map(|selection| selection.upgrade(schema, query)) - .collect(), - ), - IdSelection::FragmentSpread(name) => Selection::FragmentSpread(Fragment { + fn upgrade<'a>( + &self, + schema: &'a Schema, + query: &'a ResolvedQuery, + parent: Option>, + ) -> Selection<'a> { + let selection_set = match self { + IdSelection::Typename => SelectionSet::Typename, + IdSelection::Field(id, selection) => { + let field = schema.field(*id); + SelectionSet::Field( + field, + selection + .iter() + .map(|selection| selection.upgrade(schema, query, Some(field))) + .collect(), + ) + } + IdSelection::FragmentSpread(name) => SelectionSet::FragmentSpread(Fragment { fragment_id: ResolvedFragmentId( query .fragments @@ -348,19 +362,30 @@ impl IdSelection { query, schema, }), - IdSelection::InlineFragment(typeid, selection) => Selection::InlineFragment( + IdSelection::InlineFragment(typeid, selection) => SelectionSet::InlineFragment( typeid.upgrade(schema), selection .iter() - .map(|sel| sel.upgrade(schema, query)) + .map(|sel| sel.upgrade(schema, query, parent)) .collect(), ), + }; + + Selection { + selection_set, + parent, } } } #[derive(Debug, Clone)] -pub(crate) enum Selection<'a> { +pub(crate) struct Selection<'a> { + parent: Option>, + selection_set: SelectionSet<'a>, +} + +#[derive(Debug, Clone)] +enum SelectionSet<'a> { Typename, Field(FieldRef<'a>, Vec>), FragmentSpread(Fragment<'a>), @@ -369,22 +394,22 @@ pub(crate) enum Selection<'a> { impl Selection<'_> { fn collect_used_types(&self, used_types: &mut UsedTypes) { - match self { - Selection::Typename => (), - Selection::Field(field, selection) => { + match &self.selection_set { + SelectionSet::Typename => (), + SelectionSet::Field(field, selection) => { used_types.types.insert(field.type_id()); selection .iter() .for_each(|selection| selection.collect_used_types(used_types)); } - Selection::FragmentSpread(fragment) => { + SelectionSet::FragmentSpread(fragment) => { used_types.fragments.insert(fragment.fragment_id); fragment .selection() .for_each(|selection| selection.collect_used_types(used_types)) } - Selection::InlineFragment(on, selection) => { + SelectionSet::InlineFragment(on, selection) => { used_types.types.insert(on.type_id()); selection @@ -411,7 +436,7 @@ impl Fragment<'_> { self.get() .selection .iter() - .map(move |selection| selection.upgrade(&self.schema, &self.query)) + .map(move |selection| selection.upgrade(&self.schema, &self.query, None)) } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 4481605a7..47df3a5be 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -649,9 +649,13 @@ impl<'a> ObjectRef<'a> { pub(crate) fn schema(&self) -> SchemaRef<'a> { self.schema } + + pub(crate) fn id(&self) -> ObjectId { + self.object_id + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub(crate) struct FieldRef<'a> { schema: SchemaRef<'a>, field_id: StoredFieldId, From 8218809c075868a878c3f7f65ea8321d3f697e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 5 Feb 2020 23:56:02 +0100 Subject: [PATCH 09/93] Implement non-lossy way to walk up a selection --- graphql_client_codegen/src/resolution.rs | 45 +++++++++++++++++------- graphql_client_codegen/src/schema.rs | 2 +- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 4fd36b103..75f615a84 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -324,6 +324,12 @@ impl<'a> Variable<'a> { } } +#[derive(Debug, Clone)] +enum SelectionParent<'a> { + Field(FieldRef<'a>), + InlineFragment(TypeRef<'a>), +} + #[derive(Debug, Clone)] enum IdSelection { Typename, @@ -337,17 +343,23 @@ impl IdSelection { &self, schema: &'a Schema, query: &'a ResolvedQuery, - parent: Option>, + parent: Option>, ) -> Selection<'a> { let selection_set = match self { IdSelection::Typename => SelectionSet::Typename, IdSelection::Field(id, selection) => { let field = schema.field(*id); SelectionSet::Field( - field, + field.clone(), selection .iter() - .map(|selection| selection.upgrade(schema, query, Some(field))) + .map(move |selection| { + selection.upgrade( + schema, + query, + Some(SelectionParent::Field(field.clone())), + ) + }) .collect(), ) } @@ -357,18 +369,27 @@ impl IdSelection { .fragments .iter() .position(|frag| frag.name.as_str() == name.as_str()) - .unwrap(), + .expect("fragment not found"), ), query, schema, }), - IdSelection::InlineFragment(typeid, selection) => SelectionSet::InlineFragment( - typeid.upgrade(schema), - selection - .iter() - .map(|sel| sel.upgrade(schema, query, parent)) - .collect(), - ), + IdSelection::InlineFragment(typeid, selection) => { + let parent = typeid.upgrade(schema); + SelectionSet::InlineFragment( + parent, + selection + .iter() + .map(|sel| { + sel.upgrade( + schema, + query, + Some(SelectionParent::InlineFragment(parent)), + ) + }) + .collect(), + ) + } }; Selection { @@ -380,7 +401,7 @@ impl IdSelection { #[derive(Debug, Clone)] pub(crate) struct Selection<'a> { - parent: Option>, + parent: Option>, selection_set: SelectionSet<'a>, } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 47df3a5be..ea29695df 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -655,7 +655,7 @@ impl<'a> ObjectRef<'a> { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub(crate) struct FieldRef<'a> { schema: SchemaRef<'a>, field_id: StoredFieldId, From 198510a4422e4393e7ce8b0e9596184e5c870939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Thu, 6 Feb 2020 00:21:09 +0100 Subject: [PATCH 10/93] Finalize first version of variables struct rendering --- graphql_client_cli/src/introspect_schema.rs | 2 +- graphql_client_codegen/src/codegen.rs | 55 +++++++++++++++++++-- graphql_client_codegen/src/inputs.rs | 7 --- graphql_client_codegen/src/resolution.rs | 9 ++++ graphql_client_codegen/src/schema.rs | 44 ++++++++++++++--- 5 files changed, 99 insertions(+), 18 deletions(-) diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index 0d2f7d8fb..5a29305e1 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -8,7 +8,7 @@ use std::str::FromStr; #[graphql( schema_path = "src/graphql/introspection_schema.graphql", query_path = "src/graphql/introspection_query.graphql", - response_derives = "Serialize,Deserialize", + response_derives = "Serialize", variable_derives = "Serialize" )] #[allow(dead_code)] diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 62edadc86..280210d6d 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,4 +1,7 @@ -use crate::{normalization::Normalization, resolution::*, GraphQLClientCodegenOptions}; +use crate::{ + field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, + GraphQLClientCodegenOptions, +}; use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -199,8 +202,9 @@ fn generate_variable_struct_field(variable: Variable<'_>) -> TokenStream { Span::call_site(), ); let annotation = crate::shared::field_rename_annotation(variable.name(), &snake_case_name); + let r#type = render_variable_field_type(variable); - quote::quote!(#annotation #ident : ()) + quote::quote!(#annotation #ident : #r#type) } fn generate_scalar_definitions<'a, 'schema: 'a>( @@ -225,7 +229,12 @@ fn generate_enum_definitions<'a, 'schema: 'a>( all_used_types: &'a crate::resolution::UsedTypes, options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { - let derives = render_derives(options.additional_response_derives()); + let derives = render_derives( + options + .additional_response_derives() + .chain(options.variables_derives()) + .filter(|d| !&["Serialize", "Deserialize"].contains(d)), + ); let normalization = options.normalization(); all_used_types.enums(operation.schema()).map(move |r#enum| { @@ -296,3 +305,43 @@ fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToT quote!(#[derive(#(#idents),*)]) } + +fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { + let full_name = Ident::new(variable.type_name(), Span::call_site()); + + let mut qualified = quote!(#full_name); + + let mut non_null = false; + + // Note: we iterate over qualifiers in reverse because it is more intuitive. This + // means we start from the _inner_ type and make our way to the outside. + for qualifier in variable.type_qualifiers().iter().rev() { + match (non_null, qualifier) { + // We are in non-null context, and we wrap the non-null type into a list. + // We switch back to null context. + (true, GraphqlTypeQualifier::List) => { + qualified = quote!(Vec<#qualified>); + non_null = false; + } + // We are in nullable context, and we wrap the nullable type into a list. + (false, GraphqlTypeQualifier::List) => { + qualified = quote!(Vec>); + } + // We are in non-nullable context, but we can't double require a type + // (!!). + (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), + // We are in nullable context, and we switch to non-nullable context. + (false, GraphqlTypeQualifier::Required) => { + non_null = true; + } + } + } + + // If we are in nullable context at the end of the iteration, we wrap the whole + // type with an Option. + if !non_null { + qualified = quote!(Option<#qualified>); + } + + qualified +} diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index a09c5f161..e29ee6213 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -17,13 +17,6 @@ pub(crate) fn input_to_rust( todo!() } -/// Represents an input object type from a GraphQL schema -#[derive(Debug, Clone, PartialEq)] -pub struct InputRef<'a> { - schema: SchemaRef<'a>, - input_id: InputId, -} - // impl InputRef<'_> { // // pub(crate) fn require(&self, schema: &Schema) { // // if self.is_required.get() { diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 75f615a84..cf7caac17 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -3,6 +3,7 @@ use crate::{ constants::TYPENAME_FIELD, + field_type::GraphqlTypeQualifier, schema::{ resolve_field_type, EnumRef, FieldRef, InterfaceRef, ObjectId, ObjectRef, ScalarRef, Schema, StoredFieldId, StoredFieldType, TypeId, TypeRef, UnionRef, @@ -322,6 +323,14 @@ impl<'a> Variable<'a> { pub(crate) fn name(&self) -> &'a str { &self.get().name } + + pub(crate) fn type_name(&self) -> &'a str { + self.get().r#type.id.upgrade(self.operation.schema()).name() + } + + pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { + &self.get().r#type.qualifiers + } } #[derive(Debug, Clone)] diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index ea29695df..4f9b6f1a5 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -88,8 +88,8 @@ struct StoredInterfaceField { #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredFieldType { - id: TypeId, - qualifiers: Vec, + pub(crate) id: TypeId, + pub(crate) qualifiers: Vec, } #[derive(Debug, Clone, PartialEq)] @@ -123,12 +123,23 @@ pub(crate) enum TypeRef<'a> { Input(InputRef<'a>), } -impl TypeRef<'_> { +impl<'a> TypeRef<'a> { pub(crate) fn type_id(&self) -> TypeId { match self { _ => todo!("TypeRef::type_id"), } } + + pub(crate) fn name(&self) -> &'a str { + match self { + TypeRef::Object(obj) => obj.name(), + TypeRef::Scalar(s) => s.name(), + TypeRef::Interface(s) => s.name(), + TypeRef::Union(s) => s.name(), + TypeRef::Enum(s) => s.name(), + TypeRef::Input(s) => s.name(), + } + } } #[derive(Debug, Clone, Copy)] @@ -138,11 +149,11 @@ pub(crate) struct ScalarRef<'a> { } impl<'a> ScalarRef<'a> { - fn get(&self) -> &StoredScalar { + fn get(&self) -> &'a StoredScalar { self.schema.get_scalar(self.scalar_id) } - pub(crate) fn name(&self) -> &str { + pub(crate) fn name(&self) -> &'a str { &self.get().name } } @@ -152,6 +163,17 @@ pub(crate) struct UnionRef<'a> { union_id: UnionId, schema: &'a Schema, } + +impl<'a> UnionRef<'a> { + fn get(&self) -> &'a StoredUnion { + self.schema.stored_unions.get(self.union_id.0).unwrap() + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } +} + #[derive(Debug, Clone, Copy)] pub(crate) struct EnumRef<'a> { enum_id: EnumId, @@ -264,7 +286,7 @@ struct StoredEnum { #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredInputFieldType { id: TypeId, - qualifiers: Vec, + pub(crate) qualifiers: Vec, } impl StoredInputFieldType { @@ -618,6 +640,10 @@ impl<'a> InterfaceRef<'a> { fn get(&self) -> &'a StoredInterface { self.schema.get_interface(self.interface_id) } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } } #[derive(Debug, Clone, Copy, PartialEq)] @@ -686,10 +712,14 @@ pub(crate) struct InputRef<'a> { } impl<'a> InputRef<'a> { - fn get(&self) -> &StoredInputType { + fn get(&self) -> &'a StoredInputType { self.schema.get_stored_input(self.input_id) } + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { todo!("contains type without indirection") // let input = self.get(); From 233a88978f8fb9c465f96a5ed1594f7e726fc52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 7 Feb 2020 07:52:38 +0100 Subject: [PATCH 11/93] Start rendering query selection --- graphql_client_codegen/src/codegen.rs | 30 +++++++++++++++++------- graphql_client_codegen/src/resolution.rs | 6 ++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 280210d6d..a2272de89 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,6 +1,6 @@ use crate::{ - field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, - GraphQLClientCodegenOptions, + field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::SelectionSet, + resolution::*, schema::FieldRef, GraphQLClientCodegenOptions, }; use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; @@ -29,15 +29,10 @@ pub(crate) fn response_for_query( let fragment_definitions: Vec<&'static str> = Vec::new(); let definitions: Vec<&'static str> = Vec::new(); let input_object_definitions: Vec<&'static str> = Vec::new(); - let variable_derives = options - .variables_derives() - .unwrap_or("Serialize") - .split(","); - let variables_struct = generate_variables_struct(operation, options); let response_derives = render_derives(options.all_response_derives()); - let response_data_fields: Vec<&'static str> = Vec::new(); + let response_data_fields = render_response_data_fields(&operation); // let mut context = QueryContext::new( // schema, @@ -345,3 +340,22 @@ fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { qualified } + +fn render_response_data_fields<'a>( + operation: &'a Operation<'_>, +) -> impl Iterator + 'a { + operation + .selection() + .map(|select| match &select.selection_set { + SelectionSet::Field(f, _) => { + let ident = field_ident(f); + + quote!(#ident: ()) + } + _ => todo!(), + }) +} + +fn field_ident(field: &FieldRef<'_>) -> Ident { + Ident::new(&field.name().to_snake_case(), Span::call_site()) +} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index cf7caac17..5dedc717d 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -241,7 +241,7 @@ impl<'a> Operation<'a> { self.get().name() } - fn selection(&self) -> impl Iterator> { + pub(crate) fn selection(&self) -> impl Iterator> { let operation = self.get(); operation .selection @@ -411,11 +411,11 @@ impl IdSelection { #[derive(Debug, Clone)] pub(crate) struct Selection<'a> { parent: Option>, - selection_set: SelectionSet<'a>, + pub(crate) selection_set: SelectionSet<'a>, } #[derive(Debug, Clone)] -enum SelectionSet<'a> { +pub(crate) enum SelectionSet<'a> { Typename, Field(FieldRef<'a>, Vec>), FragmentSpread(Fragment<'a>), From d414921a70168cb06bb03cd0f77d5ec0f06db383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 7 Feb 2020 08:20:54 +0100 Subject: [PATCH 12/93] Reintroduce support for aliases --- graphql_client_codegen/src/codegen.rs | 13 +++++--- graphql_client_codegen/src/resolution.rs | 42 +++++++++++++++++------- graphql_client_codegen/src/schema.rs | 2 +- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index a2272de89..46e876a1c 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -347,8 +347,12 @@ fn render_response_data_fields<'a>( operation .selection() .map(|select| match &select.selection_set { - SelectionSet::Field(f, _) => { - let ident = field_ident(f); + SelectionSet::Field { + field: f, + alias, + selection: _, + } => { + let ident = field_ident(f, alias.as_ref().map(String::as_str)); quote!(#ident: ()) } @@ -356,6 +360,7 @@ fn render_response_data_fields<'a>( }) } -fn field_ident(field: &FieldRef<'_>) -> Ident { - Ident::new(&field.name().to_snake_case(), Span::call_site()) +fn field_ident(field: &FieldRef<'_>, alias: Option<&str>) -> Ident { + let name = alias.unwrap_or_else(|| field.name()).to_snake_case(); + Ident::new(&name, Span::call_site()) } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 5dedc717d..af6ac282a 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -66,14 +66,15 @@ fn resolve_object_selection( let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; - Ok(IdSelection::Field( - field_ref.id(), - resolve_selection( + Ok(IdSelection::Field { + field_id: field_ref.id(), + alias: field.alias.clone(), + selection: resolve_selection( object.schema(), field_ref.type_id(), &field.selection_set, )?, - )) + }) } graphql_parser::query::Selection::InlineFragment(inline) => { resolve_inline_fragment(object.schema(), inline) @@ -342,7 +343,11 @@ enum SelectionParent<'a> { #[derive(Debug, Clone)] enum IdSelection { Typename, - Field(StoredFieldId, Vec), + Field { + field_id: StoredFieldId, + alias: Option, + selection: Vec, + }, FragmentSpread(String), InlineFragment(TypeId, Vec), } @@ -356,11 +361,16 @@ impl IdSelection { ) -> Selection<'a> { let selection_set = match self { IdSelection::Typename => SelectionSet::Typename, - IdSelection::Field(id, selection) => { + IdSelection::Field { + field_id: id, + alias, + selection, + } => { let field = schema.field(*id); - SelectionSet::Field( - field.clone(), - selection + SelectionSet::Field { + field: field.clone(), + alias: alias.to_owned(), + selection: selection .iter() .map(move |selection| { selection.upgrade( @@ -370,7 +380,7 @@ impl IdSelection { ) }) .collect(), - ) + } } IdSelection::FragmentSpread(name) => SelectionSet::FragmentSpread(Fragment { fragment_id: ResolvedFragmentId( @@ -417,7 +427,11 @@ pub(crate) struct Selection<'a> { #[derive(Debug, Clone)] pub(crate) enum SelectionSet<'a> { Typename, - Field(FieldRef<'a>, Vec>), + Field { + field: FieldRef<'a>, + selection: Vec>, + alias: Option, + }, FragmentSpread(Fragment<'a>), InlineFragment(TypeRef<'a>, Vec>), } @@ -426,7 +440,11 @@ impl Selection<'_> { fn collect_used_types(&self, used_types: &mut UsedTypes) { match &self.selection_set { SelectionSet::Typename => (), - SelectionSet::Field(field, selection) => { + SelectionSet::Field { + field, + selection, + alias: _, + } => { used_types.types.insert(field.type_id()); selection diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 4f9b6f1a5..cf72b6690 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -537,7 +537,7 @@ impl Schema { pub(crate) fn subscription_type(&self) -> ObjectRef<'_> { ObjectRef { object_id: self - .mutation_type + .subscription_type // TODO: make this return an option .expect("Subscription operation type must be defined"), schema: self, From 45fe183d868bbcf6e67e8c318f2bd60859488d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 7 Feb 2020 21:48:17 +0100 Subject: [PATCH 13/93] Make progress on selection rendering --- graphql_client_codegen/src/codegen.rs | 74 +++++++++++++++++---------- graphql_client_codegen/src/schema.rs | 8 +++ 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 46e876a1c..505cbe24e 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,6 +1,10 @@ use crate::{ - field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::SelectionSet, - resolution::*, schema::FieldRef, GraphQLClientCodegenOptions, + field_type::GraphqlTypeQualifier, + normalization::Normalization, + resolution::SelectionSet, + resolution::*, + schema::{FieldRef, TypeRef}, + GraphQLClientCodegenOptions, }; use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; @@ -304,13 +308,51 @@ fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToT fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { let full_name = Ident::new(variable.type_name(), Span::call_site()); - let mut qualified = quote!(#full_name); + decorate_type(&full_name, variable.type_qualifiers()) +} + +fn render_response_data_fields<'a>( + operation: &'a Operation<'_>, +) -> impl Iterator + 'a { + operation + .selection() + .map(|select| match &select.selection_set { + SelectionSet::Field { + field: f, + alias, + selection: _, + } => { + let ident = field_ident(f, alias.as_ref().map(String::as_str)); + let tpe = match f.field_type() { + TypeRef::Enum(enm) => Ident::new(enm.name(), Span::call_site()), + TypeRef::Scalar(scalar) => Ident::new(scalar.name(), Span::call_site()), + TypeRef::Input(_) => unreachable!("input object in selection"), + other => { + Ident::new("String", Span::call_site()) + // unimplemented!("selection on {:?}", other) + } + }; + let tpe = decorate_type(&tpe, f.type_qualifiers()); + + quote!(pub #ident: #tpe) + } + _ => todo!("render non-field selection"), + }) +} + +fn field_ident(field: &FieldRef<'_>, alias: Option<&str>) -> Ident { + let name = alias.unwrap_or_else(|| field.name()).to_snake_case(); + Ident::new(&name, Span::call_site()) +} + +fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStream { + let mut qualified = quote!(#ident); let mut non_null = false; // Note: we iterate over qualifiers in reverse because it is more intuitive. This // means we start from the _inner_ type and make our way to the outside. - for qualifier in variable.type_qualifiers().iter().rev() { + for qualifier in qualifiers.iter().rev() { match (non_null, qualifier) { // We are in non-null context, and we wrap the non-null type into a list. // We switch back to null context. @@ -340,27 +382,3 @@ fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { qualified } - -fn render_response_data_fields<'a>( - operation: &'a Operation<'_>, -) -> impl Iterator + 'a { - operation - .selection() - .map(|select| match &select.selection_set { - SelectionSet::Field { - field: f, - alias, - selection: _, - } => { - let ident = field_ident(f, alias.as_ref().map(String::as_str)); - - quote!(#ident: ()) - } - _ => todo!(), - }) -} - -fn field_ident(field: &FieldRef<'_>, alias: Option<&str>) -> Ident { - let name = alias.unwrap_or_else(|| field.name()).to_snake_case(); - Ident::new(&name, Span::call_site()) -} diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index cf72b6690..e90afa96e 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -703,6 +703,14 @@ impl<'a> FieldRef<'a> { pub(crate) fn type_id(&self) -> TypeId { self.get().r#type.id } + + pub(crate) fn field_type(&self) -> TypeRef<'_> { + self.get().r#type.id.upgrade(self.schema) + } + + pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { + &self.get().r#type.qualifiers + } } #[derive(Debug, Clone, Copy, PartialEq)] From 6d107ce781f985be07c7fcbc79f6380c843f3618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 7 Feb 2020 22:06:08 +0100 Subject: [PATCH 14/93] First attempt at recursing through selection --- graphql_client_codegen/src/codegen.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 505cbe24e..84f06ce06 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -36,7 +36,7 @@ pub(crate) fn response_for_query( let variables_struct = generate_variables_struct(operation, options); let response_derives = render_derives(options.all_response_derives()); - let response_data_fields = render_response_data_fields(&operation); + let (definitions, response_data_fields) = render_response_data_fields(&operation); // let mut context = QueryContext::new( // schema, @@ -313,10 +313,22 @@ fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { fn render_response_data_fields<'a>( operation: &'a Operation<'_>, -) -> impl Iterator + 'a { - operation - .selection() - .map(|select| match &select.selection_set { +) -> (Vec, Vec) { + let mut response_types = Vec::new(); + let mut fields = Vec::new(); + + render_selection(operation.selection(), &mut fields, &mut response_types); + + (response_types, fields) +} + +fn render_selection<'a>( + selection: impl Iterator>, + field_buffer: &mut Vec, + response_type_buffer: &mut Vec, +) { + for select in selection { + match &select.selection_set { SelectionSet::Field { field: f, alias, @@ -334,10 +346,11 @@ fn render_response_data_fields<'a>( }; let tpe = decorate_type(&tpe, f.type_qualifiers()); - quote!(pub #ident: #tpe) + field_buffer.push(quote!(pub #ident: #tpe)) } _ => todo!("render non-field selection"), - }) + } + } } fn field_ident(field: &FieldRef<'_>, alias: Option<&str>) -> Ident { From 48e41d005a8d38fa30d4adec26a00cbc5f26d6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 7 Feb 2020 22:53:53 +0100 Subject: [PATCH 15/93] Progress on selection rendering --- graphql_client_codegen/src/codegen.rs | 106 +++++------------- graphql_client_codegen/src/resolution.rs | 135 +++++++++++------------ 2 files changed, 97 insertions(+), 144 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 84f06ce06..ef4a3358b 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,7 +1,7 @@ use crate::{ field_type::GraphqlTypeQualifier, normalization::Normalization, - resolution::SelectionSet, + resolution::SelectionItem, resolution::*, schema::{FieldRef, TypeRef}, GraphQLClientCodegenOptions, @@ -59,20 +59,6 @@ pub(crate) fn response_for_query( // let module = context.types_for_operation(operation); - // let response_data_fields = { - // let root_name = operation.root_name(&schema); - // let opt_definition = schema.get_object_by_name(&root_name); - // let definition = if let Some(definition) = opt_definition { - // definition - // } else { - // panic!( - // "operation type '{:?}' not in schema", - // operation.operation_type - // ); - // }; - // let prefix = &operation.name; - // let selection = &operation.selection; - // if operation.is_subscription() && selection.len() > 1 { // return Err(format_err!( // "{}", @@ -80,58 +66,6 @@ pub(crate) fn response_for_query( // )); // } - // definitions.extend(definition.field_impls_for_selection(&context, &selection, &prefix)?); - // definition.response_fields_for_selection(&context, &selection, &prefix)? - // }; - - // let enum_definitions = schema.enums.values().filter_map(|enm| { - // if enm.is_required.get() { - // Some(enm.to_rust(&context)) - // } else { - // None - // } - // }); - // let fragment_definitions: Result, _> = context - // .fragments - // .values() - // .filter_map(|fragment| { - // if fragment.is_required.get() { - // Some(fragment.to_rust(&context)) - // } else { - // None - // } - // }) - // .collect(); - // let fragment_definitions = fragment_definitions?; - // let variables_struct = operation.expand_variables(&context); - - // let input_object_definitions: Result, _> = schema - // .inputs - // .values() - // .filter_map(|i| { - // if i.is_required.get() { - // Some(i.to_rust(&context)) - // } else { - // None - // } - // }) - // .collect(); - // let input_object_definitions = input_object_definitions?; - - // let scalar_definitions: Vec = schema - // .scalars - // .values() - // .filter_map(|s| { - // if s.is_required.get() { - // Some(s.to_rust(context.normalization)) - // } else { - // None - // } - // }) - // .collect(); - - // let response_derives = context.response_derives(); - let q = quote! { use serde::{Serialize, Deserialize}; @@ -323,30 +257,50 @@ fn render_response_data_fields<'a>( } fn render_selection<'a>( - selection: impl Iterator>, + selection: impl Iterator>, field_buffer: &mut Vec, response_type_buffer: &mut Vec, ) { for select in selection { match &select.selection_set { - SelectionSet::Field { + SelectionItem::Field { field: f, alias, - selection: _, + selection: subselection, } => { let ident = field_ident(f, alias.as_ref().map(String::as_str)); let tpe = match f.field_type() { - TypeRef::Enum(enm) => Ident::new(enm.name(), Span::call_site()), - TypeRef::Scalar(scalar) => Ident::new(scalar.name(), Span::call_site()), + TypeRef::Enum(enm) => { + let type_name = Ident::new(enm.name(), Span::call_site()); + let type_name = decorate_type(&type_name, f.type_qualifiers()); + + field_buffer.push(quote!(pub #ident: #type_name)); + } + TypeRef::Scalar(scalar) => { + let type_name = Ident::new(scalar.name(), Span::call_site()); + let type_name = decorate_type(&type_name, f.type_qualifiers()); + + field_buffer.push(quote!(pub #ident: #type_name)); + } TypeRef::Input(_) => unreachable!("input object in selection"), + TypeRef::Object(object) => { + let mut fields = Vec::new(); + let struct_name = Ident::new(object.name(), Span::call_site()); + + render_selection( + subselection.iter(), + &mut fields, + &mut response_type_buffer, + ); + + field_buffer.push(quote!(pub #ident: #struct_name)); + response_type_buffer.push(quote!(struct #struct_name;)); + } other => { - Ident::new("String", Span::call_site()) + Ident::new("String", Span::call_site()); // unimplemented!("selection on {:?}", other) } }; - let tpe = decorate_type(&tpe, f.type_qualifiers()); - - field_buffer.push(quote!(pub #ident: #tpe)) } _ => todo!("render non-field selection"), } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index af6ac282a..2825085ec 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -52,21 +52,21 @@ fn resolve_fragment( fn resolve_object_selection( object: ObjectRef<'_>, selection_set: &graphql_parser::query::SelectionSet, -) -> anyhow::Result> { - let id_selection: Vec = selection_set +) -> anyhow::Result { + let id_selection: Vec = selection_set .items .iter() .map(|item| -> anyhow::Result<_> { match item { graphql_parser::query::Selection::Field(field) => { if field.name == TYPENAME_FIELD { - return Ok(IdSelection::Typename); + return Ok(IdSelectionItem::Typename); } let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; - Ok(IdSelection::Field { + Ok(IdSelectionItem::Field { field_id: field_ref.id(), alias: field.alias.clone(), selection: resolve_selection( @@ -80,20 +80,20 @@ fn resolve_object_selection( resolve_inline_fragment(object.schema(), inline) } graphql_parser::query::Selection::FragmentSpread(fragment_spread) => Ok( - IdSelection::FragmentSpread(fragment_spread.fragment_name.clone()), + IdSelectionItem::FragmentSpread(fragment_spread.fragment_name.clone()), ), } }) .collect::>()?; - Ok(id_selection) + Ok(IdSelection(id_selection)) } fn resolve_selection( schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, -) -> anyhow::Result> { +) -> anyhow::Result { match on { TypeId::Object(oid) => { let object = schema.object(oid); @@ -109,7 +109,7 @@ fn resolve_selection( "Selection set on non-object, non-interface type. ({:?})", other ); - Ok(Vec::new()) + Ok(IdSelection(Vec::new())) } } } @@ -117,7 +117,7 @@ fn resolve_selection( fn resolve_inline_fragment( schema: &Schema, inline_fragment: &graphql_parser::query::InlineFragment, -) -> anyhow::Result { +) -> anyhow::Result { let graphql_parser::query::TypeCondition::On(on) = inline_fragment .type_condition .as_ref() @@ -125,7 +125,7 @@ fn resolve_inline_fragment( let type_id = schema .find_type(on) .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; - Ok(IdSelection::InlineFragment( + Ok(IdSelectionItem::InlineFragment( type_id, resolve_selection(schema, type_id, &inline_fragment.selection_set)?, )) @@ -211,7 +211,7 @@ pub(crate) struct ResolvedQuery { struct ResolvedFragment { name: String, on: crate::schema::TypeId, - selection: Vec, + selection: IdSelection, } #[derive(Debug, Clone, Copy)] @@ -242,7 +242,7 @@ impl<'a> Operation<'a> { self.get().name() } - pub(crate) fn selection(&self) -> impl Iterator> { + pub(crate) fn selection(&self) -> impl Iterator> { let operation = self.get(); operation .selection @@ -289,7 +289,7 @@ pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, variables: Vec, - selection: Vec, + selection: IdSelection, object_id: ObjectId, } @@ -335,39 +335,39 @@ impl<'a> Variable<'a> { } #[derive(Debug, Clone)] -enum SelectionParent<'a> { - Field(FieldRef<'a>), - InlineFragment(TypeRef<'a>), +struct IdSelection { + on: TypeId, + selection_set: Vec, } #[derive(Debug, Clone)] -enum IdSelection { +enum IdSelectionItem { Typename, Field { field_id: StoredFieldId, alias: Option, - selection: Vec, + selection: IdSelection, }, FragmentSpread(String), - InlineFragment(TypeId, Vec), + InlineFragment(TypeId, IdSelection), } -impl IdSelection { +impl IdSelectionItem { fn upgrade<'a>( &self, schema: &'a Schema, query: &'a ResolvedQuery, - parent: Option>, - ) -> Selection<'a> { + parent: Option>, + ) -> SelectionRef<'a> { let selection_set = match self { - IdSelection::Typename => SelectionSet::Typename, - IdSelection::Field { + IdSelectionItem::Typename => SelectionItem::Typename, + IdSelectionItem::Field { field_id: id, alias, selection, } => { let field = schema.field(*id); - SelectionSet::Field { + SelectionItem::Field { field: field.clone(), alias: alias.to_owned(), selection: selection @@ -376,13 +376,13 @@ impl IdSelection { selection.upgrade( schema, query, - Some(SelectionParent::Field(field.clone())), + Some(SelectionOn::Field(field.clone())), ) }) .collect(), } } - IdSelection::FragmentSpread(name) => SelectionSet::FragmentSpread(Fragment { + IdSelectionItem::FragmentSpread(name) => SelectionItem::FragmentSpread(Fragment { fragment_id: ResolvedFragmentId( query .fragments @@ -393,25 +393,21 @@ impl IdSelection { query, schema, }), - IdSelection::InlineFragment(typeid, selection) => { - let parent = typeid.upgrade(schema); - SelectionSet::InlineFragment( - parent, + IdSelectionItem::InlineFragment(typeid, selection) => { + let on = typeid.upgrade(schema); + SelectionItem::InlineFragment( + on, selection .iter() .map(|sel| { - sel.upgrade( - schema, - query, - Some(SelectionParent::InlineFragment(parent)), - ) + sel.upgrade(schema, query, Some(SelectionOn::InlineFragment(parent))) }) .collect(), ) } }; - Selection { + SelectionRef { selection_set, parent, } @@ -419,50 +415,53 @@ impl IdSelection { } #[derive(Debug, Clone)] -pub(crate) struct Selection<'a> { - parent: Option>, - pub(crate) selection_set: SelectionSet<'a>, +pub(crate) struct SelectionRef<'a> { + parent: Option<(SelectionRef<'a>, usize)>, + on: TypeRef<'a>, + pub(crate) selection_set: Vec>, } #[derive(Debug, Clone)] -pub(crate) enum SelectionSet<'a> { +pub(crate) enum SelectionItem<'a> { Typename, Field { field: FieldRef<'a>, - selection: Vec>, + selection: Vec>, alias: Option, }, FragmentSpread(Fragment<'a>), - InlineFragment(TypeRef<'a>, Vec>), + InlineFragment(TypeRef<'a>, Vec>), } -impl Selection<'_> { +impl SelectionRef<'_> { fn collect_used_types(&self, used_types: &mut UsedTypes) { - match &self.selection_set { - SelectionSet::Typename => (), - SelectionSet::Field { - field, - selection, - alias: _, - } => { - used_types.types.insert(field.type_id()); + for item in &self.selection_set { + match item { + SelectionItem::Typename => (), + SelectionItem::Field { + field, + selection, + alias: _, + } => { + used_types.types.insert(field.type_id()); - selection - .iter() - .for_each(|selection| selection.collect_used_types(used_types)); - } - SelectionSet::FragmentSpread(fragment) => { - used_types.fragments.insert(fragment.fragment_id); - fragment - .selection() - .for_each(|selection| selection.collect_used_types(used_types)) - } - SelectionSet::InlineFragment(on, selection) => { - used_types.types.insert(on.type_id()); + selection + .iter() + .for_each(|selection| selection.collect_used_types(used_types)); + } + SelectionItem::FragmentSpread(fragment) => { + used_types.fragments.insert(fragment.fragment_id); + fragment + .selection() + .for_each(|selection| selection.collect_used_types(used_types)) + } + SelectionItem::InlineFragment(on, selection) => { + used_types.types.insert(on.type_id()); - selection - .iter() - .for_each(|selection| selection.collect_used_types(used_types)) + selection + .iter() + .for_each(|selection| selection.collect_used_types(used_types)) + } } } } @@ -480,7 +479,7 @@ impl Fragment<'_> { self.query.fragments.get(self.fragment_id.0).unwrap() } - pub(crate) fn selection(&self) -> impl Iterator> { + pub(crate) fn selection(&self) -> impl Iterator> { self.get() .selection .iter() From bba6682b4e7426d7dabd2d81c1229774158d8353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Thu, 13 Feb 2020 21:24:44 +0100 Subject: [PATCH 16/93] Try reifying selections --- graphql_client_codegen/src/codegen.rs | 15 +- graphql_client_codegen/src/resolution.rs | 532 ++++++++++++++--------- 2 files changed, 343 insertions(+), 204 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index ef4a3358b..7899b8fcc 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -262,13 +262,10 @@ fn render_selection<'a>( response_type_buffer: &mut Vec, ) { for select in selection { - match &select.selection_set { - SelectionItem::Field { - field: f, - alias, - selection: subselection, - } => { - let ident = field_ident(f, alias.as_ref().map(String::as_str)); + match &select.refine() { + SelectionItem::Field(selected_field) => { + let f = selected_field.field(); + let ident = field_ident(&f, selected_field.alias()); let tpe = match f.field_type() { TypeRef::Enum(enm) => { let type_name = Ident::new(enm.name(), Span::call_site()); @@ -288,9 +285,9 @@ fn render_selection<'a>( let struct_name = Ident::new(object.name(), Span::call_site()); render_selection( - subselection.iter(), + selected_field.subselection(), &mut fields, - &mut response_type_buffer, + response_type_buffer, ); field_buffer.push(quote!(pub #ident: #struct_name)); diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 2825085ec..064bed205 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -11,12 +11,71 @@ use crate::{ }; use std::collections::HashSet; +// enum QueryNode { +// Field(StoredFieldId), +// InlineFragment(TypeId), +// FragmentSpread(FragmentId), +// } + +// enum QueryEdge { +// Selection, +// } + +#[derive(Debug, Clone, Copy)] +enum SelectionId { + FieldId(usize), + InlineFragmentId(usize), + FragmentSpread(usize), + Typename(Option), +} + +#[derive(Debug, Clone, Copy)] +enum SelectionParentId { + FieldId(usize), + InlineFragmentId(usize), +} + +#[derive(Debug)] +struct Field { + parent: Option, + alias: Option, + field_id: StoredFieldId, +} + +#[derive(Debug)] +struct FragmentSpread { + parent: Option, + fragment_id: ResolvedFragmentId, +} + +#[derive(Debug)] +struct InlineFragment { + parent: Option, + on: TypeId, +} + pub(crate) fn resolve( schema: &Schema, query: &graphql_parser::query::Document, ) -> anyhow::Result { let mut resolved_query: ResolvedQuery = Default::default(); + // First, give ids to all fragments. + for definition in &query.definitions { + match definition { + graphql_parser::query::Definition::Fragment(fragment) => { + let graphql_parser::query::TypeCondition::On(on) = &fragment.type_condition; + resolved_query.fragments.push(ResolvedFragment { + name: fragment.name.clone(), + on: schema.find_type(on).expect("TODO: proper error message"), + selection: Vec::new(), + }); + } + _ => (), + } + } + + // Then resolve the selections. for definition in &query.definitions { match definition { graphql_parser::query::Definition::Fragment(fragment) => { @@ -34,70 +93,102 @@ pub(crate) fn resolve( fn resolve_fragment( query: &mut ResolvedQuery, schema: &Schema, - fragment: &graphql_parser::query::FragmentDefinition, + fragment_definition: &graphql_parser::query::FragmentDefinition, ) -> anyhow::Result<()> { - let graphql_parser::query::TypeCondition::On(on) = &fragment.type_condition; - let on = schema.find_type(on).expect("TODO: proper error message"); - let resolved_fragment = ResolvedFragment { - name: fragment.name.clone(), + let graphql_parser::query::TypeCondition::On(on) = &fragment_definition.type_condition; + let on = schema.find_type(&on).unwrap(); + + let mut acc = + SelectionAccumulator::with_capacity(fragment_definition.selection_set.items.len()); + + resolve_selection( + query, + schema, on, - selection: resolve_selection(schema, on, &fragment.selection_set)?, - }; + &fragment_definition.selection_set, + None, + &mut acc, + )?; + + let (_, mut fragment) = query + .find_fragment(&fragment_definition.name) + .expect("TODO: fragment resolution"); - query.fragments.push(resolved_fragment); + fragment.selection = acc.into_vec(); Ok(()) } fn resolve_object_selection( + query: &mut ResolvedQuery, object: ObjectRef<'_>, selection_set: &graphql_parser::query::SelectionSet, -) -> anyhow::Result { - let id_selection: Vec = selection_set - .items - .iter() - .map(|item| -> anyhow::Result<_> { - match item { - graphql_parser::query::Selection::Field(field) => { - if field.name == TYPENAME_FIELD { - return Ok(IdSelectionItem::Typename); - } - - let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { - anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) - })?; - Ok(IdSelectionItem::Field { - field_id: field_ref.id(), - alias: field.alias.clone(), - selection: resolve_selection( - object.schema(), - field_ref.type_id(), - &field.selection_set, - )?, - }) - } - graphql_parser::query::Selection::InlineFragment(inline) => { - resolve_inline_fragment(object.schema(), inline) + parent: Option, + acc: &mut SelectionAccumulator, +) -> anyhow::Result<()> { + for item in selection_set.items.iter() { + match item { + graphql_parser::query::Selection::Field(field) => { + if field.name == TYPENAME_FIELD { + acc.push(SelectionId::Typename(parent)); + continue; } - graphql_parser::query::Selection::FragmentSpread(fragment_spread) => Ok( - IdSelectionItem::FragmentSpread(fragment_spread.fragment_name.clone()), - ), + + let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { + anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) + })?; + + let id = query.selected_fields.len(); + query.selected_fields.push(Field { + parent, + alias: field.alias.clone(), + field_id: field_ref.id(), + }); + + resolve_selection( + query, + object.schema(), + field_ref.type_id(), + &field.selection_set, + parent, + &mut SelectionAccumulator::noop(), + )?; + + acc.push(SelectionId::FieldId(id)) } - }) - .collect::>()?; + graphql_parser::query::Selection::InlineFragment(inline) => { + let selection_id = resolve_inline_fragment(query, object.schema(), inline, parent)?; + } + graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { + let (fragment_id, _) = query + .find_fragment(&fragment_spread.fragment_name) + .expect("TODO: fragment resolution"); + let id = query.fragment_spreads.len(); + query.fragment_spreads.push(FragmentSpread { + fragment_id: ResolvedFragmentId(fragment_id), + parent, + }); + + acc.push(SelectionId::FragmentSpread(id)) + } + } + } - Ok(IdSelection(id_selection)) + Ok(()) } fn resolve_selection( + ctx: &mut ResolvedQuery, schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, -) -> anyhow::Result { - match on { + parent: Option, + acc: &mut SelectionAccumulator, +) -> anyhow::Result<()> { + let selection = match on { TypeId::Object(oid) => { let object = schema.object(oid); - resolve_object_selection(object, selection_set) + resolve_object_selection(ctx, object, selection_set, parent, acc)?; } TypeId::Interface(interface_id) => { let interface = schema.interface(interface_id); @@ -109,26 +200,42 @@ fn resolve_selection( "Selection set on non-object, non-interface type. ({:?})", other ); - Ok(IdSelection(Vec::new())) } - } + }; + + Ok(()) } fn resolve_inline_fragment( + query: &mut ResolvedQuery, schema: &Schema, inline_fragment: &graphql_parser::query::InlineFragment, -) -> anyhow::Result { + parent: Option, +) -> anyhow::Result { let graphql_parser::query::TypeCondition::On(on) = inline_fragment .type_condition .as_ref() - .expect("missing type condition"); + .expect("missing type condition on inline fragment"); let type_id = schema .find_type(on) .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; - Ok(IdSelectionItem::InlineFragment( + + let id = query.inline_fragments.len(); + query.inline_fragments.push(InlineFragment { + parent, + on: type_id, + }); + + resolve_selection( + query, + schema, type_id, - resolve_selection(schema, type_id, &inline_fragment.selection_set)?, - )) + &inline_fragment.selection_set, + Some(SelectionParentId::InlineFragmentId(id)), + &mut SelectionAccumulator::noop(), + )?; + + Ok(SelectionId::InlineFragmentId(id)) } fn resolve_operation( @@ -139,6 +246,8 @@ fn resolve_operation( match operation { graphql_parser::query::OperationDefinition::Mutation(m) => { let on = schema.mutation_type(); + let mut acc = SelectionAccumulator::with_capacity(m.selection_set.items.len()); + resolve_object_selection(query, on, &m.selection_set, None, &mut acc)?; let resolved_operation: ResolvedOperation = ResolvedOperation { object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), @@ -148,13 +257,15 @@ fn resolve_operation( schema, query.operations.len(), )?, - selection: resolve_object_selection(on, &m.selection_set)?, + selection: acc.into_vec(), }; query.operations.push(resolved_operation); } graphql_parser::query::OperationDefinition::Query(q) => { let on = schema.query_type(); + let mut acc = SelectionAccumulator::with_capacity(q.selection_set.items.len()); + resolve_object_selection(query, on, &q.selection_set, None, &mut acc)?; let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), @@ -165,13 +276,16 @@ fn resolve_operation( query.operations.len(), )?, object_id: on.id(), - selection: resolve_object_selection(on, &q.selection_set)?, + selection: acc.into_vec(), }; query.operations.push(resolved_operation); } graphql_parser::query::OperationDefinition::Subscription(s) => { let on = schema.subscription_type(); + let mut acc = SelectionAccumulator::with_capacity(s.selection_set.items.len()); + resolve_object_selection(query, on, &s.selection_set, None, &mut acc)?; + let resolved_operation: ResolvedOperation = ResolvedOperation { name: s .name @@ -185,7 +299,7 @@ fn resolve_operation( query.operations.len(), )?, object_id: on.id(), - selection: resolve_object_selection(on, &s.selection_set)?, + selection: acc.into_vec(), }; query.operations.push(resolved_operation); @@ -205,13 +319,25 @@ struct ResolvedFragmentId(usize); pub(crate) struct ResolvedQuery { pub(crate) operations: Vec, fragments: Vec, + selected_fields: Vec, + inline_fragments: Vec, + fragment_spreads: Vec, +} + +impl ResolvedQuery { + fn find_fragment(&mut self, name: &str) -> Option<(usize, &mut ResolvedFragment)> { + self.fragments + .iter_mut() + .enumerate() + .find(|(_, frag)| frag.name == name) + } } #[derive(Debug)] struct ResolvedFragment { name: String, on: crate::schema::TypeId, - selection: IdSelection, + selection: Vec, } #[derive(Debug, Clone, Copy)] @@ -247,7 +373,11 @@ impl<'a> Operation<'a> { operation .selection .iter() - .map(move |id_selection| id_selection.upgrade(&self.schema, &self.query, None)) + .map(move |selection_id| SelectionRef { + selection_id: *selection_id, + query: self.query, + schema: self.schema, + }) } pub(crate) fn schema(&self) -> &'a Schema { @@ -284,12 +414,136 @@ impl<'a> Operation<'a> { } } +#[derive(Debug)] +pub(crate) struct SelectionRef<'a> { + query: &'a ResolvedQuery, + schema: &'a Schema, + selection_id: SelectionId, +} + +impl<'a> SelectionRef<'a> { + fn collect_used_types(&self, used_types: &mut UsedTypes) { + match self.refine() { + SelectionItem::Field(selected_field_ref) => { + used_types + .types + .insert(selected_field_ref.field().type_id()); + + for item in selected_field_ref.subselection() { + item.collect_used_types(used_types) + } + } + SelectionItem::InlineFragment(inline_fragment_ref) => { + todo!(); + } + SelectionItem::FragmentSpread(fragment_spread_ref) => fragment_spread_ref + .fragment() + .collect_used_types(used_types), + SelectionItem::Typename => (), + } + } + + pub(crate) fn refine(&self) -> SelectionItem<'a> { + match self.selection_id { + SelectionId::FieldId(field_id) => SelectionItem::Field(SelectedFieldRef { + query: self.query, + schema: self.schema, + field_id, + }), + SelectionId::InlineFragmentId(inline_fragment_id) => { + SelectionItem::InlineFragment(InlineFragmentRef { + query: self.query, + schema: self.schema, + inline_fragment_id, + }) + } + SelectionId::FragmentSpread(fragment_spread_id) => { + SelectionItem::FragmentSpread(FragmentSpreadRef { + query: self.query, + schema: self.schema, + fragment_spread_id, + }) + } + SelectionId::Typename(_) => todo!(), + } + } +} + +pub(crate) enum SelectionItem<'a> { + Field(SelectedFieldRef<'a>), + InlineFragment(InlineFragmentRef<'a>), + FragmentSpread(FragmentSpreadRef<'a>), + Typename, +} + +pub(crate) struct SelectedFieldRef<'a> { + query: &'a ResolvedQuery, + schema: &'a Schema, + field_id: usize, +} + +pub(crate) struct FragmentSpreadRef<'a> { + query: &'a ResolvedQuery, + schema: &'a Schema, + fragment_spread_id: usize, +} + +impl<'a> FragmentSpreadRef<'a> { + fn get(&self) -> &'a FragmentSpread { + self.query + .fragment_spreads + .get(self.fragment_spread_id) + .unwrap() + } + + fn fragment(&self) -> Fragment<'a> { + Fragment { + query: self.query, + schema: self.schema, + fragment_id: self.get().fragment_id, + } + } +} + +impl<'a> SelectedFieldRef<'a> { + fn get(&self) -> &'a Field { + self.query.selected_fields.get(self.field_id).unwrap() + } + + pub(crate) fn field(&self) -> crate::schema::FieldRef<'_> { + self.schema.field(self.get().field_id) + } + + pub(crate) fn alias(&self) -> Option<&'a str> { + self.get().alias.as_ref().map(String::as_str) + } + + pub(crate) fn subselection(&self) -> impl Iterator> { + std::iter::empty() + } +} + +pub(crate) struct InlineFragmentRef<'a> { + query: &'a ResolvedQuery, + schema: &'a Schema, + inline_fragment_id: usize, +} + +impl<'a> InlineFragmentRef<'a> { + fn get(&self) -> &'a InlineFragment { + self.query + .inline_fragments + .get(self.inline_fragment_id) + .unwrap() + } +} + #[derive(Debug)] pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, variables: Vec, - selection: IdSelection, + selection: Vec, object_id: ObjectId, } @@ -334,144 +588,11 @@ impl<'a> Variable<'a> { } } -#[derive(Debug, Clone)] -struct IdSelection { - on: TypeId, - selection_set: Vec, -} - -#[derive(Debug, Clone)] -enum IdSelectionItem { - Typename, - Field { - field_id: StoredFieldId, - alias: Option, - selection: IdSelection, - }, - FragmentSpread(String), - InlineFragment(TypeId, IdSelection), -} - -impl IdSelectionItem { - fn upgrade<'a>( - &self, - schema: &'a Schema, - query: &'a ResolvedQuery, - parent: Option>, - ) -> SelectionRef<'a> { - let selection_set = match self { - IdSelectionItem::Typename => SelectionItem::Typename, - IdSelectionItem::Field { - field_id: id, - alias, - selection, - } => { - let field = schema.field(*id); - SelectionItem::Field { - field: field.clone(), - alias: alias.to_owned(), - selection: selection - .iter() - .map(move |selection| { - selection.upgrade( - schema, - query, - Some(SelectionOn::Field(field.clone())), - ) - }) - .collect(), - } - } - IdSelectionItem::FragmentSpread(name) => SelectionItem::FragmentSpread(Fragment { - fragment_id: ResolvedFragmentId( - query - .fragments - .iter() - .position(|frag| frag.name.as_str() == name.as_str()) - .expect("fragment not found"), - ), - query, - schema, - }), - IdSelectionItem::InlineFragment(typeid, selection) => { - let on = typeid.upgrade(schema); - SelectionItem::InlineFragment( - on, - selection - .iter() - .map(|sel| { - sel.upgrade(schema, query, Some(SelectionOn::InlineFragment(parent))) - }) - .collect(), - ) - } - }; - - SelectionRef { - selection_set, - parent, - } - } -} - -#[derive(Debug, Clone)] -pub(crate) struct SelectionRef<'a> { - parent: Option<(SelectionRef<'a>, usize)>, - on: TypeRef<'a>, - pub(crate) selection_set: Vec>, -} - -#[derive(Debug, Clone)] -pub(crate) enum SelectionItem<'a> { - Typename, - Field { - field: FieldRef<'a>, - selection: Vec>, - alias: Option, - }, - FragmentSpread(Fragment<'a>), - InlineFragment(TypeRef<'a>, Vec>), -} - -impl SelectionRef<'_> { - fn collect_used_types(&self, used_types: &mut UsedTypes) { - for item in &self.selection_set { - match item { - SelectionItem::Typename => (), - SelectionItem::Field { - field, - selection, - alias: _, - } => { - used_types.types.insert(field.type_id()); - - selection - .iter() - .for_each(|selection| selection.collect_used_types(used_types)); - } - SelectionItem::FragmentSpread(fragment) => { - used_types.fragments.insert(fragment.fragment_id); - fragment - .selection() - .for_each(|selection| selection.collect_used_types(used_types)) - } - SelectionItem::InlineFragment(on, selection) => { - used_types.types.insert(on.type_id()); - - selection - .iter() - .for_each(|selection| selection.collect_used_types(used_types)) - } - } - } - } -} - #[derive(Debug, Clone)] pub(crate) struct Fragment<'a> { - fragment_id: ResolvedFragmentId, query: &'a ResolvedQuery, schema: &'a Schema, + fragment_id: ResolvedFragmentId, } impl Fragment<'_> { @@ -479,11 +600,10 @@ impl Fragment<'_> { self.query.fragments.get(self.fragment_id.0).unwrap() } - pub(crate) fn selection(&self) -> impl Iterator> { - self.get() - .selection - .iter() - .map(move |selection| selection.upgrade(&self.schema, &self.query, None)) + fn collect_used_types(&self, used_types: &mut UsedTypes) { + used_types.fragments.insert(self.fragment_id); + + todo!() } } @@ -533,3 +653,25 @@ fn resolve_variables( }) .collect() } + +struct SelectionAccumulator(Option>); + +impl SelectionAccumulator { + fn with_capacity(cap: usize) -> Self { + SelectionAccumulator(Some(Vec::with_capacity(cap))) + } + + fn noop() -> Self { + SelectionAccumulator(None) + } + + fn push(&mut self, item: SelectionId) { + if let Some(v) = &mut self.0 { + v.push(item); + } + } + + fn into_vec(self) -> Vec { + self.0.unwrap_or_else(Vec::new) + } +} From 1beaed01de30d2d04d8a7e64ed1673dcd0e6e8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 14 Feb 2020 00:56:44 +0100 Subject: [PATCH 17/93] Go back to codegen --- graphql_client_cli/src/introspect_schema.rs | 2 +- graphql_client_codegen/src/codegen.rs | 68 ++++++++----------- graphql_client_codegen/src/codegen_options.rs | 17 ++++- graphql_client_codegen/src/inputs.rs | 2 +- graphql_client_codegen/src/resolution.rs | 59 ++++++++++++++-- graphql_client_codegen/src/schema.rs | 42 ++++++++++++ graphql_client_codegen/src/variables.rs | 2 +- 7 files changed, 139 insertions(+), 53 deletions(-) diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index 5a29305e1..2f4d9dc89 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -9,7 +9,7 @@ use std::str::FromStr; schema_path = "src/graphql/introspection_schema.graphql", query_path = "src/graphql/introspection_query.graphql", response_derives = "Serialize", - variable_derives = "Serialize" + variable_derives = "Deserialize" )] #[allow(dead_code)] struct IntrospectionQuery; diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 7899b8fcc..c67513093 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -36,35 +36,8 @@ pub(crate) fn response_for_query( let variables_struct = generate_variables_struct(operation, options); let response_derives = render_derives(options.all_response_derives()); - let (definitions, response_data_fields) = render_response_data_fields(&operation); - - // let mut context = QueryContext::new( - // schema, - // options.deprecation_strategy(), - // options.normalization(), - // ); - - // if let Some(derives) = options.variables_derives() { - // context.ingest_variables_derives(&derives)?; - // } - - // if let Some(derives) = options.response_derives() { - // context.ingest_response_derives(&derives)?; - // } - - // let resolved_query = crate::resolution::resolve(schema, query)?; - // crate::rendering::render(schema, &resolved_query) - - // context.resolve_fragments(&query.definitions); - - // let module = context.types_for_operation(operation); - - // if operation.is_subscription() && selection.len() > 1 { - // return Err(format_err!( - // "{}", - // crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR - // )); - // } + let (definitions, response_data_fields) = + render_response_data_fields(&operation, &response_derives); let q = quote! { use serde::{Serialize, Deserialize}; @@ -103,10 +76,7 @@ fn generate_variables_struct( operation: Operation<'_>, options: &GraphQLClientCodegenOptions, ) -> TokenStream { - let variable_derives = options - .variables_derives() - .unwrap_or("Serialize") - .split(","); + let variable_derives = options.all_variable_derives(); let variable_derives = render_derives(variable_derives); if operation.has_no_variables() { @@ -137,7 +107,7 @@ fn generate_variable_struct_field(variable: Variable<'_>) -> TokenStream { let annotation = crate::shared::field_rename_annotation(variable.name(), &snake_case_name); let r#type = render_variable_field_type(variable); - quote::quote!(#annotation #ident : #r#type) + quote::quote!(#annotation pub #ident : #r#type) } fn generate_scalar_definitions<'a, 'schema: 'a>( @@ -164,8 +134,7 @@ fn generate_enum_definitions<'a, 'schema: 'a>( ) -> impl Iterator + 'a { let derives = render_derives( options - .additional_response_derives() - .chain(options.variables_derives()) + .all_response_derives() .filter(|d| !&["Serialize", "Deserialize"].contains(d)), ); let normalization = options.normalization(); @@ -247,11 +216,17 @@ fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { fn render_response_data_fields<'a>( operation: &'a Operation<'_>, + response_derives: &impl quote::ToTokens, ) -> (Vec, Vec) { let mut response_types = Vec::new(); let mut fields = Vec::new(); - render_selection(operation.selection(), &mut fields, &mut response_types); + render_selection( + operation.selection(), + &mut fields, + &mut response_types, + response_derives, + ); (response_types, fields) } @@ -260,6 +235,7 @@ fn render_selection<'a>( selection: impl Iterator>, field_buffer: &mut Vec, response_type_buffer: &mut Vec, + response_derives: &impl quote::ToTokens, ) { for select in selection { match &select.refine() { @@ -283,15 +259,18 @@ fn render_selection<'a>( TypeRef::Object(object) => { let mut fields = Vec::new(); let struct_name = Ident::new(object.name(), Span::call_site()); - render_selection( selected_field.subselection(), &mut fields, response_type_buffer, + response_derives, ); - field_buffer.push(quote!(pub #ident: #struct_name)); - response_type_buffer.push(quote!(struct #struct_name;)); + let field_type = decorate_type(&struct_name, f.type_qualifiers()); + + field_buffer.push(quote!(pub #ident: #field_type)); + response_type_buffer + .push(quote!(#response_derives pub struct #struct_name;)); } other => { Ident::new("String", Span::call_site()); @@ -299,7 +278,14 @@ fn render_selection<'a>( } }; } - _ => todo!("render non-field selection"), + SelectionItem::Typename => { + field_buffer.push(quote!( + #[serde(rename = "__typename")] + pub typename: String + )); + } + SelectionItem::InlineFragment(inline) => todo!("render inline fragment"), + SelectionItem::FragmentSpread(frag) => todo!("render fragment spread"), } } } diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index a7ad1c96c..9700b04b4 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -87,6 +87,18 @@ impl GraphQLClientCodegenOptions { self.variables_derives = Some(variables_derives); } + /// All the variable derives to be rendered. + pub fn all_variable_derives(&self) -> impl Iterator { + let additional = self + .variables_derives + .as_ref() + .map(String::as_str) + .into_iter() + .flat_map(|s| s.split(",")); + + std::iter::once("Serialize").chain(additional) + } + /// Traits we want to derive for responses. pub fn all_response_derives(&self) -> impl Iterator { let base_derives = std::iter::once("Deserialize"); @@ -99,8 +111,9 @@ impl GraphQLClientCodegenOptions { self.response_derives .as_ref() .map(String::as_str) - .unwrap_or("") - .split(",") + .into_iter() + .flat_map(|s| s.split(",")) + .map(|s| s.trim()) } /// Comma-separated list of additional traits we want to derive for responses. diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index e29ee6213..da90f1dbe 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -14,7 +14,7 @@ pub(crate) fn input_to_rust( ctx: &mut QueryContext<'_>, input: crate::schema::InputRef<'_>, ) -> Result { - todo!() + todo!("input to rust") } // impl InputRef<'_> { diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 064bed205..244641793 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -11,6 +11,12 @@ use crate::{ }; use std::collections::HashSet; +pub(crate) struct WithQuery<'a, T> { + query: &'a ResolvedQuery, + schema: &'a Schema, + item: T, +} + // enum QueryNode { // Field(StoredFieldId), // InlineFragment(TypeId), @@ -119,9 +125,9 @@ fn resolve_fragment( Ok(()) } -fn resolve_object_selection( +fn resolve_object_selection<'a>( query: &mut ResolvedQuery, - object: ObjectRef<'_>, + object: impl crate::schema::ObjectRefLike<'a>, selection_set: &graphql_parser::query::SelectionSet, parent: Option, acc: &mut SelectionAccumulator, @@ -192,7 +198,7 @@ fn resolve_selection( } TypeId::Interface(interface_id) => { let interface = schema.interface(interface_id); - todo!("interface thing") + resolve_object_selection(ctx, interface, selection_set, parent, acc)?; } other => { anyhow::ensure!( @@ -331,6 +337,14 @@ impl ResolvedQuery { .enumerate() .find(|(_, frag)| frag.name == name) } + + fn children_of<'a>( + &'a self, + parent_id: SelectionParentId, + schema: &'a Schema, + ) -> impl Iterator> { + todo!() + } } #[derive(Debug)] @@ -434,7 +448,11 @@ impl<'a> SelectionRef<'a> { } } SelectionItem::InlineFragment(inline_fragment_ref) => { - todo!(); + used_types.types.insert(inline_fragment_ref.on().type_id()); + + for item in inline_fragment_ref.subselection() { + item.collect_used_types(used_types); + } } SelectionItem::FragmentSpread(fragment_spread_ref) => fragment_spread_ref .fragment() @@ -464,7 +482,7 @@ impl<'a> SelectionRef<'a> { fragment_spread_id, }) } - SelectionId::Typename(_) => todo!(), + SelectionId::Typename(_) => SelectionItem::Typename, } } } @@ -496,6 +514,10 @@ impl<'a> FragmentSpreadRef<'a> { .unwrap() } + pub(crate) fn parent(&self) -> Option> { + todo!("FragmentSpreadRef::parent") + } + fn fragment(&self) -> Fragment<'a> { Fragment { query: self.query, @@ -518,6 +540,11 @@ impl<'a> SelectedFieldRef<'a> { self.get().alias.as_ref().map(String::as_str) } + pub(crate) fn parent(&self) -> Option> { + self.query + .children_of(SelectionParentId::FieldId(self.field_id), self.schema) + } + pub(crate) fn subselection(&self) -> impl Iterator> { std::iter::empty() } @@ -536,6 +563,18 @@ impl<'a> InlineFragmentRef<'a> { .get(self.inline_fragment_id) .unwrap() } + + pub(crate) fn on(&self) -> crate::schema::TypeRef<'a> { + self.get().on.upgrade(self.schema) + } + + pub(crate) fn parent(&self) -> Option> { + todo!() + } + + pub(crate) fn subselection(&self) -> impl Iterator> { + std::iter::empty() + } } #[derive(Debug)] @@ -595,7 +634,7 @@ pub(crate) struct Fragment<'a> { fragment_id: ResolvedFragmentId, } -impl Fragment<'_> { +impl<'a> Fragment<'a> { fn get(&self) -> &ResolvedFragment { self.query.fragments.get(self.fragment_id.0).unwrap() } @@ -603,7 +642,13 @@ impl Fragment<'_> { fn collect_used_types(&self, used_types: &mut UsedTypes) { used_types.fragments.insert(self.fragment_id); - todo!() + for selection in self.selection() { + selection.collect_used_types(used_types); + } + } + + fn selection(&self) -> impl Iterator> { + std::iter::empty() } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index e90afa96e..ea2f815a1 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -1,5 +1,6 @@ mod graphql_parser_conversion; mod json_conversion; + use crate::field_type::GraphqlTypeQualifier; use std::collections::HashMap; @@ -820,3 +821,44 @@ pub(crate) fn resolve_field_type( } } } + +pub(crate) trait ObjectRefLike<'a> { + fn name(&self) -> &'a str; + + fn get_field_by_name(&self, name: &str) -> Option>; + + fn schema(&self) -> SchemaRef<'a>; +} + +impl<'a> ObjectRefLike<'a> for ObjectRef<'a> { + fn name(&self) -> &'a str { + self.name() + } + + fn get_field_by_name(&self, name: &str) -> Option> { + self.get_field_by_name(name) + } + + fn schema(&self) -> SchemaRef<'a> { + self.schema() + } +} + +impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { + fn name(&self) -> &'a str { + self.name() + } + + fn get_field_by_name(&self, name: &str) -> Option> { + self.get_field_by_name(name) + } + + fn schema(&self) -> SchemaRef<'a> { + self.schema() + } +} + +pub(crate) struct WithSchema<'a, T> { + schema: SchemaRef<'a>, + item: T, +} diff --git a/graphql_client_codegen/src/variables.rs b/graphql_client_codegen/src/variables.rs index b1f764669..4c56c2b66 100644 --- a/graphql_client_codegen/src/variables.rs +++ b/graphql_client_codegen/src/variables.rs @@ -17,7 +17,7 @@ impl<'query> Variable<'query> { context: &QueryContext<'_>, schema: &crate::schema::Schema, ) -> Option { - todo!() + todo!("generate default value constructor") // // TODO // // context.schema.require(&self.ty.inner_name_str()); // match &self.default { From a6c0ecfdef4eed1e41893f8d7d0b3082e02b0a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 14 Feb 2020 02:20:21 +0100 Subject: [PATCH 18/93] Experiment with petgraph --- graphql_client_codegen/Cargo.toml | 1 + graphql_client_codegen/src/codegen.rs | 21 +- graphql_client_codegen/src/resolution.rs | 450 +++++++++++------------ 3 files changed, 219 insertions(+), 253 deletions(-) diff --git a/graphql_client_codegen/Cargo.toml b/graphql_client_codegen/Cargo.toml index 1906e1c1b..746c1390b 100644 --- a/graphql_client_codegen/Cargo.toml +++ b/graphql_client_codegen/Cargo.toml @@ -19,3 +19,4 @@ serde_json = "1.0" serde = { version = "^1.0", features = ["derive"] } syn = "^1.0" thiserror = "1.0.10" +petgraph = "0.5.0" diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index c67513093..f6a7bdb3d 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,7 +1,6 @@ use crate::{ field_type::GraphqlTypeQualifier, normalization::Normalization, - resolution::SelectionItem, resolution::*, schema::{FieldRef, TypeRef}, GraphQLClientCodegenOptions, @@ -222,7 +221,7 @@ fn render_response_data_fields<'a>( let mut fields = Vec::new(); render_selection( - operation.selection(), + operation.rich_selection(), &mut fields, &mut response_types, response_derives, @@ -232,16 +231,16 @@ fn render_response_data_fields<'a>( } fn render_selection<'a>( - selection: impl Iterator>, + selection: impl Iterator>>, field_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, ) { for select in selection { - match &select.refine() { - SelectionItem::Field(selected_field) => { - let f = selected_field.field(); - let ident = field_ident(&f, selected_field.alias()); + match &select.variant() { + SelectionVariant::SelectedField { field, alias } => { + let f = field; + let ident = field_ident(&f, *alias); let tpe = match f.field_type() { TypeRef::Enum(enm) => { let type_name = Ident::new(enm.name(), Span::call_site()); @@ -260,7 +259,7 @@ fn render_selection<'a>( let mut fields = Vec::new(); let struct_name = Ident::new(object.name(), Span::call_site()); render_selection( - selected_field.subselection(), + select.subselection(), &mut fields, response_type_buffer, response_derives, @@ -278,14 +277,14 @@ fn render_selection<'a>( } }; } - SelectionItem::Typename => { + SelectionVariant::Typename => { field_buffer.push(quote!( #[serde(rename = "__typename")] pub typename: String )); } - SelectionItem::InlineFragment(inline) => todo!("render inline fragment"), - SelectionItem::FragmentSpread(frag) => todo!("render fragment spread"), + SelectionVariant::InlineFragment(inline) => todo!("render inline fragment"), + SelectionVariant::FragmentSpread(frag) => todo!("render fragment spread"), } } } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 244641793..32b583d8e 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -9,55 +9,140 @@ use crate::{ Schema, StoredFieldId, StoredFieldType, TypeId, TypeRef, UnionRef, }, }; +use petgraph::prelude::EdgeRef; use std::collections::HashSet; +pub(crate) struct SelectionItem<'a> { + parent_id: Option, + node_id: NodeId, + variant: SelectionVariant<'a>, +} + +pub(crate) enum SelectionVariant<'a> { + SelectedField { + alias: Option<&'a str>, + field: FieldRef<'a>, + }, + FragmentSpread(&'a ResolvedFragment), + InlineFragment(TypeRef<'a>), + Typename, +} + +impl<'a> WithQuery<'a, SelectionItem<'a>> { + pub(crate) fn variant(&self) -> &SelectionVariant<'a> { + &self.item.variant + } + + pub(crate) fn parent(&self) -> Option>> { + self.item + .parent_id + .map(|parent_id| self.refocus(parent_id).upgrade()) + } + + pub(crate) fn subselection<'b>( + &'b self, + ) -> impl Iterator>> + 'b { + let id_selection = self.refocus(self.item.node_id); + + id_selection.into_subselection().map(move |s| s.upgrade()) + } +} + +#[derive(Debug, Clone, Copy)] pub(crate) struct WithQuery<'a, T> { query: &'a ResolvedQuery, schema: &'a Schema, item: T, } -// enum QueryNode { -// Field(StoredFieldId), -// InlineFragment(TypeId), -// FragmentSpread(FragmentId), -// } +impl<'a, T> WithQuery<'a, T> { + fn refocus(&self, new_item: U) -> WithQuery<'a, U> { + WithQuery { + query: self.query, + schema: self.schema, + item: new_item, + } + } +} -// enum QueryEdge { -// Selection, -// } +type NodeId = petgraph::prelude::NodeIndex; +type SelectionGraph = petgraph::Graph; -#[derive(Debug, Clone, Copy)] -enum SelectionId { - FieldId(usize), - InlineFragmentId(usize), - FragmentSpread(usize), - Typename(Option), -} +impl<'a> WithQuery<'a, NodeId> { + fn get_node(&self) -> WithQuery<'a, &'a QueryNode> { + let item = &self.query.selection_graph[self.item]; + self.refocus(item) + } -#[derive(Debug, Clone, Copy)] -enum SelectionParentId { - FieldId(usize), - InlineFragmentId(usize), + pub fn into_subselection(self) -> impl Iterator> { + self.query + .selection_graph + .edges_directed(self.item, petgraph::Direction::Outgoing) + .filter(|edge| match edge.weight() { + QueryEdge::Selection => true, + }) + .map(move |edge| self.refocus(edge.target())) + } + + pub(crate) fn subselection<'b>(&'b self) -> impl Iterator> + 'b { + self.into_subselection() + } + + pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { + let node = self.get_node(); + match node.item { + QueryNode::SelectedField(field) => { + let field_ref = self.schema.field(field.field_id); + used_types.types.insert(field_ref.type_id()); + + for item in self.subselection() { + item.collect_used_types(used_types); + } + } + QueryNode::InlineFragment(type_id) => { + used_types.types.insert(*type_id); + + for item in self.subselection() { + item.collect_used_types(used_types); + } + } + QueryNode::FragmentSpread(fragment_id) => { + used_types.fragments.insert(*fragment_id); + + for item in self.refocus(*fragment_id).selection() { + item.collect_used_types(used_types); + } + } + QueryNode::Typename => (), + } + } + + fn upgrade(&self) -> WithQuery<'a, SelectionItem<'a>> { + let node = self.get_node(); + + match node { + _ => panic!(), + } + } } #[derive(Debug)] -struct Field { - parent: Option, - alias: Option, - field_id: StoredFieldId, +enum QueryNode { + SelectedField(SelectedField), + InlineFragment(TypeId), + FragmentSpread(ResolvedFragmentId), + Typename, } #[derive(Debug)] -struct FragmentSpread { - parent: Option, - fragment_id: ResolvedFragmentId, +enum QueryEdge { + Selection, } #[derive(Debug)] -struct InlineFragment { - parent: Option, - on: TypeId, +struct SelectedField { + alias: Option, + field_id: StoredFieldId, } pub(crate) fn resolve( @@ -129,14 +214,14 @@ fn resolve_object_selection<'a>( query: &mut ResolvedQuery, object: impl crate::schema::ObjectRefLike<'a>, selection_set: &graphql_parser::query::SelectionSet, - parent: Option, + parent: Option, acc: &mut SelectionAccumulator, ) -> anyhow::Result<()> { for item in selection_set.items.iter() { match item { graphql_parser::query::Selection::Field(field) => { if field.name == TYPENAME_FIELD { - acc.push(SelectionId::Typename(parent)); + let id = query.push_typename(parent); continue; } @@ -144,23 +229,24 @@ fn resolve_object_selection<'a>( anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; - let id = query.selected_fields.len(); - query.selected_fields.push(Field { + let id = query.push_selected_field( + SelectedField { + alias: field.alias.clone(), + field_id: field_ref.id(), + }, parent, - alias: field.alias.clone(), - field_id: field_ref.id(), - }); + ); resolve_selection( query, object.schema(), field_ref.type_id(), &field.selection_set, - parent, + Some(id), &mut SelectionAccumulator::noop(), )?; - acc.push(SelectionId::FieldId(id)) + acc.push(id) } graphql_parser::query::Selection::InlineFragment(inline) => { let selection_id = resolve_inline_fragment(query, object.schema(), inline, parent)?; @@ -169,13 +255,8 @@ fn resolve_object_selection<'a>( let (fragment_id, _) = query .find_fragment(&fragment_spread.fragment_name) .expect("TODO: fragment resolution"); - let id = query.fragment_spreads.len(); - query.fragment_spreads.push(FragmentSpread { - fragment_id: ResolvedFragmentId(fragment_id), - parent, - }); - acc.push(SelectionId::FragmentSpread(id)) + acc.push(query.push_fragment_spread(ResolvedFragmentId(fragment_id), parent)); } } } @@ -188,7 +269,7 @@ fn resolve_selection( schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, - parent: Option, + parent: Option, acc: &mut SelectionAccumulator, ) -> anyhow::Result<()> { let selection = match on { @@ -216,8 +297,8 @@ fn resolve_inline_fragment( query: &mut ResolvedQuery, schema: &Schema, inline_fragment: &graphql_parser::query::InlineFragment, - parent: Option, -) -> anyhow::Result { + parent: Option, +) -> anyhow::Result { let graphql_parser::query::TypeCondition::On(on) = inline_fragment .type_condition .as_ref() @@ -226,22 +307,18 @@ fn resolve_inline_fragment( .find_type(on) .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; - let id = query.inline_fragments.len(); - query.inline_fragments.push(InlineFragment { - parent, - on: type_id, - }); + let id = query.push_inline_fragment(type_id, parent); resolve_selection( query, schema, type_id, &inline_fragment.selection_set, - Some(SelectionParentId::InlineFragmentId(id)), + Some(id), &mut SelectionAccumulator::noop(), )?; - Ok(SelectionId::InlineFragmentId(id)) + Ok(id) } fn resolve_operation( @@ -319,18 +396,62 @@ fn resolve_operation( } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -struct ResolvedFragmentId(usize); +pub(crate) struct ResolvedFragmentId(usize); #[derive(Debug, Default)] pub(crate) struct ResolvedQuery { pub(crate) operations: Vec, fragments: Vec, - selected_fields: Vec, - inline_fragments: Vec, - fragment_spreads: Vec, + selection_graph: SelectionGraph, } impl ResolvedQuery { + fn push_typename(&mut self, parent: Option) -> NodeId { + let idx = self.selection_graph.add_node(QueryNode::Typename); + + self.push_optional_parent(idx, parent); + + idx + } + + fn push_fragment_spread( + &mut self, + fragment_id: ResolvedFragmentId, + parent: Option, + ) -> NodeId { + let id = self + .selection_graph + .add_node(QueryNode::FragmentSpread(fragment_id)); + + self.push_optional_parent(id, parent); + + id + } + + fn push_inline_fragment(&mut self, type_id: TypeId, parent: Option) -> NodeId { + let id = self + .selection_graph + .add_node(QueryNode::InlineFragment(type_id)); + + self.push_optional_parent(id, parent); + + id + } + + fn push_selected_field( + &mut self, + selected_field: SelectedField, + parent: Option, + ) -> NodeId { + let id = self + .selection_graph + .add_node(QueryNode::SelectedField(selected_field)); + + self.push_optional_parent(id, parent); + + id + } + fn find_fragment(&mut self, name: &str) -> Option<(usize, &mut ResolvedFragment)> { self.fragments .iter_mut() @@ -338,20 +459,19 @@ impl ResolvedQuery { .find(|(_, frag)| frag.name == name) } - fn children_of<'a>( - &'a self, - parent_id: SelectionParentId, - schema: &'a Schema, - ) -> impl Iterator> { - todo!() + fn push_optional_parent(&mut self, id: NodeId, parent: Option) { + if let Some(parent) = parent { + self.selection_graph + .add_edge(parent, id, QueryEdge::Selection); + } } } #[derive(Debug)] -struct ResolvedFragment { +pub(crate) struct ResolvedFragment { name: String, on: crate::schema::TypeId, - selection: Vec, + selection: Vec, } #[derive(Debug, Clone, Copy)] @@ -382,13 +502,19 @@ impl<'a> Operation<'a> { self.get().name() } - pub(crate) fn selection(&self) -> impl Iterator> { + pub(crate) fn rich_selection<'b>( + &'b self, + ) -> impl Iterator>> + 'b { + self.selection().map(|s| s.upgrade()) + } + + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { let operation = self.get(); operation .selection .iter() - .map(move |selection_id| SelectionRef { - selection_id: *selection_id, + .map(move |selection_id| WithQuery { + item: *selection_id, query: self.query, schema: self.schema, }) @@ -428,161 +554,12 @@ impl<'a> Operation<'a> { } } -#[derive(Debug)] -pub(crate) struct SelectionRef<'a> { - query: &'a ResolvedQuery, - schema: &'a Schema, - selection_id: SelectionId, -} - -impl<'a> SelectionRef<'a> { - fn collect_used_types(&self, used_types: &mut UsedTypes) { - match self.refine() { - SelectionItem::Field(selected_field_ref) => { - used_types - .types - .insert(selected_field_ref.field().type_id()); - - for item in selected_field_ref.subselection() { - item.collect_used_types(used_types) - } - } - SelectionItem::InlineFragment(inline_fragment_ref) => { - used_types.types.insert(inline_fragment_ref.on().type_id()); - - for item in inline_fragment_ref.subselection() { - item.collect_used_types(used_types); - } - } - SelectionItem::FragmentSpread(fragment_spread_ref) => fragment_spread_ref - .fragment() - .collect_used_types(used_types), - SelectionItem::Typename => (), - } - } - - pub(crate) fn refine(&self) -> SelectionItem<'a> { - match self.selection_id { - SelectionId::FieldId(field_id) => SelectionItem::Field(SelectedFieldRef { - query: self.query, - schema: self.schema, - field_id, - }), - SelectionId::InlineFragmentId(inline_fragment_id) => { - SelectionItem::InlineFragment(InlineFragmentRef { - query: self.query, - schema: self.schema, - inline_fragment_id, - }) - } - SelectionId::FragmentSpread(fragment_spread_id) => { - SelectionItem::FragmentSpread(FragmentSpreadRef { - query: self.query, - schema: self.schema, - fragment_spread_id, - }) - } - SelectionId::Typename(_) => SelectionItem::Typename, - } - } -} - -pub(crate) enum SelectionItem<'a> { - Field(SelectedFieldRef<'a>), - InlineFragment(InlineFragmentRef<'a>), - FragmentSpread(FragmentSpreadRef<'a>), - Typename, -} - -pub(crate) struct SelectedFieldRef<'a> { - query: &'a ResolvedQuery, - schema: &'a Schema, - field_id: usize, -} - -pub(crate) struct FragmentSpreadRef<'a> { - query: &'a ResolvedQuery, - schema: &'a Schema, - fragment_spread_id: usize, -} - -impl<'a> FragmentSpreadRef<'a> { - fn get(&self) -> &'a FragmentSpread { - self.query - .fragment_spreads - .get(self.fragment_spread_id) - .unwrap() - } - - pub(crate) fn parent(&self) -> Option> { - todo!("FragmentSpreadRef::parent") - } - - fn fragment(&self) -> Fragment<'a> { - Fragment { - query: self.query, - schema: self.schema, - fragment_id: self.get().fragment_id, - } - } -} - -impl<'a> SelectedFieldRef<'a> { - fn get(&self) -> &'a Field { - self.query.selected_fields.get(self.field_id).unwrap() - } - - pub(crate) fn field(&self) -> crate::schema::FieldRef<'_> { - self.schema.field(self.get().field_id) - } - - pub(crate) fn alias(&self) -> Option<&'a str> { - self.get().alias.as_ref().map(String::as_str) - } - - pub(crate) fn parent(&self) -> Option> { - self.query - .children_of(SelectionParentId::FieldId(self.field_id), self.schema) - } - - pub(crate) fn subselection(&self) -> impl Iterator> { - std::iter::empty() - } -} - -pub(crate) struct InlineFragmentRef<'a> { - query: &'a ResolvedQuery, - schema: &'a Schema, - inline_fragment_id: usize, -} - -impl<'a> InlineFragmentRef<'a> { - fn get(&self) -> &'a InlineFragment { - self.query - .inline_fragments - .get(self.inline_fragment_id) - .unwrap() - } - - pub(crate) fn on(&self) -> crate::schema::TypeRef<'a> { - self.get().on.upgrade(self.schema) - } - - pub(crate) fn parent(&self) -> Option> { - todo!() - } - - pub(crate) fn subselection(&self) -> impl Iterator> { - std::iter::empty() - } -} - #[derive(Debug)] pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, variables: Vec, - selection: Vec, + selection: Vec, object_id: ObjectId, } @@ -627,28 +604,17 @@ impl<'a> Variable<'a> { } } -#[derive(Debug, Clone)] -pub(crate) struct Fragment<'a> { - query: &'a ResolvedQuery, - schema: &'a Schema, - fragment_id: ResolvedFragmentId, -} - -impl<'a> Fragment<'a> { - fn get(&self) -> &ResolvedFragment { - self.query.fragments.get(self.fragment_id.0).unwrap() - } - - fn collect_used_types(&self, used_types: &mut UsedTypes) { - used_types.fragments.insert(self.fragment_id); - - for selection in self.selection() { - selection.collect_used_types(used_types); - } +impl<'a> WithQuery<'a, ResolvedFragmentId> { + fn get(&self) -> &'a ResolvedFragment { + self.query.fragments.get(self.item.0).unwrap() } - fn selection(&self) -> impl Iterator> { - std::iter::empty() + fn selection<'b>(&'b self) -> impl Iterator> + 'b { + let fragment = self.get(); + fragment + .selection + .iter() + .map(move |item| self.refocus(*item)) } } @@ -699,7 +665,7 @@ fn resolve_variables( .collect() } -struct SelectionAccumulator(Option>); +struct SelectionAccumulator(Option>); impl SelectionAccumulator { fn with_capacity(cap: usize) -> Self { @@ -710,13 +676,13 @@ impl SelectionAccumulator { SelectionAccumulator(None) } - fn push(&mut self, item: SelectionId) { + fn push(&mut self, item: NodeId) { if let Some(v) = &mut self.0 { v.push(item); } } - fn into_vec(self) -> Vec { + fn into_vec(self) -> Vec { self.0.unwrap_or_else(Vec::new) } } From 8cc46784d41cb3bfb905ec251f05cd832cb01f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 14 Feb 2020 03:54:30 +0100 Subject: [PATCH 19/93] implement upgrade --- graphql_client_codegen/src/resolution.rs | 36 ++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 32b583d8e..a28b9841b 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -120,9 +120,36 @@ impl<'a> WithQuery<'a, NodeId> { fn upgrade(&self) -> WithQuery<'a, SelectionItem<'a>> { let node = self.get_node(); - match node { - _ => panic!(), - } + let variant = match node.item { + QueryNode::FragmentSpread(frag_id) => { + SelectionVariant::FragmentSpread(self.query.fragments.get(frag_id.0).unwrap()) + } + QueryNode::InlineFragment(type_id) => { + SelectionVariant::InlineFragment(type_id.upgrade(self.schema)) + } + QueryNode::Typename => SelectionVariant::Typename, + QueryNode::SelectedField(f) => SelectionVariant::SelectedField { + alias: f.alias.as_ref().map(String::as_str), + field: self.schema.field(f.field_id), + }, + }; + + self.refocus(SelectionItem { + node_id: self.item, + parent_id: self.parent_id(), + variant, + }) + } + + fn parent_id(&self) -> Option { + self.query + .selection_graph + .edges_directed(self.item, petgraph::Direction::Incoming) + .filter(|edge| match edge.weight() { + QueryEdge::Selection => true, + }) + .map(|edge| edge.source()) + .next() } } @@ -222,6 +249,7 @@ fn resolve_object_selection<'a>( graphql_parser::query::Selection::Field(field) => { if field.name == TYPENAME_FIELD { let id = query.push_typename(parent); + acc.push(id); continue; } @@ -250,6 +278,8 @@ fn resolve_object_selection<'a>( } graphql_parser::query::Selection::InlineFragment(inline) => { let selection_id = resolve_inline_fragment(query, object.schema(), inline, parent)?; + + acc.push(selection_id); } graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { let (fragment_id, _) = query From c2a5fe968a1c8fc16e6255434abcf6527a336404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 14 Feb 2020 21:24:42 +0100 Subject: [PATCH 20/93] Simplify method on ResolvedQuery --- graphql_client_codegen/src/codegen.rs | 2 +- graphql_client_codegen/src/resolution.rs | 73 ++++++------------------ 2 files changed, 18 insertions(+), 57 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index f6a7bdb3d..c82c32ccf 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -12,6 +12,7 @@ use quote::quote; /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. pub(crate) fn select_operation<'a>( query: &'a ResolvedQuery, + struct_name: &str, norm: Normalization, ) -> Option { @@ -30,7 +31,6 @@ pub(crate) fn response_for_query( let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); let fragment_definitions: Vec<&'static str> = Vec::new(); - let definitions: Vec<&'static str> = Vec::new(); let input_object_definitions: Vec<&'static str> = Vec::new(); let variables_struct = generate_variables_struct(operation, options); diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index a28b9841b..f395bc9ed 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -5,8 +5,8 @@ use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, schema::{ - resolve_field_type, EnumRef, FieldRef, InterfaceRef, ObjectId, ObjectRef, ScalarRef, - Schema, StoredFieldId, StoredFieldType, TypeId, TypeRef, UnionRef, + resolve_field_type, EnumRef, FieldRef, ObjectId, ScalarRef, Schema, StoredFieldId, + StoredFieldType, TypeId, TypeRef, }, }; use petgraph::prelude::EdgeRef; @@ -248,7 +248,7 @@ fn resolve_object_selection<'a>( match item { graphql_parser::query::Selection::Field(field) => { if field.name == TYPENAME_FIELD { - let id = query.push_typename(parent); + let id = query.push_node(QueryNode::Typename, parent); acc.push(id); continue; } @@ -257,11 +257,11 @@ fn resolve_object_selection<'a>( anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; - let id = query.push_selected_field( - SelectedField { + let id = query.push_node( + QueryNode::SelectedField(SelectedField { alias: field.alias.clone(), field_id: field_ref.id(), - }, + }), parent, ); @@ -286,7 +286,10 @@ fn resolve_object_selection<'a>( .find_fragment(&fragment_spread.fragment_name) .expect("TODO: fragment resolution"); - acc.push(query.push_fragment_spread(ResolvedFragmentId(fragment_id), parent)); + acc.push(query.push_node( + QueryNode::FragmentSpread(ResolvedFragmentId(fragment_id)), + parent, + )); } } } @@ -337,7 +340,7 @@ fn resolve_inline_fragment( .find_type(on) .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; - let id = query.push_inline_fragment(type_id, parent); + let id = query.push_node(QueryNode::InlineFragment(type_id), parent); resolve_selection( query, @@ -436,48 +439,13 @@ pub(crate) struct ResolvedQuery { } impl ResolvedQuery { - fn push_typename(&mut self, parent: Option) -> NodeId { - let idx = self.selection_graph.add_node(QueryNode::Typename); - - self.push_optional_parent(idx, parent); - - idx - } - - fn push_fragment_spread( - &mut self, - fragment_id: ResolvedFragmentId, - parent: Option, - ) -> NodeId { - let id = self - .selection_graph - .add_node(QueryNode::FragmentSpread(fragment_id)); - - self.push_optional_parent(id, parent); - - id - } - - fn push_inline_fragment(&mut self, type_id: TypeId, parent: Option) -> NodeId { - let id = self - .selection_graph - .add_node(QueryNode::InlineFragment(type_id)); - - self.push_optional_parent(id, parent); - - id - } - - fn push_selected_field( - &mut self, - selected_field: SelectedField, - parent: Option, - ) -> NodeId { - let id = self - .selection_graph - .add_node(QueryNode::SelectedField(selected_field)); + fn push_node(&mut self, node: QueryNode, parent: Option) -> NodeId { + let id = self.selection_graph.add_node(node); - self.push_optional_parent(id, parent); + if let Some(parent) = parent { + self.selection_graph + .add_edge(parent, id, QueryEdge::Selection); + } id } @@ -488,13 +456,6 @@ impl ResolvedQuery { .enumerate() .find(|(_, frag)| frag.name == name) } - - fn push_optional_parent(&mut self, id: NodeId, parent: Option) { - if let Some(parent) = parent { - self.selection_graph - .add_edge(parent, id, QueryEdge::Selection); - } - } } #[derive(Debug)] From a8596615ed2dd76eb3179fbbb8c82f5304672a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 14 Feb 2020 23:29:12 +0100 Subject: [PATCH 21/93] Make progress on paths --- graphql_client_codegen/src/codegen.rs | 55 +++++++++++++++++++--- graphql_client_codegen/src/resolution.rs | 58 ++++++++++++++++++++---- graphql_client_codegen/src/schema.rs | 2 +- graphql_client_codegen/src/shared.rs | 7 +-- 4 files changed, 104 insertions(+), 18 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index c82c32ccf..298701169 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -3,6 +3,7 @@ use crate::{ normalization::Normalization, resolution::*, schema::{FieldRef, TypeRef}, + shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; use heck::SnakeCase; @@ -68,6 +69,8 @@ pub(crate) fn response_for_query( #variables_struct }; + // panic!("{}", q); + Ok(q) } @@ -225,6 +228,7 @@ fn render_response_data_fields<'a>( &mut fields, &mut response_types, response_derives, + operation.name(), ); (response_types, fields) @@ -235,13 +239,14 @@ fn render_selection<'a>( field_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, + root_name: &str, ) { for select in selection { match &select.variant() { SelectionVariant::SelectedField { field, alias } => { let f = field; let ident = field_ident(&f, *alias); - let tpe = match f.field_type() { + match f.field_type() { TypeRef::Enum(enm) => { let type_name = Ident::new(enm.name(), Span::call_site()); let type_name = decorate_type(&type_name, f.type_qualifiers()); @@ -257,21 +262,30 @@ fn render_selection<'a>( TypeRef::Input(_) => unreachable!("input object in selection"), TypeRef::Object(object) => { let mut fields = Vec::new(); - let struct_name = Ident::new(object.name(), Span::call_site()); + let struct_name = + Ident::new(&select.full_path_prefix(root_name), Span::call_site()); render_selection( select.subselection(), &mut fields, response_type_buffer, response_derives, + root_name, ); let field_type = decorate_type(&struct_name, f.type_qualifiers()); field_buffer.push(quote!(pub #ident: #field_type)); - response_type_buffer - .push(quote!(#response_derives pub struct #struct_name;)); + + let struct_definition = quote! { + #response_derives + pub struct #struct_name { + #(#fields),* + } + }; + + response_type_buffer.push(struct_definition); } - other => { + _other => { Ident::new("String", Span::call_site()); // unimplemented!("selection on {:?}", other) } @@ -284,7 +298,36 @@ fn render_selection<'a>( )); } SelectionVariant::InlineFragment(inline) => todo!("render inline fragment"), - SelectionVariant::FragmentSpread(frag) => todo!("render fragment spread"), + SelectionVariant::FragmentSpread(frag) => { + let frag = select.refocus(*frag); + let original_field_name = frag.name().to_snake_case(); + let final_field_name = keyword_replace(&original_field_name); + let annotation = field_rename_annotation(&original_field_name, &final_field_name); + let field_ident = Ident::new(&final_field_name, Span::call_site()); + let type_name = Ident::new(frag.name(), Span::call_site()); + field_buffer.push(quote! { + #[serde(flatten)] + pub #annotation #field_ident: #type_name + }); + } // SelectionVariant::FragmentSpread(frag) => { + // let struct_name = frag.name(); + // let struct_ident = Ident::new(struct_name, Span::call_site()); + // let mut fields = Vec::with_capacity(frag.selection_len()); + + // render_selection( + // frag.selection(), + // &mut fields, + // response_type_buffer, + // response_derives, + // ); + + // let fragment_definition = quote! { + // #response_derives + // struct #struct_ident { + // #(#fields),* + // } + // }; + // } } } } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index f395bc9ed..4edf22a31 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -9,21 +9,24 @@ use crate::{ StoredFieldType, TypeId, TypeRef, }, }; +use heck::CamelCase; use petgraph::prelude::EdgeRef; use std::collections::HashSet; +#[derive(Debug, Clone, Copy)] pub(crate) struct SelectionItem<'a> { parent_id: Option, node_id: NodeId, variant: SelectionVariant<'a>, } +#[derive(Debug, Clone, Copy)] pub(crate) enum SelectionVariant<'a> { SelectedField { alias: Option<&'a str>, field: FieldRef<'a>, }, - FragmentSpread(&'a ResolvedFragment), + FragmentSpread(ResolvedFragmentId), InlineFragment(TypeRef<'a>), Typename, } @@ -39,6 +42,33 @@ impl<'a> WithQuery<'a, SelectionItem<'a>> { .map(|parent_id| self.refocus(parent_id).upgrade()) } + pub(crate) fn full_path_prefix(&self, root_name: &str) -> String { + let mut path = vec![self.to_path_segment()]; + + let mut item = *self; + + while let Some(parent) = item.parent() { + item = parent; + path.push(parent.to_path_segment()); + } + + path.push(root_name.to_owned()); + path.reverse(); + path.join("") + } + + fn to_path_segment(&self) -> String { + match self.item.variant { + SelectionVariant::SelectedField { alias, field } => alias + .map(|alias| alias.to_camel_case()) + .unwrap_or_else(|| field.name().to_camel_case()), + SelectionVariant::InlineFragment(type_ref) => { + format!("On{}", type_ref.name().to_camel_case()) + } + _ => unreachable!(), + } + } + pub(crate) fn subselection<'b>( &'b self, ) -> impl Iterator>> + 'b { @@ -56,7 +86,7 @@ pub(crate) struct WithQuery<'a, T> { } impl<'a, T> WithQuery<'a, T> { - fn refocus(&self, new_item: U) -> WithQuery<'a, U> { + pub(crate) fn refocus(&self, new_item: U) -> WithQuery<'a, U> { WithQuery { query: self.query, schema: self.schema, @@ -109,7 +139,7 @@ impl<'a> WithQuery<'a, NodeId> { QueryNode::FragmentSpread(fragment_id) => { used_types.fragments.insert(*fragment_id); - for item in self.refocus(*fragment_id).selection() { + for item in self.refocus(*fragment_id).selection_ids() { item.collect_used_types(used_types); } } @@ -121,9 +151,7 @@ impl<'a> WithQuery<'a, NodeId> { let node = self.get_node(); let variant = match node.item { - QueryNode::FragmentSpread(frag_id) => { - SelectionVariant::FragmentSpread(self.query.fragments.get(frag_id.0).unwrap()) - } + QueryNode::FragmentSpread(frag_id) => SelectionVariant::FragmentSpread(*frag_id), QueryNode::InlineFragment(type_id) => { SelectionVariant::InlineFragment(type_id.upgrade(self.schema)) } @@ -489,7 +517,7 @@ impl<'a> Operation<'a> { self.query.operations.get(self.operation_id).unwrap() } - fn name(&self) -> &'a str { + pub(crate) fn name(&self) -> &'a str { self.get().name() } @@ -600,13 +628,27 @@ impl<'a> WithQuery<'a, ResolvedFragmentId> { self.query.fragments.get(self.item.0).unwrap() } - fn selection<'b>(&'b self) -> impl Iterator> + 'b { + fn selection_ids<'b>(&'b self) -> impl Iterator> + 'b { let fragment = self.get(); fragment .selection .iter() .map(move |item| self.refocus(*item)) } + + pub(crate) fn selection<'b>( + &'b self, + ) -> impl Iterator>> + 'b { + self.selection_ids().map(|sel| sel.upgrade()) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn selection_len(&self) -> usize { + self.get().selection.len() + } } #[derive(Debug, Default)] diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index ea2f815a1..64631af13 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -682,7 +682,7 @@ impl<'a> ObjectRef<'a> { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub(crate) struct FieldRef<'a> { schema: SchemaRef<'a>, field_id: StoredFieldId, diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 130c03701..63c709ae5 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -6,6 +6,7 @@ // use heck::{CamelCase, SnakeCase}; use proc_macro2::TokenStream; use quote::quote; +use std::borrow::Cow; // List of keywords based on https://doc.rust-lang.org/grammar.html#keywords const RUST_KEYWORDS: &[&str] = &[ @@ -70,10 +71,10 @@ const RUST_KEYWORDS: &[&str] = &[ "yield", ]; -pub(crate) fn keyword_replace(needle: &str) -> String { +pub(crate) fn keyword_replace(needle: &str) -> Cow<'_, str> { match RUST_KEYWORDS.binary_search(&needle) { - Ok(index) => [RUST_KEYWORDS[index], "_"].concat(), - Err(_) => needle.to_owned(), + Ok(index) => [RUST_KEYWORDS[index], "_"].concat().into(), + Err(_) => Cow::Borrowed(needle), } } From 2f831d80787151222923022a5621b77edfafd92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 15 Feb 2020 10:58:02 +0100 Subject: [PATCH 22/93] Collect used types from variables too --- graphql_client_codegen/src/codegen.rs | 14 ++++++++++++- graphql_client_codegen/src/resolution.rs | 25 ++++++++++++++++++++++++ graphql_client_codegen/src/schema.rs | 11 +++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 298701169..9cb12be78 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -32,7 +32,8 @@ pub(crate) fn response_for_query( let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); let fragment_definitions: Vec<&'static str> = Vec::new(); - let input_object_definitions: Vec<&'static str> = Vec::new(); + let input_object_definitions = + generate_input_object_definitions(operation, &all_used_types, options); let variables_struct = generate_variables_struct(operation, options); let response_derives = render_derives(options.all_response_derives()); @@ -374,3 +375,14 @@ fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStr qualified } + +fn generate_input_object_definitions( + operation: Operation<'_>, + all_used_types: &UsedTypes, + options: &GraphQLClientCodegenOptions, +) -> Vec { + all_used_types + .inputs(operation.schema()) + .map(|input| quote!(heh)) + .collect() +} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 4edf22a31..364891346 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,6 +1,7 @@ //! The responsibility of this module is to resolve and validate a query //! against a given schema. +use crate::schema::InputRef; use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, @@ -554,6 +555,10 @@ impl<'a> Operation<'a> { selection.collect_used_types(&mut all_used_types); } + for variable in self.variables() { + variable.collect_used_types(&mut all_used_types); + } + all_used_types } @@ -621,6 +626,17 @@ impl<'a> Variable<'a> { pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { &self.get().r#type.qualifiers } + + fn collect_used_types(&self, used_types: &mut UsedTypes) { + match self.get().r#type.id { + type_id @ TypeId::Input(_) + | type_id @ TypeId::Scalar(_) + | type_id @ TypeId::Enum(_) => { + used_types.types.insert(type_id); + } + _ => (), + } + } } impl<'a> WithQuery<'a, ResolvedFragmentId> { @@ -658,6 +674,15 @@ pub(crate) struct UsedTypes { } impl UsedTypes { + pub(crate) fn inputs<'s, 'a: 's>( + &'s self, + schema: &'a Schema, + ) -> impl Iterator> + 's { + schema + .inputs() + .filter(move |input_ref| self.types.contains(&input_ref.type_id())) + } + pub(crate) fn scalars<'s, 'a: 's>( &'s self, schema: &'a Schema, diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 64631af13..fa2a6091c 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -623,6 +623,13 @@ impl Schema { } } } + + pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { + (0..self.stored_inputs.len()).map(move |id| InputRef { + schema: self, + input_id: InputId(id), + }) + } } pub(crate) struct FieldsRef<'a> { @@ -725,6 +732,10 @@ impl<'a> InputRef<'a> { self.schema.get_stored_input(self.input_id) } + pub(crate) fn type_id(&self) -> TypeId { + TypeId::Input(self.input_id) + } + pub(crate) fn name(&self) -> &'a str { &self.get().name } From bb51d419dd6e30cfae31318ce609107fb3df42c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 01:22:42 +0100 Subject: [PATCH 23/93] Ditch refs --- graphql_client_codegen/src/codegen.rs | 70 ++- .../src/generated_module.rs | 25 +- graphql_client_codegen/src/resolution.rs | 590 ++++++++++-------- graphql_client_codegen/src/schema.rs | 209 +++++-- 4 files changed, 534 insertions(+), 360 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 9cb12be78..955be3db2 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -2,7 +2,7 @@ use crate::{ field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, - schema::{FieldRef, TypeRef}, + schema::{FieldRef, TypeId, TypeRef}, shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; @@ -25,7 +25,7 @@ pub(crate) fn select_operation<'a>( /// The main code generation function. pub(crate) fn response_for_query( - operation: Operation<'_>, + operation: WithQuery<'_, OperationId>, options: &GraphQLClientCodegenOptions, ) -> anyhow::Result { let all_used_types = operation.all_used_types(); @@ -76,7 +76,7 @@ pub(crate) fn response_for_query( } fn generate_variables_struct( - operation: Operation<'_>, + operation: WithQuery<'_, OperationId>, options: &GraphQLClientCodegenOptions, ) -> TokenStream { let variable_derives = options.all_variable_derives(); @@ -101,7 +101,7 @@ fn generate_variables_struct( variables_struct.into() } -fn generate_variable_struct_field(variable: Variable<'_>) -> TokenStream { +fn generate_variable_struct_field(variable: WithQuery<'_, VariableId>) -> TokenStream { let snake_case_name = variable.name().to_snake_case(); let ident = Ident::new( &crate::shared::keyword_replace(&snake_case_name), @@ -114,7 +114,7 @@ fn generate_variable_struct_field(variable: Variable<'_>) -> TokenStream { } fn generate_scalar_definitions<'a, 'schema: 'a>( - operation: Operation<'schema>, + operation: WithQuery<'schema, OperationId>, all_used_types: &'a crate::resolution::UsedTypes, ) -> impl Iterator + 'a { all_used_types.scalars(operation.schema()).map(|scalar| { @@ -131,7 +131,7 @@ fn generate_scalar_definitions<'a, 'schema: 'a>( * Generated serialize line: "AnEnum::where_ => "where"," */ fn generate_enum_definitions<'a, 'schema: 'a>( - operation: Operation<'schema>, + operation: WithQuery<'schema, OperationId>, all_used_types: &'a crate::resolution::UsedTypes, options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { @@ -211,21 +211,21 @@ fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToT quote!(#[derive(#(#idents),*)]) } -fn render_variable_field_type(variable: Variable<'_>) -> TokenStream { +fn render_variable_field_type(variable: WithQuery<'_, VariableId>) -> TokenStream { let full_name = Ident::new(variable.type_name(), Span::call_site()); decorate_type(&full_name, variable.type_qualifiers()) } fn render_response_data_fields<'a>( - operation: &'a Operation<'_>, + operation: &WithQuery<'a, OperationId>, response_derives: &impl quote::ToTokens, ) -> (Vec, Vec) { let mut response_types = Vec::new(); let mut fields = Vec::new(); render_selection( - operation.rich_selection(), + operation.selection(), &mut fields, &mut response_types, response_derives, @@ -236,35 +236,37 @@ fn render_response_data_fields<'a>( } fn render_selection<'a>( - selection: impl Iterator>>, + selection: impl Iterator>, field_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, root_name: &str, ) { for select in selection { - match &select.variant() { - SelectionVariant::SelectedField { field, alias } => { - let f = field; - let ident = field_ident(&f, *alias); - match f.field_type() { - TypeRef::Enum(enm) => { - let type_name = Ident::new(enm.name(), Span::call_site()); - let type_name = decorate_type(&type_name, f.type_qualifiers()); + match &select.get() { + Selection::Field(field) => { + let field = select.refocus(field); + let ident = field_ident(&field); + match field.schema_field().field_type().item { + TypeId::Enum(enm) => { + let type_name = + Ident::new(field.with_schema(enm).name(), Span::call_site()); + let type_name = + decorate_type(&type_name, field.schema_field().type_qualifiers()); field_buffer.push(quote!(pub #ident: #type_name)); } - TypeRef::Scalar(scalar) => { - let type_name = Ident::new(scalar.name(), Span::call_site()); - let type_name = decorate_type(&type_name, f.type_qualifiers()); + TypeId::Scalar(scalar) => { + let type_name = + Ident::new(field.with_schema(scalar).name(), Span::call_site()); + let type_name = + decorate_type(&type_name, field.schema_field().type_qualifiers()); field_buffer.push(quote!(pub #ident: #type_name)); } - TypeRef::Input(_) => unreachable!("input object in selection"), - TypeRef::Object(object) => { + TypeId::Object(object) => { let mut fields = Vec::new(); - let struct_name = - Ident::new(&select.full_path_prefix(root_name), Span::call_site()); + let struct_name = Ident::new(&select.full_path_prefix(), Span::call_site()); render_selection( select.subselection(), &mut fields, @@ -273,7 +275,8 @@ fn render_selection<'a>( root_name, ); - let field_type = decorate_type(&struct_name, f.type_qualifiers()); + let field_type = + decorate_type(&struct_name, field.schema_field().type_qualifiers()); field_buffer.push(quote!(pub #ident: #field_type)); @@ -292,14 +295,14 @@ fn render_selection<'a>( } }; } - SelectionVariant::Typename => { + Selection::Typename => { field_buffer.push(quote!( #[serde(rename = "__typename")] pub typename: String )); } - SelectionVariant::InlineFragment(inline) => todo!("render inline fragment"), - SelectionVariant::FragmentSpread(frag) => { + Selection::InlineFragment(inline) => todo!("render inline fragment"), + Selection::FragmentSpread(frag) => { let frag = select.refocus(*frag); let original_field_name = frag.name().to_snake_case(); let final_field_name = keyword_replace(&original_field_name); @@ -333,8 +336,11 @@ fn render_selection<'a>( } } -fn field_ident(field: &FieldRef<'_>, alias: Option<&str>) -> Ident { - let name = alias.unwrap_or_else(|| field.name()).to_snake_case(); +fn field_ident(field: &WithQuery<'_, &SelectedField>) -> Ident { + let name = field + .alias() + .unwrap_or_else(|| field.name()) + .to_snake_case(); Ident::new(&name, Span::call_site()) } @@ -377,7 +383,7 @@ fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStr } fn generate_input_object_definitions( - operation: Operation<'_>, + operation: WithQuery<'_, OperationId>, all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, ) -> Vec { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 2273dc1b9..80c0db3f1 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,4 +1,7 @@ -use crate::{codegen_options::*, resolution::Operation}; +use crate::{ + codegen_options::*, + resolution::{Operation, OperationId, WithQuery}, +}; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -16,19 +19,25 @@ pub(crate) struct GeneratedModule<'a> { impl<'a> GeneratedModule<'a> { /// Generate the items for the variables and the response that will go inside the module. fn build_impls(&self) -> anyhow::Result { - let root = crate::codegen::select_operation( + Ok(crate::codegen::response_for_query( + self.root(), + &self.options, + )?) + } + + fn root(&self) -> WithQuery<'_, OperationId> { + let operation = crate::codegen::select_operation( &self.resolved_query, &self.operation, self.options.normalization(), ) .expect("TODO: handle operation not found"); - let operation = Operation::new(root, self.schema, self.resolved_query); - - Ok(crate::codegen::response_for_query( - operation, - &self.options, - )?) + WithQuery::new( + self.resolved_query, + self.schema, + OperationId::new(operation), + ) } /// Generate the module and all the code inside. diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 364891346..93a8280e0 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,128 +1,163 @@ //! The responsibility of this module is to resolve and validate a query //! against a given schema. -use crate::schema::InputRef; use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, schema::{ - resolve_field_type, EnumRef, FieldRef, ObjectId, ScalarRef, Schema, StoredFieldId, - StoredFieldType, TypeId, TypeRef, + resolve_field_type, EnumRef, FieldRef, InputId, ObjectId, ScalarRef, Schema, StoredFieldId, + StoredFieldType, TypeId, TypeRef, WithSchema, }, }; use heck::CamelCase; use petgraph::prelude::EdgeRef; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; -#[derive(Debug, Clone, Copy)] -pub(crate) struct SelectionItem<'a> { - parent_id: Option, - node_id: NodeId, - variant: SelectionVariant<'a>, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum SelectionVariant<'a> { - SelectedField { - alias: Option<&'a str>, - field: FieldRef<'a>, - }, - FragmentSpread(ResolvedFragmentId), - InlineFragment(TypeRef<'a>), - Typename, -} +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) struct SelectionId(u32); -impl<'a> WithQuery<'a, SelectionItem<'a>> { - pub(crate) fn variant(&self) -> &SelectionVariant<'a> { - &self.item.variant - } +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct OperationId(u32); - pub(crate) fn parent(&self) -> Option>> { - self.item - .parent_id - .map(|parent_id| self.refocus(parent_id).upgrade()) +impl OperationId { + pub(crate) fn new(idx: usize) -> Self { + OperationId(idx as u32) } +} - pub(crate) fn full_path_prefix(&self, root_name: &str) -> String { - let mut path = vec![self.to_path_segment()]; - - let mut item = *self; - - while let Some(parent) = item.parent() { - item = parent; - path.push(parent.to_path_segment()); - } +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) struct ResolvedFragmentId(u32); - path.push(root_name.to_owned()); - path.reverse(); - path.join("") - } +#[derive(Debug, Clone, Copy)] +pub(crate) struct VariableId(u32); - fn to_path_segment(&self) -> String { - match self.item.variant { - SelectionVariant::SelectedField { alias, field } => alias - .map(|alias| alias.to_camel_case()) - .unwrap_or_else(|| field.name().to_camel_case()), - SelectionVariant::InlineFragment(type_ref) => { - format!("On{}", type_ref.name().to_camel_case()) - } - _ => unreachable!(), - } +impl VariableId { + fn new(idx: usize) -> Self { + VariableId(idx as u32) } +} - pub(crate) fn subselection<'b>( - &'b self, - ) -> impl Iterator>> + 'b { - let id_selection = self.refocus(self.item.node_id); +#[derive(Debug, Clone, Copy)] +enum SelectionParent { + Selection(SelectionId), + Fragment(ResolvedFragmentId), + Operation(OperationId), +} - id_selection.into_subselection().map(move |s| s.upgrade()) +impl SelectionParent { + fn add_to_selection_set(&self, q: &mut ResolvedQuery, selection_id: SelectionId) { + match self { + SelectionParent::Selection(selection_id) => todo!(), + SelectionParent::Fragment(fragment_id) => todo!(), + SelectionParent::Operation(operation_id) => todo!(), + } } } +// #[derive(Debug, Clone, Copy)] +// pub(crate) struct SelectionItem<'a> { +// parent_id: Option, +// node_id: NodeId, +// variant: SelectionVariant<'a>, +// } + +// #[derive(Debug, Clone, Copy)] +// pub(crate) enum SelectionVariant<'a> { +// SelectedField { +// alias: Option<&'a str>, +// field: FieldRef<'a>, +// }, +// FragmentSpread(ResolvedFragmentId), +// InlineFragment(TypeRef<'a>), +// Typename, +// } + +// impl<'a> WithQuery<'a, SelectionItem<'a>> { +// pub(crate) fn variant(&self) -> &SelectionVariant<'a> { +// &self.item.variant +// } + +// pub(crate) fn parent(&self) -> Option>> { +// self.item +// .parent_id +// .map(|parent_id| self.refocus(parent_id).upgrade()) +// } + +// fn to_path_segment(&self) -> String { +// match self.item.variant { +// SelectionVariant::SelectedField { alias, field } => alias +// .map(|alias| alias.to_camel_case()) +// .unwrap_or_else(|| field.name().to_camel_case()), +// SelectionVariant::InlineFragment(type_ref) => { +// format!("On{}", type_ref.name().to_camel_case()) +// } +// _ => unreachable!(), +// } +// } + +// pub(crate) fn subselection<'b>( +// &'b self, +// ) -> impl Iterator>> + 'b { +// let id_selection = self.refocus(self.item.node_id); + +// id_selection.into_subselection().map(move |s| s.upgrade()) +// } +// } + #[derive(Debug, Clone, Copy)] pub(crate) struct WithQuery<'a, T> { query: &'a ResolvedQuery, schema: &'a Schema, - item: T, + pub(crate) item: T, } impl<'a, T> WithQuery<'a, T> { - pub(crate) fn refocus(&self, new_item: U) -> WithQuery<'a, U> { + pub(crate) fn new(query: &'a ResolvedQuery, schema: &'a Schema, item: T) -> WithQuery<'a, T> { + WithQuery { + query, + schema, + item, + } + } + + pub(crate) fn refocus(&self, item: U) -> WithQuery<'a, U> { WithQuery { query: self.query, schema: self.schema, - item: new_item, + item, } } + + pub(crate) fn with_schema(&self, item: U) -> WithSchema<'a, U> { + WithSchema::new(self.schema, item) + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.schema + } } type NodeId = petgraph::prelude::NodeIndex; type SelectionGraph = petgraph::Graph; -impl<'a> WithQuery<'a, NodeId> { - fn get_node(&self) -> WithQuery<'a, &'a QueryNode> { - let item = &self.query.selection_graph[self.item]; - self.refocus(item) - } - - pub fn into_subselection(self) -> impl Iterator> { +impl<'a> WithQuery<'a, SelectionId> { + pub(crate) fn get(&self) -> &'a Selection { self.query - .selection_graph - .edges_directed(self.item, petgraph::Direction::Outgoing) - .filter(|edge| match edge.weight() { - QueryEdge::Selection => true, - }) - .map(move |edge| self.refocus(edge.target())) + .selections + .get(self.item.0 as usize) + .expect("SelectionId resolution") } - pub(crate) fn subselection<'b>(&'b self) -> impl Iterator> + 'b { - self.into_subselection() + pub(crate) fn subselection<'b>( + &'b self, + ) -> impl Iterator> + 'b { + self.get().subselection().map(move |s| self.refocus(*s)) } pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { - let node = self.get_node(); - match node.item { - QueryNode::SelectedField(field) => { + let selection = self.get(); + match selection { + Selection::Field(field) => { let field_ref = self.schema.field(field.field_id); used_types.types.insert(field_ref.type_id()); @@ -130,58 +165,118 @@ impl<'a> WithQuery<'a, NodeId> { item.collect_used_types(used_types); } } - QueryNode::InlineFragment(type_id) => { - used_types.types.insert(*type_id); + Selection::InlineFragment(inline_fragment) => { + used_types.types.insert(inline_fragment.type_id); for item in self.subselection() { item.collect_used_types(used_types); } } - QueryNode::FragmentSpread(fragment_id) => { + Selection::FragmentSpread(fragment_id) => { used_types.fragments.insert(*fragment_id); - for item in self.refocus(*fragment_id).selection_ids() { + for item in self.refocus(*fragment_id).selection() { item.collect_used_types(used_types); } } - QueryNode::Typename => (), + Selection::Typename => (), } } - fn upgrade(&self) -> WithQuery<'a, SelectionItem<'a>> { - let node = self.get_node(); + // fn upgrade(&self) -> WithQuery<'a, SelectionItem<'a>> { + // let node = self.get_node(); + + // let variant = match node.item { + // QueryNode::FragmentSpread(frag_id) => SelectionVariant::FragmentSpread(*frag_id), + // QueryNode::InlineFragment(type_id) => { + // SelectionVariant::InlineFragment(type_id.upgrade(self.schema)) + // } + // QueryNode::Typename => SelectionVariant::Typename, + // QueryNode::SelectedField(f) => SelectionVariant::SelectedField { + // alias: f.alias.as_ref().map(String::as_str), + // field: self.schema.field(f.field_id), + // }, + // }; + + // self.refocus(SelectionItem { + // node_id: self.item, + // parent_id: self.parent_id(), + // variant, + // }) + // } + + pub(crate) fn full_path_prefix(&self) -> String { + let mut path = vec![self.to_path_segment()]; + + let mut item = self.item; + + while let Some(parent) = self.query.selection_parent_idx.get(&item) { + path.push(self.refocus(*parent).to_path_segment()); - let variant = match node.item { - QueryNode::FragmentSpread(frag_id) => SelectionVariant::FragmentSpread(*frag_id), - QueryNode::InlineFragment(type_id) => { - SelectionVariant::InlineFragment(type_id.upgrade(self.schema)) + match parent { + SelectionParent::Selection(id) => { + item = *id; + } + _ => break, } - QueryNode::Typename => SelectionVariant::Typename, - QueryNode::SelectedField(f) => SelectionVariant::SelectedField { - alias: f.alias.as_ref().map(String::as_str), - field: self.schema.field(f.field_id), - }, - }; - - self.refocus(SelectionItem { - node_id: self.item, - parent_id: self.parent_id(), - variant, - }) + } + + path.reverse(); + path.join("") } - fn parent_id(&self) -> Option { - self.query - .selection_graph - .edges_directed(self.item, petgraph::Direction::Incoming) - .filter(|edge| match edge.weight() { - QueryEdge::Selection => true, - }) - .map(|edge| edge.source()) - .next() + fn to_path_segment(&self) -> String { + match self.get() { + Selection::Field(field) => field + .alias + .as_ref() + .map(|alias| alias.to_camel_case()) + .unwrap_or_else(move || self.with_schema(field.field_id).name().to_camel_case()), + Selection::InlineFragment(inline_fragment) => format!( + "On{}", + self.with_schema(inline_fragment.type_id) + .name() + .to_camel_case() + ), + _ => unreachable!(), + } + } +} + +impl<'a> WithQuery<'a, SelectionParent> { + pub(crate) fn to_path_segment(&self) -> String { + match self.item { + SelectionParent::Selection(id) => self.refocus(id).to_path_segment(), + SelectionParent::Operation(id) => self.refocus(id).to_path_segment(), + SelectionParent::Fragment(id) => self.refocus(id).to_path_segment(), + } } } +#[derive(Debug)] +pub(crate) enum Selection { + Field(SelectedField), + InlineFragment(InlineFragment), + FragmentSpread(ResolvedFragmentId), + Typename, +} + +impl Selection { + fn subselection(&self) -> impl Iterator { + match self { + Selection::Field(field) => field.selection_set.iter(), + Selection::InlineFragment(inline_fragment) => inline_fragment.selection_set.iter(), + _ => [].iter(), + } + } +} + +#[derive(Debug)] +pub(crate) struct InlineFragment { + type_id: TypeId, + selection_set: Vec, +} + #[derive(Debug)] enum QueryNode { SelectedField(SelectedField), @@ -196,9 +291,33 @@ enum QueryEdge { } #[derive(Debug)] -struct SelectedField { +pub(crate) struct SelectedField { alias: Option, field_id: StoredFieldId, + selection_set: Vec, +} + +impl<'a> WithQuery<'a, &'a SelectedField> { + pub(crate) fn alias(&self) -> Option<&str> { + self.item.alias.as_ref().map(String::as_str) + } + + pub(crate) fn name(&self) -> &'a str { + self.with_schema(self.item.field_id).name() + } + + pub(crate) fn selection_set<'b>( + &'b self, + ) -> impl Iterator> + 'b { + self.item + .selection_set + .iter() + .map(move |id| self.refocus(*id)) + } + + pub(crate) fn schema_field(&self) -> WithSchema<'a, StoredFieldId> { + self.with_schema(self.item.field_id) + } } pub(crate) fn resolve( @@ -245,24 +364,18 @@ fn resolve_fragment( let graphql_parser::query::TypeCondition::On(on) = &fragment_definition.type_condition; let on = schema.find_type(&on).unwrap(); - let mut acc = - SelectionAccumulator::with_capacity(fragment_definition.selection_set.items.len()); + let (id, _) = query + .find_fragment(&fragment_definition.name) + .expect("TODO: fragment resolution"); resolve_selection( query, schema, on, &fragment_definition.selection_set, - None, - &mut acc, + SelectionParent::Fragment(id), )?; - let (_, mut fragment) = query - .find_fragment(&fragment_definition.name) - .expect("TODO: fragment resolution"); - - fragment.selection = acc.into_vec(); - Ok(()) } @@ -270,15 +383,14 @@ fn resolve_object_selection<'a>( query: &mut ResolvedQuery, object: impl crate::schema::ObjectRefLike<'a>, selection_set: &graphql_parser::query::SelectionSet, - parent: Option, - acc: &mut SelectionAccumulator, + parent: SelectionParent, ) -> anyhow::Result<()> { for item in selection_set.items.iter() { match item { graphql_parser::query::Selection::Field(field) => { if field.name == TYPENAME_FIELD { - let id = query.push_node(QueryNode::Typename, parent); - acc.push(id); + let id = query.push_selection(Selection::Typename, parent); + parent.add_to_selection_set(query, id); continue; } @@ -286,10 +398,11 @@ fn resolve_object_selection<'a>( anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) })?; - let id = query.push_node( - QueryNode::SelectedField(SelectedField { + let id = query.push_selection( + Selection::Field(SelectedField { alias: field.alias.clone(), field_id: field_ref.id(), + selection_set: Vec::with_capacity(selection_set.items.len()), }), parent, ); @@ -299,26 +412,24 @@ fn resolve_object_selection<'a>( object.schema(), field_ref.type_id(), &field.selection_set, - Some(id), - &mut SelectionAccumulator::noop(), + SelectionParent::Selection(id), )?; - acc.push(id) + parent.add_to_selection_set(query, id); } graphql_parser::query::Selection::InlineFragment(inline) => { let selection_id = resolve_inline_fragment(query, object.schema(), inline, parent)?; - acc.push(selection_id); + parent.add_to_selection_set(query, selection_id); } graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { let (fragment_id, _) = query .find_fragment(&fragment_spread.fragment_name) .expect("TODO: fragment resolution"); - acc.push(query.push_node( - QueryNode::FragmentSpread(ResolvedFragmentId(fragment_id)), - parent, - )); + let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent); + + parent.add_to_selection_set(query, id); } } } @@ -331,17 +442,16 @@ fn resolve_selection( schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, - parent: Option, - acc: &mut SelectionAccumulator, + parent: SelectionParent, ) -> anyhow::Result<()> { let selection = match on { TypeId::Object(oid) => { let object = schema.object(oid); - resolve_object_selection(ctx, object, selection_set, parent, acc)?; + resolve_object_selection(ctx, object, selection_set, parent)?; } TypeId::Interface(interface_id) => { let interface = schema.interface(interface_id); - resolve_object_selection(ctx, interface, selection_set, parent, acc)?; + resolve_object_selection(ctx, interface, selection_set, parent)?; } other => { anyhow::ensure!( @@ -359,8 +469,8 @@ fn resolve_inline_fragment( query: &mut ResolvedQuery, schema: &Schema, inline_fragment: &graphql_parser::query::InlineFragment, - parent: Option, -) -> anyhow::Result { + parent: SelectionParent, +) -> anyhow::Result { let graphql_parser::query::TypeCondition::On(on) = inline_fragment .type_condition .as_ref() @@ -369,15 +479,20 @@ fn resolve_inline_fragment( .find_type(on) .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; - let id = query.push_node(QueryNode::InlineFragment(type_id), parent); + let id = query.push_selection( + Selection::InlineFragment(InlineFragment { + type_id, + selection_set: Vec::with_capacity(inline_fragment.selection_set.items.len()), + }), + parent, + ); resolve_selection( query, schema, type_id, &inline_fragment.selection_set, - Some(id), - &mut SelectionAccumulator::noop(), + SelectionParent::Selection(id), )?; Ok(id) @@ -391,8 +506,12 @@ fn resolve_operation( match operation { graphql_parser::query::OperationDefinition::Mutation(m) => { let on = schema.mutation_type(); - let mut acc = SelectionAccumulator::with_capacity(m.selection_set.items.len()); - resolve_object_selection(query, on, &m.selection_set, None, &mut acc)?; + resolve_object_selection( + query, + on, + &m.selection_set, + SelectionParent::Operation(OperationId(query.operations.len() as u32)), + )?; let resolved_operation: ResolvedOperation = ResolvedOperation { object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), @@ -400,17 +519,21 @@ fn resolve_operation( variables: resolve_variables( &m.variable_definitions, schema, - query.operations.len(), + OperationId(query.operations.len() as u32), )?, - selection: acc.into_vec(), + selection: Vec::with_capacity(m.selection_set.items.len()), }; query.operations.push(resolved_operation); } graphql_parser::query::OperationDefinition::Query(q) => { let on = schema.query_type(); - let mut acc = SelectionAccumulator::with_capacity(q.selection_set.items.len()); - resolve_object_selection(query, on, &q.selection_set, None, &mut acc)?; + resolve_object_selection( + query, + on, + &q.selection_set, + SelectionParent::Operation(OperationId(query.operations.len() as u32)), + )?; let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), @@ -418,18 +541,22 @@ fn resolve_operation( variables: resolve_variables( &q.variable_definitions, schema, - query.operations.len(), + OperationId(query.operations.len() as u32), )?, object_id: on.id(), - selection: acc.into_vec(), + selection: Vec::with_capacity(q.selection_set.items.len()), }; query.operations.push(resolved_operation); } graphql_parser::query::OperationDefinition::Subscription(s) => { let on = schema.subscription_type(); - let mut acc = SelectionAccumulator::with_capacity(s.selection_set.items.len()); - resolve_object_selection(query, on, &s.selection_set, None, &mut acc)?; + resolve_object_selection( + query, + on, + &s.selection_set, + SelectionParent::Operation(OperationId(query.operations.len() as u32)), + )?; let resolved_operation: ResolvedOperation = ResolvedOperation { name: s @@ -441,10 +568,10 @@ fn resolve_operation( variables: resolve_variables( &s.variable_definitions, schema, - query.operations.len(), + OperationId(query.operations.len() as u32), )?, object_id: on.id(), - selection: acc.into_vec(), + selection: Vec::with_capacity(s.selection_set.items.len()), }; query.operations.push(resolved_operation); @@ -457,33 +584,31 @@ fn resolve_operation( Ok(()) } -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub(crate) struct ResolvedFragmentId(usize); - #[derive(Debug, Default)] pub(crate) struct ResolvedQuery { - pub(crate) operations: Vec, fragments: Vec, - selection_graph: SelectionGraph, + pub(crate) operations: Vec, + selection_parent_idx: HashMap, + selections: Vec, + variables: Vec, } impl ResolvedQuery { - fn push_node(&mut self, node: QueryNode, parent: Option) -> NodeId { - let id = self.selection_graph.add_node(node); + fn push_selection(&mut self, node: Selection, parent: SelectionParent) -> SelectionId { + let id = SelectionId(self.selections.len() as u32); + self.selections.push(node); - if let Some(parent) = parent { - self.selection_graph - .add_edge(parent, id, QueryEdge::Selection); - } + self.selection_parent_idx.insert(id, parent); id } - fn find_fragment(&mut self, name: &str) -> Option<(usize, &mut ResolvedFragment)> { + fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> { self.fragments .iter_mut() .enumerate() .find(|(_, frag)| frag.name == name) + .map(|(id, f)| (ResolvedFragmentId(id as u32), f)) } } @@ -491,7 +616,7 @@ impl ResolvedQuery { pub(crate) struct ResolvedFragment { name: String, on: crate::schema::TypeId, - selection: Vec, + selection: Vec, } #[derive(Debug, Clone, Copy)] @@ -501,51 +626,13 @@ pub(crate) struct Operation<'a> { query: &'a ResolvedQuery, } -impl<'a> Operation<'a> { - pub(crate) fn new( - operation_id: usize, - schema: &'a Schema, - query: &'a ResolvedQuery, - ) -> Operation<'a> { - Operation { - operation_id, - schema, - query, - } - } - +impl<'a> WithQuery<'a, OperationId> { fn get(&self) -> &'a ResolvedOperation { - self.query.operations.get(self.operation_id).unwrap() - } - - pub(crate) fn name(&self) -> &'a str { - self.get().name() - } - - pub(crate) fn rich_selection<'b>( - &'b self, - ) -> impl Iterator>> + 'b { - self.selection().map(|s| s.upgrade()) - } - - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { - let operation = self.get(); - operation - .selection - .iter() - .map(move |selection_id| WithQuery { - item: *selection_id, - query: self.query, - schema: self.schema, - }) - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.schema + self.query.operations.get(self.item.0 as usize).unwrap() } - pub(crate) fn query(&self) -> &'a ResolvedQuery { - self.query + fn to_path_segment(&self) -> String { + self.get().name.to_camel_case() } pub(crate) fn all_used_types(&self) -> UsedTypes { @@ -562,19 +649,29 @@ impl<'a> Operation<'a> { all_used_types } - pub(crate) fn has_no_variables(&self) -> bool { - self.get().variables.is_empty() + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { + let operation = self.get(); + operation + .selection + .iter() + .map(move |selection_id| self.refocus(*selection_id)) } - pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { - self.get() + pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { + self.query .variables .iter() .enumerate() - .map(move |(idx, _)| Variable { - variable_id: idx, - operation: *self, - }) + .filter(move |(_, variable)| variable.operation_id == self.item) + .map(move |(id, _)| self.refocus(VariableId::new(id))) + } + + pub(crate) fn name(&self) -> &'a str { + self.get().name() + } + + pub(crate) fn has_no_variables(&self) -> bool { + self.get().variables.is_empty() } } @@ -583,7 +680,7 @@ pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, variables: Vec, - selection: Vec, + selection: Vec, object_id: ObjectId, } @@ -595,24 +692,15 @@ impl ResolvedOperation { #[derive(Debug)] struct ResolvedVariable { - operation_id: usize, + operation_id: OperationId, name: String, default: Option, r#type: StoredFieldType, } -pub(crate) struct Variable<'a> { - operation: Operation<'a>, - variable_id: usize, -} - -impl<'a> Variable<'a> { +impl<'a> WithQuery<'a, VariableId> { fn get(&self) -> &'a ResolvedVariable { - self.operation - .get() - .variables - .get(self.variable_id) - .unwrap() + self.query.variables.get(self.item.0 as usize).unwrap() } pub(crate) fn name(&self) -> &'a str { @@ -620,7 +708,7 @@ impl<'a> Variable<'a> { } pub(crate) fn type_name(&self) -> &'a str { - self.get().r#type.id.upgrade(self.operation.schema()).name() + self.with_schema(self.get().r#type.id).name() } pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { @@ -641,10 +729,10 @@ impl<'a> Variable<'a> { impl<'a> WithQuery<'a, ResolvedFragmentId> { fn get(&self) -> &'a ResolvedFragment { - self.query.fragments.get(self.item.0).unwrap() + self.query.fragments.get(self.item.0 as usize).unwrap() } - fn selection_ids<'b>(&'b self) -> impl Iterator> + 'b { + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { let fragment = self.get(); fragment .selection @@ -652,10 +740,8 @@ impl<'a> WithQuery<'a, ResolvedFragmentId> { .map(move |item| self.refocus(*item)) } - pub(crate) fn selection<'b>( - &'b self, - ) -> impl Iterator>> + 'b { - self.selection_ids().map(|sel| sel.upgrade()) + fn to_path_segment(&self) -> String { + self.get().name.to_camel_case() } pub(crate) fn name(&self) -> &'a str { @@ -677,7 +763,7 @@ impl UsedTypes { pub(crate) fn inputs<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator> + 's { schema .inputs() .filter(move |input_ref| self.types.contains(&input_ref.type_id())) @@ -708,7 +794,7 @@ impl UsedTypes { fn resolve_variables( variables: &[graphql_parser::query::VariableDefinition], schema: &Schema, - operation_id: usize, + operation_id: OperationId, ) -> Result, anyhow::Error> { variables .iter() @@ -722,25 +808,3 @@ fn resolve_variables( }) .collect() } - -struct SelectionAccumulator(Option>); - -impl SelectionAccumulator { - fn with_capacity(cap: usize) -> Self { - SelectionAccumulator(Some(Vec::with_capacity(cap))) - } - - fn noop() -> Self { - SelectionAccumulator(None) - } - - fn push(&mut self, item: NodeId) { - if let Some(v) = &mut self.0 { - v.push(item); - } - } - - fn into_vec(self) -> Vec { - self.0.unwrap_or_else(Vec::new) - } -} diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index fa2a6091c..7a4e028a5 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -4,6 +4,25 @@ mod json_conversion; use crate::field_type::GraphqlTypeQualifier; use std::collections::HashMap; +#[derive(Debug, Clone, Copy)] +pub(crate) struct WithSchema<'a, T> { + schema: &'a Schema, + pub(crate) item: T, +} + +impl<'a, T> WithSchema<'a, T> { + fn refocus(&self, item: U) -> WithSchema<'a, U> { + WithSchema { + schema: self.schema, + item, + } + } + + pub(crate) fn new(schema: &'a Schema, item: T) -> WithSchema<'a, T> { + WithSchema { schema, item } + } +} + // use crate::deprecation::DeprecationStatus; // use crate::enums::{EnumVariant, GqlEnum}; // use crate::field_type::FieldType; @@ -114,6 +133,19 @@ pub(crate) enum TypeId { Input(InputId), } +impl<'a> WithSchema<'a, TypeId> { + pub(crate) fn name(&self) -> &'a str { + match self.item { + TypeId::Object(obj) => self.refocus(obj).name(), + TypeId::Scalar(s) => self.refocus(s).name(), + TypeId::Interface(s) => self.refocus(s).name(), + TypeId::Union(s) => self.refocus(s).name(), + TypeId::Enum(s) => self.refocus(s).name(), + TypeId::Input(s) => self.refocus(s).name(), + } + } +} + #[derive(Debug, Clone, Copy)] pub(crate) enum TypeRef<'a> { Object(ObjectRef<'a>), @@ -121,16 +153,9 @@ pub(crate) enum TypeRef<'a> { Interface(InterfaceRef<'a>), Union(UnionRef<'a>), Enum(EnumRef<'a>), - Input(InputRef<'a>), } impl<'a> TypeRef<'a> { - pub(crate) fn type_id(&self) -> TypeId { - match self { - _ => todo!("TypeRef::type_id"), - } - } - pub(crate) fn name(&self) -> &'a str { match self { TypeRef::Object(obj) => obj.name(), @@ -138,7 +163,6 @@ impl<'a> TypeRef<'a> { TypeRef::Interface(s) => s.name(), TypeRef::Union(s) => s.name(), TypeRef::Enum(s) => s.name(), - TypeRef::Input(s) => s.name(), } } } @@ -148,6 +172,15 @@ pub(crate) struct ScalarRef<'a> { scalar_id: ScalarId, schema: &'a Schema, } +impl<'a> WithSchema<'a, ScalarId> { + fn get(&self) -> &'a StoredScalar { + self.schema.get_scalar(self.item) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } +} impl<'a> ScalarRef<'a> { fn get(&self) -> &'a StoredScalar { @@ -165,6 +198,16 @@ pub(crate) struct UnionRef<'a> { schema: &'a Schema, } +impl<'a> WithSchema<'a, UnionId> { + fn get(&self) -> &'a StoredUnion { + self.schema.stored_unions.get(self.item.0).unwrap() + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } +} + impl<'a> UnionRef<'a> { fn get(&self) -> &'a StoredUnion { self.schema.stored_unions.get(self.union_id.0).unwrap() @@ -181,6 +224,20 @@ pub(crate) struct EnumRef<'a> { schema: &'a Schema, } +impl<'a> WithSchema<'a, EnumId> { + fn get(&self) -> &'a StoredEnum { + self.schema.get_enum(self.item) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn variants(&self) -> &'a [String] { + &self.get().variants + } +} + impl<'a> EnumRef<'a> { fn get(&self) -> &'a StoredEnum { self.schema.get_enum(self.enum_id) @@ -248,34 +305,30 @@ impl TypeId { } } - pub(crate) fn upgrade(self, schema: &Schema) -> TypeRef<'_> { - match self { - TypeId::Enum(id) => TypeRef::Enum(EnumRef { - enum_id: id, - schema, - }), - TypeId::Interface(id) => TypeRef::Interface(InterfaceRef { - interface_id: id, - schema, - }), - TypeId::Object(id) => TypeRef::Object(ObjectRef { - object_id: id, - schema, - }), - TypeId::Scalar(id) => TypeRef::Scalar(ScalarRef { - scalar_id: id, - schema, - }), - TypeId::Union(id) => TypeRef::Union(UnionRef { - union_id: id, - schema, - }), - TypeId::Input(id) => TypeRef::Input(InputRef { - input_id: id, - schema, - }), - } - } + // pub(crate) fn upgrade(self, schema: &Schema) -> TypeRef<'_> { + // match self { + // TypeId::Enum(id) => TypeRef::Enum(EnumRef { + // enum_id: id, + // schema, + // }), + // TypeId::Interface(id) => TypeRef::Interface(InterfaceRef { + // interface_id: id, + // schema, + // }), + // TypeId::Object(id) => TypeRef::Object(ObjectRef { + // object_id: id, + // schema, + // }), + // TypeId::Scalar(id) => TypeRef::Scalar(ScalarRef { + // scalar_id: id, + // schema, + // }), + // TypeId::Union(id) => TypeRef::Union(UnionRef { + // union_id: id, + // schema, + // }), + // } + // } } #[derive(Debug, Clone, PartialEq)] @@ -624,10 +677,10 @@ impl Schema { } } - pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { - (0..self.stored_inputs.len()).map(move |id| InputRef { + pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { + (0..self.stored_inputs.len()).map(move |id| WithSchema { schema: self, - input_id: InputId(id), + item: InputId(id), }) } } @@ -644,6 +697,16 @@ pub(crate) struct InterfaceRef<'a> { interface_id: InterfaceId, } +impl<'a> WithSchema<'a, InterfaceId> { + fn get(&self) -> &'a StoredInterface { + self.schema.get_interface(self.item) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } +} + impl<'a> InterfaceRef<'a> { fn get(&self) -> &'a StoredInterface { self.schema.get_interface(self.interface_id) @@ -660,6 +723,35 @@ pub(crate) struct ObjectRef<'a> { object_id: ObjectId, } +impl<'a> WithSchema<'a, ObjectId> { + fn get(&self) -> &'a StoredObject { + self.schema.get_object(self.item) + } + + fn fields<'b>(&'b self) -> impl Iterator> + 'b { + self.get() + .fields + .iter() + .map(move |field| self.refocus(*field)) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { + self.fields().find(|field| field.name() == name) + } + + pub(crate) fn schema(&self) -> SchemaRef<'a> { + self.schema + } + + pub(crate) fn id(&self) -> ObjectId { + self.item + } +} + impl<'a> ObjectRef<'a> { fn get(&self) -> &'a StoredObject { self.schema.get_object(self.object_id) @@ -689,6 +781,24 @@ impl<'a> ObjectRef<'a> { } } +impl<'a> WithSchema<'a, StoredFieldId> { + fn get(&self) -> &'a StoredField { + self.schema.get_field(self.item) + } + + pub(crate) fn name(&self) -> &'a str { + &self.get().name + } + + pub(crate) fn field_type(&self) -> WithSchema<'a, TypeId> { + self.refocus(self.get().r#type.id) + } + + pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { + &self.get().r#type.qualifiers + } +} + #[derive(Debug, Clone, Copy)] pub(crate) struct FieldRef<'a> { schema: SchemaRef<'a>, @@ -712,28 +822,18 @@ impl<'a> FieldRef<'a> { self.get().r#type.id } - pub(crate) fn field_type(&self) -> TypeRef<'_> { - self.get().r#type.id.upgrade(self.schema) - } - pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { &self.get().r#type.qualifiers } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct InputRef<'a> { - schema: SchemaRef<'a>, - input_id: InputId, -} - -impl<'a> InputRef<'a> { +impl<'a> WithSchema<'a, InputId> { fn get(&self) -> &'a StoredInputType { - self.schema.get_stored_input(self.input_id) + self.schema.get_stored_input(self.item) } pub(crate) fn type_id(&self) -> TypeId { - TypeId::Input(self.input_id) + TypeId::Input(self.item) } pub(crate) fn name(&self) -> &'a str { @@ -868,8 +968,3 @@ impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { self.schema() } } - -pub(crate) struct WithSchema<'a, T> { - schema: SchemaRef<'a>, - item: T, -} From 723b60d06d3580787efdd868c0613b167e553ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 01:46:27 +0100 Subject: [PATCH 24/93] Cleanup --- graphql_client_codegen/Cargo.toml | 1 - graphql_client_codegen/src/codegen.rs | 6 +- .../src/generated_module.rs | 3 +- graphql_client_codegen/src/resolution.rs | 319 +++++++----------- graphql_client_codegen/src/schema.rs | 307 ++--------------- 5 files changed, 162 insertions(+), 474 deletions(-) diff --git a/graphql_client_codegen/Cargo.toml b/graphql_client_codegen/Cargo.toml index 746c1390b..1906e1c1b 100644 --- a/graphql_client_codegen/Cargo.toml +++ b/graphql_client_codegen/Cargo.toml @@ -19,4 +19,3 @@ serde_json = "1.0" serde = { version = "^1.0", features = ["derive"] } syn = "^1.0" thiserror = "1.0.10" -petgraph = "0.5.0" diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 955be3db2..093c97982 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -2,7 +2,7 @@ use crate::{ field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, - schema::{FieldRef, TypeId, TypeRef}, + schema::{FieldRef, TypeId}, shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; @@ -31,7 +31,7 @@ pub(crate) fn response_for_query( let all_used_types = operation.all_used_types(); let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); - let fragment_definitions: Vec<&'static str> = Vec::new(); + let fragment_definitions: Vec<&'static str> = todo!("fragment definitions"); let input_object_definitions = generate_input_object_definitions(operation, &all_used_types, options); let variables_struct = generate_variables_struct(operation, options); @@ -242,6 +242,8 @@ fn render_selection<'a>( response_derives: &impl quote::ToTokens, root_name: &str, ) { + // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) + for select in selection { match &select.get() { Selection::Field(field) => { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 80c0db3f1..147da20a7 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,6 +1,6 @@ use crate::{ codegen_options::*, - resolution::{Operation, OperationId, WithQuery}, + resolution::{OperationId, WithQuery}, }; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; @@ -11,7 +11,6 @@ pub(crate) struct GeneratedModule<'a> { pub operation: &'a str, pub query_string: &'a str, pub resolved_query: &'a crate::resolution::ResolvedQuery, - // pub query_document: &'a graphql_parser::query::Document, pub schema: &'a crate::schema::Schema, pub options: &'a crate::GraphQLClientCodegenOptions, } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 93a8280e0..a6f07d60e 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,18 +1,24 @@ //! The responsibility of this module is to resolve and validate a query //! against a given schema. +use crate::schema::ScalarId; use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, schema::{ - resolve_field_type, EnumRef, FieldRef, InputId, ObjectId, ScalarRef, Schema, StoredFieldId, - StoredFieldType, TypeId, TypeRef, WithSchema, + resolve_field_type, EnumRef, InputId, ObjectId, Schema, StoredFieldId, StoredFieldType, + TypeId, WithSchema, }, }; + use heck::CamelCase; -use petgraph::prelude::EdgeRef; use std::collections::{HashMap, HashSet}; +pub(crate) type OperationRef<'a> = WithQuery<'a, OperationId>; +pub(crate) type FragmentRef<'a> = WithQuery<'a, ResolvedFragmentId>; +pub(crate) type VariableRef<'a> = WithQuery<'a, VariableId>; +pub(crate) type SelectionRef<'a> = WithQuery<'a, SelectionId>; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) struct SelectionId(u32); @@ -47,63 +53,38 @@ enum SelectionParent { impl SelectionParent { fn add_to_selection_set(&self, q: &mut ResolvedQuery, selection_id: SelectionId) { match self { - SelectionParent::Selection(selection_id) => todo!(), - SelectionParent::Fragment(fragment_id) => todo!(), - SelectionParent::Operation(operation_id) => todo!(), + SelectionParent::Selection(parent_selection_id) => { + let parent_selection = q + .selections + .get_mut(parent_selection_id.0 as usize) + .expect("get parent selection"); + + match parent_selection { + Selection::Field(f) => f.selection_set.push(selection_id), + Selection::InlineFragment(inline) => inline.selection_set.push(selection_id), + _ => unreachable!("impossible parent selection"), + } + } + SelectionParent::Fragment(fragment_id) => { + let fragment = q + .fragments + .get_mut(fragment_id.0 as usize) + .expect("get fragment"); + + fragment.selection.push(selection_id); + } + SelectionParent::Operation(operation_id) => { + let operation = q + .operations + .get_mut(operation_id.0 as usize) + .expect("get operation"); + + operation.selection.push(selection_id); + } } } } -// #[derive(Debug, Clone, Copy)] -// pub(crate) struct SelectionItem<'a> { -// parent_id: Option, -// node_id: NodeId, -// variant: SelectionVariant<'a>, -// } - -// #[derive(Debug, Clone, Copy)] -// pub(crate) enum SelectionVariant<'a> { -// SelectedField { -// alias: Option<&'a str>, -// field: FieldRef<'a>, -// }, -// FragmentSpread(ResolvedFragmentId), -// InlineFragment(TypeRef<'a>), -// Typename, -// } - -// impl<'a> WithQuery<'a, SelectionItem<'a>> { -// pub(crate) fn variant(&self) -> &SelectionVariant<'a> { -// &self.item.variant -// } - -// pub(crate) fn parent(&self) -> Option>> { -// self.item -// .parent_id -// .map(|parent_id| self.refocus(parent_id).upgrade()) -// } - -// fn to_path_segment(&self) -> String { -// match self.item.variant { -// SelectionVariant::SelectedField { alias, field } => alias -// .map(|alias| alias.to_camel_case()) -// .unwrap_or_else(|| field.name().to_camel_case()), -// SelectionVariant::InlineFragment(type_ref) => { -// format!("On{}", type_ref.name().to_camel_case()) -// } -// _ => unreachable!(), -// } -// } - -// pub(crate) fn subselection<'b>( -// &'b self, -// ) -> impl Iterator>> + 'b { -// let id_selection = self.refocus(self.item.node_id); - -// id_selection.into_subselection().map(move |s| s.upgrade()) -// } -// } - #[derive(Debug, Clone, Copy)] pub(crate) struct WithQuery<'a, T> { query: &'a ResolvedQuery, @@ -137,10 +118,7 @@ impl<'a, T> WithQuery<'a, T> { } } -type NodeId = petgraph::prelude::NodeIndex; -type SelectionGraph = petgraph::Graph; - -impl<'a> WithQuery<'a, SelectionId> { +impl<'a> SelectionRef<'a> { pub(crate) fn get(&self) -> &'a Selection { self.query .selections @@ -183,28 +161,6 @@ impl<'a> WithQuery<'a, SelectionId> { } } - // fn upgrade(&self) -> WithQuery<'a, SelectionItem<'a>> { - // let node = self.get_node(); - - // let variant = match node.item { - // QueryNode::FragmentSpread(frag_id) => SelectionVariant::FragmentSpread(*frag_id), - // QueryNode::InlineFragment(type_id) => { - // SelectionVariant::InlineFragment(type_id.upgrade(self.schema)) - // } - // QueryNode::Typename => SelectionVariant::Typename, - // QueryNode::SelectedField(f) => SelectionVariant::SelectedField { - // alias: f.alias.as_ref().map(String::as_str), - // field: self.schema.field(f.field_id), - // }, - // }; - - // self.refocus(SelectionItem { - // node_id: self.item, - // parent_id: self.parent_id(), - // variant, - // }) - // } - pub(crate) fn full_path_prefix(&self) -> String { let mut path = vec![self.to_path_segment()]; @@ -277,19 +233,6 @@ pub(crate) struct InlineFragment { selection_set: Vec, } -#[derive(Debug)] -enum QueryNode { - SelectedField(SelectedField), - InlineFragment(TypeId), - FragmentSpread(ResolvedFragmentId), - Typename, -} - -#[derive(Debug)] -enum QueryEdge { - Selection, -} - #[derive(Debug)] pub(crate) struct SelectedField { alias: Option, @@ -306,15 +249,6 @@ impl<'a> WithQuery<'a, &'a SelectedField> { self.with_schema(self.item.field_id).name() } - pub(crate) fn selection_set<'b>( - &'b self, - ) -> impl Iterator> + 'b { - self.item - .selection_set - .iter() - .map(move |id| self.refocus(*id)) - } - pub(crate) fn schema_field(&self) -> WithSchema<'a, StoredFieldId> { self.with_schema(self.item.field_id) } @@ -326,7 +260,8 @@ pub(crate) fn resolve( ) -> anyhow::Result { let mut resolved_query: ResolvedQuery = Default::default(); - // First, give ids to all fragments. + // First, give ids to all fragments and operations. + // TODO: refactor this into a "create_roots" function. for definition in &query.definitions { match definition { graphql_parser::query::Definition::Fragment(fragment) => { @@ -337,7 +272,68 @@ pub(crate) fn resolve( selection: Vec::new(), }); } - _ => (), + graphql_parser::query::Definition::Operation( + graphql_parser::query::OperationDefinition::Mutation(m), + ) => { + let on = schema.mutation_type(); + let resolved_operation: ResolvedOperation = ResolvedOperation { + object_id: on.id(), + name: m.name.as_ref().expect("mutation without name").to_owned(), + operation_type: crate::operations::OperationType::Mutation, + variables: resolve_variables( + &m.variable_definitions, + schema, + OperationId(resolved_query.operations.len() as u32), + )?, + selection: Vec::with_capacity(m.selection_set.items.len()), + }; + + resolved_query.operations.push(resolved_operation); + } + graphql_parser::query::Definition::Operation( + graphql_parser::query::OperationDefinition::Query(q), + ) => { + let on = schema.query_type(); + let resolved_operation: ResolvedOperation = ResolvedOperation { + name: q.name.as_ref().expect("query without name").to_owned(), + operation_type: crate::operations::OperationType::Query, + variables: resolve_variables( + &q.variable_definitions, + schema, + OperationId(resolved_query.operations.len() as u32), + )?, + object_id: on.id(), + selection: Vec::with_capacity(q.selection_set.items.len()), + }; + + resolved_query.operations.push(resolved_operation); + } + graphql_parser::query::Definition::Operation( + graphql_parser::query::OperationDefinition::Subscription(s), + ) => { + let on = schema.subscription_type(); + + let resolved_operation: ResolvedOperation = ResolvedOperation { + name: s + .name + .as_ref() + .expect("subscription without name") + .to_owned(), + operation_type: crate::operations::OperationType::Subscription, + variables: resolve_variables( + &s.variable_definitions, + schema, + OperationId(resolved_query.operations.len() as u32), + )?, + object_id: on.id(), + selection: Vec::with_capacity(s.selection_set.items.len()), + }; + + resolved_query.operations.push(resolved_operation); + } + graphql_parser::query::Definition::Operation( + graphql_parser::query::OperationDefinition::SelectionSet(_), + ) => unreachable!("unnamed queries are not supported"), } } @@ -401,7 +397,7 @@ fn resolve_object_selection<'a>( let id = query.push_selection( Selection::Field(SelectedField { alias: field.alias.clone(), - field_id: field_ref.id(), + field_id: field_ref.item, selection_set: Vec::with_capacity(selection_set.items.len()), }), parent, @@ -444,7 +440,7 @@ fn resolve_selection( selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, ) -> anyhow::Result<()> { - let selection = match on { + match on { TypeId::Object(oid) => { let object = schema.object(oid); resolve_object_selection(ctx, object, selection_set, parent)?; @@ -506,75 +502,21 @@ fn resolve_operation( match operation { graphql_parser::query::OperationDefinition::Mutation(m) => { let on = schema.mutation_type(); - resolve_object_selection( - query, - on, - &m.selection_set, - SelectionParent::Operation(OperationId(query.operations.len() as u32)), - )?; - let resolved_operation: ResolvedOperation = ResolvedOperation { - object_id: on.id(), - name: m.name.as_ref().expect("mutation without name").to_owned(), - operation_type: crate::operations::OperationType::Mutation, - variables: resolve_variables( - &m.variable_definitions, - schema, - OperationId(query.operations.len() as u32), - )?, - selection: Vec::with_capacity(m.selection_set.items.len()), - }; - - query.operations.push(resolved_operation); + let (id, _) = query.find_operation(m.name.as_ref().unwrap()).unwrap(); + + resolve_object_selection(query, on, &m.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Query(q) => { let on = schema.query_type(); - resolve_object_selection( - query, - on, - &q.selection_set, - SelectionParent::Operation(OperationId(query.operations.len() as u32)), - )?; - - let resolved_operation: ResolvedOperation = ResolvedOperation { - name: q.name.as_ref().expect("query without name").to_owned(), - operation_type: crate::operations::OperationType::Query, - variables: resolve_variables( - &q.variable_definitions, - schema, - OperationId(query.operations.len() as u32), - )?, - object_id: on.id(), - selection: Vec::with_capacity(q.selection_set.items.len()), - }; - - query.operations.push(resolved_operation); + let (id, _) = query.find_operation(q.name.as_ref().unwrap()).unwrap(); + + resolve_object_selection(query, on, &q.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Subscription(s) => { - let on = schema.subscription_type(); - resolve_object_selection( - query, - on, - &s.selection_set, - SelectionParent::Operation(OperationId(query.operations.len() as u32)), - )?; - - let resolved_operation: ResolvedOperation = ResolvedOperation { - name: s - .name - .as_ref() - .expect("subscription without name") - .to_owned(), - operation_type: crate::operations::OperationType::Subscription, - variables: resolve_variables( - &s.variable_definitions, - schema, - OperationId(query.operations.len() as u32), - )?, - object_id: on.id(), - selection: Vec::with_capacity(s.selection_set.items.len()), - }; - - query.operations.push(resolved_operation); + let on = schema.query_type(); + let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap(); + + resolve_object_selection(query, on, &s.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::SelectionSet(_) => { unreachable!("unnamed queries are not supported") @@ -610,6 +552,14 @@ impl ResolvedQuery { .find(|(_, frag)| frag.name == name) .map(|(id, f)| (ResolvedFragmentId(id as u32), f)) } + + fn find_operation(&mut self, name: &str) -> Option<(OperationId, &mut ResolvedOperation)> { + self.operations + .iter_mut() + .enumerate() + .find(|(_, op)| op.name == name) + .map(|(id, op)| (OperationId::new(id), op)) + } } #[derive(Debug)] @@ -619,14 +569,7 @@ pub(crate) struct ResolvedFragment { selection: Vec, } -#[derive(Debug, Clone, Copy)] -pub(crate) struct Operation<'a> { - operation_id: usize, - schema: &'a Schema, - query: &'a ResolvedQuery, -} - -impl<'a> WithQuery<'a, OperationId> { +impl<'a> OperationRef<'a> { fn get(&self) -> &'a ResolvedOperation { self.query.operations.get(self.item.0 as usize).unwrap() } @@ -649,7 +592,7 @@ impl<'a> WithQuery<'a, OperationId> { all_used_types } - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { let operation = self.get(); operation .selection @@ -657,7 +600,7 @@ impl<'a> WithQuery<'a, OperationId> { .map(move |selection_id| self.refocus(*selection_id)) } - pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { + pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { self.query .variables .iter() @@ -698,7 +641,7 @@ struct ResolvedVariable { r#type: StoredFieldType, } -impl<'a> WithQuery<'a, VariableId> { +impl<'a> VariableRef<'a> { fn get(&self) -> &'a ResolvedVariable { self.query.variables.get(self.item.0 as usize).unwrap() } @@ -727,12 +670,12 @@ impl<'a> WithQuery<'a, VariableId> { } } -impl<'a> WithQuery<'a, ResolvedFragmentId> { +impl<'a> FragmentRef<'a> { fn get(&self) -> &'a ResolvedFragment { self.query.fragments.get(self.item.0 as usize).unwrap() } - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { let fragment = self.get(); fragment .selection @@ -747,10 +690,6 @@ impl<'a> WithQuery<'a, ResolvedFragmentId> { pub(crate) fn name(&self) -> &'a str { &self.get().name } - - pub(crate) fn selection_len(&self) -> usize { - self.get().selection.len() - } } #[derive(Debug, Default)] @@ -772,7 +711,7 @@ impl UsedTypes { pub(crate) fn scalars<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator> + 's { self.types .iter() .filter_map(TypeId::as_scalar_id) diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 7a4e028a5..6a68d39d6 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -146,45 +146,11 @@ impl<'a> WithSchema<'a, TypeId> { } } -#[derive(Debug, Clone, Copy)] -pub(crate) enum TypeRef<'a> { - Object(ObjectRef<'a>), - Scalar(ScalarRef<'a>), - Interface(InterfaceRef<'a>), - Union(UnionRef<'a>), - Enum(EnumRef<'a>), -} - -impl<'a> TypeRef<'a> { - pub(crate) fn name(&self) -> &'a str { - match self { - TypeRef::Object(obj) => obj.name(), - TypeRef::Scalar(s) => s.name(), - TypeRef::Interface(s) => s.name(), - TypeRef::Union(s) => s.name(), - TypeRef::Enum(s) => s.name(), - } - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) struct ScalarRef<'a> { - scalar_id: ScalarId, - schema: &'a Schema, -} -impl<'a> WithSchema<'a, ScalarId> { - fn get(&self) -> &'a StoredScalar { - self.schema.get_scalar(self.item) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } -} +pub(crate) type ScalarRef<'a> = WithSchema<'a, ScalarId>; impl<'a> ScalarRef<'a> { fn get(&self) -> &'a StoredScalar { - self.schema.get_scalar(self.scalar_id) + self.schema.get_scalar(self.item) } pub(crate) fn name(&self) -> &'a str { @@ -192,13 +158,9 @@ impl<'a> ScalarRef<'a> { } } -#[derive(Debug, Clone, Copy)] -pub(crate) struct UnionRef<'a> { - union_id: UnionId, - schema: &'a Schema, -} +pub(crate) type UnionRef<'a> = WithSchema<'a, UnionId>; -impl<'a> WithSchema<'a, UnionId> { +impl<'a> UnionRef<'a> { fn get(&self) -> &'a StoredUnion { self.schema.stored_unions.get(self.item.0).unwrap() } @@ -208,39 +170,11 @@ impl<'a> WithSchema<'a, UnionId> { } } -impl<'a> UnionRef<'a> { - fn get(&self) -> &'a StoredUnion { - self.schema.stored_unions.get(self.union_id.0).unwrap() - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) struct EnumRef<'a> { - enum_id: EnumId, - schema: &'a Schema, -} - -impl<'a> WithSchema<'a, EnumId> { - fn get(&self) -> &'a StoredEnum { - self.schema.get_enum(self.item) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn variants(&self) -> &'a [String] { - &self.get().variants - } -} +pub(crate) type EnumRef<'a> = WithSchema<'a, EnumId>; impl<'a> EnumRef<'a> { fn get(&self) -> &'a StoredEnum { - self.schema.get_enum(self.enum_id) + self.schema.get_enum(self.item) } pub(crate) fn name(&self) -> &'a str { @@ -414,69 +348,6 @@ impl Schema { } } - // pub(crate) fn ingest_interface_implementations( - // &mut self, - // impls: BTreeMap<&'schema str, Vec<&'schema str>>, - // ) -> Result<(), anyhow::Error> { - // impls - // .into_iter() - // .map(|(iface_name, implementors)| { - // let iface = self - // .interfaces - // .get_mut(&iface_name) - // .ok_or_else(|| format_err!("interface not found: {}", iface_name))?; - // iface.implemented_by = implementors.iter().cloned().collect(); - // Ok(()) - // }) - // .collect() - // } - - // pub(crate) fn require(&self, typename_: &str) { - // DEFAULT_SCALARS - // .iter() - // .find(|&&s| s == typename_) - // .map(|_| ()) - // .or_else(|| { - // self.enums - // .get(typename_) - // .map(|enm| enm.is_required.set(true)) - // }) - // .or_else(|| self.inputs.get(typename_).map(|input| input.require(self))) - // .or_else(|| { - // self.objects - // .get(typename_) - // .map(|object| object.require(self)) - // }) - // .or_else(|| { - // self.scalars - // .get(typename_) - // .map(|scalar| scalar.is_required.set(true)) - // }); - // } - - // pub(crate) fn contains_scalar(&self, type_name: &str) -> bool { - // DEFAULT_SCALARS.iter().any(|s| s == &type_name) || self.scalars.contains_key(type_name) - // } - - // pub(crate) fn fragment_target( - // &self, - // target_name: &str, - // ) -> Option> { - // self.objects - // .get(target_name) - // .map(crate::fragments::FragmentTarget::Object) - // .or_else(|| { - // self.interfaces - // .get(target_name) - // .map(crate::fragments::FragmentTarget::Interface) - // }) - // .or_else(|| { - // self.unions - // .get(target_name) - // .map(crate::fragments::FragmentTarget::Union) - // }) - // } - fn get_object_mut(&mut self, object_id: ObjectId) -> &mut StoredObject { self.stored_objects.get_mut(object_id.0).unwrap() } @@ -485,17 +356,6 @@ impl Schema { self.stored_interfaces.get_mut(id.0).unwrap() } - // fn get_interface_by_name_mut( - // &mut self, - // interface_name: &str, - // ) -> Option<(InterfaceId, &mut StoredInterface)> { - // self.stored_interfaces - // .iter_mut() - // .enumerate() - // .find(|(idx, iface)| iface.name == interface_name) - // .map(|(idx, iface)| (InterfaceId(idx), iface)) - // } - fn push_object(&mut self, object: StoredObject) -> ObjectId { let id = ObjectId(self.stored_objects.len()); self.stored_objects.push(object); @@ -503,14 +363,6 @@ impl Schema { id } - // fn push_object_field(&mut self, object_field: StoredObjectField) -> ObjectFieldId { - // let id = ObjectFieldId(self.stored_object_fields.len()); - - // self.stored_object_fields.push(object_field); - - // id - // } - fn push_interface(&mut self, interface: StoredInterface) -> InterfaceId { let id = InterfaceId(self.stored_interfaces.len()); @@ -519,14 +371,6 @@ impl Schema { id } - // fn push_interface_field(&mut self, interface_field: StoredInterfaceField) -> InterfaceFieldId { - // let id = InterfaceFieldId(self.stored_interface_fields.len()); - - // self.stored_interface_fields.push(interface_field); - - // id - // } - fn push_scalar(&mut self, scalar: StoredScalar) -> ScalarId { let id = ScalarId(self.stored_scalars.len()); @@ -551,51 +395,29 @@ impl Schema { id } - // pub(crate) fn get_input_type_by_name(&self, name: &str) -> Option> { - // self.stored_inputs - // .iter() - // .position(|input| input.name == name) - // .map(InputId) - // .map(|idx| InputRef { - // schema: self, - // input_id: idx, - // }) - // } - - // pub(crate) fn get_object_by_name(&self, name: &str) -> Option<()> { - // Some(()) - // } - - // pub(crate) fn lookup_type(&self, name: &str) -> Option { - // todo!() - // } - pub(crate) fn query_type(&self) -> ObjectRef<'_> { - ObjectRef { - object_id: self - .query_type + WithSchema::new( + self, + self.query_type .expect("Query operation type must be defined"), - schema: self, - } + ) } pub(crate) fn mutation_type(&self) -> ObjectRef<'_> { - ObjectRef { - object_id: self - .mutation_type + WithSchema::new( + self, + self.mutation_type .expect("Mutation operation type must be defined"), - schema: self, - } + ) } pub(crate) fn subscription_type(&self) -> ObjectRef<'_> { - ObjectRef { - object_id: self - .subscription_type + WithSchema::new( + self, + self.subscription_type // TODO: make this return an option .expect("Subscription operation type must be defined"), - schema: self, - } + ) } fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface { @@ -622,11 +444,8 @@ impl Schema { self.stored_scalars.get(scalar_id.0).unwrap() } - pub(crate) fn object(&self, id: ObjectId) -> ObjectRef<'_> { - ObjectRef { - object_id: id, - schema: self, - } + pub(crate) fn object(&self, id: ObjectId) -> WithSchema<'_, ObjectId> { + WithSchema::new(self, id) } pub(crate) fn interface(&self, interface_id: InterfaceId) -> InterfaceRef<'_> { @@ -637,24 +456,15 @@ impl Schema { } pub(crate) fn field(&self, field_id: StoredFieldId) -> FieldRef<'_> { - FieldRef { - field_id, - schema: self, - } + WithSchema::new(self, field_id) } - pub(crate) fn scalar(&self, scalar_id: ScalarId) -> ScalarRef<'_> { - ScalarRef { - scalar_id, - schema: self, - } + pub(crate) fn scalar(&self, scalar_id: ScalarId) -> WithSchema<'_, ScalarId> { + WithSchema::new(self, scalar_id) } pub(crate) fn r#enum(&self, enum_id: EnumId) -> EnumRef<'_> { - EnumRef { - enum_id, - schema: self, - } + WithSchema::new(self, enum_id) } fn find_interface(&self, interface_name: &str) -> InterfaceId { @@ -685,12 +495,6 @@ impl Schema { } } -pub(crate) struct FieldsRef<'a> { - parent_type: StoredFieldParent, - schema: SchemaRef<'a>, - fields: &'a [StoredFieldId], -} - #[derive(Clone, Debug, Copy, PartialEq)] pub(crate) struct InterfaceRef<'a> { schema: SchemaRef<'a>, @@ -717,13 +521,9 @@ impl<'a> InterfaceRef<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct ObjectRef<'a> { - schema: SchemaRef<'a>, - object_id: ObjectId, -} +type ObjectRef<'a> = WithSchema<'a, ObjectId>; -impl<'a> WithSchema<'a, ObjectId> { +impl<'a> ObjectRef<'a> { fn get(&self) -> &'a StoredObject { self.schema.get_object(self.item) } @@ -752,36 +552,9 @@ impl<'a> WithSchema<'a, ObjectId> { } } -impl<'a> ObjectRef<'a> { - fn get(&self) -> &'a StoredObject { - self.schema.get_object(self.object_id) - } - - fn fields<'b>(&'b self) -> impl Iterator> + 'b { - self.get().fields.iter().map(move |field| FieldRef { - schema: self.schema, - field_id: *field, - }) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } +pub(crate) type FieldRef<'a> = WithSchema<'a, StoredFieldId>; - pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { - self.fields().find(|field| field.name() == name) - } - - pub(crate) fn schema(&self) -> SchemaRef<'a> { - self.schema - } - - pub(crate) fn id(&self) -> ObjectId { - self.object_id - } -} - -impl<'a> WithSchema<'a, StoredFieldId> { +impl<'a> FieldRef<'a> { fn get(&self) -> &'a StoredField { self.schema.get_field(self.item) } @@ -797,34 +570,10 @@ impl<'a> WithSchema<'a, StoredFieldId> { pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { &self.get().r#type.qualifiers } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) struct FieldRef<'a> { - schema: SchemaRef<'a>, - field_id: StoredFieldId, -} - -impl<'a> FieldRef<'a> { - fn get(&self) -> &'a StoredField { - self.schema.get_field(self.field_id) - } - - pub(crate) fn id(&self) -> StoredFieldId { - self.field_id - } - - pub(crate) fn name(&self) -> &str { - &self.get().name - } pub(crate) fn type_id(&self) -> TypeId { self.get().r#type.id } - - pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { - &self.get().r#type.qualifiers - } } impl<'a> WithSchema<'a, InputId> { From a27746d704ecdc4b192d10f38d5e62b7f7d357b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 02:29:49 +0100 Subject: [PATCH 25/93] More simplification --- graphql_client_codegen/src/fragments.rs | 5 ----- graphql_client_codegen/src/lib.rs | 8 +++----- graphql_client_codegen/src/schema.rs | 14 -------------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/graphql_client_codegen/src/fragments.rs b/graphql_client_codegen/src/fragments.rs index 202b04f60..48341f68a 100644 --- a/graphql_client_codegen/src/fragments.rs +++ b/graphql_client_codegen/src/fragments.rs @@ -54,9 +54,4 @@ impl<'query> GqlFragment<'query> { pub(crate) fn is_recursive(&self) -> bool { self.selection.contains_fragment(&self.name) } - - pub(crate) fn require<'schema>(&self, context: &QueryContext<'query>) { - self.is_required.set(true); - self.selection.require_items(context); - } } diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index a072e7d10..14cf5909d 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -76,20 +76,18 @@ pub fn generate_module_token_stream( } }; - let mut parsed_schema = match schema_extension { + let schema = match schema_extension { "graphql" | "gql" => { let s = graphql_parser::schema::parse_schema(&schema_string).expect("TODO: error conversion"); - schema::ParsedSchema::GraphQLParser(s) + schema::Schema::from(s) } "json" => { let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string)?; - schema::ParsedSchema::Json(parsed) + schema::Schema::from(parsed) } extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension) }; - let schema = schema::Schema::from(parsed_schema); - // We need to qualify the query with the path to the crate it is part of let (query_string, query) = { let mut lock = QUERY_CACHE.lock().expect("query cache is poisoned"); diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 6a68d39d6..a592417b2 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -635,20 +635,6 @@ impl std::convert::From for Schema { - fn from(parsed_schema: ParsedSchema) -> Schema { - match parsed_schema { - ParsedSchema::GraphQLParser(s) => s.into(), - ParsedSchema::Json(s) => s.into(), - } - } -} - pub(crate) fn resolve_field_type( schema: &Schema, inner: &graphql_parser::schema::Type, From e40ead28b7bd341d442cff130c638b9c54bb2390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 02:34:01 +0100 Subject: [PATCH 26/93] Cache parsed schemas --- graphql_client_codegen/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 14cf5909d..37158ef30 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -46,7 +46,7 @@ use std::collections::HashMap; type CacheMap = std::sync::Mutex>; lazy_static! { - static ref SCHEMA_CACHE: CacheMap = CacheMap::default(); + static ref SCHEMA_CACHE: CacheMap = CacheMap::default(); static ref QUERY_CACHE: CacheMap<(String, graphql_parser::query::Document)> = CacheMap::default(); } @@ -65,18 +65,13 @@ pub fn generate_module_token_stream( .unwrap_or("INVALID"); // Check the schema cache. - let schema_string: String = { + let schema: schema::Schema = { let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned"); match lock.entry(schema_path.to_path_buf()) { hash_map::Entry::Occupied(o) => o.get().clone(), hash_map::Entry::Vacant(v) => { let schema_string = read_file(v.key())?; - (*v.insert(schema_string)).to_string() - } - } - }; - - let schema = match schema_extension { + let schema = match schema_extension { "graphql" | "gql" => { let s = graphql_parser::schema::parse_schema(&schema_string).expect("TODO: error conversion"); schema::Schema::from(s) @@ -88,6 +83,11 @@ pub fn generate_module_token_stream( extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension) }; + v.insert(schema).clone() + } + } + }; + // We need to qualify the query with the path to the crate it is part of let (query_string, query) = { let mut lock = QUERY_CACHE.lock().expect("query cache is poisoned"); From 8d4b05288e8508164d600d571c5a13f58b270821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 09:08:37 +0100 Subject: [PATCH 27/93] Expand fragment definitions --- graphql_client_codegen/src/codegen.rs | 101 ++++++++++++++--------- graphql_client_codegen/src/resolution.rs | 12 +++ 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 093c97982..7ab3cf370 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -29,14 +29,16 @@ pub(crate) fn response_for_query( options: &GraphQLClientCodegenOptions, ) -> anyhow::Result { let all_used_types = operation.all_used_types(); + let response_derives = render_derives(options.all_response_derives()); + let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); - let fragment_definitions: Vec<&'static str> = todo!("fragment definitions"); + let (fragment_definitions, fragment_nested_definitions) = + generate_fragment_definitions(operation, &all_used_types, &response_derives); let input_object_definitions = generate_input_object_definitions(operation, &all_used_types, options); let variables_struct = generate_variables_struct(operation, options); - let response_derives = render_derives(options.all_response_derives()); let (definitions, response_data_fields) = render_response_data_fields(&operation, &response_derives); @@ -60,6 +62,8 @@ pub(crate) fn response_for_query( #(#input_object_definitions)* + #(#fragment_nested_definitions)* + #(#definitions)* #response_derives @@ -70,8 +74,6 @@ pub(crate) fn response_for_query( #variables_struct }; - // panic!("{}", q); - Ok(q) } @@ -218,7 +220,7 @@ fn render_variable_field_type(variable: WithQuery<'_, VariableId>) -> TokenStrea } fn render_response_data_fields<'a>( - operation: &WithQuery<'a, OperationId>, + operation: &OperationRef<'a>, response_derives: &impl quote::ToTokens, ) -> (Vec, Vec) { let mut response_types = Vec::new(); @@ -229,7 +231,6 @@ fn render_response_data_fields<'a>( &mut fields, &mut response_types, response_derives, - operation.name(), ); (response_types, fields) @@ -240,7 +241,6 @@ fn render_selection<'a>( field_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, - root_name: &str, ) { // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) @@ -248,7 +248,7 @@ fn render_selection<'a>( match &select.get() { Selection::Field(field) => { let field = select.refocus(field); - let ident = field_ident(&field); + let ident = field_name(&field); match field.schema_field().field_type().item { TypeId::Enum(enm) => { let type_name = @@ -256,7 +256,7 @@ fn render_selection<'a>( let type_name = decorate_type(&type_name, field.schema_field().type_qualifiers()); - field_buffer.push(quote!(pub #ident: #type_name)); + field_buffer.push(quote!(#ident: #type_name)); } TypeId::Scalar(scalar) => { let type_name = @@ -264,24 +264,23 @@ fn render_selection<'a>( let type_name = decorate_type(&type_name, field.schema_field().type_qualifiers()); - field_buffer.push(quote!(pub #ident: #type_name)); + field_buffer.push(quote!(#ident: #type_name)); } - TypeId::Object(object) => { - let mut fields = Vec::new(); + TypeId::Object(_) => { let struct_name = Ident::new(&select.full_path_prefix(), Span::call_site()); + let field_type = + decorate_type(&struct_name, field.schema_field().type_qualifiers()); + + field_buffer.push(quote!(#ident: #field_type)); + + let mut fields = Vec::new(); render_selection( select.subselection(), &mut fields, response_type_buffer, response_derives, - root_name, ); - let field_type = - decorate_type(&struct_name, field.schema_field().type_qualifiers()); - - field_buffer.push(quote!(pub #ident: #field_type)); - let struct_definition = quote! { #response_derives pub struct #struct_name { @@ -315,35 +314,22 @@ fn render_selection<'a>( #[serde(flatten)] pub #annotation #field_ident: #type_name }); - } // SelectionVariant::FragmentSpread(frag) => { - // let struct_name = frag.name(); - // let struct_ident = Ident::new(struct_name, Span::call_site()); - // let mut fields = Vec::with_capacity(frag.selection_len()); - - // render_selection( - // frag.selection(), - // &mut fields, - // response_type_buffer, - // response_derives, - // ); - - // let fragment_definition = quote! { - // #response_derives - // struct #struct_ident { - // #(#fields),* - // } - // }; - // } + } } } } -fn field_ident(field: &WithQuery<'_, &SelectedField>) -> Ident { +fn field_name(field: &WithQuery<'_, &SelectedField>) -> impl quote::ToTokens { let name = field .alias() .unwrap_or_else(|| field.name()) .to_snake_case(); - Ident::new(&name, Span::call_site()) + let final_name = keyword_replace(&name); + let rename_annotation = field_rename_annotation(&name, &final_name); + + let ident = Ident::new(&final_name, Span::call_site()); + + quote!(#rename_annotation pub #ident) } fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStream { @@ -394,3 +380,40 @@ fn generate_input_object_definitions( .map(|input| quote!(heh)) .collect() } + +fn generate_fragment_definitions( + operation: OperationRef<'_>, + all_used_types: &UsedTypes, + response_derives: &impl quote::ToTokens, +) -> (Vec, Vec) { + let mut response_type_buffer = Vec::new(); + let mut fragment_definitions = Vec::with_capacity(all_used_types.fragments_len()); + + let fragments = all_used_types + .fragment_ids() + .map(move |id| operation.refocus(id)); + + for fragment in fragments { + let struct_name = fragment.name(); + let struct_ident = Ident::new(struct_name, Span::call_site()); + let mut fields = Vec::with_capacity(fragment.selection_set_len()); + + render_selection( + fragment.selection(), + &mut fields, + &mut response_type_buffer, + response_derives, + ); + + let fragment_definition = quote! { + #response_derives + pub struct #struct_ident { + #(#fields),* + } + }; + + fragment_definitions.push(fragment_definition.into()) + } + + (fragment_definitions, response_type_buffer) +} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index a6f07d60e..c64d22ab0 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -690,6 +690,10 @@ impl<'a> FragmentRef<'a> { pub(crate) fn name(&self) -> &'a str { &self.get().name } + + pub(crate) fn selection_set_len(&self) -> usize { + self.get().selection.len() + } } #[derive(Debug, Default)] @@ -728,6 +732,14 @@ impl UsedTypes { .filter_map(TypeId::as_enum_id) .map(move |enum_id| schema.r#enum(enum_id)) } + + pub(crate) fn fragment_ids<'b>(&'b self) -> impl Iterator + 'b { + self.fragments.iter().map(|v| *v) + } + + pub(crate) fn fragments_len(&self) -> usize { + self.fragments.len() + } } fn resolve_variables( From 328c4f8a26f7adf64e2b6727cfc3a9736e46e41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 09:38:42 +0100 Subject: [PATCH 28/93] Start reimplementing deprecation handling --- graphql_client_codegen/src/codegen.rs | 36 +++++++++++++++++-- graphql_client_codegen/src/schema.rs | 36 +++++++++---------- .../src/schema/graphql_parser_conversion.rs | 32 ++++++++++++++++- .../src/schema/json_conversion.rs | 17 +++++++-- 4 files changed, 95 insertions(+), 26 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 7ab3cf370..099845467 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,4 +1,5 @@ use crate::{ + deprecation::DeprecationStrategy, field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, @@ -34,13 +35,13 @@ pub(crate) fn response_for_query( let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); let (fragment_definitions, fragment_nested_definitions) = - generate_fragment_definitions(operation, &all_used_types, &response_derives); + generate_fragment_definitions(operation, &all_used_types, &response_derives, options); let input_object_definitions = generate_input_object_definitions(operation, &all_used_types, options); let variables_struct = generate_variables_struct(operation, options); let (definitions, response_data_fields) = - render_response_data_fields(&operation, &response_derives); + render_response_data_fields(&operation, &response_derives, options); let q = quote! { use serde::{Serialize, Deserialize}; @@ -92,12 +93,27 @@ fn generate_variables_struct( } let variable_fields = operation.variables().map(generate_variable_struct_field); + let variable_defaults = operation.variables().map(|variable| { + let method_name = format!("default_{}", variable.name()); + let method_name = Ident::new(&method_name, Span::call_site()); + let method_return_type = quote!(String); + + quote!( + pub fn #method_name() -> #method_return_type { + todo!() + } + ) + }); let variables_struct = quote!( #variable_derives pub struct Variables { #(#variable_fields,)* } + + impl Variables { + #(#variable_defaults)* + } ); variables_struct.into() @@ -222,6 +238,7 @@ fn render_variable_field_type(variable: WithQuery<'_, VariableId>) -> TokenStrea fn render_response_data_fields<'a>( operation: &OperationRef<'a>, response_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, ) -> (Vec, Vec) { let mut response_types = Vec::new(); let mut fields = Vec::new(); @@ -231,6 +248,7 @@ fn render_response_data_fields<'a>( &mut fields, &mut response_types, response_derives, + options, ); (response_types, fields) @@ -241,6 +259,7 @@ fn render_selection<'a>( field_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, ) { // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) @@ -248,6 +267,16 @@ fn render_selection<'a>( match &select.get() { Selection::Field(field) => { let field = select.refocus(field); + + match ( + field.schema_field().is_deprecated(), + options.deprecation_strategy(), + ) { + (false, _) | (true, DeprecationStrategy::Allow) => (), + (true, DeprecationStrategy::Warn) => todo!("deprecation annotation"), + (true, DeprecationStrategy::Deny) => continue, + } + let ident = field_name(&field); match field.schema_field().field_type().item { TypeId::Enum(enm) => { @@ -279,6 +308,7 @@ fn render_selection<'a>( &mut fields, response_type_buffer, response_derives, + options, ); let struct_definition = quote! { @@ -385,6 +415,7 @@ fn generate_fragment_definitions( operation: OperationRef<'_>, all_used_types: &UsedTypes, response_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, ) -> (Vec, Vec) { let mut response_type_buffer = Vec::new(); let mut fragment_definitions = Vec::with_capacity(all_used_types.fragments_len()); @@ -403,6 +434,7 @@ fn generate_fragment_definitions( &mut fields, &mut response_type_buffer, response_derives, + options, ); let fragment_definition = quote! { diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index a592417b2..1c0d5865d 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -55,6 +55,8 @@ struct StoredField { name: String, r#type: StoredFieldType, parent: StoredFieldParent, + /// `Some(None)` should be interpreted as "deprecated, without reason" + deprecation: Option>, } #[derive(Debug, PartialEq, Clone)] @@ -449,10 +451,7 @@ impl Schema { } pub(crate) fn interface(&self, interface_id: InterfaceId) -> InterfaceRef<'_> { - InterfaceRef { - interface_id, - schema: self, - } + WithSchema::new(self, interface_id) } pub(crate) fn field(&self, field_id: StoredFieldId) -> FieldRef<'_> { @@ -495,11 +494,7 @@ impl Schema { } } -#[derive(Clone, Debug, Copy, PartialEq)] -pub(crate) struct InterfaceRef<'a> { - schema: SchemaRef<'a>, - interface_id: InterfaceId, -} +type InterfaceRef<'a> = WithSchema<'a, InterfaceId>; impl<'a> WithSchema<'a, InterfaceId> { fn get(&self) -> &'a StoredInterface { @@ -511,16 +506,6 @@ impl<'a> WithSchema<'a, InterfaceId> { } } -impl<'a> InterfaceRef<'a> { - fn get(&self) -> &'a StoredInterface { - self.schema.get_interface(self.interface_id) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } -} - type ObjectRef<'a> = WithSchema<'a, ObjectId>; impl<'a> ObjectRef<'a> { @@ -574,6 +559,17 @@ impl<'a> FieldRef<'a> { pub(crate) fn type_id(&self) -> TypeId { self.get().r#type.id } + + pub(crate) fn is_deprecated(&self) -> bool { + self.get().deprecation.is_some() + } + + pub(crate) fn deprecation_message(&self) -> Option<&'a str> { + self.get() + .deprecation + .as_ref() + .and_then(|item| item.as_ref().map(String::as_str)) + } } impl<'a> WithSchema<'a, InputId> { @@ -700,6 +696,6 @@ impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { } fn schema(&self) -> SchemaRef<'a> { - self.schema() + InterfaceRef::schema(self) } } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index fa63a857e..f923c1a83 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -50,6 +50,18 @@ fn convert(src: &mut graphql_parser::schema::Document, schema: &mut Schema) { .as_mut() .and_then(|n| schema.names.get(n)) .and_then(|id| id.as_object_id()); + } else { + schema.query_type = schema.names.get("Query").and_then(|id| id.as_object_id()); + + schema.mutation_type = schema + .names + .get("Mutation") + .and_then(|id| id.as_object_id()); + + schema.subscription_type = schema + .names + .get("Subscription") + .and_then(|id| id.as_object_id()); }; } @@ -120,7 +132,7 @@ fn populate_names_map(schema: &mut Schema, definitions: &[Definition]) { }); } -fn ingest_union(schema: &mut Schema, union: &mut graphql_parser::schema::UnionType) { +fn ingest_union(schema: &mut Schema, union: &mut UnionType) { let stored_union = super::StoredUnion { name: std::mem::replace(&mut union.name, String::new()), variants: union @@ -142,6 +154,7 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy name: std::mem::replace(&mut field.name, String::new()), 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)); @@ -200,6 +213,7 @@ fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema: name: std::mem::replace(&mut field.name, String::new()), r#type: resolve_field_type(schema, &field.field_type), parent: super::StoredFieldParent::Interface(interface_id), + deprecation: find_deprecation(&field.directives), }; field_ids.push(schema.push_field(field)); @@ -213,6 +227,22 @@ fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema: schema.push_interface(new_interface); } +fn find_deprecation(directives: &[parser::Directive]) -> Option> { + directives + .iter() + .find(|directive| directive.name == "deprecated") + .map(|directive| { + directive + .arguments + .iter() + .find(|(name, _)| name == "reason") + .and_then(|(_, value)| match value { + graphql_parser::query::Value::String(s) => Some(s.clone()), + _ => None, + }) + }) +} + fn ingest_input(schema: &mut Schema, input: &mut parser::InputObjectType) { unimplemented!() } diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index ca635daff..357e6bb36 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -284,7 +284,10 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { } fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { - let interface_id = schema.find_type_id(iface.name.as_ref().unwrap()).as_interface_id().unwrap(); + let interface_id = schema + .find_type_id(iface.name.as_ref().unwrap()) + .as_interface_id() + .unwrap(); let fields = iface.fields.as_mut().unwrap(); let mut field_ids = Vec::with_capacity(fields.len()); @@ -293,6 +296,9 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { parent: super::StoredFieldParent::Interface(interface_id), name: field.name.take().unwrap(), r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + deprecation: Some(None) + .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) + .map(|_: Option<()>| field.deprecation_reason.clone()), }; field_ids.push(schema.push_field(field)); @@ -307,7 +313,10 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { } fn ingest_object(schema: &mut Schema, object: &mut FullType) { - let object_id = schema.find_type_id(object.name.as_ref().unwrap()).as_object_id().unwrap(); + let object_id = schema + .find_type_id(object.name.as_ref().unwrap()) + .as_object_id() + .unwrap(); let fields = object.fields.as_mut().unwrap(); let mut field_ids = Vec::with_capacity(fields.len()); @@ -317,12 +326,14 @@ fn ingest_object(schema: &mut Schema, object: &mut FullType) { parent: super::StoredFieldParent::Object(object_id), name: field.name.take().unwrap(), r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + deprecation: Some(None) + .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) + .map(|_: Option<()>| field.deprecation_reason.clone()), }; field_ids.push(schema.push_field(field)); } - let object = super::StoredObject { name: object.name.take().unwrap(), implements_interfaces: Vec::new(), From d48c63d257acb725108922809a4fc3fd932fd873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 13:17:18 +0100 Subject: [PATCH 29/93] Make progress on input object handling --- graphql_client_codegen/src/codegen.rs | 2 +- graphql_client_codegen/src/objects.rs | 76 +++++++++---------- graphql_client_codegen/src/resolution.rs | 43 ++++------- graphql_client_codegen/src/schema.rs | 6 ++ .../src/schema/graphql_parser_conversion.rs | 10 ++- graphql_client_codegen/src/unions.rs | 7 -- 6 files changed, 68 insertions(+), 76 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 099845467..9cc654981 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -96,7 +96,7 @@ fn generate_variables_struct( let variable_defaults = operation.variables().map(|variable| { let method_name = format!("default_{}", variable.name()); let method_name = Ident::new(&method_name, Span::call_site()); - let method_return_type = quote!(String); + let method_return_type = render_variable_field_type(variable); quote!( pub fn #method_name() -> #method_return_type { diff --git a/graphql_client_codegen/src/objects.rs b/graphql_client_codegen/src/objects.rs index 6c1112c45..46ff39dad 100644 --- a/graphql_client_codegen/src/objects.rs +++ b/graphql_client_codegen/src/objects.rs @@ -111,46 +111,46 @@ impl<'schema> GqlObject<'schema> { // }) // } - pub(crate) fn response_for_selection( - &self, - query_context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result { - unimplemented!() - // let derives = query_context.response_derives(); - // let name = Ident::new(prefix, Span::call_site()); - // let fields = self.response_fields_for_selection(query_context, selection, prefix)?; - // let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?; - // let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc])); - // Ok(quote! { - // #(#field_impls)* - - // #derives - // #description - // pub struct #name { - // #(#fields,)* - // } - // }) - } + // pub(crate) fn response_for_selection( + // &self, + // query_context: &QueryContext<'_>, + // selection: &Selection<'_>, + // prefix: &str, + // ) -> Result { + // unimplemented!() + // // let derives = query_context.response_derives(); + // // let name = Ident::new(prefix, Span::call_site()); + // // let fields = self.response_fields_for_selection(query_context, selection, prefix)?; + // // let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?; + // // let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc])); + // // Ok(quote! { + // // #(#field_impls)* + + // // #derives + // // #description + // // pub struct #name { + // // #(#fields,)* + // // } + // // }) + // } - pub(crate) fn field_impls_for_selection( - &self, - query_context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result, anyhow::Error> { - field_impls_for_selection(&self.fields, query_context, selection, prefix) - } + // pub(crate) fn field_impls_for_selection( + // &self, + // query_context: &QueryContext<'_>, + // selection: &Selection<'_>, + // prefix: &str, + // ) -> Result, anyhow::Error> { + // field_impls_for_selection(&self.fields, query_context, selection, prefix) + // } - pub(crate) fn response_fields_for_selection( - &self, - query_context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result, anyhow::Error> { - response_fields_for_selection(&self.name, &self.fields, query_context, selection, prefix) - } + // pub(crate) fn response_fields_for_selection( + // &self, + // query_context: &QueryContext<'_>, + // selection: &Selection<'_>, + // prefix: &str, + // ) -> Result, anyhow::Error> { + // response_fields_for_selection(&self.name, &self.fields, query_context, selection, prefix) + // } } #[cfg(test)] diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index c64d22ab0..d005bfa76 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -280,11 +280,6 @@ pub(crate) fn resolve( object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), operation_type: crate::operations::OperationType::Mutation, - variables: resolve_variables( - &m.variable_definitions, - schema, - OperationId(resolved_query.operations.len() as u32), - )?, selection: Vec::with_capacity(m.selection_set.items.len()), }; @@ -297,11 +292,6 @@ pub(crate) fn resolve( let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), operation_type: crate::operations::OperationType::Query, - variables: resolve_variables( - &q.variable_definitions, - schema, - OperationId(resolved_query.operations.len() as u32), - )?, object_id: on.id(), selection: Vec::with_capacity(q.selection_set.items.len()), }; @@ -320,11 +310,6 @@ pub(crate) fn resolve( .expect("subscription without name") .to_owned(), operation_type: crate::operations::OperationType::Subscription, - variables: resolve_variables( - &s.variable_definitions, - schema, - OperationId(resolved_query.operations.len() as u32), - )?, object_id: on.id(), selection: Vec::with_capacity(s.selection_set.items.len()), }; @@ -504,18 +489,21 @@ fn resolve_operation( let on = schema.mutation_type(); let (id, _) = query.find_operation(m.name.as_ref().unwrap()).unwrap(); + resolve_variables(query, &m.variable_definitions, schema, id); resolve_object_selection(query, on, &m.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Query(q) => { let on = schema.query_type(); let (id, _) = query.find_operation(q.name.as_ref().unwrap()).unwrap(); + resolve_variables(query, &q.variable_definitions, schema, id); resolve_object_selection(query, on, &q.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Subscription(s) => { let on = schema.query_type(); let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap(); + resolve_variables(query, &s.variable_definitions, schema, id); resolve_object_selection(query, on, &s.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::SelectionSet(_) => { @@ -614,7 +602,7 @@ impl<'a> OperationRef<'a> { } pub(crate) fn has_no_variables(&self) -> bool { - self.get().variables.is_empty() + self.variables().next().is_none() } } @@ -622,7 +610,6 @@ impl<'a> OperationRef<'a> { pub(crate) struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, - variables: Vec, selection: Vec, object_id: ObjectId, } @@ -743,19 +730,17 @@ impl UsedTypes { } fn resolve_variables( + query: &mut ResolvedQuery, variables: &[graphql_parser::query::VariableDefinition], schema: &Schema, operation_id: OperationId, -) -> Result, anyhow::Error> { - variables - .iter() - .map(|var| { - Ok(ResolvedVariable { - operation_id, - name: var.name.clone(), - default: var.default_value.clone(), - r#type: resolve_field_type(schema, &var.var_type), - }) - }) - .collect() +) { + for var in variables { + query.variables.push(ResolvedVariable { + operation_id, + name: var.name.clone(), + default: var.default_value.clone(), + r#type: resolve_field_type(schema, &var.var_type), + }); + } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 1c0d5865d..b06b9370c 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -89,6 +89,12 @@ pub(crate) struct EnumId(usize); #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct InputId(usize); +impl InputId { + fn new(idx: usize) -> Self { + InputId(idx) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct StoredFieldId(usize); diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index f923c1a83..2efb69c8a 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -244,7 +244,15 @@ fn find_deprecation(directives: &[parser::Directive]) -> Option> } fn ingest_input(schema: &mut Schema, input: &mut parser::InputObjectType) { - unimplemented!() + let input_id = InputId::new(schema.stored_inputs.len()); + + // TODO: input object fields + let input = super::StoredInputType { + name: std::mem::replace(&mut input.name, String::new()), + fields: Vec::new(), + }; + + schema.stored_inputs.push(input); } fn objects_mut(doc: &mut Document) -> impl Iterator { diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index b8e48b904..c6b8dd19a 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -6,13 +6,6 @@ use quote::quote; use std::cell::Cell; use std::collections::BTreeSet; -pub(crate) fn union_type_to_rust( - ctx: &mut QueryContext<'_>, - union: (), -) -> Result { - todo!() -} - /// A GraphQL union (simplified schema representation). /// /// For code generation purposes, unions will "flatten" fragment spreads, so there is only one enum for the selection. See the tests in the graphql_client crate for examples. From efee0df688abdf91e718dd53ecfaf0fb44386a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 22 Feb 2020 23:05:07 +0100 Subject: [PATCH 30/93] Address some of the test panics --- graphql_client_codegen/src/codegen.rs | 26 ++++++++++++------- graphql_client_codegen/src/resolution.rs | 17 ++++++++++-- graphql_client_codegen/src/schema.rs | 4 +++ .../src/schema/graphql_parser_conversion.rs | 2 +- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 9cc654981..316b9eee2 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -3,7 +3,7 @@ use crate::{ field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, - schema::{FieldRef, TypeId}, + schema::TypeId, shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; @@ -14,7 +14,6 @@ use quote::quote; /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. pub(crate) fn select_operation<'a>( query: &'a ResolvedQuery, - struct_name: &str, norm: Normalization, ) -> Option { @@ -268,14 +267,21 @@ fn render_selection<'a>( Selection::Field(field) => { let field = select.refocus(field); - match ( + let deprecation_annotation = match ( field.schema_field().is_deprecated(), options.deprecation_strategy(), ) { - (false, _) | (true, DeprecationStrategy::Allow) => (), - (true, DeprecationStrategy::Warn) => todo!("deprecation annotation"), + (false, _) | (true, DeprecationStrategy::Allow) => None, + (true, DeprecationStrategy::Warn) => { + let msg = field + .schema_field() + .deprecation_message() + .unwrap_or("This field is deprecated."); + + Some(quote!(#[deprecated(note = #msg)])) + } (true, DeprecationStrategy::Deny) => continue, - } + }; let ident = field_name(&field); match field.schema_field().field_type().item { @@ -285,7 +291,7 @@ fn render_selection<'a>( let type_name = decorate_type(&type_name, field.schema_field().type_qualifiers()); - field_buffer.push(quote!(#ident: #type_name)); + field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); } TypeId::Scalar(scalar) => { let type_name = @@ -293,14 +299,14 @@ fn render_selection<'a>( let type_name = decorate_type(&type_name, field.schema_field().type_qualifiers()); - field_buffer.push(quote!(#ident: #type_name)); + field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); } TypeId::Object(_) => { let struct_name = Ident::new(&select.full_path_prefix(), Span::call_site()); let field_type = decorate_type(&struct_name, field.schema_field().type_qualifiers()); - field_buffer.push(quote!(#ident: #field_type)); + field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); let mut fields = Vec::new(); render_selection( @@ -332,7 +338,7 @@ fn render_selection<'a>( pub typename: String )); } - Selection::InlineFragment(inline) => todo!("render inline fragment"), + Selection::InlineFragment(_inline) => todo!("render inline fragment"), Selection::FragmentSpread(frag) => { let frag = select.refocus(*frag); let original_field_name = frag.name().to_snake_case(); diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index d005bfa76..2736e75d8 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -7,7 +7,7 @@ use crate::{ field_type::GraphqlTypeQualifier, schema::{ resolve_field_type, EnumRef, InputId, ObjectId, Schema, StoredFieldId, StoredFieldType, - TypeId, WithSchema, + TypeId, UnionRef, WithSchema, }, }; @@ -360,6 +360,15 @@ fn resolve_fragment( Ok(()) } +fn resolve_union_selection( + query: &mut ResolvedQuery, + union: UnionRef<'_>, + selection_set: &graphql_parser::query::SelectionSet, + parent: SelectionParent, +) -> anyhow::Result<()> { + Ok(()) +} + fn resolve_object_selection<'a>( query: &mut ResolvedQuery, object: impl crate::schema::ObjectRefLike<'a>, @@ -434,6 +443,10 @@ fn resolve_selection( let interface = schema.interface(interface_id); resolve_object_selection(ctx, interface, selection_set, parent)?; } + TypeId::Union(union_id) => { + let union = schema.union(union_id); + resolve_union_selection(ctx, union, selection_set, parent)?; + } other => { anyhow::ensure!( selection_set.items.is_empty(), @@ -500,7 +513,7 @@ fn resolve_operation( resolve_object_selection(query, on, &q.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Subscription(s) => { - let on = schema.query_type(); + let on = schema.subscription_type(); let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &s.variable_definitions, schema, id); diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index b06b9370c..0a5e6e45d 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -452,6 +452,10 @@ impl Schema { self.stored_scalars.get(scalar_id.0).unwrap() } + pub(crate) fn union(&self, id: UnionId) -> UnionRef<'_> { + WithSchema::new(self, id) + } + pub(crate) fn object(&self, id: ObjectId) -> WithSchema<'_, ObjectId> { WithSchema::new(self, id) } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 2efb69c8a..defeb3983 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,4 @@ -use super::{EnumId, InputId, InterfaceId, ObjectId, ScalarId, Schema, TypeId, UnionId}; +use super::{InputId, Schema, TypeId, UnionId}; use crate::schema::resolve_field_type; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; From 018b38b1494a183ceb5be023603981d44df447dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 23 Feb 2020 12:40:28 +0100 Subject: [PATCH 31/93] Explore rendering of selections on unions --- graphql_client_codegen/src/codegen.rs | 31 +++++++++++++++++++++--- graphql_client_codegen/src/resolution.rs | 13 ++++++---- graphql_client_codegen/src/schema.rs | 8 ++++-- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 316b9eee2..76a801f4c 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -301,7 +301,7 @@ fn render_selection<'a>( field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); } - TypeId::Object(_) => { + TypeId::Object(_) | TypeId::Interface(_) => { let struct_name = Ident::new(&select.full_path_prefix(), Span::call_site()); let field_type = decorate_type(&struct_name, field.schema_field().type_qualifiers()); @@ -326,10 +326,33 @@ fn render_selection<'a>( response_type_buffer.push(struct_definition); } - _other => { - Ident::new("String", Span::call_site()); - // unimplemented!("selection on {:?}", other) + TypeId::Union(_) => { + // Generate the union enum here + let enum_name = Ident::new(&select.full_path_prefix(), Span::call_site()); + let field_type = + decorate_type(&enum_name, field.schema_field().type_qualifiers()); + + field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); + + let mut fields = Vec::with_capacity(select.get().subselection().len()); + render_selection( + select.subselection(), + &mut fields, + response_type_buffer, + response_derives, + options, + ); + + let enum_definition = quote! { + #response_derives + pub enum #enum_name { + + } + }; + + response_type_buffer.push(enum_definition); } + TypeId::Input(_) => unreachable!("field selection on input type"), }; } Selection::Typename => { diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 2736e75d8..de9ed9839 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -129,7 +129,10 @@ impl<'a> SelectionRef<'a> { pub(crate) fn subselection<'b>( &'b self, ) -> impl Iterator> + 'b { - self.get().subselection().map(move |s| self.refocus(*s)) + self.get() + .subselection() + .iter() + .map(move |s| self.refocus(*s)) } pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { @@ -218,11 +221,11 @@ pub(crate) enum Selection { } impl Selection { - fn subselection(&self) -> impl Iterator { + pub(crate) fn subselection(&self) -> &[SelectionId] { match self { - Selection::Field(field) => field.selection_set.iter(), - Selection::InlineFragment(inline_fragment) => inline_fragment.selection_set.iter(), - _ => [].iter(), + Selection::Field(field) => field.selection_set.as_slice(), + Selection::InlineFragment(inline_fragment) => &inline_fragment.selection_set, + _ => &[], } } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 0a5e6e45d..304077982 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -702,10 +702,14 @@ impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { } fn get_field_by_name(&self, name: &str) -> Option> { - self.get_field_by_name(name) + self.get() + .fields + .iter() + .map(|field_id| self.schema.field(*field_id)) + .find(|field| field.name() == name) } fn schema(&self) -> SchemaRef<'a> { - InterfaceRef::schema(self) + self.schema } } From 60ec91d6b75bd745919c9c5627f2614b90bcb09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 23 Feb 2020 21:23:32 +0100 Subject: [PATCH 32/93] Progress on unions codegen --- graphql_client_codegen/src/codegen.rs | 45 +++++++++++++++++++++--- graphql_client_codegen/src/resolution.rs | 38 +++++++++++++++++++- graphql_client_codegen/src/schema.rs | 10 +++--- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 76a801f4c..1104a1f8c 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -241,10 +241,12 @@ fn render_response_data_fields<'a>( ) -> (Vec, Vec) { let mut response_types = Vec::new(); let mut fields = Vec::new(); + let mut variants = Vec::new(); render_selection( operation.selection(), &mut fields, + &mut variants, &mut response_types, response_derives, options, @@ -256,6 +258,7 @@ fn render_response_data_fields<'a>( fn render_selection<'a>( selection: impl Iterator>, field_buffer: &mut Vec, + variants_buffer: &mut Vec, response_type_buffer: &mut Vec, response_derives: &impl quote::ToTokens, options: &GraphQLClientCodegenOptions, @@ -302,25 +305,38 @@ fn render_selection<'a>( field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); } TypeId::Object(_) | TypeId::Interface(_) => { - let struct_name = Ident::new(&select.full_path_prefix(), Span::call_site()); + let struct_name_string = select.full_path_prefix(); + let struct_name = Ident::new(&struct_name_string, Span::call_site()); let field_type = decorate_type(&struct_name, field.schema_field().type_qualifiers()); field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); let mut fields = Vec::new(); + let mut variants = Vec::new(); render_selection( select.subselection(), &mut fields, + &mut variants, response_type_buffer, response_derives, options, ); + let on_field = if variants.len() > 0 { + let enum_name = format!("{}On", struct_name_string); + let enum_name = Ident::new(&enum_name, Span::call_site()); + + Some(quote!(on: #enum_name,)) + } else { + None + }; + let struct_definition = quote! { #response_derives pub struct #struct_name { #(#fields),* + #on_field } }; @@ -334,19 +350,30 @@ fn render_selection<'a>( field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); - let mut fields = Vec::with_capacity(select.get().subselection().len()); + let mut fields = Vec::new(); + let mut variants = Vec::new(); render_selection( select.subselection(), &mut fields, + &mut variants, response_type_buffer, response_derives, options, ); + // Idea: typed selection representation. + // UnionSelection { variants } + // InterfaceSelection { fields variants } + // ObjectSelection { fields } + + // alternatively: pass &mut fields and &mut variants, here assert that we __only__ get variants. + // on interfaces expect both optionally (with the On field and struct) + // on objects expect no variants + let enum_definition = quote! { #response_derives pub enum #enum_name { - + #(#variants),* } }; @@ -361,7 +388,15 @@ fn render_selection<'a>( pub typename: String )); } - Selection::InlineFragment(_inline) => todo!("render inline fragment"), + Selection::InlineFragment(inline) => { + let variant_name = select.refocus(inline).on().name(); + let variant_name = Ident::new(variant_name, Span::call_site()); + let variant_struct_name = select.full_path_prefix(); + let variant_struct_name = Ident::new(&variant_struct_name, Span::call_site()); + + let variant = quote!(#variant_name(#variant_struct_name)); + variants_buffer.push(variant); + } Selection::FragmentSpread(frag) => { let frag = select.refocus(*frag); let original_field_name = frag.name().to_snake_case(); @@ -457,10 +492,12 @@ fn generate_fragment_definitions( let struct_name = fragment.name(); let struct_ident = Ident::new(struct_name, Span::call_site()); let mut fields = Vec::with_capacity(fragment.selection_set_len()); + let mut variants = Vec::new(); render_selection( fragment.selection(), &mut fields, + &mut variants, &mut response_type_buffer, response_derives, options, diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index de9ed9839..897693943 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -7,7 +7,7 @@ use crate::{ field_type::GraphqlTypeQualifier, schema::{ resolve_field_type, EnumRef, InputId, ObjectId, Schema, StoredFieldId, StoredFieldType, - TypeId, UnionRef, WithSchema, + TypeId, TypeRef, UnionRef, WithSchema, }, }; @@ -18,6 +18,7 @@ pub(crate) type OperationRef<'a> = WithQuery<'a, OperationId>; pub(crate) type FragmentRef<'a> = WithQuery<'a, ResolvedFragmentId>; pub(crate) type VariableRef<'a> = WithQuery<'a, VariableId>; pub(crate) type SelectionRef<'a> = WithQuery<'a, SelectionId>; +pub(crate) type InlineFragmentRef<'a> = WithQuery<'a, &'a InlineFragment>; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) struct SelectionId(u32); @@ -236,6 +237,12 @@ pub(crate) struct InlineFragment { selection_set: Vec, } +impl<'a> InlineFragmentRef<'a> { + pub(crate) fn on(&self) -> TypeRef<'a> { + self.with_schema(self.item.type_id) + } +} + #[derive(Debug)] pub(crate) struct SelectedField { alias: Option, @@ -369,6 +376,35 @@ fn resolve_union_selection( selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, ) -> anyhow::Result<()> { + for item in selection_set.items.iter() { + match item { + graphql_parser::query::Selection::Field(field) => { + if field.name == TYPENAME_FIELD { + let id = query.push_selection(Selection::Typename, parent); + parent.add_to_selection_set(query, id); + } else { + anyhow::bail!("Invalid field selection on union field ({:?})", parent); + } + } + graphql_parser::query::Selection::InlineFragment(inline_fragment) => { + let selection_id = + resolve_inline_fragment(query, union.schema(), inline_fragment, parent)?; + + parent.add_to_selection_set(query, selection_id); + } + graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { + // TODO: this is very duplicated. + let (fragment_id, _) = query + .find_fragment(&fragment_spread.fragment_name) + .expect("TODO: fragment resolution"); + + let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent); + + parent.add_to_selection_set(query, id); + } + } + } + Ok(()) } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 304077982..678bdd967 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -4,6 +4,8 @@ mod json_conversion; use crate::field_type::GraphqlTypeQualifier; use std::collections::HashMap; +pub(crate) type TypeRef<'a> = WithSchema<'a, TypeId>; + #[derive(Debug, Clone, Copy)] pub(crate) struct WithSchema<'a, T> { schema: &'a Schema, @@ -18,6 +20,10 @@ impl<'a, T> WithSchema<'a, T> { } } + pub(crate) fn schema(&self) -> &'a Schema { + self.schema + } + pub(crate) fn new(schema: &'a Schema, item: T) -> WithSchema<'a, T> { WithSchema { schema, item } } @@ -538,10 +544,6 @@ impl<'a> ObjectRef<'a> { self.fields().find(|field| field.name() == name) } - pub(crate) fn schema(&self) -> SchemaRef<'a> { - self.schema - } - pub(crate) fn id(&self) -> ObjectId { self.item } From 6380559c5baf8d25593bc3012d1742a8412468e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Mon, 24 Feb 2020 00:06:55 +0100 Subject: [PATCH 33/93] Work towards rendering fragments on unions --- graphql_client_codegen/src/codegen.rs | 99 ++++++++++++++---------- graphql_client_codegen/src/resolution.rs | 4 + graphql_client_codegen/src/schema.rs | 2 +- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 1104a1f8c..37da81ad1 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -323,30 +323,21 @@ fn render_selection<'a>( options, ); - let on_field = if variants.len() > 0 { - let enum_name = format!("{}On", struct_name_string); - let enum_name = Ident::new(&enum_name, Span::call_site()); - - Some(quote!(on: #enum_name,)) - } else { - None - }; - - let struct_definition = quote! { - #response_derives - pub struct #struct_name { - #(#fields),* - #on_field - } - }; + let struct_definition = render_object_like_struct( + response_derives, + &struct_name_string, + &fields, + &variants, + ); response_type_buffer.push(struct_definition); } TypeId::Union(_) => { // Generate the union enum here - let enum_name = Ident::new(&select.full_path_prefix(), Span::call_site()); + let enum_name = select.full_path_prefix(); + let enum_name_ident = Ident::new(&enum_name, Span::call_site()); let field_type = - decorate_type(&enum_name, field.schema_field().type_qualifiers()); + decorate_type(&enum_name_ident, field.schema_field().type_qualifiers()); field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); @@ -361,22 +352,8 @@ fn render_selection<'a>( options, ); - // Idea: typed selection representation. - // UnionSelection { variants } - // InterfaceSelection { fields variants } - // ObjectSelection { fields } - - // alternatively: pass &mut fields and &mut variants, here assert that we __only__ get variants. - // on interfaces expect both optionally (with the On field and struct) - // on objects expect no variants - - let enum_definition = quote! { - #response_derives - pub enum #enum_name { - #(#variants),* - } - }; - + let enum_definition = + render_union_enum(response_derives, &enum_name, &variants); response_type_buffer.push(enum_definition); } TypeId::Input(_) => unreachable!("field selection on input type"), @@ -490,7 +467,6 @@ fn generate_fragment_definitions( for fragment in fragments { let struct_name = fragment.name(); - let struct_ident = Ident::new(struct_name, Span::call_site()); let mut fields = Vec::with_capacity(fragment.selection_set_len()); let mut variants = Vec::new(); @@ -503,15 +479,58 @@ fn generate_fragment_definitions( options, ); - let fragment_definition = quote! { - #response_derives - pub struct #struct_ident { - #(#fields),* + let definition = match fragment.on().item { + TypeId::Interface(_) | TypeId::Object(_) => { + render_object_like_struct(response_derives, struct_name, &fields, &variants) } + TypeId::Union(_) => render_union_enum(response_derives, struct_name, &variants), + other => panic!("Fragment on invalid type: {:?}", other), }; - fragment_definitions.push(fragment_definition.into()) + fragment_definitions.push(definition) } (fragment_definitions, response_type_buffer) } + +/// Render a struct for a selection on an object or interface. +fn render_object_like_struct( + response_derives: &impl quote::ToTokens, + struct_name: &str, + fields: &[TokenStream], + variants: &[TokenStream], +) -> TokenStream { + let on_field = if variants.len() > 0 { + let enum_name = format!("{}On", struct_name); + let enum_name = Ident::new(&enum_name, Span::call_site()); + + Some(quote!(on: #enum_name,)) + } else { + None + }; + + let struct_ident = Ident::new(struct_name, Span::call_site()); + + quote! { + #response_derives + pub struct #struct_ident { + #(#fields),* + #on_field + } + } +} + +fn render_union_enum( + response_derives: &impl quote::ToTokens, + enum_name: &str, + variants: &[TokenStream], +) -> TokenStream { + let enum_ident = Ident::new(enum_name, Span::call_site()); + + quote! { + #response_derives + pub enum #enum_ident { + #(#variants),* + } + } +} diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 897693943..5c3bf89bf 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -733,6 +733,10 @@ impl<'a> FragmentRef<'a> { pub(crate) fn selection_set_len(&self) -> usize { self.get().selection.len() } + + pub(crate) fn on(&self) -> TypeRef<'a> { + self.with_schema(self.get().on) + } } #[derive(Debug, Default)] diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 678bdd967..677fa0f4a 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -560,7 +560,7 @@ impl<'a> FieldRef<'a> { &self.get().name } - pub(crate) fn field_type(&self) -> WithSchema<'a, TypeId> { + pub(crate) fn field_type(&self) -> TypeRef<'a> { self.refocus(self.get().r#type.id) } From 1632d83ea3a7f6495f8661d41033e6da4d71d5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Mon, 24 Feb 2020 00:32:24 +0100 Subject: [PATCH 34/93] Expose a flaw in union type generation We always generate an enum, even when there is a fragment. The more correct thing to do would be generating potentially two enums, one for the fragment and one for the rest of the fields. This also means we can branch less, most likely. --- graphql_client/tests/union_query.rs | 15 ++++++++++++++- graphql_client/tests/unions/union_query.graphql | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 10c0c6b59..56ce4d4f8 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -2,6 +2,14 @@ use graphql_client::*; const RESPONSE: &str = include_str!("unions/union_query_response.json"); +#[derive(GraphQLQuery)] +#[graphql( + query_path = "tests/unions/union_query.graphql", + schema_path = "tests/unions/union_schema.graphql", + response_derives = "PartialEq, Debug" +)] +pub struct FragmentOnUnion; + #[derive(GraphQLQuery)] #[graphql( query_path = "tests/unions/union_query.graphql", @@ -16,7 +24,7 @@ pub struct UnionQuery; schema_path = "tests/unions/union_schema.graphql", response_derives = "PartialEq, Debug" )] -pub struct FragmentOnUnion; +pub struct FragmentAndMoreOnUnion; #[test] fn union_query_deserialization() { @@ -78,3 +86,8 @@ fn fragment_on_union() { assert_eq!(response_data, expected); } + +#[test] +fn fragment_and_more_on_union() { + todo!() +} diff --git a/graphql_client/tests/unions/union_query.graphql b/graphql_client/tests/unions/union_query.graphql index 28387924f..aec63d083 100644 --- a/graphql_client/tests/unions/union_query.graphql +++ b/graphql_client/tests/unions/union_query.graphql @@ -32,3 +32,12 @@ query FragmentOnUnion { ...NamesFragment } } + +query FragmentAndMoreOnUnion { + names { + ...NamesFragment + ... on Dog { + isGoodDog + } + } +} From a0b21a0d8c67da7cc22770b7f17a0e9894ab6596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Mon, 24 Feb 2020 01:26:17 +0100 Subject: [PATCH 35/93] Identify next steps for codegen --- graphql_client/tests/union_query.rs | 16 ++++--- graphql_client_codegen/src/codegen.rs | 64 ++++++++++++++++++++------- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 56ce4d4f8..4fcdf14f4 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -8,7 +8,7 @@ const RESPONSE: &str = include_str!("unions/union_query_response.json"); schema_path = "tests/unions/union_schema.graphql", response_derives = "PartialEq, Debug" )] -pub struct FragmentOnUnion; +pub struct UnionQuery; #[derive(GraphQLQuery)] #[graphql( @@ -16,7 +16,7 @@ pub struct FragmentOnUnion; schema_path = "tests/unions/union_schema.graphql", response_derives = "PartialEq, Debug" )] -pub struct UnionQuery; +pub struct FragmentOnUnion; #[derive(GraphQLQuery)] #[graphql( @@ -76,11 +76,13 @@ fn fragment_on_union() { title: "Mozilla".to_string(), }, ), - fragment_on_union::FragmentOnUnionNames::Dog( - fragment_on_union::FragmentOnUnionNamesOnDog { - name: "Norbert".to_string(), - }, - ), + fragment_on_union::FragmentOnUnionNames { + names_fragment: fragment_on_union::NamesFragment::Dog( + fragment_on_union::NamesFragmentOnDog { + name: "Norbert".to_string(), + }, + ), + }, ]), }; diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 37da81ad1..9657bcb3d 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -333,11 +333,17 @@ fn render_selection<'a>( response_type_buffer.push(struct_definition); } TypeId::Union(_) => { - // Generate the union enum here - let enum_name = select.full_path_prefix(); - let enum_name_ident = Ident::new(&enum_name, Span::call_site()); - let field_type = - decorate_type(&enum_name_ident, field.schema_field().type_qualifiers()); + // Generate the union struct here. + // + // We want a struct, because we want to preserve fragments in the output, + // and there can be fragment and inline spreads for a given selection set + // on an enum. + let struct_name = select.full_path_prefix(); + let struct_name_ident = Ident::new(&struct_name, Span::call_site()); + let field_type = decorate_type( + &struct_name_ident, + field.schema_field().type_qualifiers(), + ); field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); @@ -352,9 +358,13 @@ fn render_selection<'a>( options, ); - let enum_definition = - render_union_enum(response_derives, &enum_name, &variants); - response_type_buffer.push(enum_definition); + let struct_definition = render_object_like_struct( + response_derives, + &struct_name, + &fields, + &variants, + ); + response_type_buffer.push(struct_definition); } TypeId::Input(_) => unreachable!("field selection on input type"), }; @@ -373,6 +383,19 @@ fn render_selection<'a>( let variant = quote!(#variant_name(#variant_struct_name)); variants_buffer.push(variant); + + // Render the struct for the selection + + todo!("We have to do more here"); + + render_selection( + select.subselection(), + &mut fields, + &mut variants, + response_type_buffer, + response_derives, + options, + ); } Selection::FragmentSpread(frag) => { let frag = select.refocus(*frag); @@ -500,13 +523,20 @@ fn render_object_like_struct( fields: &[TokenStream], variants: &[TokenStream], ) -> TokenStream { - let on_field = if variants.len() > 0 { - let enum_name = format!("{}On", struct_name); - let enum_name = Ident::new(&enum_name, Span::call_site()); - - Some(quote!(on: #enum_name,)) + let (on_field, on_enum) = if variants.len() > 0 { + let enum_name_str = format!("{}On", struct_name); + let enum_name = Ident::new(&enum_name_str, Span::call_site()); + + ( + Some(quote!(on: #enum_name,)), + Some(render_union_enum( + response_derives, + &enum_name_str, + variants, + )), + ) } else { - None + (None, None) }; let struct_ident = Ident::new(struct_name, Span::call_site()); @@ -514,9 +544,11 @@ fn render_object_like_struct( quote! { #response_derives pub struct #struct_ident { - #(#fields),* + #(#fields,)* #on_field } + + #on_enum } } @@ -530,7 +562,7 @@ fn render_union_enum( quote! { #response_derives pub enum #enum_ident { - #(#variants),* + #(#variants,)* } } } From 4ec9674f202a41836a4caf23f7ac3e54690b68fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Mon, 24 Feb 2020 23:53:10 +0100 Subject: [PATCH 36/93] Use selection ids to resolve selections --- graphql_client/tests/union_query.rs | 64 +++++++++++++--------- graphql_client_codegen/src/codegen.rs | 70 +++++++++++++++--------- graphql_client_codegen/src/resolution.rs | 12 ++++ 3 files changed, 94 insertions(+), 52 deletions(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 4fcdf14f4..fff8a56d9 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -32,21 +32,19 @@ fn union_query_deserialization() { let expected = union_query::ResponseData { names: Some(vec![ - union_query::UnionQueryNames::Person(union_query::UnionQueryNamesOnPerson { + union_query::UnionQueryNames::Person { first_name: "Audrey".to_string(), last_name: Some("Lorde".to_string()), - }), - union_query::UnionQueryNames::Dog(union_query::UnionQueryNamesOnDog { + }, + union_query::UnionQueryNames::Dog { name: "Laïka".to_string(), - }), - union_query::UnionQueryNames::Organization( - union_query::UnionQueryNamesOnOrganization { - title: "Mozilla".to_string(), - }, - ), - union_query::UnionQueryNames::Dog(union_query::UnionQueryNamesOnDog { + }, + union_query::UnionQueryNames::Organization { + title: "Mozilla".to_string(), + }, + union_query::UnionQueryNames::Dog { name: "Norbert".to_string(), - }), + }, ]), }; @@ -61,27 +59,25 @@ fn fragment_on_union() { let expected = fragment_on_union::ResponseData { names: Some(vec![ - fragment_on_union::FragmentOnUnionNames::Person( - fragment_on_union::FragmentOnUnionNamesOnPerson { + fragment_on_union::FragmentOnUnionNames { + names_fragment: fragment_on_union::NamesFragment::Person { first_name: "Audrey".to_string(), }, - ), - fragment_on_union::FragmentOnUnionNames::Dog( - fragment_on_union::FragmentOnUnionNamesOnDog { + }, + fragment_on_union::FragmentOnUnionNames { + names_fragment: fragment_on_union::NamesFragment::Dog { name: "Laïka".to_string(), }, - ), - fragment_on_union::FragmentOnUnionNames::Organization( - fragment_on_union::FragmentOnUnionNamesOnOrganization { + }, + fragment_on_union::FragmentOnUnionNames { + names_fragment: fragment_on_union::NamesFragment::Organization { title: "Mozilla".to_string(), }, - ), + }, fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Dog( - fragment_on_union::NamesFragmentOnDog { - name: "Norbert".to_string(), - }, - ), + names_fragment: fragment_on_union::NamesFragment::Dog { + name: "Norbert".to_string(), + }, }, ]), }; @@ -91,5 +87,21 @@ fn fragment_on_union() { #[test] fn fragment_and_more_on_union() { - todo!() + let _expected = fragment_and_more_on_union::ResponseData { + names: Some(vec![ + fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Person { + first_name: "Audrey".to_string(), + last_name: Some("Lorde".to_string()), + }, + fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { + name: "Laïka".to_string(), + }, + fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Organization { + title: "Mozilla".to_string(), + }, + fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { + name: "Norbert".to_string(), + }, + ]), + }; } diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 9657bcb3d..3643647bb 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -244,7 +244,8 @@ fn render_response_data_fields<'a>( let mut variants = Vec::new(); render_selection( - operation.selection(), + operation.refocus(()), + operation.selection_ids(), &mut fields, &mut variants, &mut response_types, @@ -256,7 +257,8 @@ fn render_response_data_fields<'a>( } fn render_selection<'a>( - selection: impl Iterator>, + q: WithQuery<'a, ()>, + selection: &[SelectionId], field_buffer: &mut Vec, variants_buffer: &mut Vec, response_type_buffer: &mut Vec, @@ -266,9 +268,9 @@ fn render_selection<'a>( // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) for select in selection { - match &select.get() { + match q.refocus(*select).get() { Selection::Field(field) => { - let field = select.refocus(field); + let field = q.refocus(field); let deprecation_annotation = match ( field.schema_field().is_deprecated(), @@ -305,7 +307,7 @@ fn render_selection<'a>( field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); } TypeId::Object(_) | TypeId::Interface(_) => { - let struct_name_string = select.full_path_prefix(); + let struct_name_string = q.refocus(*select).full_path_prefix(); let struct_name = Ident::new(&struct_name_string, Span::call_site()); let field_type = decorate_type(&struct_name, field.schema_field().type_qualifiers()); @@ -315,7 +317,8 @@ fn render_selection<'a>( let mut fields = Vec::new(); let mut variants = Vec::new(); render_selection( - select.subselection(), + q, + q.refocus(*select).subselection_ids(), &mut fields, &mut variants, response_type_buffer, @@ -338,7 +341,7 @@ fn render_selection<'a>( // We want a struct, because we want to preserve fragments in the output, // and there can be fragment and inline spreads for a given selection set // on an enum. - let struct_name = select.full_path_prefix(); + let struct_name = q.refocus(*select).full_path_prefix(); let struct_name_ident = Ident::new(&struct_name, Span::call_site()); let field_type = decorate_type( &struct_name_ident, @@ -350,7 +353,8 @@ fn render_selection<'a>( let mut fields = Vec::new(); let mut variants = Vec::new(); render_selection( - select.subselection(), + q, + q.refocus(*select).subselection_ids(), &mut fields, &mut variants, response_type_buffer, @@ -358,13 +362,9 @@ fn render_selection<'a>( options, ); - let struct_definition = render_object_like_struct( - response_derives, - &struct_name, - &fields, - &variants, - ); - response_type_buffer.push(struct_definition); + let enum_definition = + render_union_enum(response_derives, &struct_name, &variants); + response_type_buffer.push(enum_definition); } TypeId::Input(_) => unreachable!("field selection on input type"), }; @@ -376,29 +376,46 @@ fn render_selection<'a>( )); } Selection::InlineFragment(inline) => { - let variant_name = select.refocus(inline).on().name(); - let variant_name = Ident::new(variant_name, Span::call_site()); - let variant_struct_name = select.full_path_prefix(); - let variant_struct_name = Ident::new(&variant_struct_name, Span::call_site()); - - let variant = quote!(#variant_name(#variant_struct_name)); - variants_buffer.push(variant); + let variant_name_str = q.refocus(inline).on().name(); + let variant_name = Ident::new(variant_name_str, Span::call_site()); + let variant_struct_name_str = q.refocus(*select).full_path_prefix(); + let variant_struct_name = Ident::new(&variant_struct_name_str, Span::call_site()); // Render the struct for the selection - todo!("We have to do more here"); + let mut fields = Vec::new(); + let mut variants = Vec::new(); render_selection( - select.subselection(), + q, + q.refocus(*select).subselection_ids(), &mut fields, &mut variants, response_type_buffer, response_derives, options, ); + + let variant = quote!(#variant_name { }); + variants_buffer.push(variant); + + match q.refocus(inline).on().item { + TypeId::Object(_) | TypeId::Interface(_) => { + let struct_definition = render_object_like_struct( + response_derives, + &variant_struct_name_str, + &fields, + &variants, + ); + + response_type_buffer.push(struct_definition); + } + TypeId::Union(_) => todo!("inline fragment on union"), + other => unreachable!("Inline fragment on non composite type ({:?})", other), + } } Selection::FragmentSpread(frag) => { - let frag = select.refocus(*frag); + let frag = q.refocus(*frag); let original_field_name = frag.name().to_snake_case(); let final_field_name = keyword_replace(&original_field_name); let annotation = field_rename_annotation(&original_field_name, &final_field_name); @@ -494,7 +511,8 @@ fn generate_fragment_definitions( let mut variants = Vec::new(); render_selection( - fragment.selection(), + fragment.refocus(()), + fragment.selection_ids(), &mut fields, &mut variants, &mut response_type_buffer, diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 5c3bf89bf..9ea9f5d10 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -136,6 +136,10 @@ impl<'a> SelectionRef<'a> { .map(move |s| self.refocus(*s)) } + pub(crate) fn subselection_ids(&self) -> &'a [SelectionId] { + self.get().subselection() + } + pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { let selection = self.get(); match selection { @@ -640,6 +644,10 @@ impl<'a> OperationRef<'a> { .map(move |selection_id| self.refocus(*selection_id)) } + pub(crate) fn selection_ids(&self) -> &[SelectionId] { + &self.get().selection + } + pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { self.query .variables @@ -722,6 +730,10 @@ impl<'a> FragmentRef<'a> { .map(move |item| self.refocus(*item)) } + pub(crate) fn selection_ids(&self) -> &[SelectionId] { + &self.get().selection + } + fn to_path_segment(&self) -> String { self.get().name.to_camel_case() } From 053c34c77da7d6ab61522d998b78ee441addf452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Tue, 25 Feb 2020 00:51:08 +0100 Subject: [PATCH 37/93] Test union codegen --- graphql_client/tests/union_query.rs | 99 +++++++++++++++++---------- graphql_client_codegen/src/codegen.rs | 43 ++++++------ 2 files changed, 81 insertions(+), 61 deletions(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index fff8a56d9..39052f800 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -32,18 +32,32 @@ fn union_query_deserialization() { let expected = union_query::ResponseData { names: Some(vec![ - union_query::UnionQueryNames::Person { - first_name: "Audrey".to_string(), - last_name: Some("Lorde".to_string()), + union_query::UnionQueryNames { + typename: "Person".into(), + on: union_query::UnionQueryNamesOn::Person(union_query::UnionQueryNamesOnPerson { + first_name: "Audrey".to_string(), + last_name: Some("Lorde".to_string()), + }), }, - union_query::UnionQueryNames::Dog { - name: "Laïka".to_string(), + union_query::UnionQueryNames { + typename: "Dog".into(), + on: union_query::UnionQueryNamesOn::Dog(union_query::UnionQueryNamesOnDog { + name: "Laïka".to_string(), + }), }, - union_query::UnionQueryNames::Organization { - title: "Mozilla".to_string(), + union_query::UnionQueryNames { + typename: "Organization".into(), + on: union_query::UnionQueryNamesOn::Organization( + union_query::UnionQueryNamesOnOrganization { + title: "Mozilla".to_string(), + }, + ), }, - union_query::UnionQueryNames::Dog { - name: "Norbert".to_string(), + union_query::UnionQueryNames { + typename: "Dog".into(), + on: union_query::UnionQueryNamesOn::Dog(union_query::UnionQueryNamesOnDog { + name: "Norbert".to_string(), + }), }, ]), }; @@ -60,24 +74,32 @@ fn fragment_on_union() { let expected = fragment_on_union::ResponseData { names: Some(vec![ fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Person { - first_name: "Audrey".to_string(), - }, + names_fragment: fragment_on_union::NamesFragment::Person( + fragment_on_union::NamesFragmentOnPerson { + first_name: "Audrey".to_string(), + }, + ), }, fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Dog { - name: "Laïka".to_string(), - }, + names_fragment: fragment_on_union::NamesFragment::Dog( + fragment_on_union::NamesFragmentOnDog { + name: "Laïka".to_string(), + }, + ), }, fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Organization { - title: "Mozilla".to_string(), - }, + names_fragment: fragment_on_union::NamesFragment::Organization( + fragment_on_union::NamesFragmentOnOrganization { + title: "Mozilla".to_string(), + }, + ), }, fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Dog { - name: "Norbert".to_string(), - }, + names_fragment: fragment_on_union::NamesFragment::Dog( + fragment_on_union::NamesFragmentOnDog { + name: "Norbert".to_string(), + }, + ), }, ]), }; @@ -87,21 +109,22 @@ fn fragment_on_union() { #[test] fn fragment_and_more_on_union() { - let _expected = fragment_and_more_on_union::ResponseData { - names: Some(vec![ - fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Person { - first_name: "Audrey".to_string(), - last_name: Some("Lorde".to_string()), - }, - fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { - name: "Laïka".to_string(), - }, - fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Organization { - title: "Mozilla".to_string(), - }, - fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { - name: "Norbert".to_string(), - }, - ]), - }; + todo!(); + // let _expected = fragment_and_more_on_union::ResponseData { + // names: Some(vec![ + // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Person { + // first_name: "Audrey".to_string(), + // last_name: Some("Lorde".to_string()), + // }, + // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { + // name: "Laïka".to_string(), + // }, + // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Organization { + // title: "Mozilla".to_string(), + // }, + // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { + // name: "Norbert".to_string(), + // }, + // ]), + // }; } diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 3643647bb..9823fc4b1 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -362,9 +362,13 @@ fn render_selection<'a>( options, ); - let enum_definition = - render_union_enum(response_derives, &struct_name, &variants); - response_type_buffer.push(enum_definition); + let struct_definition = render_object_like_struct( + response_derives, + &struct_name, + &fields, + &variants, + ); + response_type_buffer.push(struct_definition); } TypeId::Input(_) => unreachable!("field selection on input type"), }; @@ -396,23 +400,17 @@ fn render_selection<'a>( options, ); - let variant = quote!(#variant_name { }); + let variant = quote!(#variant_name(#variant_struct_name)); variants_buffer.push(variant); - match q.refocus(inline).on().item { - TypeId::Object(_) | TypeId::Interface(_) => { - let struct_definition = render_object_like_struct( - response_derives, - &variant_struct_name_str, - &fields, - &variants, - ); + let struct_definition = render_object_like_struct( + response_derives, + &variant_struct_name_str, + &fields, + &variants, + ); - response_type_buffer.push(struct_definition); - } - TypeId::Union(_) => todo!("inline fragment on union"), - other => unreachable!("Inline fragment on non composite type ({:?})", other), - } + response_type_buffer.push(struct_definition); } Selection::FragmentSpread(frag) => { let frag = q.refocus(*frag); @@ -431,11 +429,9 @@ fn render_selection<'a>( } fn field_name(field: &WithQuery<'_, &SelectedField>) -> impl quote::ToTokens { - let name = field - .alias() - .unwrap_or_else(|| field.name()) - .to_snake_case(); - let final_name = keyword_replace(&name); + let name = field.alias().unwrap_or_else(|| field.name()); + let snake_case_name = name.to_snake_case(); + let final_name = keyword_replace(&snake_case_name); let rename_annotation = field_rename_annotation(&name, &final_name); let ident = Ident::new(&final_name, Span::call_site()); @@ -546,7 +542,7 @@ fn render_object_like_struct( let enum_name = Ident::new(&enum_name_str, Span::call_site()); ( - Some(quote!(on: #enum_name,)), + Some(quote!(#[serde(flatten)] pub on: #enum_name,)), Some(render_union_enum( response_derives, &enum_name_str, @@ -579,6 +575,7 @@ fn render_union_enum( quote! { #response_derives + #[serde(tag = "__typename")] pub enum #enum_ident { #(#variants,)* } From 0c394bb37e6e58d33c796fa99a0f4457b40a58c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 1 Mar 2020 22:44:51 +0100 Subject: [PATCH 38/93] Refactor selection resolution for clarity --- graphql_client/tests/interfaces.rs | 2 +- graphql_client_codegen/src/codegen.rs | 385 ++--------------- graphql_client_codegen/src/codegen/enums.rs | 87 ++++ .../src/codegen/selection.rs | 408 ++++++++++++++++++ graphql_client_codegen/src/constants.rs | 1 - graphql_client_codegen/src/field_type.rs | 2 - .../src/generated_module.rs | 20 +- graphql_client_codegen/src/lib.rs | 9 +- graphql_client_codegen/src/query.rs | 8 - graphql_client_codegen/src/resolution.rs | 329 +++++++++----- graphql_client_codegen/src/schema.rs | 219 +++++----- .../src/schema/graphql_parser_conversion.rs | 2 +- graphql_client_codegen/src/schema/schema.rs | 0 graphql_client_codegen/src/shared.rs | 7 +- 14 files changed, 874 insertions(+), 605 deletions(-) create mode 100644 graphql_client_codegen/src/codegen/enums.rs create mode 100644 graphql_client_codegen/src/codegen/selection.rs create mode 100644 graphql_client_codegen/src/schema/schema.rs diff --git a/graphql_client/tests/interfaces.rs b/graphql_client/tests/interfaces.rs index ee2d5f3ac..e6cc11157 100644 --- a/graphql_client/tests/interfaces.rs +++ b/graphql_client/tests/interfaces.rs @@ -146,7 +146,7 @@ fn fragment_in_interface() { name: "Laïka".to_string(), public_status: PublicStatus { display_name: true, - on: PublicStatusOn::Dog + on: PublicStatusOn::OTHER }, on: InterfaceWithFragmentQueryEverythingOn::Dog( InterfaceWithFragmentQueryEverythingOnDog { is_good_dog: true } diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 9823fc4b1..1d75f827e 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,46 +1,35 @@ +mod enums; +mod selection; + use crate::{ - deprecation::DeprecationStrategy, field_type::GraphqlTypeQualifier, normalization::Normalization, resolution::*, - schema::TypeId, shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; - -/// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. -pub(crate) fn select_operation<'a>( - query: &'a ResolvedQuery, - struct_name: &str, - norm: Normalization, -) -> Option { - query - .operations - .iter() - .position(|op| norm.operation(op.name()) == struct_name) -} +use selection::*; /// The main code generation function. pub(crate) fn response_for_query( - operation: WithQuery<'_, OperationId>, + operation: OperationRef<'_>, options: &GraphQLClientCodegenOptions, ) -> anyhow::Result { let all_used_types = operation.all_used_types(); let response_derives = render_derives(options.all_response_derives()); - let scalar_definitions = generate_scalar_definitions(operation, &all_used_types); - let enum_definitions = generate_enum_definitions(operation, &all_used_types, options); - let (fragment_definitions, fragment_nested_definitions) = - generate_fragment_definitions(operation, &all_used_types, &response_derives, options); + let scalar_definitions = generate_scalar_definitions(&operation, &all_used_types); + let enum_definitions = enums::generate_enum_definitions(&operation, &all_used_types, options); + let fragment_definitions = + generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); let input_object_definitions = - generate_input_object_definitions(operation, &all_used_types, options); - let variables_struct = generate_variables_struct(operation, options); + generate_input_object_definitions(&operation, &all_used_types, options); + let variables_struct = generate_variables_struct(&operation, options); - let (definitions, response_data_fields) = - render_response_data_fields(&operation, &response_derives, options); + let definitions = render_response_data_fields(&operation, &response_derives, options); let q = quote! { use serde::{Serialize, Deserialize}; @@ -58,27 +47,22 @@ pub(crate) fn response_for_query( #(#enum_definitions)* - #(#fragment_definitions)* - #(#input_object_definitions)* - #(#fragment_nested_definitions)* - - #(#definitions)* + #variables_struct - #response_derives - pub struct ResponseData { - #(#response_data_fields,)* - } + #(#fragment_definitions)* - #variables_struct + #definitions }; + // panic!("{}", q); + Ok(q) } fn generate_variables_struct( - operation: WithQuery<'_, OperationId>, + operation: &OperationRef<'_>, options: &GraphQLClientCodegenOptions, ) -> TokenStream { let variable_derives = options.all_variable_derives(); @@ -118,7 +102,7 @@ fn generate_variables_struct( variables_struct.into() } -fn generate_variable_struct_field(variable: WithQuery<'_, VariableId>) -> TokenStream { +fn generate_variable_struct_field(variable: VariableRef<'_>) -> TokenStream { let snake_case_name = variable.name().to_snake_case(); let ident = Ident::new( &crate::shared::keyword_replace(&snake_case_name), @@ -131,7 +115,7 @@ fn generate_variable_struct_field(variable: WithQuery<'_, VariableId>) -> TokenS } fn generate_scalar_definitions<'a, 'schema: 'a>( - operation: WithQuery<'schema, OperationId>, + operation: &OperationRef<'schema>, all_used_types: &'a crate::resolution::UsedTypes, ) -> impl Iterator + 'a { all_used_types.scalars(operation.schema()).map(|scalar| { @@ -140,305 +124,18 @@ fn generate_scalar_definitions<'a, 'schema: 'a>( }) } -/** - * About rust keyword escaping: variant_names and constructors must be escaped, - * variant_str not. - * Example schema: enum AnEnum { where \n self } - * Generated "variant_names" enum: pub enum AnEnum { where_, self_, Other(String), } - * Generated serialize line: "AnEnum::where_ => "where"," - */ -fn generate_enum_definitions<'a, 'schema: 'a>( - operation: WithQuery<'schema, OperationId>, - all_used_types: &'a crate::resolution::UsedTypes, - options: &'a GraphQLClientCodegenOptions, -) -> impl Iterator + 'a { - let derives = render_derives( - options - .all_response_derives() - .filter(|d| !&["Serialize", "Deserialize"].contains(d)), - ); - let normalization = options.normalization(); - - all_used_types.enums(operation.schema()).map(move |r#enum| { - let variant_names: Vec = r#enum - .variants() - .iter() - .map(|v| { - let name = normalization.enum_variant(crate::shared::keyword_replace(&v)); - let name = Ident::new(&name, Span::call_site()); - - // let description = &v.description; - // let description = description.as_ref().map(|d| quote!(#[doc = #d])); - - // quote!(#description #name) - quote!(#name) - }) - .collect(); - let variant_names = &variant_names; - let name_ident = normalization.enum_name(r#enum.name()); - let name_ident = Ident::new(&name_ident, Span::call_site()); - let constructors: Vec<_> = r#enum - .variants() - .iter() - .map(|v| { - let name = normalization.enum_variant(crate::shared::keyword_replace(v)); - let v = Ident::new(&name, Span::call_site()); - - quote!(#name_ident::#v) - }) - .collect(); - let constructors = &constructors; - let variant_str: Vec<&str> = r#enum.variants().iter().map(|s| s.as_str()).collect(); - let variant_str = &variant_str; - - let name = name_ident; - - quote! { - #derives - pub enum #name { - #(#variant_names,)* - Other(String), - } - - impl ::serde::Serialize for #name { - fn serialize(&self, ser: S) -> Result { - ser.serialize_str(match *self { - #(#constructors => #variant_str,)* - #name::Other(ref s) => &s, - }) - } - } - - impl<'de> ::serde::Deserialize<'de> for #name { - fn deserialize>(deserializer: D) -> Result { - let s = ::deserialize(deserializer)?; - - match s.as_str() { - #(#variant_str => Ok(#constructors),)* - _ => Ok(#name::Other(s)), - } - } - } - }}) -} - fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToTokens { let idents = derives.map(|s| Ident::new(s, Span::call_site())); quote!(#[derive(#(#idents),*)]) } -fn render_variable_field_type(variable: WithQuery<'_, VariableId>) -> TokenStream { +fn render_variable_field_type(variable: VariableRef<'_>) -> TokenStream { let full_name = Ident::new(variable.type_name(), Span::call_site()); decorate_type(&full_name, variable.type_qualifiers()) } -fn render_response_data_fields<'a>( - operation: &OperationRef<'a>, - response_derives: &impl quote::ToTokens, - options: &GraphQLClientCodegenOptions, -) -> (Vec, Vec) { - let mut response_types = Vec::new(); - let mut fields = Vec::new(); - let mut variants = Vec::new(); - - render_selection( - operation.refocus(()), - operation.selection_ids(), - &mut fields, - &mut variants, - &mut response_types, - response_derives, - options, - ); - - (response_types, fields) -} - -fn render_selection<'a>( - q: WithQuery<'a, ()>, - selection: &[SelectionId], - field_buffer: &mut Vec, - variants_buffer: &mut Vec, - response_type_buffer: &mut Vec, - response_derives: &impl quote::ToTokens, - options: &GraphQLClientCodegenOptions, -) { - // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) - - for select in selection { - match q.refocus(*select).get() { - Selection::Field(field) => { - let field = q.refocus(field); - - let deprecation_annotation = match ( - field.schema_field().is_deprecated(), - options.deprecation_strategy(), - ) { - (false, _) | (true, DeprecationStrategy::Allow) => None, - (true, DeprecationStrategy::Warn) => { - let msg = field - .schema_field() - .deprecation_message() - .unwrap_or("This field is deprecated."); - - Some(quote!(#[deprecated(note = #msg)])) - } - (true, DeprecationStrategy::Deny) => continue, - }; - - let ident = field_name(&field); - match field.schema_field().field_type().item { - TypeId::Enum(enm) => { - let type_name = - Ident::new(field.with_schema(enm).name(), Span::call_site()); - let type_name = - decorate_type(&type_name, field.schema_field().type_qualifiers()); - - field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); - } - TypeId::Scalar(scalar) => { - let type_name = - Ident::new(field.with_schema(scalar).name(), Span::call_site()); - let type_name = - decorate_type(&type_name, field.schema_field().type_qualifiers()); - - field_buffer.push(quote!(#deprecation_annotation #ident: #type_name)); - } - TypeId::Object(_) | TypeId::Interface(_) => { - let struct_name_string = q.refocus(*select).full_path_prefix(); - let struct_name = Ident::new(&struct_name_string, Span::call_site()); - let field_type = - decorate_type(&struct_name, field.schema_field().type_qualifiers()); - - field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); - - let mut fields = Vec::new(); - let mut variants = Vec::new(); - render_selection( - q, - q.refocus(*select).subselection_ids(), - &mut fields, - &mut variants, - response_type_buffer, - response_derives, - options, - ); - - let struct_definition = render_object_like_struct( - response_derives, - &struct_name_string, - &fields, - &variants, - ); - - response_type_buffer.push(struct_definition); - } - TypeId::Union(_) => { - // Generate the union struct here. - // - // We want a struct, because we want to preserve fragments in the output, - // and there can be fragment and inline spreads for a given selection set - // on an enum. - let struct_name = q.refocus(*select).full_path_prefix(); - let struct_name_ident = Ident::new(&struct_name, Span::call_site()); - let field_type = decorate_type( - &struct_name_ident, - field.schema_field().type_qualifiers(), - ); - - field_buffer.push(quote!(#deprecation_annotation #ident: #field_type)); - - let mut fields = Vec::new(); - let mut variants = Vec::new(); - render_selection( - q, - q.refocus(*select).subselection_ids(), - &mut fields, - &mut variants, - response_type_buffer, - response_derives, - options, - ); - - let struct_definition = render_object_like_struct( - response_derives, - &struct_name, - &fields, - &variants, - ); - response_type_buffer.push(struct_definition); - } - TypeId::Input(_) => unreachable!("field selection on input type"), - }; - } - Selection::Typename => { - field_buffer.push(quote!( - #[serde(rename = "__typename")] - pub typename: String - )); - } - Selection::InlineFragment(inline) => { - let variant_name_str = q.refocus(inline).on().name(); - let variant_name = Ident::new(variant_name_str, Span::call_site()); - let variant_struct_name_str = q.refocus(*select).full_path_prefix(); - let variant_struct_name = Ident::new(&variant_struct_name_str, Span::call_site()); - - // Render the struct for the selection - - let mut fields = Vec::new(); - let mut variants = Vec::new(); - - render_selection( - q, - q.refocus(*select).subselection_ids(), - &mut fields, - &mut variants, - response_type_buffer, - response_derives, - options, - ); - - let variant = quote!(#variant_name(#variant_struct_name)); - variants_buffer.push(variant); - - let struct_definition = render_object_like_struct( - response_derives, - &variant_struct_name_str, - &fields, - &variants, - ); - - response_type_buffer.push(struct_definition); - } - Selection::FragmentSpread(frag) => { - let frag = q.refocus(*frag); - let original_field_name = frag.name().to_snake_case(); - let final_field_name = keyword_replace(&original_field_name); - let annotation = field_rename_annotation(&original_field_name, &final_field_name); - let field_ident = Ident::new(&final_field_name, Span::call_site()); - let type_name = Ident::new(frag.name(), Span::call_site()); - field_buffer.push(quote! { - #[serde(flatten)] - pub #annotation #field_ident: #type_name - }); - } - } - } -} - -fn field_name(field: &WithQuery<'_, &SelectedField>) -> impl quote::ToTokens { - let name = field.alias().unwrap_or_else(|| field.name()); - let snake_case_name = name.to_snake_case(); - let final_name = keyword_replace(&snake_case_name); - let rename_annotation = field_rename_annotation(&name, &final_name); - - let ident = Ident::new(&final_name, Span::call_site()); - - quote!(#rename_annotation pub #ident) -} - fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStream { let mut qualified = quote!(#ident); @@ -478,56 +175,40 @@ fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStr } fn generate_input_object_definitions( - operation: WithQuery<'_, OperationId>, + operation: &OperationRef<'_>, all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, ) -> Vec { all_used_types .inputs(operation.schema()) - .map(|input| quote!(heh)) + .map(|input| { + let struct_name = Ident::new(input.name(), Span::call_site()); + quote!(pub struct #struct_name;) + }) .collect() } fn generate_fragment_definitions( - operation: OperationRef<'_>, + operation: &OperationRef<'_>, all_used_types: &UsedTypes, response_derives: &impl quote::ToTokens, options: &GraphQLClientCodegenOptions, -) -> (Vec, Vec) { - let mut response_type_buffer = Vec::new(); +) -> Vec { let mut fragment_definitions = Vec::with_capacity(all_used_types.fragments_len()); let fragments = all_used_types .fragment_ids() - .map(move |id| operation.refocus(id)); + .map(move |id| operation.query().get_fragment_ref(operation.schema(), id)); for fragment in fragments { - let struct_name = fragment.name(); - let mut fields = Vec::with_capacity(fragment.selection_set_len()); - let mut variants = Vec::new(); - - render_selection( - fragment.refocus(()), - fragment.selection_ids(), - &mut fields, - &mut variants, - &mut response_type_buffer, + fragment_definitions.push(selection::render_fragment( + &fragment, response_derives, options, - ); - - let definition = match fragment.on().item { - TypeId::Interface(_) | TypeId::Object(_) => { - render_object_like_struct(response_derives, struct_name, &fields, &variants) - } - TypeId::Union(_) => render_union_enum(response_derives, struct_name, &variants), - other => panic!("Fragment on invalid type: {:?}", other), - }; - - fragment_definitions.push(definition) + )); } - (fragment_definitions, response_type_buffer) + fragment_definitions } /// Render a struct for a selection on an object or interface. diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs new file mode 100644 index 000000000..67febd143 --- /dev/null +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -0,0 +1,87 @@ +use crate::codegen::render_derives; +use crate::codegen_options::GraphQLClientCodegenOptions; +use crate::resolution::{OperationId, OperationRef}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +/** + * About rust keyword escaping: variant_names and constructors must be escaped, + * variant_str not. + * Example schema: enum AnEnum { where \n self } + * Generated "variant_names" enum: pub enum AnEnum { where_, self_, Other(String), } + * Generated serialize line: "AnEnum::where_ => "where"," + */ +pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( + operation: &OperationRef<'schema>, + all_used_types: &'a crate::resolution::UsedTypes, + options: &'a GraphQLClientCodegenOptions, +) -> impl Iterator + 'a { + let derives = render_derives( + options + .all_response_derives() + .filter(|d| !&["Serialize", "Deserialize"].contains(d)), + ); + let normalization = options.normalization(); + + all_used_types.enums(operation.schema()).map(move |r#enum| { + let variant_names: Vec = r#enum + .variants() + .iter() + .map(|v| { + let name = normalization.enum_variant(crate::shared::keyword_replace(v.as_str())); + let name = Ident::new(&name, Span::call_site()); + + // let description = &v.description; + // let description = description.as_ref().map(|d| quote!(#[doc = #d])); + + // quote!(#description #name) + quote!(#name) + }) + .collect(); + let variant_names = &variant_names; + let name_ident = normalization.enum_name(r#enum.name()); + let name_ident = Ident::new(&name_ident, Span::call_site()); + let constructors: Vec<_> = r#enum + .variants() + .iter() + .map(|v| { + let name = normalization.enum_variant(crate::shared::keyword_replace(v)); + let v = Ident::new(&name, Span::call_site()); + + quote!(#name_ident::#v) + }) + .collect(); + let constructors = &constructors; + let variant_str: Vec<&str> = r#enum.variants().iter().map(|s| s.as_str()).collect(); + let variant_str = &variant_str; + + let name = name_ident; + + quote! { + #derives + pub enum #name { + #(#variant_names,)* + Other(String), + } + + impl ::serde::Serialize for #name { + fn serialize(&self, ser: S) -> Result { + ser.serialize_str(match *self { + #(#constructors => #variant_str,)* + #name::Other(ref s) => &s, + }) + } + } + + impl<'de> ::serde::Deserialize<'de> for #name { + fn deserialize>(deserializer: D) -> Result { + let s = ::deserialize(deserializer)?; + + match s.as_str() { + #(#variant_str => Ok(#constructors),)* + _ => Ok(#name::Other(s)), + } + } + } + }}) +} diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs new file mode 100644 index 000000000..984db9a62 --- /dev/null +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -0,0 +1,408 @@ +//! Code generation for the selection on an operation or a fragment. + +use crate::codegen::decorate_type; +use crate::resolution::FragmentRef; +use crate::resolution::ResolvedFragmentId; +use crate::resolution::SelectedField; +use crate::resolution::SelectionRef; +use crate::schema::TypeRef; +use crate::shared::field_rename_annotation; +use crate::{ + field_type::GraphqlTypeQualifier, + // deprecation::DeprecationStrategy, + resolution::{OperationRef, ResolvedQuery, Selection, SelectionId}, + schema::{Schema, TypeId}, + shared::keyword_replace, + GraphQLClientCodegenOptions, +}; +use heck::*; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use std::borrow::Cow; + +pub(crate) fn render_response_data_fields<'a>( + operation: &OperationRef<'a>, + response_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, +) -> TokenStream { + let mut expanded_selection = ExpandedSelection { + query: operation.query(), + schema: operation.schema(), + types: Vec::with_capacity(8), + variants: Vec::new(), + fields: Vec::with_capacity(operation.selection_ids().len()), + options, + }; + + let response_data_type_id = expanded_selection.push_type(ExpandedType { + name: Cow::Borrowed("ResponseData"), + schema_type: operation.on_ref(), + }); + + calculate_selection( + &mut expanded_selection, + operation.selection_ids(), + response_data_type_id, + operation.on_ref(), + ); + + expanded_selection.render(response_derives) +} + +pub(super) fn render_fragment( + fragment: &FragmentRef<'_>, + response_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, +) -> TokenStream { + let mut expanded_selection = ExpandedSelection { + query: fragment.query(), + schema: fragment.schema(), + types: Vec::with_capacity(8), + variants: Vec::new(), + fields: Vec::with_capacity(fragment.selection_ids().len()), + options, + }; + + let response_type_id = expanded_selection.push_type(ExpandedType { + name: fragment.name().into(), + schema_type: fragment.on_ref(), + }); + + calculate_selection( + &mut expanded_selection, + fragment.selection_ids(), + response_type_id, + fragment.on_ref(), + ); + + expanded_selection.render(response_derives) +} + +fn calculate_selection<'a>( + context: &mut ExpandedSelection<'a>, + selection_set: &[SelectionId], + struct_id: ResponseTypeId, + type_ref: TypeRef<'a>, +) { + // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) + + // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_, + // including an `Other { #serde(rename = "__typename") typename: String }` variant. + { + let variants: Option> = match type_ref.type_id() { + TypeId::Interface(interface_id) => { + let interface = context.schema().interface(interface_id); + + // get all variants + + todo!(); + } + TypeId::Union(union_id) => { + let union = context.schema().union(union_id); + todo!(); + } + _ => None, + }; + + // for each variant, get the corresponding fragment spread, or default to an empty variant + + // Finish by adding the Other variant + + todo!(); + } + + for id in selection_set { + let selection_ref = context.get_selection_ref(*id); + + match selection_ref.selection() { + Selection::Field(field) => { + let (graphql_name, rust_name) = context.field_name(&field); + let schema_field = field.schema_field(context.schema()); + let field_type = schema_field.field_type(); + + match field_type.type_id() { + TypeId::Enum(enm) => { + context.push_field(ExpandedField { + graphql_name, + rust_name, + struct_id, + field_type: context.schema().r#enum(enm).name().into(), + field_type_qualifiers: schema_field.type_qualifiers(), + flatten: false, + }); + } + TypeId::Scalar(scalar) => { + context.push_field(ExpandedField { + field_type: context.schema().scalar(scalar).name().into(), + field_type_qualifiers: field + .schema_field(context.schema()) + .type_qualifiers(), + graphql_name, + struct_id, + rust_name, + flatten: false, + }); + } + TypeId::Object(_) | TypeId::Interface(_) | TypeId::Union(_) => { + let struct_name_string = selection_ref.full_path_prefix(); + + context.push_field(ExpandedField { + struct_id, + graphql_name, + rust_name, + field_type_qualifiers: schema_field.type_qualifiers(), + field_type: Cow::Owned(struct_name_string.clone()), + flatten: false, + }); + + let type_id = context.push_type(ExpandedType { + name: Cow::Owned(struct_name_string), + schema_type: field_type, + }); + + calculate_selection( + context, + selection_ref.subselection_ids(), + type_id, + field_type, + ); + } + TypeId::Input(_) => unreachable!("field selection on input type"), + }; + } + Selection::Typename => { + // context.push_field(ExpandedField { + // field_type: Cow::Borrowed("String"), + // field_type_qualifiers: &[GraphqlTypeQualifier::Required], + // graphql_name: "__typename", + // rust_name: "typename".into(), + // on, + // }); + } + Selection::InlineFragment(inline) => { + let schema_type = context.schema().type_ref(inline.type_id); + let variant_name_str = schema_type.name(); + let variant_struct_name_str = selection_ref.full_path_prefix(); + + context.push_variant(ExpandedVariant { + name: variant_name_str.into(), + variant_type: variant_struct_name_str.clone().into(), + on: struct_id, + }); + + let expanded_type = ExpandedType { + name: variant_struct_name_str.into(), + schema_type, + }; + + let struct_id = context.push_type(expanded_type); + + calculate_selection( + context, + selection_ref.subselection_ids(), + struct_id, + schema_type, + ); + } + Selection::FragmentSpread(fragment_id) => { + // FIXME: we need to identify if the fragment is on the field itself, or on an union/interface variant of it. + // If it's on a field, do it here. + // If it's on a variant, push it downstream to the variant. + let fragment = context.get_fragment_ref(*fragment_id); + let original_field_name = fragment.name().to_snake_case(); + let final_field_name = keyword_replace(original_field_name); + + context.push_field(ExpandedField { + field_type: fragment.name().into(), + field_type_qualifiers: &[GraphqlTypeQualifier::Required], + graphql_name: fragment.name(), + rust_name: final_field_name, + struct_id, + flatten: true, + }); + + // We stop here, because the structs for the fragments are generated separately, to + // avoid duplication. + } + } + } +} + +#[derive(Clone, Copy, PartialEq)] +struct ResponseTypeId(u32); + +struct ExpandedField<'a> { + graphql_name: &'a str, + rust_name: Cow<'a, str>, + field_type: Cow<'a, str>, + field_type_qualifiers: &'a [GraphqlTypeQualifier], + struct_id: ResponseTypeId, + flatten: bool, +} + +impl<'a> ExpandedField<'a> { + fn render(&self) -> TokenStream { + let ident = Ident::new(&self.rust_name, Span::call_site()); + let qualified_type = decorate_type( + &Ident::new(&self.field_type, Span::call_site()), + self.field_type_qualifiers, + ); + + let optional_rename = field_rename_annotation(self.graphql_name, &self.rust_name); + let optional_flatten = if self.flatten { + Some(quote!(#[serde(flatten)])) + } else { + None + }; + + // TODO: deprecation + // let deprecation_annotation = match ( + // field.schema_field().is_deprecated(), + // options.deprecation_strategy(), + // ) { + // (false, _) | (true, DeprecationStrategy::Allow) => None, + // (true, DeprecationStrategy::Warn) => { + // let msg = field + // .schema_field() + // .deprecation_message() + // .unwrap_or("This field is deprecated."); + + // Some(quote!(#[deprecated(note = #msg)])) + // } + // (true, DeprecationStrategy::Deny) => continue, + // }; + + quote! { + #optional_flatten + #optional_rename + pub #ident: #qualified_type + } + } +} + +struct ExpandedVariant<'a> { + name: Cow<'a, str>, + variant_type: Cow<'a, str>, + on: ResponseTypeId, +} + +impl<'a> ExpandedVariant<'a> { + fn render(&self) -> TokenStream { + let name_ident = Ident::new(&self.name, Span::call_site()); + let type_ident = Ident::new(&self.variant_type, Span::call_site()); + + quote!(#name_ident(#type_ident)) + } +} + +struct ExpandedType<'a> { + name: Cow<'a, str>, + schema_type: TypeRef<'a>, +} + +struct ExpandedSelection<'a> { + query: &'a ResolvedQuery, + schema: &'a Schema, + types: Vec>, + fields: Vec>, + variants: Vec>, + options: &'a GraphQLClientCodegenOptions, +} + +impl<'a> ExpandedSelection<'a> { + pub(crate) fn schema(&self) -> &'a Schema { + self.schema + } + + pub(crate) fn push_type(&mut self, tpe: ExpandedType<'a>) -> ResponseTypeId { + let id = self.types.len(); + self.types.push(tpe); + + ResponseTypeId(id as u32) + } + + pub(crate) fn push_field(&mut self, field: ExpandedField<'a>) { + self.fields.push(field); + } + + pub(crate) fn push_variant(&mut self, variant: ExpandedVariant<'a>) { + self.variants.push(variant); + } + + pub(crate) fn get_selection_ref(&self, selection_id: SelectionId) -> SelectionRef<'a> { + self.query.get_selection_ref(self.schema, selection_id) + } + + pub(crate) fn get_fragment_ref(&self, fragment_id: ResolvedFragmentId) -> FragmentRef<'a> { + self.query.get_fragment_ref(self.schema, fragment_id) + } + + /// Returns a tuple to be interpreted as (graphql_name, rust_name). + pub(crate) fn field_name(&self, field: &'a SelectedField) -> (&'a str, Cow<'a, str>) { + let name = field + .alias() + .unwrap_or_else(|| field.schema_field(self.schema).name()); + let snake_case_name = name.to_snake_case(); + let final_name = keyword_replace(snake_case_name); + + (name, final_name) + } + + fn types(&self) -> impl Iterator)> { + self.types + .iter() + .enumerate() + .map(|(idx, ty)| (ResponseTypeId(idx as u32), ty)) + } + + pub fn render(&self, response_derives: &impl quote::ToTokens) -> TokenStream { + let mut items = Vec::with_capacity(self.types.len()); + + for (type_id, ty) in self.types() { + let struct_name = Ident::new(&ty.name, Span::call_site()); + let fields = self + .fields + .iter() + .filter(|field| field.struct_id == type_id) + .map(|field| field.render()); + + let on_variants: Vec = self + .variants + .iter() + .filter(|variant| variant.on == type_id) + .map(|variant| variant.render()) + .collect(); + + let (on_field, on_enum) = if on_variants.len() > 0 { + let enum_name = Ident::new(&format!("{}On", ty.name), Span::call_site()); + + let on_field = quote!(pub on: #enum_name); + + let on_enum = quote!( + #response_derives + pub enum #enum_name { + #(#on_variants),* + } + ); + + (Some(on_field), Some(on_enum)) + } else { + (None, None) + }; + + let tokens = quote! { + #response_derives + pub struct #struct_name { + #(#fields,)* + #on_field + } + + #on_enum + }; + + items.push(tokens); + } + + quote!(#(#items)*) + } +} diff --git a/graphql_client_codegen/src/constants.rs b/graphql_client_codegen/src/constants.rs index 095ecd64c..9375e65cf 100644 --- a/graphql_client_codegen/src/constants.rs +++ b/graphql_client_codegen/src/constants.rs @@ -1,4 +1,3 @@ -use crate::deprecation::DeprecationStatus; // use crate::field_type::FieldType; // use crate::objects::GqlObjectField; diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index a690df3fa..6b08d68c7 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -1,9 +1,7 @@ // use crate::enums::ENUMS_PREFIX; // use crate::query::QueryContext; -use crate::schema::DEFAULT_SCALARS; use graphql_introspection_query::introspection_response; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; // pub(crate) fn field_type_to_rust() -> TokenStream { // todo!() diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 147da20a7..10e2d4ec7 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,6 +1,6 @@ use crate::{ codegen_options::*, - resolution::{OperationId, WithQuery}, + resolution::{OperationId, OperationRef}, }; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; @@ -24,19 +24,11 @@ impl<'a> GeneratedModule<'a> { )?) } - fn root(&self) -> WithQuery<'_, OperationId> { - let operation = crate::codegen::select_operation( - &self.resolved_query, - &self.operation, - self.options.normalization(), - ) - .expect("TODO: handle operation not found"); - - WithQuery::new( - self.resolved_query, - self.schema, - OperationId::new(operation), - ) + fn root(&self) -> OperationRef<'_> { + let op_name = self.options.normalization().operation(self.operation); + self.resolved_query + .select_operation(self.schema, &op_name) + .expect("TODO: handle operation not found") } /// Generate the module and all the code inside. diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 37158ef30..0f3df9922 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -109,18 +109,18 @@ pub fn generate_module_token_stream( .operation_name .as_ref() .and_then(|operation_name| { - codegen::select_operation(&query, &operation_name, options.normalization()) - .and_then(|idx| query.operations.get(idx)) + query.select_operation(&schema, &options.normalization().operation(operation_name)) }) .map(|op| vec![op]); let operations = match (operations, &options.mode) { (Some(ops), _) => ops, - (None, &CodegenMode::Cli) => query.operations.iter().collect(), + (None, &CodegenMode::Cli) => query.operations(&schema).collect(), (None, &CodegenMode::Derive) => { return Err(derive_operation_not_found_error( options.struct_ident(), &query, + &schema, )); } }; @@ -168,11 +168,12 @@ fn read_file(path: &std::path::Path) -> anyhow::Result { fn derive_operation_not_found_error( ident: Option<&proc_macro2::Ident>, query: &crate::resolution::ResolvedQuery, + schema: &crate::schema::Schema, ) -> anyhow::Error { let operation_name = ident.map(ToString::to_string); let struct_ident = operation_name.as_deref().unwrap_or(""); - let available_operations: Vec<&str> = query.operations.iter().map(|op| op.name()).collect(); + let available_operations: Vec<&str> = query.operations(schema).map(|op| op.name()).collect(); let available_operations: String = available_operations.join(", "); return format_err!( diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 5c6aef472..2dd6c1c9e 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -148,14 +148,6 @@ impl<'query, 'schema> QueryContext<'query> { } } - pub(crate) fn response_derives(&self) -> TokenStream { - let derives: BTreeSet<&Ident> = self.response_derives.iter().collect(); - let derives = derives.iter(); - quote! { - #[derive( #(#derives),* )] - } - } - pub(crate) fn response_enum_derives(&self) -> TokenStream { let always_derives = [ Ident::new("Eq", Span::call_site()), diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 9ea9f5d10..20d21b2e5 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,28 +1,47 @@ //! The responsibility of this module is to resolve and validate a query //! against a given schema. +use crate::schema::InputRef; +use crate::schema::ObjectRef; use crate::schema::ScalarId; +use crate::schema::ScalarRef; use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, schema::{ resolve_field_type, EnumRef, InputId, ObjectId, Schema, StoredFieldId, StoredFieldType, - TypeId, TypeRef, UnionRef, WithSchema, + TypeId, TypeRef, UnionRef, }, }; use heck::CamelCase; use std::collections::{HashMap, HashSet}; -pub(crate) type OperationRef<'a> = WithQuery<'a, OperationId>; -pub(crate) type FragmentRef<'a> = WithQuery<'a, ResolvedFragmentId>; -pub(crate) type VariableRef<'a> = WithQuery<'a, VariableId>; -pub(crate) type SelectionRef<'a> = WithQuery<'a, SelectionId>; -pub(crate) type InlineFragmentRef<'a> = WithQuery<'a, &'a InlineFragment>; +/// This is a convenience struct that should stay private, it's an implementation detail for our `Ref` types. +struct QueryWith<'a, T> { + query: &'a ResolvedQuery, + schema: &'a Schema, + focus: T, +} + +impl<'a, T> QueryWith<'a, T> { + fn refocus(&self, new_focus: U) -> QueryWith<'a, U> { + QueryWith { + query: self.query, + schema: self.schema, + focus: new_focus, + } + } +} + +pub(crate) struct SelectionRef<'a>(QueryWith<'a, (SelectionId, &'a Selection)>); +pub(crate) struct OperationRef<'a>(QueryWith<'a, OperationId>); +pub(crate) struct VariableRef<'a>(QueryWith<'a, (VariableId, &'a ResolvedVariable)>); +pub(crate) struct InlineFragmentRef<'a>(QueryWith<'a, &'a InlineFragment>); +pub(crate) struct FragmentRef<'a>(QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>); #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) struct SelectionId(u32); - #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct OperationId(u32); @@ -86,65 +105,33 @@ impl SelectionParent { } } -#[derive(Debug, Clone, Copy)] -pub(crate) struct WithQuery<'a, T> { - query: &'a ResolvedQuery, - schema: &'a Schema, - pub(crate) item: T, -} - -impl<'a, T> WithQuery<'a, T> { - pub(crate) fn new(query: &'a ResolvedQuery, schema: &'a Schema, item: T) -> WithQuery<'a, T> { - WithQuery { - query, - schema, - item, - } - } - - pub(crate) fn refocus(&self, item: U) -> WithQuery<'a, U> { - WithQuery { - query: self.query, - schema: self.schema, - item, - } - } - - pub(crate) fn with_schema(&self, item: U) -> WithSchema<'a, U> { - WithSchema::new(self.schema, item) - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.schema +impl<'a> SelectionRef<'a> { + pub(crate) fn selection(&self) -> &'a Selection { + self.0.focus.1 } -} -impl<'a> SelectionRef<'a> { - pub(crate) fn get(&self) -> &'a Selection { - self.query - .selections - .get(self.item.0 as usize) - .expect("SelectionId resolution") + fn id(&self) -> SelectionId { + self.0.focus.0 } - pub(crate) fn subselection<'b>( - &'b self, - ) -> impl Iterator> + 'b { - self.get() + pub(crate) fn subselection<'b>(&'b self) -> impl Iterator> + 'b { + self.0 + .focus + .1 .subselection() .iter() - .map(move |s| self.refocus(*s)) + .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) } pub(crate) fn subselection_ids(&self) -> &'a [SelectionId] { - self.get().subselection() + self.selection().subselection() } pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { - let selection = self.get(); + let selection = self.selection(); match selection { Selection::Field(field) => { - let field_ref = self.schema.field(field.field_id); + let field_ref = self.0.schema.field(field.field_id); used_types.types.insert(field_ref.type_id()); for item in self.subselection() { @@ -161,7 +148,9 @@ impl<'a> SelectionRef<'a> { Selection::FragmentSpread(fragment_id) => { used_types.fragments.insert(*fragment_id); - for item in self.refocus(*fragment_id).selection() { + let fragment_ref = self.0.query.get_fragment_ref(self.0.schema, *fragment_id); + + for item in fragment_ref.selection_set() { item.collect_used_types(used_types); } } @@ -172,10 +161,10 @@ impl<'a> SelectionRef<'a> { pub(crate) fn full_path_prefix(&self) -> String { let mut path = vec![self.to_path_segment()]; - let mut item = self.item; + let mut item = self.id(); - while let Some(parent) = self.query.selection_parent_idx.get(&item) { - path.push(self.refocus(*parent).to_path_segment()); + while let Some(parent) = self.0.query.selection_parent_idx.get(&item) { + path.push(self.0.refocus(*parent).to_path_segment()); match parent { SelectionParent::Selection(id) => { @@ -190,15 +179,17 @@ impl<'a> SelectionRef<'a> { } fn to_path_segment(&self) -> String { - match self.get() { + match self.selection() { Selection::Field(field) => field .alias .as_ref() .map(|alias| alias.to_camel_case()) - .unwrap_or_else(move || self.with_schema(field.field_id).name().to_camel_case()), + .unwrap_or_else(move || self.0.schema.field(field.field_id).name().to_camel_case()), Selection::InlineFragment(inline_fragment) => format!( "On{}", - self.with_schema(inline_fragment.type_id) + self.0 + .schema + .type_ref(inline_fragment.type_id) .name() .to_camel_case() ), @@ -207,12 +198,17 @@ impl<'a> SelectionRef<'a> { } } -impl<'a> WithQuery<'a, SelectionParent> { +impl<'a> QueryWith<'a, SelectionParent> { pub(crate) fn to_path_segment(&self) -> String { - match self.item { - SelectionParent::Selection(id) => self.refocus(id).to_path_segment(), - SelectionParent::Operation(id) => self.refocus(id).to_path_segment(), - SelectionParent::Fragment(id) => self.refocus(id).to_path_segment(), + match self.focus { + SelectionParent::Selection(id) => { + SelectionRef(self.refocus((id, self.query.get_selection(id)))).to_path_segment() + } + SelectionParent::Operation(id) => OperationRef(self.refocus(id)).to_path_segment(), + SelectionParent::Fragment(id) => self + .query + .get_fragment_ref(self.schema, id) + .to_path_segment(), } } } @@ -237,13 +233,13 @@ impl Selection { #[derive(Debug)] pub(crate) struct InlineFragment { - type_id: TypeId, + pub(crate) type_id: TypeId, selection_set: Vec, } impl<'a> InlineFragmentRef<'a> { - pub(crate) fn on(&self) -> TypeRef<'a> { - self.with_schema(self.item.type_id) + pub(crate) fn on(&self) -> TypeId { + self.0.focus.type_id } } @@ -254,20 +250,30 @@ pub(crate) struct SelectedField { selection_set: Vec, } -impl<'a> WithQuery<'a, &'a SelectedField> { +impl SelectedField { pub(crate) fn alias(&self) -> Option<&str> { - self.item.alias.as_ref().map(String::as_str) - } - - pub(crate) fn name(&self) -> &'a str { - self.with_schema(self.item.field_id).name() + self.alias.as_ref().map(String::as_str) } - pub(crate) fn schema_field(&self) -> WithSchema<'a, StoredFieldId> { - self.with_schema(self.item.field_id) + pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> crate::schema::FieldRef<'a> { + schema.field(self.field_id) } } +// impl<'a> WithQuery<'a, &'a SelectedField> { +// pub(crate) fn alias(&self) -> Option<&str> { +// self.item.alias.as_ref().map(String::as_str) +// } + +// pub(crate) fn name(&self) -> &'a str { +// self.schema.field(self.item.field_id).name() +// } + +// pub(crate) fn schema_field(&self) -> WithSchema<'a, StoredFieldId> { +// self.with_schema(self.item.field_id) +// } +// } + pub(crate) fn resolve( schema: &Schema, query: &graphql_parser::query::Document, @@ -434,7 +440,7 @@ fn resolve_object_selection<'a>( let id = query.push_selection( Selection::Field(SelectedField { alias: field.alias.clone(), - field_id: field_ref.item, + field_id: field_ref.field_id(), selection_set: Vec::with_capacity(selection_set.items.len()), }), parent, @@ -570,10 +576,10 @@ fn resolve_operation( Ok(()) } -#[derive(Debug, Default)] +#[derive(Default)] pub(crate) struct ResolvedQuery { fragments: Vec, - pub(crate) operations: Vec, + operations: Vec, selection_parent_idx: HashMap, selections: Vec, variables: Vec, @@ -589,6 +595,71 @@ impl ResolvedQuery { id } + pub(crate) fn get_selection_ref<'a>( + &'a self, + schema: &'a Schema, + id: SelectionId, + ) -> SelectionRef<'a> { + let selection = self.get_selection(id); + + SelectionRef(self.with_schema(schema).refocus((id, selection))) + } + + pub(crate) fn get_selection(&self, id: SelectionId) -> &Selection { + self.selections + .get(id.0 as usize) + .expect("Query.get_selection") + } + + fn get_fragment(&self, id: ResolvedFragmentId) -> &ResolvedFragment { + self.fragments + .get(id.0 as usize) + .expect("Query.get_fragment") + } + + fn get_variable(&self, id: VariableId) -> &ResolvedVariable { + self.variables + .get(id.0 as usize) + .expect("Query.get_variable") + } + + pub(crate) fn get_fragment_ref<'a>( + &'a self, + schema: &'a Schema, + id: ResolvedFragmentId, + ) -> FragmentRef<'a> { + let fragment = self.get_fragment(id); + + FragmentRef(self.with_schema(schema).refocus((id, fragment))) + } + + pub(crate) fn get_variable_ref<'a>( + &'a self, + schema: &'a Schema, + id: VariableId, + ) -> VariableRef<'a> { + let variable = self.get_variable(id); + + VariableRef(self.with_schema(schema).refocus((id, variable))) + } + + pub(crate) fn operations<'a>( + &'a self, + schema: &'a Schema, + ) -> impl Iterator> + 'a { + (0..self.operations.len()) + .map(move |idx| OperationRef(self.with_schema(schema).refocus(OperationId(idx as u32)))) + } + + /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. + pub(crate) fn select_operation<'a>( + &'a self, + schema: &'a Schema, + name: &str, + ) -> Option> { + self.operations(schema).find(|op| op.name() == name) + } + fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> { self.fragments .iter_mut() @@ -604,18 +675,38 @@ impl ResolvedQuery { .find(|(_, op)| op.name == name) .map(|(id, op)| (OperationId::new(id), op)) } + + fn with_schema<'a>(&'a self, schema: &'a Schema) -> QueryWith<'a, ()> { + QueryWith { + focus: (), + query: self, + schema, + } + } } #[derive(Debug)] -pub(crate) struct ResolvedFragment { +pub struct ResolvedFragment { name: String, on: crate::schema::TypeId, selection: Vec, } impl<'a> OperationRef<'a> { + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.0.query + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + fn get(&self) -> &'a ResolvedOperation { - self.query.operations.get(self.item.0 as usize).unwrap() + self.0 + .query + .operations + .get(self.0.focus.0 as usize) + .unwrap() } fn to_path_segment(&self) -> String { @@ -638,10 +729,12 @@ impl<'a> OperationRef<'a> { pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { let operation = self.get(); - operation - .selection - .iter() - .map(move |selection_id| self.refocus(*selection_id)) + operation.selection.iter().map(move |selection_id| { + SelectionRef( + self.0 + .refocus((*selection_id, self.0.query.get_selection(*selection_id))), + ) + }) } pub(crate) fn selection_ids(&self) -> &[SelectionId] { @@ -649,12 +742,17 @@ impl<'a> OperationRef<'a> { } pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { - self.query + self.0 + .query .variables .iter() .enumerate() - .filter(move |(_, variable)| variable.operation_id == self.item) - .map(move |(id, _)| self.refocus(VariableId::new(id))) + .filter(move |(_, variable)| variable.operation_id == self.0.focus) + .map(move |(id, _)| { + self.0 + .query + .get_variable_ref(self.0.schema, VariableId::new(id)) + }) } pub(crate) fn name(&self) -> &'a str { @@ -664,10 +762,13 @@ impl<'a> OperationRef<'a> { pub(crate) fn has_no_variables(&self) -> bool { self.variables().next().is_none() } + + pub(crate) fn on_ref(&self) -> TypeRef<'a> { + self.0.schema.type_ref(TypeId::Object(self.get().object_id)) + } } -#[derive(Debug)] -pub(crate) struct ResolvedOperation { +struct ResolvedOperation { name: String, operation_type: crate::operations::OperationType, selection: Vec, @@ -689,24 +790,20 @@ struct ResolvedVariable { } impl<'a> VariableRef<'a> { - fn get(&self) -> &'a ResolvedVariable { - self.query.variables.get(self.item.0 as usize).unwrap() - } - pub(crate) fn name(&self) -> &'a str { - &self.get().name + &self.0.focus.1.name } pub(crate) fn type_name(&self) -> &'a str { - self.with_schema(self.get().r#type.id).name() + self.0.schema.type_ref(self.0.focus.1.r#type.id).name() } pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { - &self.get().r#type.qualifiers + &self.0.focus.1.r#type.qualifiers } fn collect_used_types(&self, used_types: &mut UsedTypes) { - match self.get().r#type.id { + match self.0.focus.1.r#type.id { type_id @ TypeId::Input(_) | type_id @ TypeId::Scalar(_) | type_id @ TypeId::Enum(_) => { @@ -718,36 +815,42 @@ impl<'a> VariableRef<'a> { } impl<'a> FragmentRef<'a> { - fn get(&self) -> &'a ResolvedFragment { - self.query.fragments.get(self.item.0 as usize).unwrap() + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema } - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { - let fragment = self.get(); - fragment - .selection - .iter() - .map(move |item| self.refocus(*item)) + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.0.query } pub(crate) fn selection_ids(&self) -> &[SelectionId] { - &self.get().selection + &self.0.focus.1.selection + } + + pub(crate) fn selection_set<'b>(&'b self) -> impl Iterator> + 'b { + self.selection_ids() + .iter() + .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) } fn to_path_segment(&self) -> String { - self.get().name.to_camel_case() + self.0.focus.1.name.to_camel_case() } pub(crate) fn name(&self) -> &'a str { - &self.get().name + &self.0.focus.1.name } pub(crate) fn selection_set_len(&self) -> usize { - self.get().selection.len() + self.0.focus.1.selection.len() + } + + pub(crate) fn on(&self) -> TypeId { + self.0.focus.1.on } - pub(crate) fn on(&self) -> TypeRef<'a> { - self.with_schema(self.get().on) + pub(crate) fn on_ref(&self) -> TypeRef<'a> { + self.0.schema.type_ref(self.0.focus.1.on) } } @@ -761,7 +864,7 @@ impl UsedTypes { pub(crate) fn inputs<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator> + 's { schema .inputs() .filter(move |input_ref| self.types.contains(&input_ref.type_id())) @@ -770,7 +873,7 @@ impl UsedTypes { pub(crate) fn scalars<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator> + 's { self.types .iter() .filter_map(TypeId::as_scalar_id) diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 677fa0f4a..c7a069f3b 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -4,40 +4,16 @@ mod json_conversion; use crate::field_type::GraphqlTypeQualifier; use std::collections::HashMap; -pub(crate) type TypeRef<'a> = WithSchema<'a, TypeId>; - -#[derive(Debug, Clone, Copy)] -pub(crate) struct WithSchema<'a, T> { +#[derive(Clone, Copy)] +/// This is a helper for the `Ref` types. It should stay private. +struct SchemaWith<'a, T> { schema: &'a Schema, - pub(crate) item: T, -} - -impl<'a, T> WithSchema<'a, T> { - fn refocus(&self, item: U) -> WithSchema<'a, U> { - WithSchema { - schema: self.schema, - item, - } - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.schema - } - - pub(crate) fn new(schema: &'a Schema, item: T) -> WithSchema<'a, T> { - WithSchema { schema, item } - } + focus: T, } -// use crate::deprecation::DeprecationStatus; -// use crate::enums::{EnumVariant, GqlEnum}; -// use crate::field_type::FieldType; -// use crate::inputs::GqlInput; -// use crate::interfaces::GqlInterface; -// use crate::objects::{GqlObject, GqlObjectField}; -// use crate::scalars::Scalar; -// use crate::unions::GqlUnion; -// use failure::*; +#[derive(Clone, Copy)] +pub(crate) struct TypeRef<'a>(SchemaWith<'a, TypeId>); +pub(crate) struct InputRef<'a>(SchemaWith<'a, InputId>); pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"]; @@ -77,9 +53,6 @@ pub(crate) struct ObjectId(usize); #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct ObjectFieldId(usize); -// #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] -// pub(crate) struct InterfaceFieldId(usize); - #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct InterfaceId(usize); @@ -113,13 +86,6 @@ struct StoredInterface { fields: Vec, } -#[derive(Debug, Clone, PartialEq)] -struct StoredInterfaceField { - name: String, - interface: InterfaceId, - r#type: StoredFieldType, -} - #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredFieldType { pub(crate) id: TypeId, @@ -147,24 +113,28 @@ pub(crate) enum TypeId { Input(InputId), } -impl<'a> WithSchema<'a, TypeId> { +impl<'a> TypeRef<'a> { + pub(crate) fn type_id(&self) -> TypeId { + self.0.focus + } + pub(crate) fn name(&self) -> &'a str { - match self.item { - TypeId::Object(obj) => self.refocus(obj).name(), - TypeId::Scalar(s) => self.refocus(s).name(), - TypeId::Interface(s) => self.refocus(s).name(), - TypeId::Union(s) => self.refocus(s).name(), - TypeId::Enum(s) => self.refocus(s).name(), - TypeId::Input(s) => self.refocus(s).name(), + match self.0.focus { + TypeId::Object(obj) => self.0.schema.object(obj).name(), + TypeId::Scalar(s) => self.0.schema.scalar(s).name(), + TypeId::Interface(s) => self.0.schema.interface(s).name(), + TypeId::Union(s) => self.0.schema.union(s).name(), + TypeId::Enum(s) => self.0.schema.r#enum(s).name(), + TypeId::Input(s) => self.0.schema.input(s).name(), } } } -pub(crate) type ScalarRef<'a> = WithSchema<'a, ScalarId>; +pub(crate) struct ScalarRef<'a>(SchemaWith<'a, ScalarId>); impl<'a> ScalarRef<'a> { fn get(&self) -> &'a StoredScalar { - self.schema.get_scalar(self.item) + self.0.schema.get_scalar(self.0.focus) } pub(crate) fn name(&self) -> &'a str { @@ -172,23 +142,31 @@ impl<'a> ScalarRef<'a> { } } -pub(crate) type UnionRef<'a> = WithSchema<'a, UnionId>; +pub(crate) struct UnionRef<'a>(SchemaWith<'a, UnionId>); impl<'a> UnionRef<'a> { fn get(&self) -> &'a StoredUnion { - self.schema.stored_unions.get(self.item.0).unwrap() + self.0.schema.get_union(self.0.focus) } pub(crate) fn name(&self) -> &'a str { &self.get().name } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + + pub(crate) fn variants(&self) -> &[TypeId] { + &self.get().variants + } } -pub(crate) type EnumRef<'a> = WithSchema<'a, EnumId>; +pub(crate) struct EnumRef<'a>(SchemaWith<'a, EnumId>); impl<'a> EnumRef<'a> { fn get(&self) -> &'a StoredEnum { - self.schema.get_enum(self.item) + self.0.schema.get_enum(self.0.focus) } pub(crate) fn name(&self) -> &'a str { @@ -410,27 +388,30 @@ impl Schema { } pub(crate) fn query_type(&self) -> ObjectRef<'_> { - WithSchema::new( - self, - self.query_type - .expect("Query operation type must be defined"), + ObjectRef( + self.with( + self.query_type + .expect("Query operation type must be defined"), + ), ) } pub(crate) fn mutation_type(&self) -> ObjectRef<'_> { - WithSchema::new( - self, - self.mutation_type - .expect("Mutation operation type must be defined"), + ObjectRef( + self.with( + self.mutation_type + .expect("Mutation operation type must be defined"), + ), ) } pub(crate) fn subscription_type(&self) -> ObjectRef<'_> { - WithSchema::new( - self, - self.subscription_type - // TODO: make this return an option - .expect("Subscription operation type must be defined"), + ObjectRef( + self.with( + self.subscription_type + // TODO: make this return an option + .expect("Subscription operation type must be defined"), + ), ) } @@ -458,28 +439,42 @@ impl Schema { self.stored_scalars.get(scalar_id.0).unwrap() } + fn get_union(&self, union_id: UnionId) -> &StoredUnion { + self.stored_unions + .get(union_id.0) + .expect("Schema.get_union") + } + pub(crate) fn union(&self, id: UnionId) -> UnionRef<'_> { - WithSchema::new(self, id) + UnionRef(self.with(id)) } - pub(crate) fn object(&self, id: ObjectId) -> WithSchema<'_, ObjectId> { - WithSchema::new(self, id) + pub(crate) fn object(&self, id: ObjectId) -> ObjectRef<'_> { + ObjectRef(self.with(id)) } pub(crate) fn interface(&self, interface_id: InterfaceId) -> InterfaceRef<'_> { - WithSchema::new(self, interface_id) + InterfaceRef(self.with(interface_id)) } - pub(crate) fn field(&self, field_id: StoredFieldId) -> FieldRef<'_> { - WithSchema::new(self, field_id) + pub(crate) fn field(&self, id: StoredFieldId) -> FieldRef<'_> { + FieldRef(self.with((id, self.get_field(id)))) } - pub(crate) fn scalar(&self, scalar_id: ScalarId) -> WithSchema<'_, ScalarId> { - WithSchema::new(self, scalar_id) + pub(crate) fn scalar(&self, scalar_id: ScalarId) -> ScalarRef<'_> { + ScalarRef(self.with(scalar_id)) } pub(crate) fn r#enum(&self, enum_id: EnumId) -> EnumRef<'_> { - WithSchema::new(self, enum_id) + EnumRef(self.with(enum_id)) + } + + pub(crate) fn type_ref(&self, id: TypeId) -> TypeRef<'_> { + TypeRef(self.with(id)) + } + + pub(crate) fn input(&self, id: InputId) -> InputRef<'_> { + InputRef(self.with(id)) } fn find_interface(&self, interface_name: &str) -> InterfaceId { @@ -502,95 +497,107 @@ impl Schema { } } - pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { - (0..self.stored_inputs.len()).map(move |id| WithSchema { + pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { + (0..self.stored_inputs.len()).map(move |id| InputRef(self.with(InputId(id)))) + } + + fn with(&self, focus: F) -> SchemaWith<'_, F> { + SchemaWith { schema: self, - item: InputId(id), - }) + focus, + } } } -type InterfaceRef<'a> = WithSchema<'a, InterfaceId>; +pub(crate) struct InterfaceRef<'a>(SchemaWith<'a, InterfaceId>); -impl<'a> WithSchema<'a, InterfaceId> { +impl<'a> InterfaceRef<'a> { fn get(&self) -> &'a StoredInterface { - self.schema.get_interface(self.item) + self.0.schema.get_interface(self.0.focus) } pub(crate) fn name(&self) -> &'a str { &self.get().name } + + pub(crate) fn variants(&self) -> impl Iterator { + todo!() + } } -type ObjectRef<'a> = WithSchema<'a, ObjectId>; +pub(crate) struct ObjectRef<'a>(SchemaWith<'a, ObjectId>); impl<'a> ObjectRef<'a> { fn get(&self) -> &'a StoredObject { - self.schema.get_object(self.item) + self.0.schema.get_object(self.0.focus) } - fn fields<'b>(&'b self) -> impl Iterator> + 'b { + fn fields<'b>(&'b self) -> impl Iterator> + 'b { self.get() .fields .iter() - .map(move |field| self.refocus(*field)) + .map(move |field| self.0.schema.field(*field)) } pub(crate) fn name(&self) -> &'a str { &self.get().name } - pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { + pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { self.fields().find(|field| field.name() == name) } pub(crate) fn id(&self) -> ObjectId { - self.item + self.0.focus } } -pub(crate) type FieldRef<'a> = WithSchema<'a, StoredFieldId>; +pub(crate) struct FieldRef<'a>(SchemaWith<'a, (StoredFieldId, &'a StoredField)>); impl<'a> FieldRef<'a> { - fn get(&self) -> &'a StoredField { - self.schema.get_field(self.item) + fn field(&self) -> &'a StoredField { + self.0.focus.1 } pub(crate) fn name(&self) -> &'a str { - &self.get().name + &self.field().name } pub(crate) fn field_type(&self) -> TypeRef<'a> { - self.refocus(self.get().r#type.id) + self.0.schema.type_ref(self.field().r#type.id) } - pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { - &self.get().r#type.qualifiers + pub(crate) fn type_qualifiers(&self) -> &'a [GraphqlTypeQualifier] { + &self.field().r#type.qualifiers + } + + pub(crate) fn field_id(&self) -> StoredFieldId { + self.0.focus.0 } pub(crate) fn type_id(&self) -> TypeId { - self.get().r#type.id + self.field().r#type.id } pub(crate) fn is_deprecated(&self) -> bool { - self.get().deprecation.is_some() + self.field().deprecation.is_some() } pub(crate) fn deprecation_message(&self) -> Option<&'a str> { - self.get() + self.field() .deprecation .as_ref() .and_then(|item| item.as_ref().map(String::as_str)) } } -impl<'a> WithSchema<'a, InputId> { +impl<'a> InputRef<'a> { fn get(&self) -> &'a StoredInputType { - self.schema.get_stored_input(self.item) + self.0.schema.get_stored_input(self.0.focus) } pub(crate) fn type_id(&self) -> TypeId { - TypeId::Input(self.item) + TypeId::Input(self.0.focus) } pub(crate) fn name(&self) -> &'a str { @@ -694,7 +701,7 @@ impl<'a> ObjectRefLike<'a> for ObjectRef<'a> { } fn schema(&self) -> SchemaRef<'a> { - self.schema() + self.0.schema } } @@ -707,11 +714,11 @@ impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { self.get() .fields .iter() - .map(|field_id| self.schema.field(*field_id)) + .map(|field_id| self.0.schema.field(*field_id)) .find(|field| field.name() == name) } fn schema(&self) -> SchemaRef<'a> { - self.schema + self.0.schema } } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index defeb3983..6e4cafa01 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,4 @@ -use super::{InputId, Schema, TypeId, UnionId}; +use super::{InputId, Schema, TypeId}; use crate::schema::resolve_field_type; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; diff --git a/graphql_client_codegen/src/schema/schema.rs b/graphql_client_codegen/src/schema/schema.rs new file mode 100644 index 000000000..e69de29bb diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 63c709ae5..3f4136a0e 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -71,10 +71,11 @@ const RUST_KEYWORDS: &[&str] = &[ "yield", ]; -pub(crate) fn keyword_replace(needle: &str) -> Cow<'_, str> { - match RUST_KEYWORDS.binary_search(&needle) { +pub(crate) fn keyword_replace<'a>(needle: impl Into>) -> Cow<'a, str> { + let needle = needle.into(); + match RUST_KEYWORDS.binary_search(&needle.as_ref()) { Ok(index) => [RUST_KEYWORDS[index], "_"].concat().into(), - Err(_) => Cow::Borrowed(needle), + Err(_) => needle, } } From ee864f952ab89a7f2a4f6578d5c0cdd1b7fa6706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 1 Mar 2020 23:46:52 +0100 Subject: [PATCH 39/93] Make progress in the selection codegen code --- .../src/codegen/selection.rs | 118 +++++++++++------- graphql_client_codegen/src/resolution.rs | 7 ++ graphql_client_codegen/src/schema.rs | 18 ++- 3 files changed, 95 insertions(+), 48 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 984db9a62..b47dc3e5a 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -89,26 +89,77 @@ fn calculate_selection<'a>( // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_, // including an `Other { #serde(rename = "__typename") typename: String }` variant. { - let variants: Option> = match type_ref.type_id() { + let variants: Option> = match type_ref.type_id() { TypeId::Interface(interface_id) => { let interface = context.schema().interface(interface_id); - // get all variants - - todo!(); + Some(interface.variants().collect()) } TypeId::Union(union_id) => { let union = context.schema().union(union_id); - todo!(); + Some(union.variants().into()) } _ => None, }; - // for each variant, get the corresponding fragment spread, or default to an empty variant + if let Some(variants) = variants { + // for each variant, get the corresponding fragment spread, or default to an empty variant + for variant in variants.as_ref() { + let schema_type = context.schema().type_ref(*variant); + let variant_name_str = schema_type.name(); + + let selection = selection_set + .iter() + .map(|id| context.get_selection_ref(*id)) + .filter_map(|selection_ref| { + selection_ref + .selection() + .as_inline_fragment() + .map(|inline_fragment| (selection_ref, inline_fragment)) + }) + .find(|(_selection_ref, inline_fragment)| inline_fragment.type_id == *variant); + + if let Some((selection_ref, inline_fragment)) = selection { + let variant_struct_name_str = selection_ref.full_path_prefix(); + + todo!("There will be a struct/type for the variant if there is an inline OR type-refining fragment there."); + + context.push_variant(ExpandedVariant { + name: variant_name_str.into(), + variant_type: Some(variant_struct_name_str.clone().into()), + on: struct_id, + }); + + let expanded_type = ExpandedType { + name: variant_struct_name_str.into(), + schema_type, + }; + + let struct_id = context.push_type(expanded_type); + + calculate_selection( + context, + selection_ref.subselection_ids(), + struct_id, + schema_type, + ); + } else { + context.push_variant(ExpandedVariant { + name: variant_name_str.into(), + on: struct_id, + variant_type: None, + }); + } + } + + // push the fragments on variants down - // Finish by adding the Other variant + // meaning get all the fragment spreads on one of the variants, and add it to the type for that variant.... + todo!("push the fragments on variants down"); - todo!(); + // Finish by adding the Other variant + todo!("add the Other variant"); + } } for id in selection_set { @@ -170,45 +221,19 @@ fn calculate_selection<'a>( TypeId::Input(_) => unreachable!("field selection on input type"), }; } - Selection::Typename => { - // context.push_field(ExpandedField { - // field_type: Cow::Borrowed("String"), - // field_type_qualifiers: &[GraphqlTypeQualifier::Required], - // graphql_name: "__typename", - // rust_name: "typename".into(), - // on, - // }); - } - Selection::InlineFragment(inline) => { - let schema_type = context.schema().type_ref(inline.type_id); - let variant_name_str = schema_type.name(); - let variant_struct_name_str = selection_ref.full_path_prefix(); - - context.push_variant(ExpandedVariant { - name: variant_name_str.into(), - variant_type: variant_struct_name_str.clone().into(), - on: struct_id, - }); - - let expanded_type = ExpandedType { - name: variant_struct_name_str.into(), - schema_type, - }; - - let struct_id = context.push_type(expanded_type); - - calculate_selection( - context, - selection_ref.subselection_ids(), - struct_id, - schema_type, - ); - } + Selection::Typename => (), + Selection::InlineFragment(_inline) => (), Selection::FragmentSpread(fragment_id) => { // FIXME: we need to identify if the fragment is on the field itself, or on an union/interface variant of it. // If it's on a field, do it here. // If it's on a variant, push it downstream to the variant. let fragment = context.get_fragment_ref(*fragment_id); + + // Assuming the query was validated properly, a fragment spread is either on the field's type itself, or on one of the variants (union or interfaces). If it's not directly a field on the struct, it will be handled in the `on` variants. + if fragment.on() != type_ref.type_id() { + continue; + } + let original_field_name = fragment.name().to_snake_case(); let final_field_name = keyword_replace(original_field_name); @@ -282,16 +307,19 @@ impl<'a> ExpandedField<'a> { struct ExpandedVariant<'a> { name: Cow<'a, str>, - variant_type: Cow<'a, str>, + variant_type: Option>, on: ResponseTypeId, } impl<'a> ExpandedVariant<'a> { fn render(&self) -> TokenStream { let name_ident = Ident::new(&self.name, Span::call_site()); - let type_ident = Ident::new(&self.variant_type, Span::call_site()); + let optional_type_ident = self.variant_type.as_ref().map(|variant_type| { + let ident = Ident::new(&variant_type, Span::call_site()); + quote!((#ident)) + }); - quote!(#name_ident(#type_ident)) + quote!(#name_ident #optional_type_ident) } } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 20d21b2e5..a08bdefaf 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -222,6 +222,13 @@ pub(crate) enum Selection { } impl Selection { + pub(crate) fn as_inline_fragment(&self) -> Option<&InlineFragment> { + match self { + Selection::InlineFragment(inline_fragment) => Some(inline_fragment), + _ => None, + } + } + pub(crate) fn subselection(&self) -> &[SelectionId] { match self { Selection::Field(field) => field.selection_set.as_slice(), diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index c7a069f3b..1a32d0903 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -157,7 +157,7 @@ impl<'a> UnionRef<'a> { self.0.schema } - pub(crate) fn variants(&self) -> &[TypeId] { + pub(crate) fn variants(&self) -> &'a [TypeId] { &self.get().variants } } @@ -445,6 +445,10 @@ impl Schema { .expect("Schema.get_union") } + fn objects<'a>(&'a self) -> impl Iterator> + 'a { + (0..self.stored_objects.len()).map(move |id| self.object(ObjectId(id))) + } + pub(crate) fn union(&self, id: UnionId) -> UnionRef<'_> { UnionRef(self.with(id)) } @@ -520,8 +524,12 @@ impl<'a> InterfaceRef<'a> { &self.get().name } - pub(crate) fn variants(&self) -> impl Iterator { - todo!() + pub(crate) fn variants<'b>(&'b self) -> impl Iterator + 'b { + self.0 + .schema + .objects() + .filter(move |object| object.implements_interface(self.0.focus)) + .map(|object| TypeId::Object(object.id())) } } @@ -550,6 +558,10 @@ impl<'a> ObjectRef<'a> { pub(crate) fn id(&self) -> ObjectId { self.0.focus } + + pub(crate) fn implements_interface(&self, id: InterfaceId) -> bool { + self.get().implements_interfaces.contains(&id) + } } pub(crate) struct FieldRef<'a>(SchemaWith<'a, (StoredFieldId, &'a StoredField)>); From 324924b0272a010345aa5c8a47c3a60a9ac86338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 11:08:45 +0100 Subject: [PATCH 40/93] Reimplement type-refining fragments and proper variants handling --- .../src/codegen/selection.rs | 72 +++++++++++++------ graphql_client_codegen/src/resolution.rs | 9 +++ 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index b47dc3e5a..5bbbe8e60 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -10,7 +10,7 @@ use crate::shared::field_rename_annotation; use crate::{ field_type::GraphqlTypeQualifier, // deprecation::DeprecationStrategy, - resolution::{OperationRef, ResolvedQuery, Selection, SelectionId}, + resolution::{InlineFragment, OperationRef, ResolvedQuery, Selection, SelectionId}, schema::{Schema, TypeId}, shared::keyword_replace, GraphQLClientCodegenOptions, @@ -78,6 +78,38 @@ pub(super) fn render_fragment( expanded_selection.render(response_derives) } +/// A sub-selection set (spread) on one of the variants of a union or interface. +enum VariantSelection<'a> { + InlineFragment(&'a InlineFragment), + FragmentSpread(FragmentRef<'a>), +} + +impl<'a> VariantSelection<'a> { + fn from_selection( + selection_ref: &SelectionRef<'a>, + type_id: TypeId, + ) -> Option> { + match selection_ref.selection() { + Selection::InlineFragment(inline_fragment) => { + Some(VariantSelection::InlineFragment(inline_fragment)) + } + Selection::FragmentSpread(fragment_id) => { + let schema = selection_ref.schema(); + let fragment_ref = selection_ref.query().get_fragment_ref(schema, *fragment_id); + + if fragment_ref.on() == type_id { + // The selection is on the type itself. + None + } else { + // The selection is on one of the variants of the type. + Some(VariantSelection::FragmentSpread(fragment_ref)) + } + } + Selection::Field(_) | Selection::Typename => None, + } + } +} + fn calculate_selection<'a>( context: &mut ExpandedSelection<'a>, selection_set: &[SelectionId], @@ -103,27 +135,24 @@ fn calculate_selection<'a>( }; if let Some(variants) = variants { - // for each variant, get the corresponding fragment spread, or default to an empty variant + // for each variant, get the corresponding fragment spreads, or default to an empty variant for variant in variants.as_ref() { let schema_type = context.schema().type_ref(*variant); let variant_name_str = schema_type.name(); - let selection = selection_set - .iter() - .map(|id| context.get_selection_ref(*id)) - .filter_map(|selection_ref| { - selection_ref - .selection() - .as_inline_fragment() - .map(|inline_fragment| (selection_ref, inline_fragment)) - }) - .find(|(_selection_ref, inline_fragment)| inline_fragment.type_id == *variant); - - if let Some((selection_ref, inline_fragment)) = selection { + let variant_selections: Vec<(SelectionRef<'_>, VariantSelection<'_>)> = + selection_set + .iter() + .map(|id| context.get_selection_ref(*id)) + .filter_map(|selection_ref| { + VariantSelection::from_selection(&selection_ref, type_ref.type_id()) + .map(|variant_selection| (selection_ref, variant_selection)) + }) + .collect(); + + if let Some((selection_ref, _)) = variant_selections.first() { let variant_struct_name_str = selection_ref.full_path_prefix(); - todo!("There will be a struct/type for the variant if there is an inline OR type-refining fragment there."); - context.push_variant(ExpandedVariant { name: variant_name_str.into(), variant_type: Some(variant_struct_name_str.clone().into()), @@ -152,13 +181,12 @@ fn calculate_selection<'a>( } } - // push the fragments on variants down - - // meaning get all the fragment spreads on one of the variants, and add it to the type for that variant.... - todo!("push the fragments on variants down"); - // Finish by adding the Other variant - todo!("add the Other variant"); + context.push_variant(ExpandedVariant { + name: "Other".into(), + on: struct_id, + variant_type: Some("String".into()), + }); } } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index a08bdefaf..b2256f9b9 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -106,6 +106,14 @@ impl SelectionParent { } impl<'a> SelectionRef<'a> { + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.0.query + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + pub(crate) fn selection(&self) -> &'a Selection { self.0.focus.1 } @@ -241,6 +249,7 @@ impl Selection { #[derive(Debug)] pub(crate) struct InlineFragment { pub(crate) type_id: TypeId, + // TODO: see if we can encode this at the top-level instead, with the selection being a parent. selection_set: Vec, } From 8ed1fd853870710613517dc1a9a4510c4e33936b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 12:21:46 +0100 Subject: [PATCH 41/93] Finish interface and union selections rendering --- graphql_client/tests/union_query.rs | 85 ++++++++------ graphql_client_codegen/src/codegen.rs | 9 +- .../src/codegen/selection.rs | 110 +++++++++++------- graphql_client_codegen/src/codegen_options.rs | 5 +- graphql_client_codegen/src/query.rs | 2 +- 5 files changed, 126 insertions(+), 85 deletions(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 39052f800..3851d275e 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -32,33 +32,26 @@ fn union_query_deserialization() { let expected = union_query::ResponseData { names: Some(vec![ - union_query::UnionQueryNames { - typename: "Person".into(), - on: union_query::UnionQueryNamesOn::Person(union_query::UnionQueryNamesOnPerson { + union_query::UnionQueryNames::Person( + union_query::UnionQueryNamesOnPerson { first_name: "Audrey".to_string(), last_name: Some("Lorde".to_string()), }), - }, - union_query::UnionQueryNames { - typename: "Dog".into(), - on: union_query::UnionQueryNamesOn::Dog(union_query::UnionQueryNamesOnDog { - name: "Laïka".to_string(), - }), - }, - union_query::UnionQueryNames { - typename: "Organization".into(), - on: union_query::UnionQueryNamesOn::Organization( + union_query::UnionQueryNames::Dog( + union_query::UnionQueryNamesOnDog { + name: "Laïka".to_string(), + } + ), + union_query::UnionQueryNames::Organization( union_query::UnionQueryNamesOnOrganization { title: "Mozilla".to_string(), }, + ), + union_query::UnionQueryNames::Dog( + union_query::UnionQueryNamesOnDog { + name: "Norbert".to_string(), + } ), - }, - union_query::UnionQueryNames { - typename: "Dog".into(), - on: union_query::UnionQueryNamesOn::Dog(union_query::UnionQueryNamesOnDog { - name: "Norbert".to_string(), - }), - }, ]), }; @@ -79,6 +72,7 @@ fn fragment_on_union() { first_name: "Audrey".to_string(), }, ), + on: fragment_on_union::FragmentOnUnionNamesOn::Person, }, fragment_on_union::FragmentOnUnionNames { names_fragment: fragment_on_union::NamesFragment::Dog( @@ -86,6 +80,7 @@ fn fragment_on_union() { name: "Laïka".to_string(), }, ), + on: fragment_on_union::FragmentOnUnionNamesOn::Dog, }, fragment_on_union::FragmentOnUnionNames { names_fragment: fragment_on_union::NamesFragment::Organization( @@ -93,6 +88,7 @@ fn fragment_on_union() { title: "Mozilla".to_string(), }, ), + on: fragment_on_union::FragmentOnUnionNamesOn::Organization, }, fragment_on_union::FragmentOnUnionNames { names_fragment: fragment_on_union::NamesFragment::Dog( @@ -100,6 +96,7 @@ fn fragment_on_union() { name: "Norbert".to_string(), }, ), + on: fragment_on_union::FragmentOnUnionNamesOn::Dog, }, ]), }; @@ -109,22 +106,36 @@ fn fragment_on_union() { #[test] fn fragment_and_more_on_union() { + use fragment_and_more_on_union::*; todo!(); - // let _expected = fragment_and_more_on_union::ResponseData { - // names: Some(vec![ - // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Person { - // first_name: "Audrey".to_string(), - // last_name: Some("Lorde".to_string()), - // }, - // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { - // name: "Laïka".to_string(), - // }, - // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Organization { - // title: "Mozilla".to_string(), - // }, - // fragment_and_more_on_union::FragmentAndMoreOnUnionNames::Dog { - // name: "Norbert".to_string(), - // }, - // ]), - // }; + let _expected = fragment_and_more_on_union::ResponseData { + names: Some(vec![ + FragmentAndMoreOnUnionNames { + names_fragment: NamesFragment::Person(NamesFragmentOnPerson { + first_name: "Larry".into(), + }), + on: FragmentAndMoreOnUnionNamesOn::Person, + }, + FragmentAndMoreOnUnionNames { + names_fragment: NamesFragment::Dog(NamesFragmentOnDog { + name: "Laïka".into(), + }), + on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog { + is_good_dog: true, + })}, + FragmentAndMoreOnUnionNames { + names_fragment: NamesFragment::Organization(NamesFragmentOnOrganization { + title: "Mozilla".into(), + }), + on: FragmentAndMoreOnUnionNamesOn::Person, + }, + FragmentAndMoreOnUnionNames { + names_fragment: NamesFragment::Dog(NamesFragmentOnDog { + name: "Norbert".into(), + }), + on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog { + is_good_dog: true + })}, + ]), + }; } diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 1d75f827e..81eac345f 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -29,7 +29,7 @@ pub(crate) fn response_for_query( generate_input_object_definitions(&operation, &all_used_types, options); let variables_struct = generate_variables_struct(&operation, options); - let definitions = render_response_data_fields(&operation, &response_derives, options); + let definitions = render_response_data_fields(&operation, options).render(&response_derives); let q = quote! { use serde::{Serialize, Deserialize}; @@ -201,11 +201,8 @@ fn generate_fragment_definitions( .map(move |id| operation.query().get_fragment_ref(operation.schema(), id)); for fragment in fragments { - fragment_definitions.push(selection::render_fragment( - &fragment, - response_derives, - options, - )); + fragment_definitions + .push(selection::render_fragment(&fragment, options).render(&response_derives)); } fragment_definitions diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 5bbbe8e60..0378dc0a5 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -22,9 +22,8 @@ use std::borrow::Cow; pub(crate) fn render_response_data_fields<'a>( operation: &OperationRef<'a>, - response_derives: &impl quote::ToTokens, - options: &GraphQLClientCodegenOptions, -) -> TokenStream { + options: &'a GraphQLClientCodegenOptions, +) -> ExpandedSelection<'a> { let mut expanded_selection = ExpandedSelection { query: operation.query(), schema: operation.schema(), @@ -46,14 +45,13 @@ pub(crate) fn render_response_data_fields<'a>( operation.on_ref(), ); - expanded_selection.render(response_derives) + expanded_selection } -pub(super) fn render_fragment( - fragment: &FragmentRef<'_>, - response_derives: &impl quote::ToTokens, - options: &GraphQLClientCodegenOptions, -) -> TokenStream { +pub(super) fn render_fragment<'a>( + fragment: &FragmentRef<'a>, + options: &'a GraphQLClientCodegenOptions, +) -> ExpandedSelection<'a> { let mut expanded_selection = ExpandedSelection { query: fragment.query(), schema: fragment.schema(), @@ -75,7 +73,7 @@ pub(super) fn render_fragment( fragment.on_ref(), ); - expanded_selection.render(response_derives) + expanded_selection } /// A sub-selection set (spread) on one of the variants of a union or interface. @@ -85,6 +83,7 @@ enum VariantSelection<'a> { } impl<'a> VariantSelection<'a> { + /// The second argument is the parent type id, so it can be excluded. fn from_selection( selection_ref: &SelectionRef<'a>, type_id: TypeId, @@ -108,6 +107,13 @@ impl<'a> VariantSelection<'a> { Selection::Field(_) | Selection::Typename => None, } } + + fn variant_type_id(&self) -> TypeId { + match self { + VariantSelection::InlineFragment(f) => f.type_id, + VariantSelection::FragmentSpread(f) => f.on(), + } + } } fn calculate_selection<'a>( @@ -116,8 +122,6 @@ fn calculate_selection<'a>( struct_id: ResponseTypeId, type_ref: TypeRef<'a>, ) { - // TODO: if the selection has one item, we can sometimes generate fewer structs (e.g. single fragment spread) - // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_, // including an `Other { #serde(rename = "__typename") typename: String }` variant. { @@ -135,22 +139,30 @@ fn calculate_selection<'a>( }; if let Some(variants) = variants { - // for each variant, get the corresponding fragment spreads, or default to an empty variant + let variant_selections: Vec<(SelectionRef<'_>, VariantSelection<'_>)> = selection_set + .iter() + .map(|id| context.get_selection_ref(*id)) + .filter_map(|selection_ref| { + VariantSelection::from_selection(&selection_ref, type_ref.type_id()) + .map(|variant_selection| (selection_ref, variant_selection)) + }) + .collect(); + + // For each variant, get the corresponding fragment spreads and + // inline fragments, or default to an empty variant (one with no + // associated data). for variant in variants.as_ref() { - let schema_type = context.schema().type_ref(*variant); - let variant_name_str = schema_type.name(); - - let variant_selections: Vec<(SelectionRef<'_>, VariantSelection<'_>)> = - selection_set - .iter() - .map(|id| context.get_selection_ref(*id)) - .filter_map(|selection_ref| { - VariantSelection::from_selection(&selection_ref, type_ref.type_id()) - .map(|variant_selection| (selection_ref, variant_selection)) - }) - .collect(); - - if let Some((selection_ref, _)) = variant_selections.first() { + let variant_schema_type = context.schema().type_ref(*variant); + let variant_name_str = variant_schema_type.name(); + + let mut variant_selections = variant_selections + .iter() + .filter(|(_selection_ref, variant)| { + variant.variant_type_id() == variant_schema_type.type_id() + }) + .peekable(); + + if let Some((selection_ref, _)) = variant_selections.peek() { let variant_struct_name_str = selection_ref.full_path_prefix(); context.push_variant(ExpandedVariant { @@ -161,16 +173,17 @@ fn calculate_selection<'a>( let expanded_type = ExpandedType { name: variant_struct_name_str.into(), - schema_type, + schema_type: variant_schema_type, }; let struct_id = context.push_type(expanded_type); calculate_selection( context, + // FIXME should be all subselections selection_ref.subselection_ids(), struct_id, - schema_type, + variant_schema_type, ); } else { context.push_variant(ExpandedVariant { @@ -252,12 +265,15 @@ fn calculate_selection<'a>( Selection::Typename => (), Selection::InlineFragment(_inline) => (), Selection::FragmentSpread(fragment_id) => { - // FIXME: we need to identify if the fragment is on the field itself, or on an union/interface variant of it. - // If it's on a field, do it here. - // If it's on a variant, push it downstream to the variant. + // Here we only render fragments that are directly on the type + // itself, and not on one of its variants. + let fragment = context.get_fragment_ref(*fragment_id); - // Assuming the query was validated properly, a fragment spread is either on the field's type itself, or on one of the variants (union or interfaces). If it's not directly a field on the struct, it will be handled in the `on` variants. + // Assuming the query was validated properly, a fragment spread + // is either on the field's type itself, or on one of the + // variants (union or interfaces). If it's not directly a field + // on the struct, it will be handled in the `on` variants. if fragment.on() != type_ref.type_id() { continue; } @@ -351,12 +367,12 @@ impl<'a> ExpandedVariant<'a> { } } -struct ExpandedType<'a> { +pub(crate) struct ExpandedType<'a> { name: Cow<'a, str>, schema_type: TypeRef<'a>, } -struct ExpandedSelection<'a> { +pub(crate) struct ExpandedSelection<'a> { query: &'a ResolvedQuery, schema: &'a Schema, types: Vec>, @@ -370,18 +386,18 @@ impl<'a> ExpandedSelection<'a> { self.schema } - pub(crate) fn push_type(&mut self, tpe: ExpandedType<'a>) -> ResponseTypeId { + fn push_type(&mut self, tpe: ExpandedType<'a>) -> ResponseTypeId { let id = self.types.len(); self.types.push(tpe); ResponseTypeId(id as u32) } - pub(crate) fn push_field(&mut self, field: ExpandedField<'a>) { + fn push_field(&mut self, field: ExpandedField<'a>) { self.fields.push(field); } - pub(crate) fn push_variant(&mut self, variant: ExpandedVariant<'a>) { + fn push_variant(&mut self, variant: ExpandedVariant<'a>) { self.variants.push(variant); } @@ -416,11 +432,12 @@ impl<'a> ExpandedSelection<'a> { for (type_id, ty) in self.types() { let struct_name = Ident::new(&ty.name, Span::call_site()); - let fields = self + let mut fields = self .fields .iter() .filter(|field| field.struct_id == type_id) - .map(|field| field.render()); + .map(|field| field.render()) + .peekable(); let on_variants: Vec = self .variants @@ -429,6 +446,19 @@ impl<'a> ExpandedSelection<'a> { .map(|variant| variant.render()) .collect(); + // If we only have an `on` field, turn the struct into the enum + // of the variants. + if fields.peek().is_none() { + let item = quote! { + #response_derives + pub enum #struct_name { + #(#on_variants),* + } + }; + items.push(item); + continue; + } + let (on_field, on_enum) = if on_variants.len() > 0 { let enum_name = Ident::new(&format!("{}On", ty.name), Span::call_site()); diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 9700b04b4..4d093a519 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -103,7 +103,10 @@ impl GraphQLClientCodegenOptions { pub fn all_response_derives(&self) -> impl Iterator { let base_derives = std::iter::once("Deserialize"); - base_derives.chain(self.additional_response_derives()) + base_derives.chain( + self.additional_response_derives() + .filter(|additional| additional != &"Deserialize"), + ) } /// Additional traits we want to derive for responses. diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 2dd6c1c9e..96db1b023 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -20,7 +20,7 @@ pub(crate) struct FragmentId(usize); // } /// This holds all the information we need during the code generation phase. -pub(crate) struct QueryContext<'query> { +struct QueryContext<'query> { pub fragments: BTreeMap<&'query str, GqlFragment<'query>>, pub schema: &'query Schema, pub deprecation_strategy: DeprecationStrategy, From c9ad62bc6e9c0cc22257594a26b14583ee316461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 12:30:57 +0100 Subject: [PATCH 42/93] Make union tests pass --- graphql_client_codegen/src/codegen/selection.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 0378dc0a5..b62d274d7 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -122,8 +122,7 @@ fn calculate_selection<'a>( struct_id: ResponseTypeId, type_ref: TypeRef<'a>, ) { - // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_, - // including an `Other { #serde(rename = "__typename") typename: String }` variant. + // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_. { let variants: Option> = match type_ref.type_id() { TypeId::Interface(interface_id) => { @@ -193,13 +192,6 @@ fn calculate_selection<'a>( }); } } - - // Finish by adding the Other variant - context.push_variant(ExpandedVariant { - name: "Other".into(), - on: struct_id, - variant_type: Some("String".into()), - }); } } @@ -451,6 +443,7 @@ impl<'a> ExpandedSelection<'a> { if fields.peek().is_none() { let item = quote! { #response_derives + #[serde(tag = "__typename")] pub enum #struct_name { #(#on_variants),* } @@ -462,12 +455,13 @@ impl<'a> ExpandedSelection<'a> { let (on_field, on_enum) = if on_variants.len() > 0 { let enum_name = Ident::new(&format!("{}On", ty.name), Span::call_site()); - let on_field = quote!(pub on: #enum_name); + let on_field = quote!(#[serde(flatten)] pub on: #enum_name); let on_enum = quote!( #response_derives + #[serde(tag = "__typename")] pub enum #enum_name { - #(#on_variants),* + #(#on_variants,)* } ); From 382d0ac4b1a3b89ee0482067a923e70bbde0f4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 12:31:10 +0100 Subject: [PATCH 43/93] rustfmt --- graphql_client/tests/union_query.rs | 39 +++++++++++++---------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 3851d275e..b1e179f4f 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -32,26 +32,21 @@ fn union_query_deserialization() { let expected = union_query::ResponseData { names: Some(vec![ - union_query::UnionQueryNames::Person( - union_query::UnionQueryNamesOnPerson { - first_name: "Audrey".to_string(), - last_name: Some("Lorde".to_string()), - }), - union_query::UnionQueryNames::Dog( - union_query::UnionQueryNamesOnDog { - name: "Laïka".to_string(), - } - ), + union_query::UnionQueryNames::Person(union_query::UnionQueryNamesOnPerson { + first_name: "Audrey".to_string(), + last_name: Some("Lorde".to_string()), + }), + union_query::UnionQueryNames::Dog(union_query::UnionQueryNamesOnDog { + name: "Laïka".to_string(), + }), union_query::UnionQueryNames::Organization( - union_query::UnionQueryNamesOnOrganization { - title: "Mozilla".to_string(), - }, + union_query::UnionQueryNamesOnOrganization { + title: "Mozilla".to_string(), + }, ), - union_query::UnionQueryNames::Dog( - union_query::UnionQueryNamesOnDog { - name: "Norbert".to_string(), - } - ), + union_query::UnionQueryNames::Dog(union_query::UnionQueryNamesOnDog { + name: "Norbert".to_string(), + }), ]), }; @@ -122,7 +117,8 @@ fn fragment_and_more_on_union() { }), on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog { is_good_dog: true, - })}, + }), + }, FragmentAndMoreOnUnionNames { names_fragment: NamesFragment::Organization(NamesFragmentOnOrganization { title: "Mozilla".into(), @@ -134,8 +130,9 @@ fn fragment_and_more_on_union() { name: "Norbert".into(), }), on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog { - is_good_dog: true - })}, + is_good_dog: true, + }), + }, ]), }; } From cbc71cb0d12ad1c66948180a5d707784e5e64435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 13:04:43 +0100 Subject: [PATCH 44/93] Light test debugging --- graphql_client/tests/interfaces.rs | 2 +- graphql_client_cli/src/generate.rs | 2 +- graphql_client_codegen/src/resolution.rs | 2 +- .../src/schema/json_conversion.rs | 54 +++++++++---------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/graphql_client/tests/interfaces.rs b/graphql_client/tests/interfaces.rs index e6cc11157..ee2d5f3ac 100644 --- a/graphql_client/tests/interfaces.rs +++ b/graphql_client/tests/interfaces.rs @@ -146,7 +146,7 @@ fn fragment_in_interface() { name: "Laïka".to_string(), public_status: PublicStatus { display_name: true, - on: PublicStatusOn::OTHER + on: PublicStatusOn::Dog }, on: InterfaceWithFragmentQueryEverythingOn::Dog( InterfaceWithFragmentQueryEverythingOnDog { is_good_dog: true } diff --git a/graphql_client_cli/src/generate.rs b/graphql_client_cli/src/generate.rs index f32fa0cc7..80bef36f3 100644 --- a/graphql_client_cli/src/generate.rs +++ b/graphql_client_cli/src/generate.rs @@ -103,5 +103,5 @@ fn format(codes: &str) -> String { return String::from_utf8(out).unwrap(); } #[cfg(not(feature = "rustfmt"))] - unreachable!() + unreachable!("called format() without the rustfmt feature") } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index b2256f9b9..5c9b3513a 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -722,7 +722,7 @@ impl<'a> OperationRef<'a> { .query .operations .get(self.0.focus.0 as usize) - .unwrap() + .expect("get operation") } fn to_path_segment(&self) -> String { diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 357e6bb36..dc090ea7a 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -5,7 +5,7 @@ use graphql_introspection_query::introspection_response::{ pub(super) fn build_schema(src: IntrospectionResponse) -> super::Schema { let converter = JsonSchemaConverter { - src: src.into_schema().schema.unwrap(), + src: src.into_schema().schema.expect("could not find schema"), schema: Schema::new(), }; @@ -23,28 +23,28 @@ impl JsonSchemaConverter { let names = &mut self.schema.names; unions_mut(&mut self.src) - .map(|u| u.name.as_ref().unwrap()) + .map(|u| u.name.as_ref().expect("union name")) .enumerate() .for_each(|(idx, name)| { names.insert(name.clone(), TypeId::union(idx)); }); interfaces_mut(&mut self.src) - .map(|iface| iface.name.as_ref().unwrap()) + .map(|iface| iface.name.as_ref().expect("interface name")) .enumerate() .for_each(|(idx, name)| { names.insert(name.clone(), TypeId::interface(idx)); }); objects_mut(&mut self.src) - .map(|obj| obj.name.as_ref().unwrap()) + .map(|obj| obj.name.as_ref().expect("object name")) .enumerate() .for_each(|(idx, name)| { names.insert(name.clone(), TypeId::object(idx)); }); inputs_mut(&mut self.src) - .map(|obj| obj.name.as_ref().unwrap()) + .map(|obj| obj.name.as_ref().expect("input name")) .enumerate() .for_each(|(idx, name)| { names.insert(name.clone(), TypeId::input(idx)); @@ -223,7 +223,7 @@ fn types_mut(schema: &mut JsonSchema) -> impl Iterator { schema .types .as_mut() - .unwrap() + .expect("schema.types.as_mut()") .iter_mut() .filter_map(|t| -> Option<&mut FullType> { t.as_mut().map(|f| &mut f.full_type) }) } @@ -251,12 +251,12 @@ fn inputs_mut(schema: &mut JsonSchema) -> impl Iterator { fn scalars_mut(schema: &mut JsonSchema) -> impl Iterator { types_mut(schema).filter(|t| { t.kind == Some(__TypeKind::SCALAR) - && !super::DEFAULT_SCALARS.contains(&t.name.as_ref().map(String::as_str).unwrap()) + && !super::DEFAULT_SCALARS.contains(&t.name.as_ref().map(String::as_str).expect("FullType.name")) }) } fn ingest_scalar(schema: &mut Schema, scalar: &mut FullType) { - let name: String = scalar.name.take().unwrap(); + let name: String = scalar.name.take().expect("scalar.name"); let names_name = name.clone(); let id = schema.push_scalar(super::StoredScalar { name }); @@ -265,15 +265,15 @@ fn ingest_scalar(schema: &mut Schema, scalar: &mut FullType) { } fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { - let name = enm.name.take().unwrap(); + let name = enm.name.take().expect("enm.name"); let names_name = name.clone(); let variants = enm .enum_values .as_mut() - .unwrap() + .expect("enm.enum_values.as_mut()") .into_iter() - .map(|v| std::mem::replace(v.name.as_mut().take().unwrap(), String::new())) + .map(|v| std::mem::replace(v.name.as_mut().take().expect("variant.name.as_mut().take()"), String::new())) .collect(); let enm = super::StoredEnum { name, variants }; @@ -285,17 +285,17 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { let interface_id = schema - .find_type_id(iface.name.as_ref().unwrap()) + .find_type_id(iface.name.as_ref().expect("iface.name")) .as_interface_id() - .unwrap(); - let fields = iface.fields.as_mut().unwrap(); + .expect("iface type id as interface id"); + let fields = iface.fields.as_mut().expect("interface.fields"); let mut field_ids = Vec::with_capacity(fields.len()); for field in fields.iter_mut() { let field = super::StoredField { parent: super::StoredFieldParent::Interface(interface_id), - name: field.name.take().unwrap(), - r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + name: field.name.take().expect("take field name"), + r#type: resolve_field_type(schema, &mut field.type_.as_mut().expect("take field type").type_ref), deprecation: Some(None) .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) .map(|_: Option<()>| field.deprecation_reason.clone()), @@ -305,7 +305,7 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { } let interface = super::StoredInterface { - name: std::mem::replace(iface.name.as_mut().unwrap(), String::new()), + name: std::mem::replace(iface.name.as_mut().expect("iface.name.as_mut"), String::new()), fields: field_ids, }; @@ -314,18 +314,18 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { fn ingest_object(schema: &mut Schema, object: &mut FullType) { let object_id = schema - .find_type_id(object.name.as_ref().unwrap()) + .find_type_id(object.name.as_ref().expect("object.name")) .as_object_id() - .unwrap(); + .expect("ingest_object > as_object_id"); - let fields = object.fields.as_mut().unwrap(); + let fields = object.fields.as_mut().expect("object.fields.as_mut()"); let mut field_ids = Vec::with_capacity(fields.len()); for field in fields.iter_mut() { let field = super::StoredField { parent: super::StoredFieldParent::Object(object_id), - name: field.name.take().unwrap(), - r#type: resolve_field_type(schema, &mut field.type_.as_mut().unwrap().type_ref), + name: field.name.take().expect("take field name"), + r#type: resolve_field_type(schema, &mut field.type_.as_mut().expect("take field type").type_ref), deprecation: Some(None) .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) .map(|_: Option<()>| field.deprecation_reason.clone()), @@ -335,7 +335,7 @@ fn ingest_object(schema: &mut Schema, object: &mut FullType) { } let object = super::StoredObject { - name: object.name.take().unwrap(), + name: object.name.take().expect("take object name"), implements_interfaces: Vec::new(), fields: field_ids, }; @@ -347,12 +347,12 @@ fn ingest_union(schema: &mut Schema, union: &mut FullType) { let variants = union .possible_types .as_ref() - .unwrap() + .expect("union.possible_types") .iter() - .map(|variant| schema.find_type_id(variant.type_ref.name.as_ref().unwrap())) + .map(|variant| schema.find_type_id(variant.type_ref.name.as_ref().expect("variant.type_ref.name"))) .collect(); let un = super::StoredUnion { - name: union.name.take().unwrap(), + name: union.name.take().expect("union.name.take"), variants, }; @@ -399,7 +399,7 @@ fn from_json_type_inner(schema: &mut Schema, inner: &mut TypeRef) -> super::Stor } (Some(_), None, Some(name)) => { return super::StoredFieldType { - id: *schema.names.get(name).unwrap(), + id: *schema.names.get(name).expect("schema.names.get(name)"), qualifiers, } } From e2a5b468c0841c17e2a8aa33007889da66730bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 13:45:49 +0100 Subject: [PATCH 45/93] Improve cryptic panic --- graphql_client_codegen/src/resolution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 5c9b3513a..c193f3e3b 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -201,7 +201,7 @@ impl<'a> SelectionRef<'a> { .name() .to_camel_case() ), - _ => unreachable!(), + other => unreachable!("{:?} in to_path_segment", other), } } } From a55488559de5b40cab262f61c12aedeeae908379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 14:48:31 +0100 Subject: [PATCH 46/93] Make type-refining fragments work --- ..._with_type_refining_fragment_query.graphql | 4 +- .../tests/type_refining_fragments.rs | 12 +++-- .../src/codegen/selection.rs | 51 +++++++++++++------ graphql_client_codegen/src/resolution.rs | 24 +++++---- .../src/schema/json_conversion.rs | 38 +++++++++++--- 5 files changed, 94 insertions(+), 35 deletions(-) diff --git a/graphql_client/tests/interfaces/interface_with_type_refining_fragment_query.graphql b/graphql_client/tests/interfaces/interface_with_type_refining_fragment_query.graphql index ec364f4fb..471177ed9 100644 --- a/graphql_client/tests/interfaces/interface_with_type_refining_fragment_query.graphql +++ b/graphql_client/tests/interfaces/interface_with_type_refining_fragment_query.graphql @@ -1,4 +1,4 @@ -fragment Birthday on Person { +fragment BirthdayFragment on Person { birthday } @@ -9,7 +9,7 @@ query QueryOnInterface { ... on Dog { isGoodDog } - ...Birthday + ...BirthdayFragment ... on Organization { industry } diff --git a/graphql_client/tests/type_refining_fragments.rs b/graphql_client/tests/type_refining_fragments.rs index 2e7497fde..a021193bd 100644 --- a/graphql_client/tests/type_refining_fragments.rs +++ b/graphql_client/tests/type_refining_fragments.rs @@ -29,7 +29,9 @@ fn type_refining_fragment_on_union() { last_name: Some("Lorde".to_string()), }), query_on_union::QueryOnUnionNames::Dog(query_on_union::QueryOnUnionNamesOnDog { - name: "Laïka".to_string(), + dog_name: query_on_union::DogName { + name: "Laïka".to_string(), + }, }), query_on_union::QueryOnUnionNames::Organization( query_on_union::QueryOnUnionNamesOnOrganization { @@ -37,7 +39,9 @@ fn type_refining_fragment_on_union() { }, ), query_on_union::QueryOnUnionNames::Dog(query_on_union::QueryOnUnionNamesOnDog { - name: "Norbert".to_string(), + dog_name: query_on_union::DogName { + name: "Norbert".to_string(), + }, }), ]), }; @@ -58,7 +62,9 @@ fn type_refining_fragment_on_interface() { QueryOnInterfaceEverything { name: "Audrey Lorde".to_string(), on: QueryOnInterfaceEverythingOn::Person(QueryOnInterfaceEverythingOnPerson { - birthday: Some("1934-02-18".to_string()), + birthday_fragment: BirthdayFragment { + birthday: Some("1934-02-18".to_string()) + }, }), }, QueryOnInterfaceEverything { diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index b62d274d7..98b2a12e1 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -161,8 +161,11 @@ fn calculate_selection<'a>( }) .peekable(); - if let Some((selection_ref, _)) = variant_selections.peek() { - let variant_struct_name_str = selection_ref.full_path_prefix(); + if let Some((selection_ref, _variant)) = variant_selections.peek() { + let mut variant_struct_name_str = selection_ref.full_path_prefix(); + variant_struct_name_str.reserve(2 + variant_name_str.len()); + variant_struct_name_str.push_str("On"); + variant_struct_name_str.push_str(variant_name_str); context.push_variant(ExpandedVariant { name: variant_name_str.into(), @@ -177,13 +180,28 @@ fn calculate_selection<'a>( let struct_id = context.push_type(expanded_type); - calculate_selection( - context, - // FIXME should be all subselections - selection_ref.subselection_ids(), - struct_id, - variant_schema_type, - ); + for (_selection, variant_selection) in variant_selections { + match variant_selection { + VariantSelection::InlineFragment(_) => { + calculate_selection( + context, + selection_ref.subselection_ids(), + struct_id, + variant_schema_type, + ); + } + VariantSelection::FragmentSpread(fragment_ref) => { + context.push_field(ExpandedField { + field_type: fragment_ref.name().into(), + field_type_qualifiers: &[GraphqlTypeQualifier::Required], + flatten: true, + graphql_name: None, + rust_name: fragment_ref.name().to_snake_case().into(), + struct_id, + }) + } + } + } } else { context.push_variant(ExpandedVariant { name: variant_name_str.into(), @@ -207,7 +225,7 @@ fn calculate_selection<'a>( match field_type.type_id() { TypeId::Enum(enm) => { context.push_field(ExpandedField { - graphql_name, + graphql_name: Some(graphql_name), rust_name, struct_id, field_type: context.schema().r#enum(enm).name().into(), @@ -221,7 +239,7 @@ fn calculate_selection<'a>( field_type_qualifiers: field .schema_field(context.schema()) .type_qualifiers(), - graphql_name, + graphql_name: Some(graphql_name), struct_id, rust_name, flatten: false, @@ -232,7 +250,7 @@ fn calculate_selection<'a>( context.push_field(ExpandedField { struct_id, - graphql_name, + graphql_name: Some(graphql_name), rust_name, field_type_qualifiers: schema_field.type_qualifiers(), field_type: Cow::Owned(struct_name_string.clone()), @@ -276,7 +294,7 @@ fn calculate_selection<'a>( context.push_field(ExpandedField { field_type: fragment.name().into(), field_type_qualifiers: &[GraphqlTypeQualifier::Required], - graphql_name: fragment.name(), + graphql_name: None, rust_name: final_field_name, struct_id, flatten: true, @@ -293,7 +311,7 @@ fn calculate_selection<'a>( struct ResponseTypeId(u32); struct ExpandedField<'a> { - graphql_name: &'a str, + graphql_name: Option<&'a str>, rust_name: Cow<'a, str>, field_type: Cow<'a, str>, field_type_qualifiers: &'a [GraphqlTypeQualifier], @@ -309,7 +327,10 @@ impl<'a> ExpandedField<'a> { self.field_type_qualifiers, ); - let optional_rename = field_rename_annotation(self.graphql_name, &self.rust_name); + let optional_rename = self + .graphql_name + .as_ref() + .map(|graphql_name| field_rename_annotation(graphql_name, &self.rust_name)); let optional_flatten = if self.flatten { Some(quote!(#[serde(flatten)])) } else { diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index c193f3e3b..6048d8fb0 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -34,7 +34,6 @@ impl<'a, T> QueryWith<'a, T> { } } -pub(crate) struct SelectionRef<'a>(QueryWith<'a, (SelectionId, &'a Selection)>); pub(crate) struct OperationRef<'a>(QueryWith<'a, OperationId>); pub(crate) struct VariableRef<'a>(QueryWith<'a, (VariableId, &'a ResolvedVariable)>); pub(crate) struct InlineFragmentRef<'a>(QueryWith<'a, &'a InlineFragment>); @@ -65,7 +64,8 @@ impl VariableId { #[derive(Debug, Clone, Copy)] enum SelectionParent { - Selection(SelectionId), + Field(SelectionId), + InlineFragment(SelectionId), Fragment(ResolvedFragmentId), Operation(OperationId), } @@ -73,7 +73,8 @@ enum SelectionParent { impl SelectionParent { fn add_to_selection_set(&self, q: &mut ResolvedQuery, selection_id: SelectionId) { match self { - SelectionParent::Selection(parent_selection_id) => { + SelectionParent::Field(parent_selection_id) + | SelectionParent::InlineFragment(parent_selection_id) => { let parent_selection = q .selections .get_mut(parent_selection_id.0 as usize) @@ -82,7 +83,7 @@ impl SelectionParent { match parent_selection { Selection::Field(f) => f.selection_set.push(selection_id), Selection::InlineFragment(inline) => inline.selection_set.push(selection_id), - _ => unreachable!("impossible parent selection"), + other => unreachable!("impossible parent selection: {:?}", other), } } SelectionParent::Fragment(fragment_id) => { @@ -105,6 +106,8 @@ impl SelectionParent { } } +pub(crate) struct SelectionRef<'a>(QueryWith<'a, (SelectionId, &'a Selection)>); + impl<'a> SelectionRef<'a> { pub(crate) fn query(&self) -> &'a ResolvedQuery { self.0.query @@ -167,7 +170,10 @@ impl<'a> SelectionRef<'a> { } pub(crate) fn full_path_prefix(&self) -> String { - let mut path = vec![self.to_path_segment()]; + let mut path = match self.selection() { + Selection::FragmentSpread(_) | Selection::InlineFragment(_) => Vec::new(), + _ => vec![self.to_path_segment()], + }; let mut item = self.id(); @@ -175,7 +181,7 @@ impl<'a> SelectionRef<'a> { path.push(self.0.refocus(*parent).to_path_segment()); match parent { - SelectionParent::Selection(id) => { + SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { item = *id; } _ => break, @@ -209,7 +215,7 @@ impl<'a> SelectionRef<'a> { impl<'a> QueryWith<'a, SelectionParent> { pub(crate) fn to_path_segment(&self) -> String { match self.focus { - SelectionParent::Selection(id) => { + SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { SelectionRef(self.refocus((id, self.query.get_selection(id)))).to_path_segment() } SelectionParent::Operation(id) => OperationRef(self.refocus(id)).to_path_segment(), @@ -467,7 +473,7 @@ fn resolve_object_selection<'a>( object.schema(), field_ref.type_id(), &field.selection_set, - SelectionParent::Selection(id), + SelectionParent::Field(id), )?; parent.add_to_selection_set(query, id); @@ -551,7 +557,7 @@ fn resolve_inline_fragment( schema, type_id, &inline_fragment.selection_set, - SelectionParent::Selection(id), + SelectionParent::InlineFragment(id), )?; Ok(id) diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index dc090ea7a..c3f786b82 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -251,7 +251,8 @@ fn inputs_mut(schema: &mut JsonSchema) -> impl Iterator { fn scalars_mut(schema: &mut JsonSchema) -> impl Iterator { types_mut(schema).filter(|t| { t.kind == Some(__TypeKind::SCALAR) - && !super::DEFAULT_SCALARS.contains(&t.name.as_ref().map(String::as_str).expect("FullType.name")) + && !super::DEFAULT_SCALARS + .contains(&t.name.as_ref().map(String::as_str).expect("FullType.name")) }) } @@ -273,7 +274,15 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { .as_mut() .expect("enm.enum_values.as_mut()") .into_iter() - .map(|v| std::mem::replace(v.name.as_mut().take().expect("variant.name.as_mut().take()"), String::new())) + .map(|v| { + std::mem::replace( + v.name + .as_mut() + .take() + .expect("variant.name.as_mut().take()"), + String::new(), + ) + }) .collect(); let enm = super::StoredEnum { name, variants }; @@ -295,7 +304,10 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { let field = super::StoredField { parent: super::StoredFieldParent::Interface(interface_id), name: field.name.take().expect("take field name"), - r#type: resolve_field_type(schema, &mut field.type_.as_mut().expect("take field type").type_ref), + r#type: resolve_field_type( + schema, + &mut field.type_.as_mut().expect("take field type").type_ref, + ), deprecation: Some(None) .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) .map(|_: Option<()>| field.deprecation_reason.clone()), @@ -305,7 +317,10 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { } let interface = super::StoredInterface { - name: std::mem::replace(iface.name.as_mut().expect("iface.name.as_mut"), String::new()), + name: std::mem::replace( + iface.name.as_mut().expect("iface.name.as_mut"), + String::new(), + ), fields: field_ids, }; @@ -325,7 +340,10 @@ fn ingest_object(schema: &mut Schema, object: &mut FullType) { let field = super::StoredField { parent: super::StoredFieldParent::Object(object_id), name: field.name.take().expect("take field name"), - r#type: resolve_field_type(schema, &mut field.type_.as_mut().expect("take field type").type_ref), + r#type: resolve_field_type( + schema, + &mut field.type_.as_mut().expect("take field type").type_ref, + ), deprecation: Some(None) .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) .map(|_: Option<()>| field.deprecation_reason.clone()), @@ -349,7 +367,15 @@ fn ingest_union(schema: &mut Schema, union: &mut FullType) { .as_ref() .expect("union.possible_types") .iter() - .map(|variant| schema.find_type_id(variant.type_ref.name.as_ref().expect("variant.type_ref.name"))) + .map(|variant| { + schema.find_type_id( + variant + .type_ref + .name + .as_ref() + .expect("variant.type_ref.name"), + ) + }) .collect(); let un = super::StoredUnion { name: union.name.take().expect("union.name.take"), From 2d2a61cc5c98a5e2625c44c35b791327e88fc50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 15:04:34 +0100 Subject: [PATCH 47/93] Reimplement field deprecation annotations --- .../src/codegen/selection.rs | 46 ++++++++++--------- graphql_client_codegen/src/schema.rs | 7 +++ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 98b2a12e1..20c575659 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -8,6 +8,7 @@ use crate::resolution::SelectionRef; use crate::schema::TypeRef; use crate::shared::field_rename_annotation; use crate::{ + deprecation::DeprecationStrategy, field_type::GraphqlTypeQualifier, // deprecation::DeprecationStrategy, resolution::{InlineFragment, OperationRef, ResolvedQuery, Selection, SelectionId}, @@ -198,6 +199,7 @@ fn calculate_selection<'a>( graphql_name: None, rust_name: fragment_ref.name().to_snake_case().into(), struct_id, + deprecation: None, }) } } @@ -231,6 +233,7 @@ fn calculate_selection<'a>( field_type: context.schema().r#enum(enm).name().into(), field_type_qualifiers: schema_field.type_qualifiers(), flatten: false, + deprecation: schema_field.deprecation(), }); } TypeId::Scalar(scalar) => { @@ -243,6 +246,7 @@ fn calculate_selection<'a>( struct_id, rust_name, flatten: false, + deprecation: schema_field.deprecation(), }); } TypeId::Object(_) | TypeId::Interface(_) | TypeId::Union(_) => { @@ -255,6 +259,7 @@ fn calculate_selection<'a>( field_type_qualifiers: schema_field.type_qualifiers(), field_type: Cow::Owned(struct_name_string.clone()), flatten: false, + deprecation: schema_field.deprecation(), }); let type_id = context.push_type(ExpandedType { @@ -298,6 +303,7 @@ fn calculate_selection<'a>( rust_name: final_field_name, struct_id, flatten: true, + deprecation: None, }); // We stop here, because the structs for the fragments are generated separately, to @@ -317,10 +323,11 @@ struct ExpandedField<'a> { field_type_qualifiers: &'a [GraphqlTypeQualifier], struct_id: ResponseTypeId, flatten: bool, + deprecation: Option>, } impl<'a> ExpandedField<'a> { - fn render(&self) -> TokenStream { + fn render(&self, options: &GraphQLClientCodegenOptions) -> Option { let ident = Ident::new(&self.rust_name, Span::call_site()); let qualified_type = decorate_type( &Ident::new(&self.field_type, Span::call_site()), @@ -337,28 +344,25 @@ impl<'a> ExpandedField<'a> { None }; - // TODO: deprecation - // let deprecation_annotation = match ( - // field.schema_field().is_deprecated(), - // options.deprecation_strategy(), - // ) { - // (false, _) | (true, DeprecationStrategy::Allow) => None, - // (true, DeprecationStrategy::Warn) => { - // let msg = field - // .schema_field() - // .deprecation_message() - // .unwrap_or("This field is deprecated."); - - // Some(quote!(#[deprecated(note = #msg)])) - // } - // (true, DeprecationStrategy::Deny) => continue, - // }; - - quote! { + let optional_deprecation_annotation = + match (self.deprecation, options.deprecation_strategy()) { + (None, _) | (Some(_), DeprecationStrategy::Allow) => None, + (Some(msg), DeprecationStrategy::Warn) => { + let optional_msg = msg.map(|msg| quote!((note = #msg))); + + Some(quote!(#[deprecated#optional_msg])) + } + (Some(_), DeprecationStrategy::Deny) => return None, + }; + + let tokens = quote! { #optional_flatten #optional_rename + #optional_deprecation_annotation pub #ident: #qualified_type - } + }; + + Some(tokens) } } @@ -449,7 +453,7 @@ impl<'a> ExpandedSelection<'a> { .fields .iter() .filter(|field| field.struct_id == type_id) - .map(|field| field.render()) + .filter_map(|field| field.render(self.options)) .peekable(); let on_variants: Vec = self diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 1a32d0903..b6c32390f 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -601,6 +601,13 @@ impl<'a> FieldRef<'a> { .as_ref() .and_then(|item| item.as_ref().map(String::as_str)) } + + pub(crate) fn deprecation(&self) -> Option> { + self.field() + .deprecation + .as_ref() + .map(|o| o.as_ref().map(String::as_str)) + } } impl<'a> InputRef<'a> { From a2934cb76cff75248f30c72c779042297cff846d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 15:41:25 +0100 Subject: [PATCH 48/93] Generate structs for all used input types --- graphql_client_codegen/src/codegen.rs | 1 + graphql_client_codegen/src/resolution.rs | 13 +++++-- graphql_client_codegen/src/schema.rs | 37 ++++++++++++++++++- .../src/schema/graphql_parser_conversion.rs | 20 +++++++--- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 81eac345f..d683df4b1 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -27,6 +27,7 @@ pub(crate) fn response_for_query( generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); let input_object_definitions = generate_input_object_definitions(&operation, &all_used_types, options); + let variables_struct = generate_variables_struct(&operation, options); let definitions = render_response_data_fields(&operation, options).render(&response_derives); diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 6048d8fb0..3180529c6 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -826,9 +826,14 @@ impl<'a> VariableRef<'a> { fn collect_used_types(&self, used_types: &mut UsedTypes) { match self.0.focus.1.r#type.id { - type_id @ TypeId::Input(_) - | type_id @ TypeId::Scalar(_) - | type_id @ TypeId::Enum(_) => { + TypeId::Input(input_id) => { + used_types.types.insert(TypeId::Input(input_id)); + + let input = self.0.schema.input(input_id); + + input.used_input_ids_recursive(used_types) + } + type_id @ TypeId::Scalar(_) | type_id @ TypeId::Enum(_) => { used_types.types.insert(type_id); } _ => (), @@ -878,7 +883,7 @@ impl<'a> FragmentRef<'a> { #[derive(Debug, Default)] pub(crate) struct UsedTypes { - types: HashSet, + pub(crate) types: HashSet, fragments: HashSet, } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index b6c32390f..4c2a6e83c 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -2,6 +2,7 @@ mod graphql_parser_conversion; mod json_conversion; use crate::field_type::GraphqlTypeQualifier; +use crate::resolution::UsedTypes; use std::collections::HashMap; #[derive(Clone, Copy)] @@ -13,7 +14,6 @@ struct SchemaWith<'a, T> { #[derive(Clone, Copy)] pub(crate) struct TypeRef<'a>(SchemaWith<'a, TypeId>); -pub(crate) struct InputRef<'a>(SchemaWith<'a, InputId>); pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"]; @@ -217,6 +217,13 @@ impl TypeId { } } + pub(crate) fn as_input_id(&self) -> Option { + match self { + TypeId::Input(id) => Some(*id), + _ => None, + } + } + pub(crate) fn as_scalar_id(&self) -> Option { match self { TypeId::Scalar(id) => Some(*id), @@ -266,7 +273,7 @@ struct StoredEnum { #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredInputFieldType { id: TypeId, - pub(crate) qualifiers: Vec, + qualifiers: Vec, } impl StoredInputFieldType { @@ -610,6 +617,8 @@ impl<'a> FieldRef<'a> { } } +pub(crate) struct InputRef<'a>(SchemaWith<'a, InputId>); + impl<'a> InputRef<'a> { fn get(&self) -> &'a StoredInputType { self.0.schema.get_stored_input(self.0.focus) @@ -623,6 +632,30 @@ impl<'a> InputRef<'a> { &self.get().name } + pub(crate) fn used_input_ids_recursive<'b>(&'b self, used_types: &mut UsedTypes) { + for input_id in self + .fields() + .map(|(_, type_id)| type_id) + .filter_map(|type_id| type_id.as_input_id()) + { + let type_id = TypeId::Input(input_id); + if used_types.types.contains(&type_id) { + continue; + } else { + used_types.types.insert(type_id); + let input_ref = self.0.schema.input(input_id); + input_ref.used_input_ids_recursive(used_types); + } + } + } + + pub(crate) fn fields<'b>(&'b self) -> impl Iterator + 'b { + self.get() + .fields + .iter() + .map(|(name, f)| (name.as_str(), f.id)) + } + pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { todo!("contains type without indirection") // let input = self.get(); diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 6e4cafa01..0493d146e 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,4 @@ -use super::{InputId, Schema, TypeId}; +use super::{InputId, Schema, StoredInputFieldType, TypeId}; use crate::schema::resolve_field_type; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; @@ -244,12 +244,22 @@ fn find_deprecation(directives: &[parser::Directive]) -> Option> } fn ingest_input(schema: &mut Schema, input: &mut parser::InputObjectType) { - let input_id = InputId::new(schema.stored_inputs.len()); - - // TODO: input object fields let input = super::StoredInputType { name: std::mem::replace(&mut input.name, String::new()), - fields: Vec::new(), + fields: input + .fields + .iter_mut() + .map(|val| { + let field_type = super::resolve_field_type(schema, &val.value_type); + ( + std::mem::replace(&mut val.name, String::new()), + StoredInputFieldType { + qualifiers: field_type.qualifiers, + id: field_type.id, + }, + ) + }) + .collect(), }; schema.stored_inputs.push(input); From 57dd097945a9f55b233d9b778129ed4f36e6e540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 14 Mar 2020 15:49:34 +0100 Subject: [PATCH 49/93] Minor cleanup --- graphql_client_codegen/src/codegen.rs | 76 +------------------- graphql_client_codegen/src/codegen/inputs.rs | 18 +++++ 2 files changed, 21 insertions(+), 73 deletions(-) create mode 100644 graphql_client_codegen/src/codegen/inputs.rs diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index d683df4b1..2407c9051 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,11 +1,9 @@ mod enums; +mod inputs; mod selection; use crate::{ - field_type::GraphqlTypeQualifier, - normalization::Normalization, - resolution::*, - shared::{field_rename_annotation, keyword_replace}, + field_type::GraphqlTypeQualifier, resolution::*, shared::keyword_replace, GraphQLClientCodegenOptions, }; use heck::SnakeCase; @@ -26,7 +24,7 @@ pub(crate) fn response_for_query( let fragment_definitions = generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); let input_object_definitions = - generate_input_object_definitions(&operation, &all_used_types, options); + inputs::generate_input_object_definitions(&operation, &all_used_types, options); let variables_struct = generate_variables_struct(&operation, options); @@ -57,8 +55,6 @@ pub(crate) fn response_for_query( #definitions }; - // panic!("{}", q); - Ok(q) } @@ -175,20 +171,6 @@ fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStr qualified } -fn generate_input_object_definitions( - operation: &OperationRef<'_>, - all_used_types: &UsedTypes, - options: &GraphQLClientCodegenOptions, -) -> Vec { - all_used_types - .inputs(operation.schema()) - .map(|input| { - let struct_name = Ident::new(input.name(), Span::call_site()); - quote!(pub struct #struct_name;) - }) - .collect() -} - fn generate_fragment_definitions( operation: &OperationRef<'_>, all_used_types: &UsedTypes, @@ -208,55 +190,3 @@ fn generate_fragment_definitions( fragment_definitions } - -/// Render a struct for a selection on an object or interface. -fn render_object_like_struct( - response_derives: &impl quote::ToTokens, - struct_name: &str, - fields: &[TokenStream], - variants: &[TokenStream], -) -> TokenStream { - let (on_field, on_enum) = if variants.len() > 0 { - let enum_name_str = format!("{}On", struct_name); - let enum_name = Ident::new(&enum_name_str, Span::call_site()); - - ( - Some(quote!(#[serde(flatten)] pub on: #enum_name,)), - Some(render_union_enum( - response_derives, - &enum_name_str, - variants, - )), - ) - } else { - (None, None) - }; - - let struct_ident = Ident::new(struct_name, Span::call_site()); - - quote! { - #response_derives - pub struct #struct_ident { - #(#fields,)* - #on_field - } - - #on_enum - } -} - -fn render_union_enum( - response_derives: &impl quote::ToTokens, - enum_name: &str, - variants: &[TokenStream], -) -> TokenStream { - let enum_ident = Ident::new(enum_name, Span::call_site()); - - quote! { - #response_derives - #[serde(tag = "__typename")] - pub enum #enum_ident { - #(#variants,)* - } - } -} diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs new file mode 100644 index 000000000..a865a39e4 --- /dev/null +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -0,0 +1,18 @@ +use crate::codegen_options::GraphQLClientCodegenOptions; +use crate::resolution::{OperationRef, UsedTypes}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +pub(super) fn generate_input_object_definitions( + operation: &OperationRef<'_>, + all_used_types: &UsedTypes, + _options: &GraphQLClientCodegenOptions, +) -> Vec { + all_used_types + .inputs(operation.schema()) + .map(|input| { + let struct_name = Ident::new(input.name(), Span::call_site()); + quote!(pub struct #struct_name;) + }) + .collect() +} From efd7dfa4d5892923a6d0f31efd58b1b18a66a779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 08:56:15 +0100 Subject: [PATCH 50/93] Start work on rendering input field types --- graphql_client_codegen/src/codegen/inputs.rs | 13 +++++++++++- graphql_client_codegen/src/schema.rs | 21 +++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index a865a39e4..00676640d 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -12,7 +12,18 @@ pub(super) fn generate_input_object_definitions( .inputs(operation.schema()) .map(|input| { let struct_name = Ident::new(input.name(), Span::call_site()); - quote!(pub struct #struct_name;) + + let fields = input.fields().map(|field| { + let name_ident = Ident::new(field.name(), Span::call_site()); + quote!(pub #name_ident: String) + }); + + quote! { + #[derive(Serialize)] + pub struct #struct_name { + #(#fields,)* + } + } }) .collect() } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 4c2a6e83c..1bfbec9e5 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -635,7 +635,7 @@ impl<'a> InputRef<'a> { pub(crate) fn used_input_ids_recursive<'b>(&'b self, used_types: &mut UsedTypes) { for input_id in self .fields() - .map(|(_, type_id)| type_id) + .map(|field| field.field_type_id()) .filter_map(|type_id| type_id.as_input_id()) { let type_id = TypeId::Input(input_id); @@ -649,11 +649,14 @@ impl<'a> InputRef<'a> { } } - pub(crate) fn fields<'b>(&'b self) -> impl Iterator + 'b { + pub(crate) fn fields<'b>(&'b self) -> impl Iterator> + 'b { self.get() .fields .iter() - .map(|(name, f)| (name.as_str(), f.id)) + .map(move |field| StoredInputFieldRef(SchemaWith { + schema: self.0.schema, + focus: field + })) } pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { @@ -686,6 +689,18 @@ impl<'a> InputRef<'a> { } } +pub(crate) struct StoredInputFieldRef<'a>(SchemaWith<'a, &'a (String, StoredInputFieldType)>); + +impl<'a> StoredInputFieldRef<'a> { + fn field_type_id(&self) -> TypeId { + self.0.focus.1.id + } + + pub(crate) fn name(&self) -> &'a str { + self.0.focus.0.as_str() + } +} + impl std::convert::From for Schema { fn from(ast: graphql_parser::schema::Document) -> Schema { graphql_parser_conversion::build_schema(ast) From 32c89a3a8f9ad8e39a0a3ef7a14f93635e0b8f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 09:13:04 +0100 Subject: [PATCH 51/93] Reimplement input field boxing --- graphql_client_codegen/src/codegen/inputs.rs | 9 ++- graphql_client_codegen/src/schema.rs | 65 +++++++++++++------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 00676640d..36236d198 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -15,7 +15,14 @@ pub(super) fn generate_input_object_definitions( let fields = input.fields().map(|field| { let name_ident = Ident::new(field.name(), Span::call_site()); - quote!(pub #name_ident: String) + let type_name = Ident::new(field.field_type_name(), Span::call_site()); + let field_type = super::decorate_type(&type_name, field.field_type_qualifiers()); + let field_type = if input.is_recursive_without_indirection() { + quote!(Box<#field_type>) + } else { + field_type + }; + quote!(pub #name_ident: #field_type) }); quote! { diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 1bfbec9e5..0528b01fe 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -659,33 +659,37 @@ impl<'a> InputRef<'a> { })) } - pub(crate) fn contains_type_without_indirection(&self, type_name: &str) -> bool { - todo!("contains type without indirection") - // let input = self.get(); + pub(crate) fn is_recursive_without_indirection(&self) -> bool { + self.contains_type_without_indirection(self.name()) + } + + fn contains_type_without_indirection(&self, type_name: &str) -> bool { + let input = self.get(); - // // the input type is recursive if any of its members contains it, without indirection - // input.fields.iter().any(|(name, r#type)| { - // // the field is indirected, so no boxing is needed - // if r#type.is_indirected() { - // return false; - // } + // The input type is recursive if any of its members contains it, without indirection + self.fields().any(|field| { + // the field is indirected, so no boxing is needed + if field.is_indirected() { + return false; + } - // let field_type_name = field.type_.inner_name_str(); - // let input = self.schema.inputs.get(field_type_name); + let input_id = field.field_type_id().as_input_id(); - // if let Some(input) = input { - // // the input contains itself, not indirected - // if input.name == type_name { - // return true; - // } + if let Some(input_id) = input_id { + // the input contains itself, not indirected + if input_id == self.0.focus { + return true + } - // // we check if the other input contains this one (without indirection) - // input.contains_type_without_indirection(context, type_name) - // } else { - // // the field is not referring to an input type - // false - // } - // }) + let input = self.0.schema.input(input_id); + + // we check if the other input contains this one (without indirection) + input.contains_type_without_indirection(type_name) + } else { + // the field is not referring to an input type + false + } + }) } } @@ -696,6 +700,21 @@ impl<'a> StoredInputFieldRef<'a> { self.0.focus.1.id } + pub(crate) fn field_type_qualifiers(&self) -> &'a [GraphqlTypeQualifier] { + &self.0.focus.1.qualifiers + } + + pub(crate) fn field_type_name(&self) -> &'a str { + TypeRef(SchemaWith { + schema: self.0.schema, + focus: self.field_type_id(), + }).name() + } + + fn is_indirected(&self) -> bool { + self.0.focus.1.is_indirected() + } + pub(crate) fn name(&self) -> &'a str { self.0.focus.0.as_str() } From 3522fc5feee204e7c2b9b889bab9180c7fe530e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 09:28:10 +0100 Subject: [PATCH 52/93] Finish the reimplementation of input fields rendering --- graphql_client_codegen/src/codegen/inputs.rs | 2 +- graphql_client_codegen/src/schema.rs | 36 +++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 36236d198..8ee27975b 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -17,7 +17,7 @@ pub(super) fn generate_input_object_definitions( let name_ident = Ident::new(field.name(), Span::call_site()); let type_name = Ident::new(field.field_type_name(), Span::call_site()); let field_type = super::decorate_type(&type_name, field.field_type_qualifiers()); - let field_type = if input.is_recursive_without_indirection() { + let field_type = if field.field_type_as_input().map(|input| input.is_recursive_without_indirection()).unwrap_or(false) { quote!(Box<#field_type>) } else { field_type diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 0528b01fe..a77f0f39c 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -633,12 +633,12 @@ impl<'a> InputRef<'a> { } pub(crate) fn used_input_ids_recursive<'b>(&'b self, used_types: &mut UsedTypes) { - for input_id in self + for type_id in self .fields() .map(|field| field.field_type_id()) - .filter_map(|type_id| type_id.as_input_id()) { - let type_id = TypeId::Input(input_id); + match type_id { + TypeId::Input(input_id) => { if used_types.types.contains(&type_id) { continue; } else { @@ -646,6 +646,12 @@ impl<'a> InputRef<'a> { let input_ref = self.0.schema.input(input_id); input_ref.used_input_ids_recursive(used_types); } + } + TypeId::Enum(_) => { + used_types.types.insert(type_id); + } + _ => (), + } } } @@ -660,12 +666,10 @@ impl<'a> InputRef<'a> { } pub(crate) fn is_recursive_without_indirection(&self) -> bool { - self.contains_type_without_indirection(self.name()) + self.contains_type_without_indirection(self.0.focus) } - fn contains_type_without_indirection(&self, type_name: &str) -> bool { - let input = self.get(); - + fn contains_type_without_indirection(&self, input_id: InputId) -> bool { // The input type is recursive if any of its members contains it, without indirection self.fields().any(|field| { // the field is indirected, so no boxing is needed @@ -673,18 +677,17 @@ impl<'a> InputRef<'a> { return false; } - let input_id = field.field_type_id().as_input_id(); + let field_input_id = field.field_type_id().as_input_id(); - if let Some(input_id) = input_id { - // the input contains itself, not indirected - if input_id == self.0.focus { + if let Some(field_input_id) = field_input_id { + if field_input_id == input_id { return true } - let input = self.0.schema.input(input_id); + let input = self.0.schema.input(field_input_id); // we check if the other input contains this one (without indirection) - input.contains_type_without_indirection(type_name) + input.contains_type_without_indirection(input_id) } else { // the field is not referring to an input type false @@ -711,6 +714,13 @@ impl<'a> StoredInputFieldRef<'a> { }).name() } + /// This is used for recursion checking. + pub(crate) fn field_type_as_input(&self) -> Option> { + self.field_type_id().as_input_id().map(|input_id| { + InputRef(self.0.schema.with(input_id)) + }) + } + fn is_indirected(&self) -> bool { self.0.focus.1.is_indirected() } From 8167564bd4f9df6ef41fd6a9ca31fc5320f80f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 09:31:42 +0100 Subject: [PATCH 53/93] Generate custom scalars in queries --- graphql_client_codegen/src/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index a77f0f39c..20cfcab49 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -647,7 +647,7 @@ impl<'a> InputRef<'a> { input_ref.used_input_ids_recursive(used_types); } } - TypeId::Enum(_) => { + TypeId::Enum(_) | TypeId::Scalar(_) => { used_types.types.insert(type_id); } _ => (), From 3ea2f01d0512a47baf2d3012d0ca1abead1a0168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 09:43:48 +0100 Subject: [PATCH 54/93] Avoid infinite recursion on fragment resolution --- graphql_client_codegen/src/resolution.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 3180529c6..854197eee 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -157,6 +157,11 @@ impl<'a> SelectionRef<'a> { } } Selection::FragmentSpread(fragment_id) => { + // This is necessary to avoid infinite recursion. + if used_types.fragments.contains(fragment_id) { + return + } + used_types.fragments.insert(*fragment_id); let fragment_ref = self.0.query.get_fragment_ref(self.0.schema, *fragment_id); From 6f0eece6abd41e6b9979f1075fcab6f064d3442f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 09:49:36 +0100 Subject: [PATCH 55/93] Delete commented-out code --- graphql_client_codegen/src/resolution.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 854197eee..6e4723c1f 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -287,20 +287,6 @@ impl SelectedField { } } -// impl<'a> WithQuery<'a, &'a SelectedField> { -// pub(crate) fn alias(&self) -> Option<&str> { -// self.item.alias.as_ref().map(String::as_str) -// } - -// pub(crate) fn name(&self) -> &'a str { -// self.schema.field(self.item.field_id).name() -// } - -// pub(crate) fn schema_field(&self) -> WithSchema<'a, StoredFieldId> { -// self.with_schema(self.item.field_id) -// } -// } - pub(crate) fn resolve( schema: &Schema, query: &graphql_parser::query::Document, From 386b05fb98a158f6229f084b0f46af8cbdc44199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 15 Mar 2020 22:26:43 +0100 Subject: [PATCH 56/93] Add back recursive fragment handling --- .../tests/type_refining_fragments.rs | 2 +- graphql_client_codegen/src/codegen/inputs.rs | 6 ++- .../src/codegen/selection.rs | 12 +++++ graphql_client_codegen/src/resolution.rs | 49 ++++++++++++------- graphql_client_codegen/src/schema.rs | 41 +++++++--------- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/graphql_client/tests/type_refining_fragments.rs b/graphql_client/tests/type_refining_fragments.rs index a021193bd..b27462024 100644 --- a/graphql_client/tests/type_refining_fragments.rs +++ b/graphql_client/tests/type_refining_fragments.rs @@ -63,7 +63,7 @@ fn type_refining_fragment_on_interface() { name: "Audrey Lorde".to_string(), on: QueryOnInterfaceEverythingOn::Person(QueryOnInterfaceEverythingOnPerson { birthday_fragment: BirthdayFragment { - birthday: Some("1934-02-18".to_string()) + birthday: Some("1934-02-18".to_string()), }, }), }, diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 8ee27975b..32b6c2f18 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -17,7 +17,11 @@ pub(super) fn generate_input_object_definitions( let name_ident = Ident::new(field.name(), Span::call_site()); let type_name = Ident::new(field.field_type_name(), Span::call_site()); let field_type = super::decorate_type(&type_name, field.field_type_qualifiers()); - let field_type = if field.field_type_as_input().map(|input| input.is_recursive_without_indirection()).unwrap_or(false) { + let field_type = if field + .field_type_as_input() + .map(|input| input.is_recursive_without_indirection()) + .unwrap_or(false) + { quote!(Box<#field_type>) } else { field_type diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 20c575659..28405ad39 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -200,6 +200,7 @@ fn calculate_selection<'a>( rust_name: fragment_ref.name().to_snake_case().into(), struct_id, deprecation: None, + boxed: fragment_ref.is_recursive(), }) } } @@ -234,6 +235,7 @@ fn calculate_selection<'a>( field_type_qualifiers: schema_field.type_qualifiers(), flatten: false, deprecation: schema_field.deprecation(), + boxed: false, }); } TypeId::Scalar(scalar) => { @@ -247,6 +249,7 @@ fn calculate_selection<'a>( rust_name, flatten: false, deprecation: schema_field.deprecation(), + boxed: false, }); } TypeId::Object(_) | TypeId::Interface(_) | TypeId::Union(_) => { @@ -259,6 +262,7 @@ fn calculate_selection<'a>( field_type_qualifiers: schema_field.type_qualifiers(), field_type: Cow::Owned(struct_name_string.clone()), flatten: false, + boxed: false, deprecation: schema_field.deprecation(), }); @@ -304,6 +308,7 @@ fn calculate_selection<'a>( struct_id, flatten: true, deprecation: None, + boxed: fragment.is_recursive(), }); // We stop here, because the structs for the fragments are generated separately, to @@ -324,6 +329,7 @@ struct ExpandedField<'a> { struct_id: ResponseTypeId, flatten: bool, deprecation: Option>, + boxed: bool, } impl<'a> ExpandedField<'a> { @@ -334,6 +340,12 @@ impl<'a> ExpandedField<'a> { self.field_type_qualifiers, ); + let qualified_type = if self.boxed { + quote!(Box<#qualified_type>) + } else { + qualified_type + }; + let optional_rename = self .graphql_name .as_ref() diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 6e4723c1f..6161d20c2 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -109,6 +109,14 @@ impl SelectionParent { pub(crate) struct SelectionRef<'a>(QueryWith<'a, (SelectionId, &'a Selection)>); impl<'a> SelectionRef<'a> { + pub(crate) fn contains_fragment(&self, fragment_id: ResolvedFragmentId) -> bool { + match self.selection() { + Selection::FragmentSpread(id) => *id == fragment_id, + _ => self + .subselection() + .any(|selection| selection.contains_fragment(fragment_id)), + } + } pub(crate) fn query(&self) -> &'a ResolvedQuery { self.0.query } @@ -159,7 +167,7 @@ impl<'a> SelectionRef<'a> { Selection::FragmentSpread(fragment_id) => { // This is necessary to avoid infinite recursion. if used_types.fragments.contains(fragment_id) { - return + return; } used_types.fragments.insert(*fragment_id); @@ -833,14 +841,33 @@ impl<'a> VariableRef<'a> { } impl<'a> FragmentRef<'a> { - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema + pub(crate) fn is_recursive(&self) -> bool { + let id = self.0.focus.0; + + self.selection_set() + .any(|selection| selection.contains_fragment(id)) } pub(crate) fn query(&self) -> &'a ResolvedQuery { self.0.query } + pub(crate) fn name(&self) -> &'a str { + &self.0.focus.1.name + } + + pub(crate) fn on(&self) -> TypeId { + self.0.focus.1.on + } + + pub(crate) fn on_ref(&self) -> TypeRef<'a> { + self.0.schema.type_ref(self.0.focus.1.on) + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + pub(crate) fn selection_ids(&self) -> &[SelectionId] { &self.0.focus.1.selection } @@ -851,24 +878,12 @@ impl<'a> FragmentRef<'a> { .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) } - fn to_path_segment(&self) -> String { - self.0.focus.1.name.to_camel_case() - } - - pub(crate) fn name(&self) -> &'a str { - &self.0.focus.1.name - } - pub(crate) fn selection_set_len(&self) -> usize { self.0.focus.1.selection.len() } - pub(crate) fn on(&self) -> TypeId { - self.0.focus.1.on - } - - pub(crate) fn on_ref(&self) -> TypeRef<'a> { - self.0.schema.type_ref(self.0.focus.1.on) + fn to_path_segment(&self) -> String { + self.0.focus.1.name.to_camel_case() } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 20cfcab49..fb97ba967 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -633,19 +633,16 @@ impl<'a> InputRef<'a> { } pub(crate) fn used_input_ids_recursive<'b>(&'b self, used_types: &mut UsedTypes) { - for type_id in self - .fields() - .map(|field| field.field_type_id()) - { + for type_id in self.fields().map(|field| field.field_type_id()) { match type_id { TypeId::Input(input_id) => { - if used_types.types.contains(&type_id) { - continue; - } else { - used_types.types.insert(type_id); - let input_ref = self.0.schema.input(input_id); - input_ref.used_input_ids_recursive(used_types); - } + if used_types.types.contains(&type_id) { + continue; + } else { + used_types.types.insert(type_id); + let input_ref = self.0.schema.input(input_id); + input_ref.used_input_ids_recursive(used_types); + } } TypeId::Enum(_) | TypeId::Scalar(_) => { used_types.types.insert(type_id); @@ -656,13 +653,12 @@ impl<'a> InputRef<'a> { } pub(crate) fn fields<'b>(&'b self) -> impl Iterator> + 'b { - self.get() - .fields - .iter() - .map(move |field| StoredInputFieldRef(SchemaWith { + self.get().fields.iter().map(move |field| { + StoredInputFieldRef(SchemaWith { schema: self.0.schema, - focus: field - })) + focus: field, + }) + }) } pub(crate) fn is_recursive_without_indirection(&self) -> bool { @@ -681,7 +677,7 @@ impl<'a> InputRef<'a> { if let Some(field_input_id) = field_input_id { if field_input_id == input_id { - return true + return true; } let input = self.0.schema.input(field_input_id); @@ -711,14 +707,15 @@ impl<'a> StoredInputFieldRef<'a> { TypeRef(SchemaWith { schema: self.0.schema, focus: self.field_type_id(), - }).name() + }) + .name() } /// This is used for recursion checking. pub(crate) fn field_type_as_input(&self) -> Option> { - self.field_type_id().as_input_id().map(|input_id| { - InputRef(self.0.schema.with(input_id)) - }) + self.field_type_id() + .as_input_id() + .map(|input_id| InputRef(self.0.schema.with(input_id))) } fn is_indirected(&self) -> bool { From e91f6e63ed14f6f4977e97a0615d2c795b2d501c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 21:24:12 +0100 Subject: [PATCH 57/93] Make more tests pass --- CHANGELOG.md | 5 +- graphql_client/tests/json_schema/query.rs | 1 + graphql_client/tests/union_query.rs | 2 +- graphql_client_codegen/src/codegen.rs | 104 +++++++++++++++++- graphql_client_codegen/src/field_type.rs | 6 + graphql_client_codegen/src/resolution.rs | 23 +++- graphql_client_codegen/src/schema.rs | 35 +++++- .../src/schema/json_conversion.rs | 4 + graphql_client_codegen/src/schema/schema.rs | 0 graphql_client_codegen/src/variables.rs | 84 -------------- 10 files changed, 166 insertions(+), 98 deletions(-) create mode 100644 graphql_client/tests/json_schema/query.rs delete mode 100644 graphql_client_codegen/src/schema/schema.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 371fee526..18a5d0c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -258,7 +258,10 @@ There are a number of breaking changes due to the new features, read the `Added` ### Added -- Copy documentation from the GraphQL schema to the generated types (including their fields) as normal Rust documentation. Documentation will show up in the generated docs as well as IDEs that support expanding derive macros (which does not include the RLS yet). +- Copy documentation from the GraphQL schema to the generated types (including + their fields) as normal Rust documentation. Documentation will show up in the + generated docs as well as IDEs that support expanding derive macros (which + does not include the RLS yet). - Implement and test deserializing subscription responses. We also try to provide helpful error messages when a subscription query is not valid (i.e. when it has more than one top-level field). - Support the [new top-level errors shape from the June 2018 spec](https://github.com/facebook/graphql/blob/master/spec/Section%207%20--%20Response.md), except for the `extensions` field (see issue #64). diff --git a/graphql_client/tests/json_schema/query.rs b/graphql_client/tests/json_schema/query.rs new file mode 100644 index 000000000..ab98aad6e --- /dev/null +++ b/graphql_client/tests/json_schema/query.rs @@ -0,0 +1 @@ +pub struct WithSchema1 ; pub mod with_schema1 { # ! [ allow ( dead_code ) ] pub const OPERATION_NAME : & 'static str = "WithSchema1" ; pub const QUERY : & 'static str = "query WithSchema1 {\n currentSession {\n accountId\n }\n}\n" ; use serde :: { Serialize , Deserialize } ; # [ allow ( dead_code ) ] type Boolean = bool ; # [ allow ( dead_code ) ] type Float = f64 ; # [ allow ( dead_code ) ] type Int = i64 ; # [ allow ( dead_code ) ] type ID = String ; type Uuid = super :: Uuid ; # [ derive ( Serialize ) ] pub struct Variables ; # [ derive ( Deserialize ) ] pub struct ResponseData { # [ serde ( rename = "currentSession" ) ] # [ deprecated ] pub current_session : Option < WithSchema1CurrentSession > , } # [ derive ( Deserialize ) ] pub struct WithSchema1CurrentSession { # [ serde ( rename = "accountId" ) ] # [ deprecated ] pub account_id : Uuid , } } impl graphql_client :: GraphQLQuery for WithSchema1 { type Variables = with_schema1 :: Variables ; type ResponseData = with_schema1 :: ResponseData ; fn build_query ( variables : Self :: Variables ) -> :: graphql_client :: QueryBody < Self :: Variables > { graphql_client :: QueryBody { variables , query : with_schema1 :: QUERY , operation_name : with_schema1 :: OPERATION_NAME , } } } \ No newline at end of file diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index b1e179f4f..6cf81f752 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -102,7 +102,7 @@ fn fragment_on_union() { #[test] fn fragment_and_more_on_union() { use fragment_and_more_on_union::*; - todo!(); + todo!("fragment_and_more_on_union"); let _expected = fragment_and_more_on_union::ResponseData { names: Some(vec![ FragmentAndMoreOnUnionNames { diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 2407c9051..856831514 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -3,7 +3,9 @@ mod inputs; mod selection; use crate::{ - field_type::GraphqlTypeQualifier, resolution::*, shared::keyword_replace, + field_type::GraphqlTypeQualifier, + resolution::*, + schema::{InputRef, TypeRef}, GraphQLClientCodegenOptions, }; use heck::SnakeCase; @@ -78,11 +80,23 @@ fn generate_variables_struct( let method_name = Ident::new(&method_name, Span::call_site()); let method_return_type = render_variable_field_type(variable); - quote!( - pub fn #method_name() -> #method_return_type { - todo!() - } - ) + variable.default().map(|default| { + let value = graphql_parser_value_to_literal( + default, + variable.variable_type(), + variable + .type_qualifiers() + .get(0) + .map(|qual| !qual.is_required()) + .unwrap_or(true), + ); + + quote!( + pub fn #method_name() -> #method_return_type { + #value + } + ) + }) }); let variables_struct = quote!( @@ -190,3 +204,81 @@ fn generate_fragment_definitions( fragment_definitions } + +/// For default value constructors. +fn graphql_parser_value_to_literal( + value: &graphql_parser::query::Value, + ty: TypeRef<'_>, + is_optional: bool, +) -> TokenStream { + use graphql_parser::query::Value; + + let inner = match value { + Value::Boolean(b) => { + if *b { + quote!(true) + } else { + quote!(false) + } + } + Value::String(s) => quote!(#s.to_string()), + Value::Variable(_) => panic!("variable in variable"), + Value::Null => panic!("null as default value"), + Value::Float(f) => quote!(#f), + Value::Int(i) => { + let i = i.as_i64(); + quote!(#i) + } + Value::Enum(en) => quote!(#en), + Value::List(inner) => { + let elements = inner + .iter() + .map(|val| graphql_parser_value_to_literal(val, ty, false)); + quote! { + vec![ + #(#elements,)* + ] + } + } + Value::Object(obj) => { + render_object_literal(obj, ty.as_input_ref().expect("TODO: error handling")) + } + }; + + if is_optional { + quote!(Some(#inner)) + } else { + inner + } +} + +/// For default value constructors. +fn render_object_literal( + object: &std::collections::BTreeMap, + ty: InputRef<'_>, +) -> TokenStream { + let type_name = ty.name(); + let constructor = Ident::new(&type_name, Span::call_site()); + let fields: Vec = ty + .fields() + .map(|field| { + let field_name = Ident::new(field.name(), Span::call_site()); + let provided_value = object.get(field.name()); + match provided_value { + Some(default_value) => { + let value = graphql_parser_value_to_literal( + default_value, + field.field_type(), + field.is_optional(), + ); + quote!(#field_name: #value) + } + None => quote!(#field_name: None), + } + }) + .collect(); + + quote!(#constructor { + #(#fields,)* + }) +} diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index 6b08d68c7..b5429c368 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -13,6 +13,12 @@ pub(crate) enum GraphqlTypeQualifier { List, } +impl GraphqlTypeQualifier { + pub(crate) fn is_required(&self) -> bool { + *self == GraphqlTypeQualifier::Required + } +} + #[derive(Clone, Debug, PartialEq, Hash)] pub struct FieldType<'a> { /// The type name of the field. diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 6161d20c2..6c0079125 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -2,15 +2,13 @@ //! against a given schema. use crate::schema::InputRef; -use crate::schema::ObjectRef; -use crate::schema::ScalarId; use crate::schema::ScalarRef; use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, schema::{ - resolve_field_type, EnumRef, InputId, ObjectId, Schema, StoredFieldId, StoredFieldType, - TypeId, TypeRef, UnionRef, + resolve_field_type, EnumRef, ObjectId, Schema, StoredFieldId, StoredFieldType, TypeId, + TypeRef, UnionRef, }, }; @@ -18,6 +16,7 @@ use heck::CamelCase; use std::collections::{HashMap, HashSet}; /// This is a convenience struct that should stay private, it's an implementation detail for our `Ref` types. +#[derive(Copy, Clone)] struct QueryWith<'a, T> { query: &'a ResolvedQuery, schema: &'a Schema, @@ -35,7 +34,6 @@ impl<'a, T> QueryWith<'a, T> { } pub(crate) struct OperationRef<'a>(QueryWith<'a, OperationId>); -pub(crate) struct VariableRef<'a>(QueryWith<'a, (VariableId, &'a ResolvedVariable)>); pub(crate) struct InlineFragmentRef<'a>(QueryWith<'a, &'a InlineFragment>); pub(crate) struct FragmentRef<'a>(QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>); @@ -810,11 +808,26 @@ struct ResolvedVariable { r#type: StoredFieldType, } +#[derive(Copy, Clone)] +pub(crate) struct VariableRef<'a>(QueryWith<'a, (VariableId, &'a ResolvedVariable)>); + impl<'a> VariableRef<'a> { + pub(crate) fn default(&self) -> Option<&graphql_parser::query::Value> { + self.0.focus.1.default.as_ref() + } + pub(crate) fn name(&self) -> &'a str { &self.0.focus.1.name } + pub(crate) fn is_optional(&self) -> bool { + self.0.focus.1.r#type.is_optional() + } + + pub(crate) fn variable_type(&self) -> TypeRef<'a> { + self.0.schema.type_ref(self.0.focus.1.r#type.id) + } + pub(crate) fn type_name(&self) -> &'a str { self.0.schema.type_ref(self.0.focus.1.r#type.id).name() } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index fb97ba967..94bb07316 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -92,6 +92,15 @@ pub(crate) struct StoredFieldType { pub(crate) qualifiers: Vec, } +impl StoredFieldType { + pub(crate) fn is_optional(&self) -> bool { + self.qualifiers + .get(0) + .map(|qualifier| !qualifier.is_required()) + .unwrap_or(true) + } +} + #[derive(Debug, Clone, PartialEq)] struct StoredUnion { name: String, @@ -128,6 +137,13 @@ impl<'a> TypeRef<'a> { TypeId::Input(s) => self.0.schema.input(s).name(), } } + + pub(crate) fn as_input_ref(&self) -> Option> { + match self.type_id() { + TypeId::Input(input_id) => Some(self.0.schema.input(input_id)), + _ => None, + } + } } pub(crate) struct ScalarRef<'a>(SchemaWith<'a, ScalarId>); @@ -285,6 +301,13 @@ impl StoredInputFieldType { .iter() .any(|qualifier| qualifier == &GraphqlTypeQualifier::List) } + + pub(crate) fn is_optional(&self) -> bool { + self.qualifiers + .get(0) + .map(|qualifier| !qualifier.is_required()) + .unwrap_or(true) + } } #[derive(Debug, Clone, PartialEq)] @@ -711,6 +734,17 @@ impl<'a> StoredInputFieldRef<'a> { .name() } + pub(crate) fn field_type(&self) -> TypeRef<'a> { + TypeRef(SchemaWith { + schema: self.0.schema, + focus: self.field_type_id(), + }) + } + + pub(crate) fn is_optional(&self) -> bool { + self.0.focus.1.is_optional() + } + /// This is used for recursion checking. pub(crate) fn field_type_as_input(&self) -> Option> { self.field_type_id() @@ -748,7 +782,6 @@ pub(crate) fn resolve_field_type( inner: &graphql_parser::schema::Type, ) -> StoredFieldType { use crate::field_type::graphql_parser_depth; - use graphql_parser::schema::Type::*; let qualifiers_depth = graphql_parser_depth(inner); diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index c3f786b82..66111b75f 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -70,6 +70,10 @@ impl JsonSchemaConverter { ingest_interface(&mut schema, interface); } + for object in objects_mut(&mut src) { + ingest_object(&mut schema, object); + } + // for ty in src // .types // .as_ref() diff --git a/graphql_client_codegen/src/schema/schema.rs b/graphql_client_codegen/src/schema/schema.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/graphql_client_codegen/src/variables.rs b/graphql_client_codegen/src/variables.rs index 4c56c2b66..af344f35a 100644 --- a/graphql_client_codegen/src/variables.rs +++ b/graphql_client_codegen/src/variables.rs @@ -53,87 +53,3 @@ impl<'query> std::convert::From<&'query graphql_parser::query::VariableDefinitio } } } - -fn graphql_parser_value_to_literal( - value: &graphql_parser::query::Value, - context: &QueryContext<'_>, - ty: &FieldType<'_>, - is_optional: bool, -) -> TokenStream { - use graphql_parser::query::Value; - - let inner = match value { - Value::Boolean(b) => { - if *b { - quote!(true) - } else { - quote!(false) - } - } - Value::String(s) => quote!(#s.to_string()), - Value::Variable(_) => panic!("variable in variable"), - Value::Null => panic!("null as default value"), - Value::Float(f) => quote!(#f), - Value::Int(i) => { - let i = i.as_i64(); - quote!(#i) - } - Value::Enum(en) => quote!(#en), - Value::List(inner) => { - let elements = inner - .iter() - .map(|val| graphql_parser_value_to_literal(val, context, ty, false)); - quote! { - vec![ - #(#elements,)* - ] - } - } - Value::Object(obj) => render_object_literal(obj, ty, context), - }; - - if is_optional { - quote!(Some(#inner)) - } else { - inner - } -} - -fn render_object_literal( - object: &BTreeMap, - ty: &FieldType<'_>, - context: &QueryContext<'_>, -) -> TokenStream { - unimplemented!() - // let type_name = ty.inner_name_str(); - // let constructor = Ident::new(&type_name, Span::call_site()); - // let schema_type = context - // .schema - // .inputs - // .get(type_name) - // .expect("unknown input type"); - // let fields: Vec = schema_type - // .fields - // .iter() - // .map(|(name, field)| { - // let field_name = Ident::new(&name, Span::call_site()); - // let provided_value = object.get(name.to_owned()); - // match provided_value { - // Some(default_value) => { - // let value = graphql_parser_value_to_literal( - // default_value, - // context, - // &field.type_, - // field.type_.is_optional(), - // ); - // quote!(#field_name: #value) - // } - // None => quote!(#field_name: None), - // } - // }) - // .collect(); - - // quote!(#constructor { - // #(#fields,)* - // }) -} From 0bee2b035c028294cd782501b2be6ed787970481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 21:30:07 +0100 Subject: [PATCH 58/93] Fix test expectation --- graphql_client/tests/input_object_variables.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/graphql_client/tests/input_object_variables.rs b/graphql_client/tests/input_object_variables.rs index 573ca5247..6d8e58e3e 100644 --- a/graphql_client/tests/input_object_variables.rs +++ b/graphql_client/tests/input_object_variables.rs @@ -39,12 +39,13 @@ fn input_object_variables_default() { msg: default_input_object_variables_query::Variables::default_msg(), }; - let out = serde_json::to_string(&variables).unwrap(); + let out = serde_json::to_value(&variables).unwrap(); - assert_eq!( - out, - r#"{"msg":{"content":null,"to":{"category":null,"email":"rosa.luxemburg@example.com","name":null}}}"#, - ); + let expected_default = serde_json::json!({ + "msg":{"content":null,"to":{"category":null,"email":"rosa.luxemburg@example.com","name":null}} + }); + + assert_eq!(out, expected_default); } #[derive(GraphQLQuery)] From 4b6acedfb06fb1865445997daab93ec6151c0997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 21:37:32 +0100 Subject: [PATCH 59/93] Fix last graphql_client e2e tests --- graphql_client/tests/union_query.rs | 12 +++++++--- .../unions/fragment_and_more_response.json | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 graphql_client/tests/unions/fragment_and_more_response.json diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 6cf81f752..713620f92 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -1,6 +1,7 @@ use graphql_client::*; const RESPONSE: &str = include_str!("unions/union_query_response.json"); +const FRAGMENT_AND_MORE_RESPONSE: &str = include_str!("unions/fragment_and_more_response.json"); #[derive(GraphQLQuery)] #[graphql( @@ -102,8 +103,11 @@ fn fragment_on_union() { #[test] fn fragment_and_more_on_union() { use fragment_and_more_on_union::*; - todo!("fragment_and_more_on_union"); - let _expected = fragment_and_more_on_union::ResponseData { + + let response_data: fragment_and_more_on_union::ResponseData = + serde_json::from_str(FRAGMENT_AND_MORE_RESPONSE).unwrap(); + + let expected = fragment_and_more_on_union::ResponseData { names: Some(vec![ FragmentAndMoreOnUnionNames { names_fragment: NamesFragment::Person(NamesFragmentOnPerson { @@ -123,7 +127,7 @@ fn fragment_and_more_on_union() { names_fragment: NamesFragment::Organization(NamesFragmentOnOrganization { title: "Mozilla".into(), }), - on: FragmentAndMoreOnUnionNamesOn::Person, + on: FragmentAndMoreOnUnionNamesOn::Organization, }, FragmentAndMoreOnUnionNames { names_fragment: NamesFragment::Dog(NamesFragmentOnDog { @@ -135,4 +139,6 @@ fn fragment_and_more_on_union() { }, ]), }; + + assert_eq!(response_data, expected); } diff --git a/graphql_client/tests/unions/fragment_and_more_response.json b/graphql_client/tests/unions/fragment_and_more_response.json new file mode 100644 index 000000000..d009dcb76 --- /dev/null +++ b/graphql_client/tests/unions/fragment_and_more_response.json @@ -0,0 +1,22 @@ +{ + "names": [ + { + "__typename": "Person", + "firstName": "Larry" + }, + { + "__typename": "Dog", + "name": "Laïka", + "isGoodDog": true + }, + { + "__typename": "Organization", + "title": "Mozilla" + }, + { + "__typename": "Dog", + "name": "Norbert", + "isGoodDog": true + } + ] + } From 7c2dafd96bf272952dbbb79362896ab61b3149d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 21:40:39 +0100 Subject: [PATCH 60/93] Make graphql_client doctests pass --- README.md | 3 ++- graphql_client/src/lib.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac6353d08..41b1eecc5 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ A typed GraphQL client library for Rust. ```rust use graphql_client::{GraphQLQuery, Response}; + use std::error::Error; #[derive(GraphQLQuery)] #[graphql( @@ -68,7 +69,7 @@ A typed GraphQL client library for Rust. )] pub struct UnionQuery; - fn perform_my_query(variables: union_query::Variables) -> Result<(), anyhow::Error> { + fn perform_my_query(variables: union_query::Variables) -> Result<(), Box> { // this is the important line let request_body = UnionQuery::build_query(variables); diff --git a/graphql_client/src/lib.rs b/graphql_client/src/lib.rs index c83a750b1..b0dca88dd 100644 --- a/graphql_client/src/lib.rs +++ b/graphql_client/src/lib.rs @@ -31,6 +31,7 @@ doc_comment::doctest!("../../README.md"); /// ``` /// use graphql_client::*; /// use serde_json::json; +/// use std::error::Error; /// /// #[derive(GraphQLQuery)] /// #[graphql( @@ -39,7 +40,7 @@ doc_comment::doctest!("../../README.md"); /// )] /// struct StarWarsQuery; /// -/// fn main() -> Result<(), anyhow::Error> { +/// fn main() -> Result<(), Box> { /// use graphql_client::GraphQLQuery; /// /// let variables = star_wars_query::Variables { @@ -124,13 +125,14 @@ impl Display for PathFragment { /// # use serde_json::json; /// # use serde::Deserialize; /// # use graphql_client::GraphQLQuery; +/// # use std::error::Error; /// # /// # #[derive(Debug, Deserialize, PartialEq)] /// # struct ResponseData { /// # something: i32 /// # } /// # -/// # fn main() -> Result<(), anyhow::Error> { +/// # fn main() -> Result<(), Box> { /// use graphql_client::*; /// /// let body: Response = serde_json::from_value(json!({ @@ -230,6 +232,7 @@ impl Display for Error { /// # use serde_json::json; /// # use serde::Deserialize; /// # use graphql_client::GraphQLQuery; +/// # use std::error::Error; /// # /// # #[derive(Debug, Deserialize, PartialEq)] /// # struct User { @@ -247,7 +250,7 @@ impl Display for Error { /// # dogs: Vec, /// # } /// # -/// # fn main() -> Result<(), anyhow::Error> { +/// # fn main() -> Result<(), Box> { /// use graphql_client::Response; /// /// let body: Response = serde_json::from_value(json!({ From 2f6fc186ca84fbe0f2e4213ad622ae78a7cb2490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 22:21:10 +0100 Subject: [PATCH 61/93] Reimplement normalization --- graphql_client_codegen/src/codegen.rs | 41 +++++++++---- graphql_client_codegen/src/codegen/enums.rs | 7 ++- graphql_client_codegen/src/codegen/inputs.rs | 9 ++- .../src/codegen/selection.rs | 15 ++++- graphql_client_codegen/src/codegen_options.rs | 4 +- .../src/generated_module.rs | 2 +- graphql_client_codegen/src/lib.rs | 2 +- graphql_client_codegen/src/normalization.rs | 59 ++++++------------- graphql_client_codegen/src/resolution.rs | 5 +- 9 files changed, 81 insertions(+), 63 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 856831514..bf781a46b 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -21,7 +21,7 @@ pub(crate) fn response_for_query( let all_used_types = operation.all_used_types(); let response_derives = render_derives(options.all_response_derives()); - let scalar_definitions = generate_scalar_definitions(&operation, &all_used_types); + let scalar_definitions = generate_scalar_definitions(&operation, &all_used_types, &options); let enum_definitions = enums::generate_enum_definitions(&operation, &all_used_types, options); let fragment_definitions = generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); @@ -74,11 +74,13 @@ fn generate_variables_struct( ); } - let variable_fields = operation.variables().map(generate_variable_struct_field); + let variable_fields = operation + .variables() + .map(|variable| generate_variable_struct_field(variable, options)); let variable_defaults = operation.variables().map(|variable| { let method_name = format!("default_{}", variable.name()); let method_name = Ident::new(&method_name, Span::call_site()); - let method_return_type = render_variable_field_type(variable); + let method_return_type = render_variable_field_type(variable, options); variable.default().map(|default| { let value = graphql_parser_value_to_literal( @@ -113,14 +115,17 @@ fn generate_variables_struct( variables_struct.into() } -fn generate_variable_struct_field(variable: VariableRef<'_>) -> TokenStream { +fn generate_variable_struct_field( + variable: VariableRef<'_>, + options: &GraphQLClientCodegenOptions, +) -> TokenStream { let snake_case_name = variable.name().to_snake_case(); let ident = Ident::new( &crate::shared::keyword_replace(&snake_case_name), Span::call_site(), ); let annotation = crate::shared::field_rename_annotation(variable.name(), &snake_case_name); - let r#type = render_variable_field_type(variable); + let r#type = render_variable_field_type(variable, options); quote::quote!(#annotation pub #ident : #r#type) } @@ -128,11 +133,21 @@ fn generate_variable_struct_field(variable: VariableRef<'_>) -> TokenStream { fn generate_scalar_definitions<'a, 'schema: 'a>( operation: &OperationRef<'schema>, all_used_types: &'a crate::resolution::UsedTypes, + options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { - all_used_types.scalars(operation.schema()).map(|scalar| { - let ident = syn::Ident::new(scalar.name(), proc_macro2::Span::call_site()); - quote!(type #ident = super::#ident;) - }) + all_used_types + .scalars(operation.schema()) + .map(move |scalar| { + let ident = syn::Ident::new( + options + .normalization() + .scalar_name(scalar.name().into()) + .as_ref(), + proc_macro2::Span::call_site(), + ); + + quote!(type #ident = super::#ident;) + }) } fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToTokens { @@ -141,8 +156,12 @@ fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToT quote!(#[derive(#(#idents),*)]) } -fn render_variable_field_type(variable: VariableRef<'_>) -> TokenStream { - let full_name = Ident::new(variable.type_name(), Span::call_site()); +fn render_variable_field_type( + variable: VariableRef<'_>, + options: &GraphQLClientCodegenOptions, +) -> TokenStream { + let normalized_name = options.normalization().input_name(variable.type_name()); + let full_name = Ident::new(normalized_name.as_ref(), Span::call_site()); decorate_type(&full_name, variable.type_qualifiers()) } diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index 67febd143..49d597795 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -28,9 +28,11 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( .variants() .iter() .map(|v| { - let name = normalization.enum_variant(crate::shared::keyword_replace(v.as_str())); + let safe_name = crate::shared::keyword_replace(v.as_str()); + let name = normalization.enum_variant(safe_name.as_ref()); let name = Ident::new(&name, Span::call_site()); + // TODO // let description = &v.description; // let description = description.as_ref().map(|d| quote!(#[doc = #d])); @@ -45,7 +47,8 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( .variants() .iter() .map(|v| { - let name = normalization.enum_variant(crate::shared::keyword_replace(v)); + let safe_name = crate::shared::keyword_replace(v); + let name = normalization.enum_variant(safe_name.as_ref()); let v = Ident::new(&name, Span::call_site()); quote!(#name_ident::#v) diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 32b6c2f18..3fcf6e71e 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -6,16 +6,19 @@ use quote::quote; pub(super) fn generate_input_object_definitions( operation: &OperationRef<'_>, all_used_types: &UsedTypes, - _options: &GraphQLClientCodegenOptions, + options: &GraphQLClientCodegenOptions, ) -> Vec { all_used_types .inputs(operation.schema()) .map(|input| { - let struct_name = Ident::new(input.name(), Span::call_site()); + let normalized_name = options.normalization().input_name(input.name()); + let struct_name = Ident::new(normalized_name.as_ref(), Span::call_site()); let fields = input.fields().map(|field| { let name_ident = Ident::new(field.name(), Span::call_site()); - let type_name = Ident::new(field.field_type_name(), Span::call_site()); + let normalized_field_type_name = + options.normalization().field_type(field.field_type_name()); + let type_name = Ident::new(normalized_field_type_name.as_ref(), Span::call_site()); let field_type = super::decorate_type(&type_name, field.field_type_qualifiers()); let field_type = if field .field_type_as_input() diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 28405ad39..e8456425b 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -44,6 +44,7 @@ pub(crate) fn render_response_data_fields<'a>( operation.selection_ids(), response_data_type_id, operation.on_ref(), + options, ); expanded_selection @@ -72,6 +73,7 @@ pub(super) fn render_fragment<'a>( fragment.selection_ids(), response_type_id, fragment.on_ref(), + options, ); expanded_selection @@ -122,6 +124,7 @@ fn calculate_selection<'a>( selection_set: &[SelectionId], struct_id: ResponseTypeId, type_ref: TypeRef<'a>, + options: &'a GraphQLClientCodegenOptions, ) { // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_. { @@ -189,6 +192,7 @@ fn calculate_selection<'a>( selection_ref.subselection_ids(), struct_id, variant_schema_type, + options, ); } VariantSelection::FragmentSpread(fragment_ref) => { @@ -231,7 +235,10 @@ fn calculate_selection<'a>( graphql_name: Some(graphql_name), rust_name, struct_id, - field_type: context.schema().r#enum(enm).name().into(), + field_type: options + .normalization() + .field_type(context.schema().r#enum(enm).name()) + .into(), field_type_qualifiers: schema_field.type_qualifiers(), flatten: false, deprecation: schema_field.deprecation(), @@ -240,7 +247,10 @@ fn calculate_selection<'a>( } TypeId::Scalar(scalar) => { context.push_field(ExpandedField { - field_type: context.schema().scalar(scalar).name().into(), + field_type: options + .normalization() + .field_type(context.schema().scalar(scalar).name()) + .into(), field_type_qualifiers: field .schema_field(context.schema()) .type_qualifiers(), @@ -276,6 +286,7 @@ fn calculate_selection<'a>( selection_ref.subselection_ids(), type_id, field_type, + options, ); } TypeId::Input(_) => unreachable!("field selection on input type"), diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 4d093a519..12c7ce1f4 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -173,7 +173,7 @@ impl GraphQLClientCodegenOptions { } /// The normalization mode for the generated code. - pub fn normalization(&self) -> Normalization { - self.normalization + pub fn normalization(&self) -> &Normalization { + &self.normalization } } diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 10e2d4ec7..2afe3afc7 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -27,7 +27,7 @@ impl<'a> GeneratedModule<'a> { fn root(&self) -> OperationRef<'_> { let op_name = self.options.normalization().operation(self.operation); self.resolved_query - .select_operation(self.schema, &op_name) + .select_operation(self.schema, &op_name, self.options.normalization()) .expect("TODO: handle operation not found") } diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 0f3df9922..6d88d1905 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -109,7 +109,7 @@ pub fn generate_module_token_stream( .operation_name .as_ref() .and_then(|operation_name| { - query.select_operation(&schema, &options.normalization().operation(operation_name)) + query.select_operation(&schema, operation_name, &options.normalization()) }) .map(|op| vec![op]); diff --git a/graphql_client_codegen/src/normalization.rs b/graphql_client_codegen/src/normalization.rs index 3fb1a04b0..a7bc72e95 100644 --- a/graphql_client_codegen/src/normalization.rs +++ b/graphql_client_codegen/src/normalization.rs @@ -11,75 +11,54 @@ pub enum Normalization { } impl Normalization { - fn camel_case(self, name: Cow<'_, str>) -> Cow<'_, str> { + fn camel_case(self, name: &str) -> Cow<'_, str> { match self { - Self::None => name, + Self::None => name.into(), Self::Rust => name.to_camel_case().into(), } } - fn snake_case(self, name: Cow<'_, str>) -> Cow<'_, str> { + fn snake_case(self, name: &str) -> Cow<'_, str> { match self { - Self::None => name, + Self::None => name.into(), Self::Rust => name.to_snake_case().into(), } } - pub(crate) fn operation<'a, S>(self, op: S) -> Cow<'a, str> - where - S: Into>, - { - self.camel_case(op.into()) + pub(crate) fn operation<'a>(self, op: &'a str) -> Cow<'a, str> { + self.camel_case(op) } - pub(crate) fn enum_variant<'a, S>(self, enm: S) -> Cow<'a, str> - where - S: Into>, - { + pub(crate) fn enum_variant<'a>(self, enm: &'a str) -> Cow<'a, str> { self.camel_case(enm.into()) } - pub(crate) fn enum_name<'a, S>(self, enm: S) -> Cow<'a, str> - where - S: Into>, - { - self.camel_case(enm.into()) + pub(crate) fn enum_name<'a>(self, enm: &'a str) -> Cow<'a, str> { + self.camel_case(enm) } - fn field_type_impl(self, fty: Cow<'_, str>) -> Cow<'_, str> { + fn field_type_impl(self, fty: &str) -> Cow<'_, str> { if fty == "ID" || fty.starts_with("__") { - fty + fty.into() } else { self.camel_case(fty) } } - pub(crate) fn field_type<'a, S>(self, fty: S) -> Cow<'a, str> - where - S: Into>, - { - self.field_type_impl(fty.into()) + pub(crate) fn field_type<'a>(self, fty: &'a str) -> Cow<'a, str> { + self.field_type_impl(fty) } - pub(crate) fn field_name<'a, S>(self, fnm: S) -> Cow<'a, str> - where - S: Into>, - { - self.snake_case(fnm.into()) + pub(crate) fn field_name<'a>(self, fnm: &'a str) -> Cow<'a, str> { + self.snake_case(fnm) } - pub(crate) fn input_name<'a, S>(self, inm: S) -> Cow<'a, str> - where - S: Into>, - { - self.camel_case(inm.into()) + pub(crate) fn input_name<'a>(self, inm: &'a str) -> Cow<'a, str> { + self.camel_case(inm) } - pub(crate) fn scalar_name<'a, S>(self, snm: S) -> Cow<'a, str> - where - S: Into>, - { - self.camel_case(snm.into()) + pub(crate) fn scalar_name<'a>(self, snm: &'a str) -> Cow<'a, str> { + self.camel_case(snm) } } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 6c0079125..3704aacdc 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -1,6 +1,7 @@ //! The responsibility of this module is to resolve and validate a query //! against a given schema. +use crate::normalization::Normalization; use crate::schema::InputRef; use crate::schema::ScalarRef; use crate::{ @@ -675,8 +676,10 @@ impl ResolvedQuery { &'a self, schema: &'a Schema, name: &str, + normalization: &Normalization, ) -> Option> { - self.operations(schema).find(|op| op.name() == name) + self.operations(schema) + .find(|op| normalization.operation(op.name()) == name) } fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> { From e47b615c3dcc1766ddc993a1995ae995f50c0a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 22:46:29 +0100 Subject: [PATCH 62/93] Delete a bunch of dead code --- graphql_client_codegen/src/codegen/enums.rs | 6 +- graphql_client_codegen/src/constants.rs | 23 -- graphql_client_codegen/src/enums.rs | 85 ----- graphql_client_codegen/src/field_type.rs | 237 ------------- graphql_client_codegen/src/fragments.rs | 57 --- graphql_client_codegen/src/inputs.rs | 219 ------------ graphql_client_codegen/src/interfaces.rs | 275 --------------- graphql_client_codegen/src/operations.rs | 106 +----- graphql_client_codegen/src/resolution.rs | 23 +- graphql_client_codegen/src/schema.rs | 6 +- graphql_client_codegen/src/selection.rs | 362 -------------------- graphql_client_codegen/src/unions.rs | 16 +- graphql_client_codegen/src/variables.rs | 55 --- 13 files changed, 14 insertions(+), 1456 deletions(-) delete mode 100644 graphql_client_codegen/src/enums.rs delete mode 100644 graphql_client_codegen/src/fragments.rs delete mode 100644 graphql_client_codegen/src/inputs.rs delete mode 100644 graphql_client_codegen/src/interfaces.rs delete mode 100644 graphql_client_codegen/src/selection.rs delete mode 100644 graphql_client_codegen/src/variables.rs diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index 49d597795..841c31681 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -1,6 +1,6 @@ -use crate::codegen::render_derives; -use crate::codegen_options::GraphQLClientCodegenOptions; -use crate::resolution::{OperationId, OperationRef}; +use crate::{ + codegen::render_derives, codegen_options::GraphQLClientCodegenOptions, resolution::OperationRef, +}; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; diff --git a/graphql_client_codegen/src/constants.rs b/graphql_client_codegen/src/constants.rs index 9375e65cf..c051347fc 100644 --- a/graphql_client_codegen/src/constants.rs +++ b/graphql_client_codegen/src/constants.rs @@ -1,28 +1,5 @@ -// use crate::field_type::FieldType; -// use crate::objects::GqlObjectField; - pub(crate) const TYPENAME_FIELD: &str = "__typename"; -// pub(crate) fn string_type() -> &'static str { -// "String" -// } - -// #[cfg(test)] -// pub(crate) fn float_type() -> &'static str { -// "Float" -// } - -// pub(crate) fn typename_field() -> GqlObjectField<'static> { -// GqlObjectField { -// description: None, -// name: TYPENAME_FIELD, -// /// Non-nullable, see spec: -// /// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md -// type_: FieldType::new(string_type()), -// deprecation: DeprecationStatus::Current, -// } -// } - pub(crate) const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##" Multiple-field queries on the root subscription field are forbidden by the spec. diff --git a/graphql_client_codegen/src/enums.rs b/graphql_client_codegen/src/enums.rs deleted file mode 100644 index 0f0e939da..000000000 --- a/graphql_client_codegen/src/enums.rs +++ /dev/null @@ -1,85 +0,0 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; -use std::cell::Cell; - -pub const ENUMS_PREFIX: &str = ""; - -#[derive(Debug, Clone, PartialEq)] -pub struct EnumVariant<'schema> { - pub description: Option<&'schema str>, - pub name: &'schema str, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct GqlEnum<'schema> { - pub description: Option<&'schema str>, - pub name: &'schema str, - pub variants: Vec>, - pub is_required: Cell, -} - -impl<'schema> GqlEnum<'schema> { - pub(crate) fn to_rust(&self, query_context: &crate::query::QueryContext<'_>) -> TokenStream { - let derives = query_context.response_enum_derives(); - let norm = query_context.normalization; - let variant_names: Vec = self - .variants - .iter() - .map(|v| { - let name = norm.enum_variant(crate::shared::keyword_replace(&v.name)); - let name = Ident::new(&name, Span::call_site()); - - let description = &v.description; - let description = description.as_ref().map(|d| quote!(#[doc = #d])); - - quote!(#description #name) - }) - .collect(); - let variant_names = &variant_names; - let name_ident = norm.enum_name(format!("{}{}", ENUMS_PREFIX, self.name)); - let name_ident = Ident::new(&name_ident, Span::call_site()); - let constructors: Vec<_> = self - .variants - .iter() - .map(|v| { - let name = norm.enum_variant(crate::shared::keyword_replace(&v.name)); - let v = Ident::new(&name, Span::call_site()); - - quote!(#name_ident::#v) - }) - .collect(); - let constructors = &constructors; - let variant_str: Vec<&str> = self.variants.iter().map(|v| v.name).collect(); - let variant_str = &variant_str; - - let name = name_ident; - - quote! { - #derives - pub enum #name { - #(#variant_names,)* - Other(String), - } - - impl ::serde::Serialize for #name { - fn serialize(&self, ser: S) -> Result { - ser.serialize_str(match *self { - #(#constructors => #variant_str,)* - #name::Other(ref s) => &s, - }) - } - } - - impl<'de> ::serde::Deserialize<'de> for #name { - fn deserialize>(deserializer: D) -> Result { - let s = ::deserialize(deserializer)?; - - match s.as_str() { - #(#variant_str => Ok(#constructors),)* - _ => Ok(#name::Other(s)), - } - } - } - } - } -} diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index b5429c368..139207811 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -1,11 +1,4 @@ -// use crate::enums::ENUMS_PREFIX; -// use crate::query::QueryContext; use graphql_introspection_query::introspection_response; -use proc_macro2::{Ident, Span, TokenStream}; - -// pub(crate) fn field_type_to_rust() -> TokenStream { -// todo!() -// } #[derive(Clone, Debug, PartialEq, Hash)] pub(crate) enum GraphqlTypeQualifier { @@ -19,148 +12,6 @@ impl GraphqlTypeQualifier { } } -#[derive(Clone, Debug, PartialEq, Hash)] -pub struct FieldType<'a> { - /// The type name of the field. - /// - /// e.g. for `[Int]!`, this would return `Int`. - name: &'a str, - /// An ordered list of qualifiers, from outer to inner. - /// - /// e.g. `[Int]!` would have `vec![List, Optional]`, but `[Int!]` would have `vec![Optional, - /// List]`. - qualifiers: Vec, -} - -impl<'a> FieldType<'a> { - pub(crate) fn new(name: &'a str) -> Self { - FieldType { - name, - qualifiers: Vec::new(), - } - } - - #[cfg(test)] - pub(crate) fn list(mut self) -> Self { - self.qualifiers.insert(0, GraphqlTypeQualifier::List); - self - } - - #[cfg(test)] - pub(crate) fn nonnull(mut self) -> Self { - self.qualifiers.insert(0, GraphqlTypeQualifier::Required); - self - } - - /// Takes a field type with its name. - // pub(crate) fn to_rust(&self, context: &QueryContext<'_>, prefix: &str) -> TokenStream { - // todo!() - // // let prefix: &str = if prefix.is_empty() { - // // self.inner_name_str() - // // } else { - // // prefix - // // }; - - // // let full_name = { - // // if context - // // .schema - // // .scalars - // // .get(&self.name) - // // .map(|s| s.is_required.set(true)) - // // .is_some() - // // || DEFAULT_SCALARS.iter().any(|elem| elem == &self.name) - // // { - // // self.name.to_string() - // // } else if context - // // .schema - // // .enums - // // .get(&self.name) - // // .map(|enm| enm.is_required.set(true)) - // // .is_some() - // // { - // // format!("{}{}", ENUMS_PREFIX, self.name) - // // } else { - // // if prefix.is_empty() { - // // panic!("Empty prefix for {:?}", self); - // // } - // // prefix.to_string() - // // } - // // }; - - // // let norm = context.normalization; - // // let full_name = norm.field_type(crate::shared::keyword_replace(&full_name)); - - // // let full_name = Ident::new(&full_name, Span::call_site()); - // // let mut qualified = quote!(#full_name); - - // // let mut non_null = false; - - // // // Note: we iterate over qualifiers in reverse because it is more intuitive. This - // // // means we start from the _inner_ type and make our way to the outside. - // // for qualifier in self.qualifiers.iter().rev() { - // // match (non_null, qualifier) { - // // // We are in non-null context, and we wrap the non-null type into a list. - // // // We switch back to null context. - // // (true, GraphqlTypeQualifier::List) => { - // // qualified = quote!(Vec<#qualified>); - // // non_null = false; - // // } - // // // We are in nullable context, and we wrap the nullable type into a list. - // // (false, GraphqlTypeQualifier::List) => { - // // qualified = quote!(Vec>); - // // } - // // // We are in non-nullable context, but we can't double require a type - // // // (!!). - // // (true, GraphqlTypeQualifier::Required) => panic!("double required annotation"), - // // // We are in nullable context, and we switch to non-nullable context. - // // (false, GraphqlTypeQualifier::Required) => { - // // non_null = true; - // // } - // // } - // // } - - // // // If we are in nullable context at the end of the iteration, we wrap the whole - // // // type with an Option. - // // if !non_null { - // // qualified = quote!(Option<#qualified>); - // // } - - // // qualified - // } - - /// Return the innermost name - we mostly use this for looking types up in our Schema struct. - pub fn inner_name_str(&self) -> &str { - self.name - } - - /// Is the type nullable? - /// - /// Note: a list of nullable values is considered nullable only if the list itself is nullable. - pub fn is_optional(&self) -> bool { - if let Some(qualifier) = self.qualifiers.get(0) { - qualifier != &GraphqlTypeQualifier::Required - } else { - true - } - } - - /// A type is indirected if it is a (flat or nested) list type, optional or not. - /// - /// We use this to determine whether a type needs to be boxed for recursion. - pub fn is_indirected(&self) -> bool { - self.qualifiers - .iter() - .any(|qualifier| qualifier == &GraphqlTypeQualifier::List) - } -} - -// impl<'schema> std::convert::From<&'schema graphql_parser::schema::Type> for FieldType<'schema> { -// fn from(schema_type: &'schema graphql_parser::schema::Type) -> FieldType<'schema> { -// todo!() -// // from_schema_type_inner(schema_type) -// } -// } - pub(crate) fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { match schema_type { graphql_parser::schema::Type::ListType(inner) => 1 + graphql_parser_depth(inner), @@ -179,91 +30,3 @@ fn json_type_qualifiers_depth(typeref: &introspection_response::TypeRef) -> usiz _ => panic!("Non-convertible type in JSON schema: {:?}", typeref), } } - -fn from_json_type_inner(inner: &introspection_response::TypeRef) -> FieldType<'_> { - use graphql_introspection_query::introspection_response::*; - - let qualifiers_depth = json_type_qualifiers_depth(inner); - let mut qualifiers = Vec::with_capacity(qualifiers_depth); - - let mut inner = inner; - - loop { - match ( - inner.kind.as_ref(), - inner.of_type.as_ref(), - inner.name.as_ref(), - ) { - (Some(__TypeKind::NON_NULL), Some(new_inner), _) => { - qualifiers.push(GraphqlTypeQualifier::Required); - inner = &new_inner; - } - (Some(__TypeKind::LIST), Some(new_inner), _) => { - qualifiers.push(GraphqlTypeQualifier::List); - inner = &new_inner; - } - (Some(_), None, Some(name)) => return FieldType { name, qualifiers }, - _ => panic!("Non-convertible type in JSON schema: {:?}", inner), - } - } -} - -impl<'schema> std::convert::From<&'schema introspection_response::FullTypeFieldsType> - for FieldType<'schema> -{ - fn from( - schema_type: &'schema introspection_response::FullTypeFieldsType, - ) -> FieldType<'schema> { - from_json_type_inner(&schema_type.type_ref) - } -} - -impl<'a> std::convert::From<&'a introspection_response::InputValueType> for FieldType<'a> { - fn from(schema_type: &'a introspection_response::InputValueType) -> FieldType<'a> { - from_json_type_inner(&schema_type.type_ref) - } -} - -// #[cfg(test)] -// mod tests { -// use super::*; -// use graphql_introspection_query::introspection_response::{ -// FullTypeFieldsType, TypeRef, __TypeKind, -// }; -// use graphql_parser::schema::Type as GqlParserType; - -// #[test] -// fn field_type_from_graphql_parser_schema_type_works() { -// let ty = GqlParserType::NamedType("Cat".to_owned()); -// assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); - -// let ty = GqlParserType::NonNullType(Box::new(GqlParserType::NamedType("Cat".to_owned()))); - -// assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); -// } - -// #[test] -// fn field_type_from_introspection_response_works() { -// let ty = FullTypeFieldsType { -// type_ref: TypeRef { -// kind: Some(__TypeKind::OBJECT), -// name: Some("Cat".into()), -// of_type: None, -// }, -// }; -// assert_eq!(FieldType::from(&ty), FieldType::new("Cat")); - -// let ty = FullTypeFieldsType { -// type_ref: TypeRef { -// kind: Some(__TypeKind::NON_NULL), -// name: None, -// of_type: Some(Box::new(TypeRef { -// kind: Some(__TypeKind::OBJECT), -// name: Some("Cat".into()), -// of_type: None, -// })), -// }, -// }; -// assert_eq!(FieldType::from(&ty), FieldType::new("Cat").nonnull()); -// } -// } diff --git a/graphql_client_codegen/src/fragments.rs b/graphql_client_codegen/src/fragments.rs deleted file mode 100644 index 48341f68a..000000000 --- a/graphql_client_codegen/src/fragments.rs +++ /dev/null @@ -1,57 +0,0 @@ -// use crate::query::QueryContext; -// use crate::selection::Selection; -use proc_macro2::TokenStream; -use std::cell::Cell; - -/// Represents which type a fragment is defined on. This is the type mentioned in the fragment's `on` clause. -#[derive(Debug, PartialEq)] -pub(crate) enum FragmentTarget<'context> { - Object(&'context crate::objects::GqlObject<'context>), - Interface(&'context crate::interfaces::GqlInterface<'context>), - Union(&'context crate::unions::GqlUnion<'context>), -} - -impl<'context> FragmentTarget<'context> { - pub(crate) fn name(&self) -> &str { - match self { - FragmentTarget::Object(obj) => obj.name, - FragmentTarget::Interface(iface) => iface.name, - FragmentTarget::Union(unn) => unn.name, - } - } -} - -/// Represents a fragment extracted from a query document. -#[derive(Debug, PartialEq)] -pub(crate) struct GqlFragment<'query> { - /// The name of the fragment, matching one-to-one with the name in the GraphQL query document. - pub name: &'query str, - /// The `on` clause of the fragment. - pub on: crate::schema::TypeId, - /// The selected fields. - pub selection: Selection<'query>, - /// Whether the fragment is used in the current query - pub is_required: Cell, -} - -impl<'query> GqlFragment<'query> { - /// Generate all the Rust code required by the fragment's object selection. - pub(crate) fn to_rust(&self, context: &QueryContext<'_>) -> Result { - todo!("fragment to rust") - // match self.on { - // FragmentTarget::Object(obj) => { - // obj.response_for_selection(context, &self.selection, &self.name) - // } - // FragmentTarget::Interface(iface) => { - // iface.response_for_selection(context, &self.selection, &self.name) - // } - // FragmentTarget::Union(_) => { - // unreachable!("Wrong code path. Fragment on unions are treated differently.") - // } - // } - } - - pub(crate) fn is_recursive(&self) -> bool { - self.selection.contains_fragment(&self.name) - } -} diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs deleted file mode 100644 index da90f1dbe..000000000 --- a/graphql_client_codegen/src/inputs.rs +++ /dev/null @@ -1,219 +0,0 @@ -use crate::deprecation::DeprecationStatus; -use crate::objects::GqlObjectField; -use crate::query::QueryContext; -use crate::schema; -use crate::schema::{InputId, SchemaRef}; -use graphql_introspection_query::introspection_response; -// use heck::SnakeCase; -use proc_macro2::{Ident, Span, TokenStream}; -// use quote::quote; -use std::cell::Cell; -use std::collections::HashMap; - -pub(crate) fn input_to_rust( - ctx: &mut QueryContext<'_>, - input: crate::schema::InputRef<'_>, -) -> Result { - todo!("input to rust") -} - -// impl InputRef<'_> { -// // pub(crate) fn require(&self, schema: &Schema) { -// // if self.is_required.get() { -// // return; -// // } -// // self.is_required.set(true); -// // self.fields.values().for_each(|field| { -// // schema.require(&field.type_.inner_name_str()); -// // }) -// // } - -// fn is_recursive_without_indirection(&self, context: &QueryContext<'_>) -> bool { -// self.contains_type_without_indirection(context, &self.name) -// } - -// pub(crate) fn to_rust( -// &self, -// context: &QueryContext<'_>, -// ) -> Result { -// let norm = context.normalization; -// let mut fields: Vec<&GqlObjectField<'_>> = self.fields.values().collect(); -// fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); -// let fields = fields.iter().map(|field| { -// let ty = field.type_.to_rust(&context, ""); - -// // If the type is recursive, we have to box it -// let ty = if let Some(input) = context.schema.inputs.get(field.type_.inner_name_str()) { -// if input.is_recursive_without_indirection(context) { -// quote! { Box<#ty> } -// } else { -// quote!(#ty) -// } -// } else { -// quote!(#ty) -// }; - -// // context.schema.require(&field.type_.inner_name_str()); -// let name = crate::shared::keyword_replace(&field.name.to_snake_case()); -// let rename = crate::shared::field_rename_annotation(&field.name, &name); -// let name = norm.field_name(name); -// let name = Ident::new(&name, Span::call_site()); - -// quote!(#rename pub #name: #ty) -// }); -// let variables_derives = context.variables_derives(); - -// // Prevent generated code like "pub struct crate" for a schema input like "input crate { ... }" -// // This works in tandem with renamed struct Variables field types, eg: pub struct Variables { pub criteria : crate_ , } -// let name = crate::shared::keyword_replace(&self.name); -// let name = norm.input_name(name); -// let name = Ident::new(&name, Span::call_site()); -// Ok(quote! { -// #variables_derives -// pub struct #name { -// #(#fields,)* -// } -// }) -// } -// } - -// // impl<'schema> std::convert::From<&'schema mut graphql_parser::schema::InputObjectType> -// // for InputRef<'schema> -// // { -// // fn from( -// // schema_input: &'schema mut graphql_parser::schema::InputObjectType, -// // ) -> InputRef<'schema> { -// // InputRef { -// // description: schema_input.description.as_ref().map(String::as_str), -// // name: &schema_input.name, -// // fields: schema_input -// // .fields -// // .iter() -// // .map(|field| { -// // let name = field.name.as_str(); -// // let field = GqlObjectField { -// // description: None, -// // name: &field.name, -// // type_: crate::field_type::FieldType::from(&field.value_type), -// // deprecation: DeprecationStatus::Current, -// // }; -// // (name, field) -// // }) -// // .collect(), -// // is_required: false.into(), -// // } -// // } -// // } - -// // impl<'schema> std::convert::From<&'schema introspection_response::FullType> for InputRef<'schema> { -// // fn from(schema_input: &'schema introspection_response::FullType) -> InputRef<'schema> { -// // InputRef { -// // description: schema_input.description.as_ref().map(String::as_str), -// // name: schema_input -// // .name -// // .as_ref() -// // .map(String::as_str) -// // .expect("unnamed input object"), -// // fields: schema_input -// // .input_fields -// // .as_ref() -// // .expect("fields on input object") -// // .iter() -// // .filter_map(Option::as_ref) -// // .map(|f| { -// // let name = f -// // .input_value -// // .name -// // .as_ref() -// // .expect("unnamed input object field") -// // .as_str(); -// // let field = GqlObjectField { -// // description: None, -// // name: &name, -// // type_: f -// // .input_value -// // .type_ -// // .as_ref() -// // .map(|s| s.into()) -// // .expect("type on input object field"), -// // deprecation: DeprecationStatus::Current, -// // }; -// // (name, field) -// // }) -// // .collect(), -// // is_required: false.into(), -// // } -// // } -// // } - -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::constants::*; -// use crate::field_type::FieldType; - -// #[test] -// fn gql_input_to_rust() { -// let cat = InputRef { -// description: None, -// name: "Cat", -// fields: vec![ -// ( -// "pawsCount", -// GqlObjectField { -// description: None, -// name: "pawsCount", -// type_: FieldType::new(float_type()).nonnull(), -// deprecation: DeprecationStatus::Current, -// }, -// ), -// ( -// "offsprings", -// GqlObjectField { -// description: None, -// name: "offsprings", -// type_: FieldType::new("Cat").nonnull().list().nonnull(), -// deprecation: DeprecationStatus::Current, -// }, -// ), -// ( -// "requirements", -// GqlObjectField { -// description: None, -// name: "requirements", -// type_: FieldType::new("CatRequirements"), -// deprecation: DeprecationStatus::Current, -// }, -// ), -// ] -// .into_iter() -// .collect(), -// is_required: false.into(), -// }; - -// let expected: String = vec![ -// "# [ derive ( Clone , Serialize ) ] ", -// "pub struct Cat { ", -// "pub offsprings : Vec < Cat > , ", -// "# [ serde ( rename = \"pawsCount\" ) ] ", -// "pub paws_count : Float , ", -// "pub requirements : Option < CatRequirements > , ", -// "}", -// ] -// .into_iter() -// .collect(); - -// let mut schema = crate::schema::Schema::new(); -// schema.inputs.insert(cat.name, cat); -// let mut context = QueryContext::new_empty(&schema); -// context.ingest_variables_derives("Clone").unwrap(); - -// assert_eq!( -// format!( -// "{}", -// context.schema.inputs["Cat"].to_rust(&context).unwrap() -// ), -// expected -// ); -// } -// } diff --git a/graphql_client_codegen/src/interfaces.rs b/graphql_client_codegen/src/interfaces.rs deleted file mode 100644 index 8b85a2607..000000000 --- a/graphql_client_codegen/src/interfaces.rs +++ /dev/null @@ -1,275 +0,0 @@ -use crate::constants::TYPENAME_FIELD; -use crate::objects::GqlObjectField; -use crate::query::QueryContext; -use crate::selection::{Selection, SelectionField, SelectionFragmentSpread, SelectionItem}; -use crate::shared::*; -use crate::unions::union_variants; -use failure::*; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; -use std::cell::Cell; -use std::collections::HashSet; - -/// A GraphQL interface (simplified schema representation). -/// -/// In the generated code, fragments nesting is preserved, including for selection on union variants. See the tests in the graphql client crate for examples. -#[derive(Debug, Clone, PartialEq)] -pub struct GqlInterface<'schema> { - /// The documentation for the interface. Extracted from the schema. - pub description: Option<&'schema str>, - /// The set of object types implementing this interface. - pub implemented_by: HashSet<&'schema str>, - /// The name of the interface. Should match 1-to-1 to its name in the GraphQL schema. - pub name: &'schema str, - /// The interface's fields. Analogous to object fields. - pub fields: Vec>, - pub is_required: Cell, -} - -impl<'schema> GqlInterface<'schema> { - /// filters the selection to keep only the fields that refer to the interface's own. - /// - /// This does not include the __typename field because it is translated into the `on` enum. - fn object_selection<'query>( - &self, - selection: &'query Selection<'query>, - query_context: &QueryContext<'_>, - ) -> Selection<'query> { - todo!() - // (&selection) - // .into_iter() - // // Only keep what we can handle - // .filter(|f| match f { - // SelectionItem::Field(f) => f.name != TYPENAME_FIELD, - // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - // // only if the fragment refers to the interface’s own fields (to take into account type-refining fragments) - // let fragment = query_context - // .fragments - // .get(fragment_name) - // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) - // // TODO: fix this - // .unwrap(); - - // fragment.on.name() == self.name - // } - // SelectionItem::InlineFragment(_) => false, - // }) - // .map(|a| (*a).clone()) - // .collect() - } - - fn union_selection<'query>( - &self, - selection: &'query Selection<'_>, - query_context: &QueryContext<'_>, - ) -> Selection<'query> { - todo!() - // (&selection) - // .into_iter() - // // Only keep what we can handle - // .filter(|f| match f { - // SelectionItem::InlineFragment(_) => true, - // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - // let fragment = query_context - // .fragments - // .get(fragment_name) - // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name)) - // // TODO: fix this - // .unwrap(); - - // // only the fragments _not_ on the interface - // fragment.on.name() != self.name - // } - // SelectionItem::Field(SelectionField { name, .. }) => *name == "__typename", - // }) - // .map(|a| (*a).clone()) - // .collect() - } - - /// Create an empty interface. This needs to be mutated before it is useful. - pub(crate) fn new( - name: &'schema str, - description: Option<&'schema str>, - ) -> GqlInterface<'schema> { - GqlInterface { - name, - description, - implemented_by: HashSet::new(), - fields: vec![], - is_required: false.into(), - } - } - - /// The generated code for each of the selected field's types. See [shared::field_impls_for_selection]. - pub(crate) fn field_impls_for_selection( - &self, - context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result, anyhow::Error> { - crate::shared::field_impls_for_selection( - &self.fields, - context, - &self.object_selection(selection, context), - prefix, - ) - } - - /// The code for the interface's corresponding struct's fields. - pub(crate) fn response_fields_for_selection( - &self, - context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result, anyhow::Error> { - response_fields_for_selection( - &self.name, - &self.fields, - context, - &self.object_selection(selection, context), - prefix, - ) - } - - /// Generate all the code for the interface. - pub(crate) fn response_for_selection( - &self, - query_context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result { - let name = Ident::new(&prefix, Span::call_site()); - let derives = query_context.response_derives(); - - selection.extract_typename(query_context).ok_or_else(|| { - format_err!( - "Missing __typename in selection for the {} interface (type: {})", - prefix, - self.name - ) - })?; - - let object_fields = - self.response_fields_for_selection(query_context, &selection, prefix)?; - - let object_children = self.field_impls_for_selection(query_context, &selection, prefix)?; - - let union_selection = self.union_selection(&selection, &query_context); - - let (mut union_variants, union_children, used_variants) = - union_variants(&union_selection, query_context, prefix, &self.name)?; - - for used_variant in used_variants.iter() { - if !self.implemented_by.contains(used_variant) { - return Err(format_err!( - "Type {} does not implement the {} interface", - used_variant, - self.name, - )); - } - } - - // Add the non-selected variants to the generated enum's variants. - union_variants.extend( - self.implemented_by - .iter() - .filter(|obj| used_variants.iter().find(|v| v == obj).is_none()) - .map(|v| { - let v = Ident::new(v, Span::call_site()); - quote!(#v) - }), - ); - - let attached_enum_name = Ident::new(&format!("{}On", name), Span::call_site()); - let (attached_enum, last_object_field) = - if selection.extract_typename(query_context).is_some() { - let attached_enum = quote! { - #derives - #[serde(tag = "__typename")] - pub enum #attached_enum_name { - #(#union_variants,)* - } - }; - let last_object_field = quote!(#[serde(flatten)] pub on: #attached_enum_name,); - (Some(attached_enum), Some(last_object_field)) - } else { - (None, None) - }; - - Ok(quote! { - - #(#object_children)* - - #(#union_children)* - - #attached_enum - - #derives - pub struct #name { - #(#object_fields,)* - #last_object_field - } - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // to be improved - #[test] - fn union_selection_works() { - let iface = GqlInterface { - description: None, - implemented_by: HashSet::new(), - name: "MyInterface", - fields: vec![], - is_required: Cell::new(true), - }; - - let schema = crate::schema::Schema::new(); - let context = QueryContext::new_empty(&schema); - - let typename_field = - crate::selection::SelectionItem::Field(crate::selection::SelectionField { - alias: None, - name: "__typename", - fields: Selection::new_empty(), - }); - let selection = Selection::from_vec(vec![typename_field.clone()]); - - assert_eq!( - iface.union_selection(&selection, &context), - Selection::from_vec(vec![typename_field]) - ); - } - - // to be improved - #[test] - fn object_selection_works() { - let iface = GqlInterface { - description: None, - implemented_by: HashSet::new(), - name: "MyInterface", - fields: vec![], - is_required: Cell::new(true), - }; - - let schema = crate::schema::Schema::new(); - let context = QueryContext::new_empty(&schema); - - let typename_field = - crate::selection::SelectionItem::Field(crate::selection::SelectionField { - alias: None, - name: "__typename", - fields: Selection::new_empty(), - }); - let selection: Selection<'_> = vec![typename_field].into_iter().collect(); - - assert_eq!( - iface.object_selection(&selection, &context), - Selection::new_empty() - ); - } -} diff --git a/graphql_client_codegen/src/operations.rs b/graphql_client_codegen/src/operations.rs index 3be52ae4d..36b17a04a 100644 --- a/graphql_client_codegen/src/operations.rs +++ b/graphql_client_codegen/src/operations.rs @@ -1,110 +1,6 @@ -use crate::constants::*; -// use crate::query::QueryContext; -// use crate::selection::Selection; -// use crate::variables::Variable; -use graphql_parser::query::OperationDefinition; -use heck::SnakeCase; -use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::Ident; - #[derive(Debug, Clone)] -pub enum OperationType { +pub(crate) enum OperationType { Query, Mutation, Subscription, } - -// #[derive(Debug, Clone)] -// pub struct Operation<'query> { -// pub name: String, -// pub operation_type: OperationType, -// pub variables: Vec>, -// pub selection: Selection<'query>, -// } - -// impl<'query> Operation<'query> { -// pub(crate) fn root_name<'schema>( -// &self, -// schema: &'schema crate::schema::Schema, -// ) -> &'schema str { -// match self.operation_type { -// OperationType::Query => schema.query_type().name(), -// OperationType::Mutation => schema.mutation_type().name(), -// OperationType::Subscription => schema.subscription_type().name(), -// } -// } - -// pub(crate) fn is_subscription(&self) -> bool { -// match self.operation_type { -// OperationType::Subscription => true, -// _ => false, -// } -// } - -// /// Generate the Variables struct and all the necessary supporting code. -// pub(crate) fn expand_variables(&self, context: &QueryContext<'_>) -> TokenStream { -// todo!() -// // let variables = &self.variables; -// // let variables_derives = context.variables_derives(); - -// // if variables.is_empty() { -// // return quote! { -// // #variables_derives -// // pub struct Variables; -// // }; -// // } - -// // let fields = variables.iter().map(|variable| { -// // let ty = variable.ty.to_rust(context, ""); -// // let rust_safe_field_name = -// // crate::shared::keyword_replace(&variable.name.to_snake_case()); -// // let rename = -// // crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); -// // let name = Ident::new(&rust_safe_field_name, Span::call_site()); - -// // quote!(#rename pub #name: #ty) -// // }); - -// // let default_constructors = variables -// // .iter() -// // .map(|variable| variable.generate_default_value_constructor(context)); - -// // quote! { -// // #variables_derives -// // pub struct Variables { -// // #(#fields,)* -// // } - -// // impl Variables { -// // #(#default_constructors)* -// // } -// // } -// } -// } - -// impl<'query> std::convert::From<&'query OperationDefinition> for Operation<'query> { -// fn from(definition: &'query OperationDefinition) -> Operation<'query> { -// match *definition { -// OperationDefinition::Query(ref q) => Operation { -// name: q.name.clone().expect("unnamed operation"), -// operation_type: OperationType::Query, -// variables: q.variable_definitions.iter().map(|v| v.into()).collect(), -// selection: (&q.selection_set).into(), -// }, -// OperationDefinition::Mutation(ref m) => Operation { -// name: m.name.clone().expect("unnamed operation"), -// operation_type: OperationType::Mutation, -// variables: m.variable_definitions.iter().map(|v| v.into()).collect(), -// selection: (&m.selection_set).into(), -// }, -// OperationDefinition::Subscription(ref s) => Operation { -// name: s.name.clone().expect("unnamed operation"), -// operation_type: OperationType::Subscription, -// variables: s.variable_definitions.iter().map(|v| v.into()).collect(), -// selection: (&s.selection_set).into(), -// }, -// OperationDefinition::SelectionSet(_) => panic!(SELECTION_SET_AT_ROOT), -// } -// } -// } diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index 3704aacdc..edba9fe53 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -35,7 +35,6 @@ impl<'a, T> QueryWith<'a, T> { } pub(crate) struct OperationRef<'a>(QueryWith<'a, OperationId>); -pub(crate) struct InlineFragmentRef<'a>(QueryWith<'a, &'a InlineFragment>); pub(crate) struct FragmentRef<'a>(QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>); #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] @@ -271,12 +270,6 @@ pub(crate) struct InlineFragment { selection_set: Vec, } -impl<'a> InlineFragmentRef<'a> { - pub(crate) fn on(&self) -> TypeId { - self.0.focus.type_id - } -} - #[derive(Debug)] pub(crate) struct SelectedField { alias: Option, @@ -319,7 +312,7 @@ pub(crate) fn resolve( let resolved_operation: ResolvedOperation = ResolvedOperation { object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), - operation_type: crate::operations::OperationType::Mutation, + _operation_type: crate::operations::OperationType::Mutation, selection: Vec::with_capacity(m.selection_set.items.len()), }; @@ -331,7 +324,7 @@ pub(crate) fn resolve( let on = schema.query_type(); let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), - operation_type: crate::operations::OperationType::Query, + _operation_type: crate::operations::OperationType::Query, object_id: on.id(), selection: Vec::with_capacity(q.selection_set.items.len()), }; @@ -349,7 +342,7 @@ pub(crate) fn resolve( .as_ref() .expect("subscription without name") .to_owned(), - operation_type: crate::operations::OperationType::Subscription, + _operation_type: crate::operations::OperationType::Subscription, object_id: on.id(), selection: Vec::with_capacity(s.selection_set.items.len()), }; @@ -792,7 +785,7 @@ impl<'a> OperationRef<'a> { struct ResolvedOperation { name: String, - operation_type: crate::operations::OperationType, + _operation_type: crate::operations::OperationType, selection: Vec, object_id: ObjectId, } @@ -823,10 +816,6 @@ impl<'a> VariableRef<'a> { &self.0.focus.1.name } - pub(crate) fn is_optional(&self) -> bool { - self.0.focus.1.r#type.is_optional() - } - pub(crate) fn variable_type(&self) -> TypeRef<'a> { self.0.schema.type_ref(self.0.focus.1.r#type.id) } @@ -894,10 +883,6 @@ impl<'a> FragmentRef<'a> { .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) } - pub(crate) fn selection_set_len(&self) -> usize { - self.0.focus.1.selection.len() - } - fn to_path_segment(&self) -> String { self.0.focus.1.name.to_camel_case() } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 94bb07316..9dc3620eb 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -89,6 +89,10 @@ struct StoredInterface { #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredFieldType { pub(crate) id: TypeId, + /// An ordered list of qualifiers, from outer to inner. + /// + /// e.g. `[Int]!` would have `vec![List, Optional]`, but `[Int!]` would have `vec![Optional, + /// List]`. pub(crate) qualifiers: Vec, } @@ -296,7 +300,7 @@ impl StoredInputFieldType { /// A type is indirected if it is a (flat or nested) list type, optional or not. /// /// We use this to determine whether a type needs to be boxed for recursion. - pub fn is_indirected(&self) -> bool { + pub(crate) fn is_indirected(&self) -> bool { self.qualifiers .iter() .any(|qualifier| qualifier == &GraphqlTypeQualifier::List) diff --git a/graphql_client_codegen/src/selection.rs b/graphql_client_codegen/src/selection.rs deleted file mode 100644 index d514429a7..000000000 --- a/graphql_client_codegen/src/selection.rs +++ /dev/null @@ -1,362 +0,0 @@ -use crate::constants::*; -// use failure::*; -use graphql_parser::query::SelectionSet; -use std::collections::BTreeMap; - -/// A single object field as part of a selection. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SelectionField<'query> { - pub alias: Option<&'query str>, - pub name: &'query str, - pub fields: Selection<'query>, -} - -/// A spread fragment in a selection (e.g. `...MyFragment`). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SelectionFragmentSpread<'query> { - pub fragment_name: &'query str, -} - -/// An inline fragment as part of a selection (e.g. `...on MyThing { name }`). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SelectionInlineFragment<'query> { - pub on: &'query str, - pub fields: Selection<'query>, -} - -/// An element in a query selection. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SelectionItem<'query> { - Field(SelectionField<'query>), - FragmentSpread(SelectionFragmentSpread<'query>), - InlineFragment(SelectionInlineFragment<'query>), -} - -impl<'query> SelectionItem<'query> { - pub fn as_typename(&self) -> Option<&SelectionField<'_>> { - if let SelectionItem::Field(f) = self { - if f.name == TYPENAME_FIELD { - return Some(f); - } - } - None - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Selection<'query>(Vec>); - -impl<'query> Selection<'query> { - pub(crate) fn extract_typename<'s, 'context: 's>( - &'s self, - context: &'context crate::query::QueryContext<'_>, - ) -> Option<&SelectionField<'_>> { - // __typename is selected directly - if let Some(field) = self.0.iter().filter_map(SelectionItem::as_typename).next() { - return Some(field); - }; - - // typename is selected through a fragment - (&self) - .into_iter() - .filter_map(|f| match f { - SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - Some(fragment_name) - } - _ => None, - }) - .filter_map(|fragment_name| { - let fragment = context.fragments.get(fragment_name); - - fragment.and_then(|fragment| fragment.selection.extract_typename(context)) - }) - .next() - } - - // Implementation helper for `selected_variants_on_union`. - fn selected_variants_on_union_inner<'s>( - &'s self, - context: &'s crate::query::QueryContext<'_>, - selected_variants: &mut BTreeMap<&'s str, Selection<'s>>, - // the name of the type the selection applies to - selection_on: &str, - ) -> Result<(), anyhow::Error> { - unimplemented!() - // for item in self.0.iter() { - // match item { - // SelectionItem::Field(_) => (), - // SelectionItem::InlineFragment(inline_fragment) => { - // selected_variants - // .entry(inline_fragment.on) - // .and_modify(|entry| entry.0.extend(inline_fragment.fields.0.clone())) - // .or_insert_with(|| { - // let mut items = Vec::with_capacity(inline_fragment.fields.0.len()); - // items.extend(inline_fragment.fields.0.clone()); - // Selection(items) - // }); - // } - // SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) => { - // let fragment = context - // .fragments - // .get(fragment_name) - // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment_name))?; - - // // The fragment can either be on the union/interface itself, or on one of its variants (type-refining fragment). - // if fragment.on.name() == selection_on { - // // The fragment is on the union/interface itself. - // fragment.selection.selected_variants_on_union_inner( - // context, - // selected_variants, - // selection_on, - // )?; - // } else { - // // Type-refining fragment - // selected_variants - // .entry(fragment.on.name()) - // .and_modify(|entry| entry.0.extend(fragment.selection.0.clone())) - // .or_insert_with(|| { - // let mut items = Vec::with_capacity(fragment.selection.0.len()); - // items.extend(fragment.selection.0.clone()); - // Selection(items) - // }); - // } - // } - // } - // } - - // Ok(()) - } - - /// This method should only be invoked on selections on union and interface fields. It returns a map from the name of the selected variants to the corresponding selections. - /// - /// Importantly, it will "flatten" the fragments and handle multiple selections of the same variant. - /// - /// The `context` argument is required so we can expand the fragments. - pub(crate) fn selected_variants_on_union<'s>( - &'s self, - context: &'s crate::query::QueryContext<'_>, - // the name of the type the selection applies to - selection_on: &str, - ) -> Result>, anyhow::Error> { - let mut selected_variants = BTreeMap::new(); - - self.selected_variants_on_union_inner(context, &mut selected_variants, selection_on)?; - - Ok(selected_variants) - } - - #[cfg(test)] - pub(crate) fn new_empty() -> Selection<'static> { - Selection(Vec::new()) - } - - #[cfg(test)] - pub(crate) fn from_vec(vec: Vec>) -> Self { - Selection(vec) - } - - pub(crate) fn contains_fragment(&self, fragment_name: &str) -> bool { - (&self).into_iter().any(|item| match item { - SelectionItem::Field(field) => field.fields.contains_fragment(fragment_name), - SelectionItem::InlineFragment(inline_fragment) => { - inline_fragment.fields.contains_fragment(fragment_name) - } - SelectionItem::FragmentSpread(fragment) => fragment.fragment_name == fragment_name, - }) - } - - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - pub(crate) fn require_items(&self, context: &crate::query::QueryContext<'query>) { - // self.0.iter().for_each(|item| { - // if let SelectionItem::FragmentSpread(SelectionFragmentSpread { fragment_name }) = item { - // context.require_fragment(fragment_name); - // } - // }) - todo!("require_items"); - } -} - -impl<'query> std::convert::From<&'query SelectionSet> for Selection<'query> { - fn from(selection_set: &SelectionSet) -> Selection<'_> { - use graphql_parser::query::Selection; - - let mut items = Vec::with_capacity(selection_set.items.len()); - - for item in &selection_set.items { - let converted = match item { - Selection::Field(f) => SelectionItem::Field(SelectionField { - alias: f.alias.as_deref(), - name: &f.name, - fields: (&f.selection_set).into(), - }), - Selection::FragmentSpread(spread) => { - SelectionItem::FragmentSpread(SelectionFragmentSpread { - fragment_name: &spread.fragment_name, - }) - } - Selection::InlineFragment(inline) => { - let graphql_parser::query::TypeCondition::On(ref name) = inline - .type_condition - .as_ref() - .expect("Missing `on` clause."); - SelectionItem::InlineFragment(SelectionInlineFragment { - on: &name, - fields: (&inline.selection_set).into(), - }) - } - }; - items.push(converted); - } - - Selection(items) - } -} - -impl<'a, 'query> std::iter::IntoIterator for &'a Selection<'query> { - type Item = &'a SelectionItem<'query>; - type IntoIter = std::slice::Iter<'a, SelectionItem<'query>>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a> std::iter::FromIterator> for Selection<'a> { - fn from_iter>>(iter: T) -> Selection<'a> { - Selection(iter.into_iter().collect()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn selection_extract_typename_simple_case() { - let selection = Selection::new_empty(); - let schema = crate::schema::Schema::new(); - let context = crate::query::QueryContext::new_empty(&schema); - - assert!(selection.extract_typename(&context).is_none()); - } - - #[test] - fn selection_extract_typename_in_fragment() { - let mut selection = Selection::new_empty(); - selection - .0 - .push(SelectionItem::FragmentSpread(SelectionFragmentSpread { - fragment_name: "MyFragment", - })); - - let mut fragment_selection = Selection::new_empty(); - fragment_selection - .0 - .push(SelectionItem::Field(SelectionField { - alias: None, - name: "__typename", - fields: Selection::new_empty(), - })); - - let schema = crate::schema::Schema::new(); - let obj = crate::objects::GqlObject::new("MyObject", None); - let mut context = crate::query::QueryContext::new_empty(&schema); - context.fragments.insert( - "MyFragment", - crate::fragments::GqlFragment { - name: "MyFragment", - on: crate::fragments::FragmentTarget::Object(&obj), - selection: fragment_selection, - is_required: std::cell::Cell::new(false), - }, - ); - - assert!(selection.extract_typename(&context).is_some()); - } - - #[test] - fn selection_from_graphql_parser_selection_set() { - let query = r##" - query { - animal { - isCat - isHorse - ...Timestamps - barks - ...on Dog { - rating - } - pawsCount - aliased: sillyName - } - } - "##; - let parsed = graphql_parser::parse_query(query).unwrap(); - let selection_set: &graphql_parser::query::SelectionSet = parsed - .definitions - .iter() - .filter_map(|def| { - if let graphql_parser::query::Definition::Operation( - graphql_parser::query::OperationDefinition::Query(q), - ) = def - { - Some(&q.selection_set) - } else { - None - } - }) - .next() - .unwrap(); - - let selection: Selection<'_> = selection_set.into(); - - assert_eq!( - selection, - Selection(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "animal", - fields: Selection(vec![ - SelectionItem::Field(SelectionField { - alias: None, - name: "isCat", - fields: Selection(Vec::new()), - }), - SelectionItem::Field(SelectionField { - alias: None, - name: "isHorse", - fields: Selection(Vec::new()), - }), - SelectionItem::FragmentSpread(SelectionFragmentSpread { - fragment_name: "Timestamps", - }), - SelectionItem::Field(SelectionField { - alias: None, - name: "barks", - fields: Selection(Vec::new()), - }), - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "Dog", - fields: Selection(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "rating", - fields: Selection(Vec::new()), - })]), - }), - SelectionItem::Field(SelectionField { - alias: None, - name: "pawsCount", - fields: Selection(Vec::new()), - }), - SelectionItem::Field(SelectionField { - alias: Some("aliased"), - name: "sillyName", - fields: Selection(Vec::new()), - }), - ]), - })]) - ); - } -} diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index c6b8dd19a..9de734f8c 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -6,17 +6,6 @@ use quote::quote; use std::cell::Cell; use std::collections::BTreeSet; -/// A GraphQL union (simplified schema representation). -/// -/// For code generation purposes, unions will "flatten" fragment spreads, so there is only one enum for the selection. See the tests in the graphql_client crate for examples. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct GqlUnion<'schema> { - pub name: &'schema str, - pub description: Option<&'schema str>, - pub variants: BTreeSet<&'schema str>, - pub is_required: Cell, -} - #[derive(Debug, Fail)] #[fail(display = "UnionError")] enum UnionError { @@ -28,9 +17,6 @@ enum UnionError { MissingTypename { union_name: String }, } -type UnionVariantResult<'selection> = - Result<(Vec, Vec, Vec<&'selection str>), anyhow::Error>; - /// Returns a triple. /// /// - The first element is the union variants to be inserted directly into the `enum` declaration. @@ -41,7 +27,7 @@ pub(crate) fn union_variants<'selection>( context: &'selection QueryContext<'selection>, prefix: &str, selection_on: &str, -) -> UnionVariantResult<'selection> { +) -> ! { todo!() // let selection = selection.selected_variants_on_union(context, selection_on)?; // let mut used_variants: Vec<&str> = selection.keys().cloned().collect(); diff --git a/graphql_client_codegen/src/variables.rs b/graphql_client_codegen/src/variables.rs deleted file mode 100644 index af344f35a..000000000 --- a/graphql_client_codegen/src/variables.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::field_type::FieldType; -use crate::query::QueryContext; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; -use std::collections::BTreeMap; - -#[derive(Debug, Clone)] -pub struct Variable<'query> { - pub name: &'query str, - pub ty: FieldType<'query>, - pub default: Option<&'query graphql_parser::query::Value>, -} - -impl<'query> Variable<'query> { - pub(crate) fn generate_default_value_constructor( - &self, - context: &QueryContext<'_>, - schema: &crate::schema::Schema, - ) -> Option { - todo!("generate default value constructor") - // // TODO - // // context.schema.require(&self.ty.inner_name_str()); - // match &self.default { - // Some(default) => { - // let fn_name = Ident::new(&format!("default_{}", self.name), Span::call_site()); - // let ty = self.ty.to_rust(context, ""); - // let value = graphql_parser_value_to_literal( - // default, - // context, - // &self.ty, - // self.ty.is_optional(), - // ); - // Some(quote! { - // pub fn #fn_name() -> #ty { - // #value - // } - - // }) - // } - // None => None, - // } - } -} - -impl<'query> std::convert::From<&'query graphql_parser::query::VariableDefinition> - for Variable<'query> -{ - fn from(def: &'query graphql_parser::query::VariableDefinition) -> Variable<'query> { - Variable { - name: &def.name, - ty: FieldType::from(&def.var_type), - default: def.default_value.as_ref(), - } - } -} From ec9fb52783bd9c13fa67f1494898975da2434ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 22:48:35 +0100 Subject: [PATCH 63/93] Delete file committed by mistake --- graphql_client/tests/json_schema/query.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 graphql_client/tests/json_schema/query.rs diff --git a/graphql_client/tests/json_schema/query.rs b/graphql_client/tests/json_schema/query.rs deleted file mode 100644 index ab98aad6e..000000000 --- a/graphql_client/tests/json_schema/query.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct WithSchema1 ; pub mod with_schema1 { # ! [ allow ( dead_code ) ] pub const OPERATION_NAME : & 'static str = "WithSchema1" ; pub const QUERY : & 'static str = "query WithSchema1 {\n currentSession {\n accountId\n }\n}\n" ; use serde :: { Serialize , Deserialize } ; # [ allow ( dead_code ) ] type Boolean = bool ; # [ allow ( dead_code ) ] type Float = f64 ; # [ allow ( dead_code ) ] type Int = i64 ; # [ allow ( dead_code ) ] type ID = String ; type Uuid = super :: Uuid ; # [ derive ( Serialize ) ] pub struct Variables ; # [ derive ( Deserialize ) ] pub struct ResponseData { # [ serde ( rename = "currentSession" ) ] # [ deprecated ] pub current_session : Option < WithSchema1CurrentSession > , } # [ derive ( Deserialize ) ] pub struct WithSchema1CurrentSession { # [ serde ( rename = "accountId" ) ] # [ deprecated ] pub account_id : Uuid , } } impl graphql_client :: GraphQLQuery for WithSchema1 { type Variables = with_schema1 :: Variables ; type ResponseData = with_schema1 :: ResponseData ; fn build_query ( variables : Self :: Variables ) -> :: graphql_client :: QueryBody < Self :: Variables > { graphql_client :: QueryBody { variables , query : with_schema1 :: QUERY , operation_name : with_schema1 :: OPERATION_NAME , } } } \ No newline at end of file From 0bed50887b26491205767d988cc9a3956b163738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 22:54:51 +0100 Subject: [PATCH 64/93] Delete more dead code --- graphql_client_codegen/src/codegen/enums.rs | 2 +- graphql_client_codegen/src/query.rs | 226 -------------------- 2 files changed, 1 insertion(+), 227 deletions(-) delete mode 100644 graphql_client_codegen/src/query.rs diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index 841c31681..b25f2051e 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -19,7 +19,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( let derives = render_derives( options .all_response_derives() - .filter(|d| !&["Serialize", "Deserialize"].contains(d)), + .filter(|d| !&["Serialize", "Deserialize", "Default"].contains(d)), ); let normalization = options.normalization(); diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs deleted file mode 100644 index 96db1b023..000000000 --- a/graphql_client_codegen/src/query.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::deprecation::DeprecationStrategy; -use crate::fragments::GqlFragment; -use crate::normalization::Normalization; -use crate::schema::{EnumId, InputId, ScalarId, Schema, StoredFieldId, TypeId}; -use crate::selection::Selection; -use failure::*; -use proc_macro2::Span; -use proc_macro2::TokenStream; -use quote::quote; -use std::collections::{BTreeMap, BTreeSet}; - -use syn::Ident; - -#[derive(Debug, Clone, Copy)] -pub(crate) struct FragmentId(usize); - -// struct Q { -// operation: &'static str, -// selection: IdSelection, -// } - -/// This holds all the information we need during the code generation phase. -struct QueryContext<'query> { - pub fragments: BTreeMap<&'query str, GqlFragment<'query>>, - pub schema: &'query Schema, - pub deprecation_strategy: DeprecationStrategy, - pub normalization: Normalization, - variables_derives: Vec, - response_derives: Vec, - used_enums: Vec, - used_input_objects: Vec, - used_fragments: Vec, - used_scalars: Vec, -} - -impl<'query, 'schema> QueryContext<'query> { - /// Create a QueryContext with the given Schema. - pub(crate) fn new( - schema: &'query Schema, - deprecation_strategy: DeprecationStrategy, - normalization: Normalization, - ) -> QueryContext<'query> { - QueryContext { - fragments: BTreeMap::new(), - schema, - deprecation_strategy, - normalization, - variables_derives: vec![Ident::new("Serialize", Span::call_site())], - response_derives: vec![Ident::new("Deserialize", Span::call_site())], - used_enums: Vec::new(), - used_input_objects: Vec::new(), - used_fragments: Vec::new(), - used_scalars: Vec::new(), - } - } - - // pub(crate) fn resolve_query(query: &graphql_parser::query::Document) -> ResolvedQuery { - // todo!("resolve query") - // } - - /// Mark a fragment as required, so code is actually generated for it. - pub(crate) fn require_fragment(&mut self, id: FragmentId) { - self.used_fragments.push(id); - } - - /// For testing only. creates an empty QueryContext. - #[cfg(test)] - pub(crate) fn new_empty() -> QueryContext<'query> { - QueryContext { - fragments: BTreeMap::new(), - // schema, - deprecation_strategy: DeprecationStrategy::Allow, - normalization: Normalization::None, - variables_derives: vec![Ident::new("Serialize", Span::call_site())], - response_derives: vec![Ident::new("Deserialize", Span::call_site())], - } - } - - /// Expand the deserialization data structures for the given field. - pub(crate) fn maybe_expand_field( - &self, - ty: &str, - selection: &Selection<'_>, - prefix: &str, - ) -> anyhow::Result> { - unimplemented!() - // if self.schema.contains_scalar(ty) { - // Ok(None) - // } else if let Some(enm) = self.schema.enums.get(ty) { - // enm.is_required.set(true); - // Ok(None) // we already expand enums separately - // } else if let Some(obj) = self.schema.objects.get(ty) { - // obj.is_required.set(true); - // obj.response_for_selection(self, &selection, prefix) - // .map(Some) - // } else if let Some(iface) = self.schema.interfaces.get(ty) { - // iface.is_required.set(true); - // iface - // .response_for_selection(self, &selection, prefix) - // .map(Some) - // } else if let Some(unn) = self.schema.unions.get(ty) { - // unn.is_required.set(true); - // unn.response_for_selection(self, &selection, prefix) - // .map(Some) - // } else { - // Err(format_err!("Unknown type: {}", ty)) - // } - } - - pub(crate) fn ingest_response_derives(&mut self, attribute_value: &str) -> anyhow::Result<()> { - if self.response_derives.len() > 1 { - return Err(format_err!( - "ingest_response_derives should only be called once" - )); - } - - self.response_derives.extend( - attribute_value - .split(',') - .map(str::trim) - .map(|s| Ident::new(s, Span::call_site())), - ); - Ok(()) - } - - pub(crate) fn ingest_variables_derives(&mut self, attribute_value: &str) -> anyhow::Result<()> { - if self.variables_derives.len() > 1 { - return Err(format_err!( - "ingest_variables_derives should only be called once" - )); - } - - self.variables_derives.extend( - attribute_value - .split(',') - .map(str::trim) - .map(|s| Ident::new(s, Span::call_site())), - ); - Ok(()) - } - - pub(crate) fn variables_derives(&self) -> TokenStream { - let derives: BTreeSet<&Ident> = self.variables_derives.iter().collect(); - let derives = derives.iter(); - - quote! { - #[derive( #(#derives),* )] - } - } - - pub(crate) fn response_enum_derives(&self) -> TokenStream { - let always_derives = [ - Ident::new("Eq", Span::call_site()), - Ident::new("PartialEq", Span::call_site()), - ]; - let mut enum_derives: BTreeSet<_> = self - .response_derives - .iter() - .filter(|derive| { - // Do not apply the "Default" derive to enums. - let derive = derive.to_string(); - derive != "Serialize" && derive != "Deserialize" && derive != "Default" - }) - .collect(); - enum_derives.extend(always_derives.iter()); - quote! { - #[derive( #(#enum_derives),* )] - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn response_derives_ingestion_works() { - let schema = crate::schema::Schema::new(); - let mut context = QueryContext::new_empty(&schema); - - context - .ingest_response_derives("PartialEq, PartialOrd, Serialize") - .unwrap(); - - assert_eq!( - context.response_derives().to_string(), - "# [ derive ( Deserialize , PartialEq , PartialOrd , Serialize ) ]" - ); - } - - #[test] - fn response_enum_derives_does_not_produce_empty_list() { - let schema = crate::schema::Schema::new(); - let context = QueryContext::new_empty(&schema); - assert_eq!( - context.response_enum_derives().to_string(), - "# [ derive ( Eq , PartialEq ) ]" - ); - } - - #[test] - fn response_enum_derives_works() { - let schema = crate::schema::Schema::new(); - let mut context = QueryContext::new_empty(&schema); - - context - .ingest_response_derives("PartialEq, PartialOrd, Serialize") - .unwrap(); - - assert_eq!( - context.response_enum_derives().to_string(), - "# [ derive ( Eq , PartialEq , PartialOrd ) ]" - ); - } - - #[test] - fn response_derives_fails_when_called_twice() { - let schema = crate::schema::Schema::new(); - let mut context = QueryContext::new_empty(&schema); - - assert!(context - .ingest_response_derives("PartialEq, PartialOrd") - .is_ok()); - assert!(context.ingest_response_derives("Serialize").is_err()); - } -} From 6ab38467829019405674923398467a76df2150c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 22:58:33 +0100 Subject: [PATCH 65/93] Simplify normalization --- graphql_client_codegen/src/generated_module.rs | 5 +---- graphql_client_codegen/src/normalization.rs | 13 +------------ graphql_client_codegen/src/schema.rs | 9 --------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 2afe3afc7..f9b489b78 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,7 +1,4 @@ -use crate::{ - codegen_options::*, - resolution::{OperationId, OperationRef}, -}; +use crate::{codegen_options::*, resolution::OperationRef}; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; diff --git a/graphql_client_codegen/src/normalization.rs b/graphql_client_codegen/src/normalization.rs index a7bc72e95..7f8807575 100644 --- a/graphql_client_codegen/src/normalization.rs +++ b/graphql_client_codegen/src/normalization.rs @@ -1,4 +1,4 @@ -use heck::{CamelCase, SnakeCase}; +use heck::CamelCase; use std::borrow::Cow; /// Normalization conventions available for generated code. @@ -18,13 +18,6 @@ impl Normalization { } } - fn snake_case(self, name: &str) -> Cow<'_, str> { - match self { - Self::None => name.into(), - Self::Rust => name.to_snake_case().into(), - } - } - pub(crate) fn operation<'a>(self, op: &'a str) -> Cow<'a, str> { self.camel_case(op) } @@ -49,10 +42,6 @@ impl Normalization { self.field_type_impl(fty) } - pub(crate) fn field_name<'a>(self, fnm: &'a str) -> Cow<'a, str> { - self.snake_case(fnm) - } - pub(crate) fn input_name<'a>(self, inm: &'a str) -> Cow<'a, str> { self.camel_case(inm) } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 9dc3620eb..3639fe9df 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -96,15 +96,6 @@ pub(crate) struct StoredFieldType { pub(crate) qualifiers: Vec, } -impl StoredFieldType { - pub(crate) fn is_optional(&self) -> bool { - self.qualifiers - .get(0) - .map(|qualifier| !qualifier.is_required()) - .unwrap_or(true) - } -} - #[derive(Debug, Clone, PartialEq)] struct StoredUnion { name: String, From 4657dcbc5c263dbf6ba07488ede2a437bbd0289c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 23:05:01 +0100 Subject: [PATCH 66/93] Polish json conversion --- graphql_client_codegen/src/field_type.rs | 13 +- .../src/schema/json_conversion.rs | 278 +++++------------- 2 files changed, 73 insertions(+), 218 deletions(-) diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index 139207811..f8723a1fd 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -12,21 +12,10 @@ impl GraphqlTypeQualifier { } } -pub(crate) fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { +pub fn graphql_parser_depth(schema_type: &graphql_parser::schema::Type) -> usize { match schema_type { graphql_parser::schema::Type::ListType(inner) => 1 + graphql_parser_depth(inner), graphql_parser::schema::Type::NonNullType(inner) => 1 + graphql_parser_depth(inner), graphql_parser::schema::Type::NamedType(_) => 0, } } - -fn json_type_qualifiers_depth(typeref: &introspection_response::TypeRef) -> usize { - use graphql_introspection_query::introspection_response::*; - - match (typeref.kind.as_ref(), typeref.of_type.as_ref()) { - (Some(__TypeKind::NON_NULL), Some(inner)) => 1 + json_type_qualifiers_depth(inner), - (Some(__TypeKind::LIST), Some(inner)) => 1 + json_type_qualifiers_depth(inner), - (Some(_), None) => 0, - _ => panic!("Non-convertible type in JSON schema: {:?}", typeref), - } -} diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 66111b75f..77ba92cb8 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -3,223 +3,89 @@ use graphql_introspection_query::introspection_response::{ FullType, IntrospectionResponse, Schema as JsonSchema, TypeRef, __TypeKind, }; -pub(super) fn build_schema(src: IntrospectionResponse) -> super::Schema { - let converter = JsonSchemaConverter { - src: src.into_schema().schema.expect("could not find schema"), - schema: Schema::new(), - }; +pub(super) fn build_schema(src: IntrospectionResponse) -> Schema { + let mut src = src.into_schema().schema.expect("could not find schema"); + let mut schema = Schema::new(); + build_names_map(&mut src, &mut schema); + convert(&mut src, &mut schema); - converter.convert() + schema } -struct JsonSchemaConverter { - src: JsonSchema, - schema: Schema, +fn build_names_map(src: &mut JsonSchema, schema: &mut Schema) { + let mut names = &mut schema.names; + names.reserve(types_mut(src).count()); + + unions_mut(src) + .map(|u| u.name.as_ref().expect("union name")) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::union(idx)); + }); + + interfaces_mut(src) + .map(|iface| iface.name.as_ref().expect("interface name")) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::interface(idx)); + }); + + objects_mut(src) + .map(|obj| obj.name.as_ref().expect("object name")) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::object(idx)); + }); + + inputs_mut(src) + .map(|obj| obj.name.as_ref().expect("input name")) + .enumerate() + .for_each(|(idx, name)| { + names.insert(name.clone(), TypeId::input(idx)); + }); } -impl JsonSchemaConverter { - fn build_names_map(&mut self) { - self.schema.names.reserve(types_mut(&mut self.src).count()); - let names = &mut self.schema.names; - - unions_mut(&mut self.src) - .map(|u| u.name.as_ref().expect("union name")) - .enumerate() - .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::union(idx)); - }); - - interfaces_mut(&mut self.src) - .map(|iface| iface.name.as_ref().expect("interface name")) - .enumerate() - .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::interface(idx)); - }); - - objects_mut(&mut self.src) - .map(|obj| obj.name.as_ref().expect("object name")) - .enumerate() - .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::object(idx)); - }); - - inputs_mut(&mut self.src) - .map(|obj| obj.name.as_ref().expect("input name")) - .enumerate() - .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::input(idx)); - }); +fn convert(src: &mut JsonSchema, schema: &mut Schema) { + for scalar in scalars_mut(src) { + ingest_scalar(schema, scalar); } - fn convert(mut self) -> Schema { - self.build_names_map(); - let JsonSchemaConverter { - mut src, - mut schema, - } = self; - - for scalar in scalars_mut(&mut src) { - ingest_scalar(&mut schema, scalar); - } - - for enm in enums_mut(&mut src) { - ingest_enum(&mut schema, enm) - } + for enm in enums_mut(src) { + ingest_enum(schema, enm) + } - for interface in interfaces_mut(&mut src) { - ingest_interface(&mut schema, interface); - } + for interface in interfaces_mut(src) { + ingest_interface(schema, interface); + } - for object in objects_mut(&mut src) { - ingest_object(&mut schema, object); - } + for object in objects_mut(src) { + ingest_object(schema, object); + } - // for ty in src - // .types - // .as_ref() - // .expect("types in schema") - // .iter() - // .filter_map(|t| t.as_ref().map(|t| &t.full_type)) - // { - // let name: &str = ty - // .name - // .as_ref() - // .map(String::as_str) - // .expect("type definition name"); - - // match ty.kind { - // Some(__TypeKind::ENUM) => { - // // let variants: Vec> = ty - // // .enum_values - // // .as_ref() - // // .expect("enum variants") - // // .iter() - // // .map(|t| { - // // t.as_ref().map(|t| EnumVariant { - // // description: t.description.as_ref().map(String::as_str), - // // name: t - // // .name - // // .as_ref() - // // .map(String::as_str) - // // .expect("enum variant name"), - // // }) - // // }) - // // .filter_map(|t| t) - // // .collect(); - // // let enm = GqlEnum { - // // name, - // // description: ty.description.as_ref().map(String::as_str), - // // variants, - // // is_required: false.into(), - // // }; - // // schema.enums.insert(name, enm); - // } - // Some(__TypeKind::SCALAR) => { - // // if DEFAULT_SCALARS.iter().find(|s| s == &&name).is_none() { - // // schema.scalars.insert( - // // name, - // // Scalar { - // // name, - // // description: ty.description.as_ref().map(String::as_str), - // // is_required: false.into(), - // // }, - // // ); - // // } - // } - // Some(__TypeKind::UNION) => { - // // let variants: BTreeSet<&str> = ty - // // .possible_types - // // .as_ref() - // // .unwrap() - // // .iter() - // // .filter_map(|t| { - // // t.as_ref() - // // .and_then(|t| t.type_ref.name.as_ref().map(String::as_str)) - // // }) - // // .collect(); - // // schema.unions.insert( - // // name, - // // GqlUnion { - // // name: ty.name.as_ref().map(String::as_str).expect("unnamed union"), - // // description: ty.description.as_ref().map(String::as_str), - // // variants, - // // is_required: false.into(), - // // }, - // // ); - // } - // Some(__TypeKind::OBJECT) => { - // // for implementing in ty - // // .interfaces - // // .as_ref() - // // .map(Vec::as_slice) - // // .unwrap_or_else(|| &[]) - // // .iter() - // // .filter_map(Option::as_ref) - // // .map(|t| &t.type_ref.name) - // // { - // // interface_implementations - // // .entry( - // // implementing - // // .as_ref() - // // .map(String::as_str) - // // .expect("interface name"), - // // ) - // // .and_modify(|objects| objects.push(name)) - // // .or_insert_with(|| vec![name]); - // // } - - // // schema - // // .objects - // // .insert(name, GqlObject::from_introspected_schema_json(ty)); - // } - // Some(__TypeKind::INTERFACE) => { - // // let mut iface = - // // GqlInterface::new(name, ty.description.as_ref().map(String::as_str)); - // // iface.fields.extend( - // // ty.fields - // // .as_ref() - // // .expect("interface fields") - // // .iter() - // // .filter_map(Option::as_ref) - // // .map(|f| GqlObjectField { - // // description: f.description.as_ref().map(String::as_str), - // // name: f.name.as_ref().expect("field name").as_str(), - // // type_: FieldType::from(f.type_.as_ref().expect("field type")), - // // deprecation: DeprecationStatus::Current, - // // }), - // // ); - // // schema.interfaces.insert(name, iface); - // } - // Some(__TypeKind::INPUT_OBJECT) => { - // // schema.inputs.insert(name, GqlInput::from(ty)); - // } - // _ => unimplemented!("unimplemented definition"), - // } - // } - - // Define the root operations. - { - schema.query_type = src - .query_type - .as_mut() - .and_then(|n| n.name.as_mut()) - .and_then(|n| schema.names.get(n)) - .and_then(|id| id.as_object_id()); - schema.mutation_type = src - .mutation_type - .as_mut() - .and_then(|n| n.name.as_mut()) - .and_then(|n| schema.names.get(n)) - .and_then(|id| id.as_object_id()); - schema.subscription_type = src - .mutation_type - .as_mut() - .and_then(|n| n.name.as_mut()) - .and_then(|n| schema.names.get(n)) - .and_then(|id| id.as_object_id()); - } + for unn in unions_mut(src) { + ingest_union(schema, unn) + } - schema + // Define the root operations. + { + schema.query_type = src + .query_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.mutation_type = src + .mutation_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); + schema.subscription_type = src + .mutation_type + .as_mut() + .and_then(|n| n.name.as_mut()) + .and_then(|n| schema.names.get(n)) + .and_then(|id| id.as_object_id()); } } From 5ebd4f1012e04756567cf05464df4a70a3856434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 23:14:34 +0100 Subject: [PATCH 67/93] Delete more dead code --- .../src/codegen/selection.rs | 6 - graphql_client_codegen/src/lib.rs | 10 +- graphql_client_codegen/src/schema.rs | 10 -- .../src/schema/graphql_parser_conversion.rs | 2 +- graphql_client_codegen/src/shared.rs | 150 ------------------ 5 files changed, 2 insertions(+), 176 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index e8456425b..afc7f00fb 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -10,7 +10,6 @@ use crate::shared::field_rename_annotation; use crate::{ deprecation::DeprecationStrategy, field_type::GraphqlTypeQualifier, - // deprecation::DeprecationStrategy, resolution::{InlineFragment, OperationRef, ResolvedQuery, Selection, SelectionId}, schema::{Schema, TypeId}, shared::keyword_replace, @@ -36,7 +35,6 @@ pub(crate) fn render_response_data_fields<'a>( let response_data_type_id = expanded_selection.push_type(ExpandedType { name: Cow::Borrowed("ResponseData"), - schema_type: operation.on_ref(), }); calculate_selection( @@ -65,7 +63,6 @@ pub(super) fn render_fragment<'a>( let response_type_id = expanded_selection.push_type(ExpandedType { name: fragment.name().into(), - schema_type: fragment.on_ref(), }); calculate_selection( @@ -179,7 +176,6 @@ fn calculate_selection<'a>( let expanded_type = ExpandedType { name: variant_struct_name_str.into(), - schema_type: variant_schema_type, }; let struct_id = context.push_type(expanded_type); @@ -278,7 +274,6 @@ fn calculate_selection<'a>( let type_id = context.push_type(ExpandedType { name: Cow::Owned(struct_name_string), - schema_type: field_type, }); calculate_selection( @@ -409,7 +404,6 @@ impl<'a> ExpandedVariant<'a> { pub(crate) struct ExpandedType<'a> { name: Cow<'a, str>, - schema_type: TypeRef<'a>, } pub(crate) struct ExpandedSelection<'a> { diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 6d88d1905..c769c5534 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -15,27 +15,19 @@ mod codegen; mod codegen_options; /// Deprecation-related code pub mod deprecation; -// mod query; /// Contains the [Schema] type and its implementation. pub mod schema; mod constants; -// mod enums; mod field_type; -// mod fragments; mod generated_module; -// mod inputs; -// mod interfaces; /// Normalization-related code pub mod normalization; -// mod objects; mod operations; mod resolution; -// mod selection; mod shared; -// mod unions; -// mod variables; +// TODO: uncomment this // #[cfg(test)] // mod tests; diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 3639fe9df..d04bb5489 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -68,12 +68,6 @@ pub(crate) struct EnumId(usize); #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct InputId(usize); -impl InputId { - fn new(idx: usize) -> Self { - InputId(idx) - } -} - #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct StoredFieldId(usize); @@ -190,10 +184,6 @@ impl<'a> EnumRef<'a> { } impl TypeId { - fn scalar(id: usize) -> Self { - TypeId::Scalar(ScalarId(id)) - } - fn r#enum(id: usize) -> Self { TypeId::Enum(EnumId(id)) } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 0493d146e..418dea736 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -1,4 +1,4 @@ -use super::{InputId, Schema, StoredInputFieldType, TypeId}; +use super::{Schema, StoredInputFieldType, TypeId}; use crate::schema::resolve_field_type; use graphql_parser::schema::{self as parser, Definition, Document, TypeDefinition, UnionType}; diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 3f4136a0e..149e117e7 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -1,9 +1,3 @@ -// use crate::deprecation::{DeprecationStatus, DeprecationStrategy}; -// use crate::objects::GqlObjectField; -// use crate::query::QueryContext; -// use crate::selection::*; -// use failure::*; -// use heck::{CamelCase, SnakeCase}; use proc_macro2::TokenStream; use quote::quote; use std::borrow::Cow; @@ -79,150 +73,6 @@ pub(crate) fn keyword_replace<'a>(needle: impl Into>) -> Cow<'a, st } } -// pub(crate) fn render_object_field( -// field_name: &str, -// field_type: &TokenStream, -// description: Option<&str>, -// status: &DeprecationStatus, -// strategy: &DeprecationStrategy, -// ) -> Option { -// #[allow(unused_assignments)] -// let mut deprecation = quote!(); -// match (status, strategy) { -// // If the field is deprecated and we are denying usage, don't generate the -// // field in rust at all and short-circuit. -// (DeprecationStatus::Deprecated(_), DeprecationStrategy::Deny) => return None, -// // Everything is allowed so there is nothing to do. -// (_, DeprecationStrategy::Allow) => deprecation = quote!(), -// // Current so there is nothing to do. -// (DeprecationStatus::Current, _) => deprecation = quote!(), -// // A reason was provided, translate it to a note. -// (DeprecationStatus::Deprecated(Some(reason)), DeprecationStrategy::Warn) => { -// deprecation = quote!(#[deprecated(note = #reason)]) -// } -// // No reason provided, just mark as deprecated. -// (DeprecationStatus::Deprecated(None), DeprecationStrategy::Warn) => { -// deprecation = quote!(#[deprecated]) -// } -// }; - -// let description = description.map(|s| quote!(#[doc = #s])); -// let rust_safe_field_name = keyword_replace(&field_name.to_snake_case()); -// let name_ident = Ident::new(&rust_safe_field_name, Span::call_site()); -// let rename = crate::shared::field_rename_annotation(&field_name, &rust_safe_field_name); - -// Some(quote!(#description #deprecation #rename pub #name_ident: #field_type)) -// } - -// pub(crate) fn field_impls_for_selection( -// fields: &[GqlObjectField<'_>], -// context: &QueryContext<'_>, -// selection: &Selection<'_>, -// prefix: &str, -// ) -> anyhow::Result> { -// todo!("field_impls_for_selection") -// // (&selection) -// // .into_iter() -// // .map(|selected| { -// // if let SelectionItem::Field(selected) = selected { -// // let name = &selected.name; -// // let alias = selected.alias.as_ref().unwrap_or(name); - -// // let ty = fields -// // .iter() -// // .find(|f| &f.name == name) -// // .ok_or_else(|| format_err!("could not find field `{}`", name))? -// // .type_ -// // .inner_name_str(); -// // let prefix = format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()); -// // context.maybe_expand_field(&ty, &selected.fields, &prefix) -// // } else { -// // Ok(None) -// // } -// // }) -// // .filter_map(|i| i.transpose()) -// // .collect() -// } - -// pub(crate) fn response_fields_for_selection( -// type_name: &str, -// schema_fields: &[GqlObjectField<'_>], -// context: &QueryContext<'_>, -// selection: &Selection<'_>, -// prefix: &str, -// ) -> anyhow::Result> { -// todo!("response fields for selection") -// // (&selection) -// // .into_iter() -// // .map(|item| match item { -// // SelectionItem::Field(f) => { -// // let name = &f.name; -// // let alias = f.alias.as_ref().unwrap_or(name); - -// // let schema_field = &schema_fields -// // .iter() -// // .find(|field| &field.name == name) -// // .ok_or_else(|| { -// // format_err!( -// // "Could not find field `{}` on `{}`. Available fields: `{}`.", -// // *name, -// // type_name, -// // schema_fields -// // .iter() -// // .map(|ref field| &field.name) -// // .fold(String::new(), |mut acc, item| { -// // acc.push_str(item); -// // acc.push_str(", "); -// // acc -// // }) -// // .trim_end_matches(", ") -// // ) -// // })?; -// // let ty = schema_field.type_.to_rust( -// // context, -// // &format!("{}{}", prefix.to_camel_case(), alias.to_camel_case()), -// // ); - -// // Ok(render_object_field( -// // alias, -// // &ty, -// // schema_field.description.as_ref().cloned(), -// // &schema_field.deprecation, -// // &context.deprecation_strategy, -// // )) -// // } -// // SelectionItem::FragmentSpread(fragment) => { -// // let field_name = -// // Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site()); -// // context.require_fragment(&fragment.fragment_name); -// // let fragment_from_context = context -// // .fragments -// // .get(&fragment.fragment_name) -// // .ok_or_else(|| format_err!("Unknown fragment: {}", &fragment.fragment_name))?; -// // let type_name = Ident::new(&fragment.fragment_name, Span::call_site()); -// // let type_name = if fragment_from_context.is_recursive() { -// // quote!(Box<#type_name>) -// // } else { -// // quote!(#type_name) -// // }; -// // Ok(Some(quote! { -// // #[serde(flatten)] -// // pub #field_name: #type_name -// // })) -// // } -// // SelectionItem::InlineFragment(_) => Err(format_err!( -// // "unimplemented: inline fragment on object field" -// // )), -// // }) -// // .filter_map(|x| match x { -// // // Remove empty fields so callers always know a field has some -// // // tokens. -// // Ok(f) => f.map(Ok), -// // Err(err) => Some(Err(err)), -// // }) -// // .collect() -// } - /// Given the GraphQL schema name for an object/interface/input object field and /// the equivalent rust name, produces a serde annotation to map them during /// (de)serialization if it is necessary, otherwise an empty TokenStream. From 19cb078e1c04e41e245730171ba78fd3b00e73f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Fri, 20 Mar 2020 23:41:05 +0100 Subject: [PATCH 68/93] Reuse previous helpful error messages --- graphql_client_codegen/src/resolution.rs | 13 +++++-------- graphql_client_codegen/src/schema.rs | 8 -------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/resolution.rs index edba9fe53..cec1c4dc0 100644 --- a/graphql_client_codegen/src/resolution.rs +++ b/graphql_client_codegen/src/resolution.rs @@ -247,13 +247,6 @@ pub(crate) enum Selection { } impl Selection { - pub(crate) fn as_inline_fragment(&self) -> Option<&InlineFragment> { - match self { - Selection::InlineFragment(inline_fragment) => Some(inline_fragment), - _ => None, - } - } - pub(crate) fn subselection(&self) -> &[SelectionId] { match self { Selection::Field(field) => field.selection_set.as_slice(), @@ -336,6 +329,10 @@ pub(crate) fn resolve( ) => { let on = schema.subscription_type(); + if s.selection_set.items.len() != 1 { + anyhow::bail!("{}", crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR) + } + let resolved_operation: ResolvedOperation = ResolvedOperation { name: s .name @@ -351,7 +348,7 @@ pub(crate) fn resolve( } graphql_parser::query::Definition::Operation( graphql_parser::query::OperationDefinition::SelectionSet(_), - ) => unreachable!("unnamed queries are not supported"), + ) => anyhow::bail!("{}", crate::constants::SELECTION_SET_AT_ROOT), } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index d04bb5489..9c45d9390 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -355,14 +355,6 @@ impl Schema { } } - fn get_object_mut(&mut self, object_id: ObjectId) -> &mut StoredObject { - self.stored_objects.get_mut(object_id.0).unwrap() - } - - fn get_interface_mut(&mut self, id: InterfaceId) -> &mut StoredInterface { - self.stored_interfaces.get_mut(id.0).unwrap() - } - fn push_object(&mut self, object: StoredObject) -> ObjectId { let id = ObjectId(self.stored_objects.len()); self.stored_objects.push(object); From f14907848fa86481a8adfa58c8a9ff54dfff934e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 00:35:33 +0100 Subject: [PATCH 69/93] Fix remaining warnings --- graphql_client_cli/Cargo.toml | 2 +- graphql_client_cli/src/introspect_schema.rs | 2 +- graphql_client_codegen/src/field_type.rs | 2 - graphql_client_codegen/src/lib.rs | 5 +- graphql_client_codegen/src/schema.rs | 17 ------ .../src/schema/json_conversion.rs | 2 +- graphql_client_codegen/src/tests/github.rs | 59 ++++++++++--------- 7 files changed, 35 insertions(+), 54 deletions(-) diff --git a/graphql_client_cli/Cargo.toml b/graphql_client_cli/Cargo.toml index 7857e0b25..d654704cb 100644 --- a/graphql_client_cli/Cargo.toml +++ b/graphql_client_cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" reqwest = "^0.9" -graphql_client = { version = "0.9.0", path = "../graphql_client" } +graphql_client = { version = "0.9.0", path = "../graphql_client", features = [] } graphql_client_codegen = { path = "../graphql_client_codegen/", version = "0.9.0" } structopt = "0.3" serde = { version = "^1.0", features = ["derive"] } diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index 2f4d9dc89..4954fb463 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -24,7 +24,7 @@ pub fn introspect_schema( let out: Box = match output { Some(path) => Box::new(::std::fs::File::create(path)?), - None => Box::new(::std::io::stdout()), + None => Box::new(std::io::stdout()), }; let request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody { diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index f8723a1fd..0803eb82e 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -1,5 +1,3 @@ -use graphql_introspection_query::introspection_response; - #[derive(Clone, Debug, PartialEq, Hash)] pub(crate) enum GraphqlTypeQualifier { Required, diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index c769c5534..fadbb137f 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -27,9 +27,8 @@ mod operations; mod resolution; mod shared; -// TODO: uncomment this -// #[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; pub use crate::codegen_options::{CodegenMode, GraphQLClientCodegenOptions}; diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 9c45d9390..b89a0eaf6 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -301,12 +301,6 @@ struct StoredInputType { fields: Vec<(String, StoredInputFieldType)>, } -#[derive(Debug, Clone, Copy, PartialEq)] -enum InputFieldTypeId { - Scalar(ScalarId), - InputObject(InputId), -} - /// Intermediate representation for a parsed GraphQL schema used during code generation. #[derive(Debug, Clone, PartialEq)] pub(crate) struct Schema { @@ -598,17 +592,6 @@ impl<'a> FieldRef<'a> { self.field().r#type.id } - pub(crate) fn is_deprecated(&self) -> bool { - self.field().deprecation.is_some() - } - - pub(crate) fn deprecation_message(&self) -> Option<&'a str> { - self.field() - .deprecation - .as_ref() - .and_then(|item| item.as_ref().map(String::as_str)) - } - pub(crate) fn deprecation(&self) -> Option> { self.field() .deprecation diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 77ba92cb8..39fb1a313 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -13,7 +13,7 @@ pub(super) fn build_schema(src: IntrospectionResponse) -> Schema { } fn build_names_map(src: &mut JsonSchema, schema: &mut Schema) { - let mut names = &mut schema.names; + let names = &mut schema.names; names.reserve(types_mut(src).count()); unions_mut(src) diff --git a/graphql_client_codegen/src/tests/github.rs b/graphql_client_codegen/src/tests/github.rs index 7a72fa388..78823e1c0 100644 --- a/graphql_client_codegen/src/tests/github.rs +++ b/graphql_client_codegen/src/tests/github.rs @@ -13,33 +13,34 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { let json = Schema::from(&json); let gql = Schema::from(&graphql_parser_schema); - assert_eq!(json.scalars, gql.scalars); - for (json, gql) in json.objects.iter().zip(gql.objects.iter()) { - for (j, g) in json.1.fields.iter().zip(gql.1.fields.iter()) { - assert_eq!(j, g); - } - assert_eq!(json, gql) - } - for (json, gql) in json.unions.iter().zip(gql.unions.iter()) { - assert_eq!(json, gql) - } - for (json, gql) in json.interfaces.iter().zip(gql.interfaces.iter()) { - assert_eq!(json, gql) - } - assert_eq!(json.interfaces, gql.interfaces); - assert_eq!(json.query_type, gql.query_type); - assert_eq!(json.mutation_type, gql.mutation_type); - assert_eq!(json.subscription_type, gql.subscription_type); - for (json, gql) in json.inputs.iter().zip(gql.inputs.iter()) { - assert_eq!(json, gql); - } - assert_eq!(json.inputs, gql.inputs, "inputs differ"); - for ((json_name, json_value), (gql_name, gql_value)) in json.enums.iter().zip(gql.enums.iter()) - { - assert_eq!(json_name, gql_name); - assert_eq!( - HashSet::<&str>::from_iter(json_value.variants.iter().map(|v| v.name)), - HashSet::<&str>::from_iter(gql_value.variants.iter().map(|v| v.name)), - ); - } + assert_eq!(json.stored_scalars, gql.stored_scalars); + // TODO: reenable this + // for (json, gql) in json.objects.iter().zip(gql.objects.iter()) { + // for (j, g) in json.1.fields.iter().zip(gql.1.fields.iter()) { + // assert_eq!(j, g); + // } + // assert_eq!(json, gql) + // } + // for (json, gql) in json.unions.iter().zip(gql.unions.iter()) { + // assert_eq!(json, gql) + // } + // for (json, gql) in json.interfaces.iter().zip(gql.interfaces.iter()) { + // assert_eq!(json, gql) + // } + // assert_eq!(json.interfaces, gql.interfaces); + // assert_eq!(json.query_type, gql.query_type); + // assert_eq!(json.mutation_type, gql.mutation_type); + // assert_eq!(json.subscription_type, gql.subscription_type); + // for (json, gql) in json.inputs.iter().zip(gql.inputs.iter()) { + // assert_eq!(json, gql); + // } + // assert_eq!(json.inputs, gql.inputs, "inputs differ"); + // for ((json_name, json_value), (gql_name, gql_value)) in json.enums.iter().zip(gql.enums.iter()) + // { + // assert_eq!(json_name, gql_name); + // assert_eq!( + // HashSet::<&str>::from_iter(json_value.variants.iter().map(|v| v.name)), + // HashSet::<&str>::from_iter(gql_value.variants.iter().map(|v| v.name)), + // ); + // } } From 92c4403f2430cbd9fb12455cff7276de81fb4830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 00:41:01 +0100 Subject: [PATCH 70/93] Delete more dead code --- graphql_client_codegen/src/objects.rs | 228 -------------------------- graphql_client_codegen/src/unions.rs | 116 ------------- 2 files changed, 344 deletions(-) delete mode 100644 graphql_client_codegen/src/objects.rs diff --git a/graphql_client_codegen/src/objects.rs b/graphql_client_codegen/src/objects.rs deleted file mode 100644 index 46ff39dad..000000000 --- a/graphql_client_codegen/src/objects.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::constants::*; -use crate::deprecation::DeprecationStatus; -use crate::field_type::FieldType; -use crate::query::QueryContext; -// use crate::schema::Schema; -use crate::selection::*; -use crate::shared::{field_impls_for_selection, response_fields_for_selection}; -use graphql_parser::schema; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; -use std::cell::Cell; - -#[derive(Debug, Clone, PartialEq)] -pub struct GqlObject<'schema> { - pub description: Option<&'schema str>, - pub fields: Vec>, - pub name: &'schema str, - pub is_required: Cell, -} - -#[derive(Clone, Debug, PartialEq, Hash)] -pub struct GqlObjectField<'schema> { - pub description: Option<&'schema str>, - pub name: &'schema str, - pub type_: FieldType<'schema>, - pub deprecation: DeprecationStatus, -} - -fn parse_deprecation_info(field: &schema::Field) -> DeprecationStatus { - let deprecated = field - .directives - .iter() - .find(|x| x.name.to_lowercase() == "deprecated"); - let reason = if let Some(d) = deprecated { - if let Some((_, value)) = d.arguments.iter().find(|x| x.0.to_lowercase() == "reason") { - match value { - schema::Value::String(reason) => Some(reason.clone()), - schema::Value::Null => None, - _ => panic!("deprecation reason is not a string"), - } - } else { - None - } - } else { - None - }; - match deprecated { - Some(_) => DeprecationStatus::Deprecated(reason), - None => DeprecationStatus::Current, - } -} - -impl<'schema> GqlObject<'schema> { - pub fn new(name: &'schema str, description: Option<&'schema str>) -> GqlObject<'schema> { - GqlObject { - description, - name, - fields: vec![typename_field()], - is_required: false.into(), - } - } - - pub fn from_graphql_parser_object(obj: &'schema schema::ObjectType) -> Self { - let description = obj.description.as_deref(); - let mut item = GqlObject::new(&obj.name, description); - item.fields.extend(obj.fields.iter().map(|f| { - let deprecation = parse_deprecation_info(&f); - GqlObjectField { - description: f.description.as_deref(), - name: &f.name, - type_: FieldType::from(&f.field_type), - deprecation, - } - })); - item - } - - // pub fn from_introspected_schema_json( - // obj: &'schema graphql_introspection_query::introspection_response::FullType, - // ) -> Self { - // let description = obj.description.as_ref().map(String::as_str); - // let mut item = GqlObject::new(obj.name.as_ref().expect("missing object name"), description); - // let fields = obj.fields.as_ref().unwrap().iter().filter_map(|t| { - // t.as_ref().map(|t| { - // let deprecation = if t.is_deprecated.unwrap_or(false) { - // DeprecationStatus::Deprecated(t.deprecation_reason.clone()) - // } else { - // DeprecationStatus::Current - // }; - // GqlObjectField { - // description: t.description.as_ref().map(String::as_str), - // name: t.name.as_ref().expect("field name"), - // type_: FieldType::from(t.type_.as_ref().expect("field type")), - // deprecation, - // } - // }) - // }); - - // item.fields.extend(fields); - - // item - // } - - // pub(crate) fn require(&self, schema: &Schema) { - // if self.is_required.get() { - // return; - // } - // self.is_required.set(true); - // self.fields.iter().for_each(|field| { - // schema.require(&field.type_.inner_name_str()); - // }) - // } - - // pub(crate) fn response_for_selection( - // &self, - // query_context: &QueryContext<'_>, - // selection: &Selection<'_>, - // prefix: &str, - // ) -> Result { - // unimplemented!() - // // let derives = query_context.response_derives(); - // // let name = Ident::new(prefix, Span::call_site()); - // // let fields = self.response_fields_for_selection(query_context, selection, prefix)?; - // // let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?; - // // let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc])); - // // Ok(quote! { - // // #(#field_impls)* - - // // #derives - // // #description - // // pub struct #name { - // // #(#fields,)* - // // } - // // }) - // } - - // pub(crate) fn field_impls_for_selection( - // &self, - // query_context: &QueryContext<'_>, - // selection: &Selection<'_>, - // prefix: &str, - // ) -> Result, anyhow::Error> { - // field_impls_for_selection(&self.fields, query_context, selection, prefix) - // } - - // pub(crate) fn response_fields_for_selection( - // &self, - // query_context: &QueryContext<'_>, - // selection: &Selection<'_>, - // prefix: &str, - // ) -> Result, anyhow::Error> { - // response_fields_for_selection(&self.name, &self.fields, query_context, selection, prefix) - // } -} - -#[cfg(test)] -mod test { - use super::*; - use graphql_parser::query; - use graphql_parser::Pos; - - fn mock_field(directives: Vec) -> schema::Field { - schema::Field { - position: Pos::default(), - description: None, - name: "foo".to_string(), - arguments: vec![], - field_type: schema::Type::NamedType("x".to_string()), - directives, - } - } - - #[test] - fn deprecation_no_reason() { - let directive = schema::Directive { - position: Pos::default(), - name: "deprecated".to_string(), - arguments: vec![], - }; - let result = parse_deprecation_info(&mock_field(vec![directive])); - assert_eq!(DeprecationStatus::Deprecated(None), result); - } - - #[test] - fn deprecation_with_reason() { - let directive = schema::Directive { - position: Pos::default(), - name: "deprecated".to_string(), - arguments: vec![( - "reason".to_string(), - query::Value::String("whatever".to_string()), - )], - }; - let result = parse_deprecation_info(&mock_field(vec![directive])); - assert_eq!( - DeprecationStatus::Deprecated(Some("whatever".to_string())), - result - ); - } - - #[test] - fn null_deprecation_reason() { - let directive = schema::Directive { - position: Pos::default(), - name: "deprecated".to_string(), - arguments: vec![("reason".to_string(), query::Value::Null)], - }; - let result = parse_deprecation_info(&mock_field(vec![directive])); - assert_eq!(DeprecationStatus::Deprecated(None), result); - } - - #[test] - #[should_panic] - fn invalid_deprecation_reason() { - let directive = schema::Directive { - position: Pos::default(), - name: "deprecated".to_string(), - arguments: vec![("reason".to_string(), query::Value::Boolean(true))], - }; - let _ = parse_deprecation_info(&mock_field(vec![directive])); - } - - #[test] - fn no_deprecation() { - let result = parse_deprecation_info(&mock_field(vec![])); - assert_eq!(DeprecationStatus::Current, result); - } -} diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index 9de734f8c..37fabf798 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -17,122 +17,6 @@ enum UnionError { MissingTypename { union_name: String }, } -/// Returns a triple. -/// -/// - The first element is the union variants to be inserted directly into the `enum` declaration. -/// - The second is the structs for each variant's sub-selection -/// - The last one contains which fields have been selected on the union, so we can make the enum exhaustive by complementing with those missing. -pub(crate) fn union_variants<'selection>( - selection: &'selection Selection<'_>, - context: &'selection QueryContext<'selection>, - prefix: &str, - selection_on: &str, -) -> ! { - todo!() - // let selection = selection.selected_variants_on_union(context, selection_on)?; - // let mut used_variants: Vec<&str> = selection.keys().cloned().collect(); - // let mut children_definitions = Vec::with_capacity(selection.len()); - // let mut variants = Vec::with_capacity(selection.len()); - - // for (on, fields) in selection.iter() { - // let variant_name = Ident::new(&on, Span::call_site()); - // used_variants.push(on); - - // let new_prefix = format!("{}On{}", prefix, on); - - // let variant_type = Ident::new(&new_prefix, Span::call_site()); - - // let field_object_type = context - // .schema - // .objects - // .get(on) - // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - // let field_interface = context - // .schema - // .interfaces - // .get(on) - // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - // let field_union_type = context - // .schema - // .unions - // .get(on) - // .map(|_f| context.maybe_expand_field(&on, fields, &new_prefix)); - - // match field_object_type.or(field_interface).or(field_union_type) { - // Some(Ok(Some(tokens))) => children_definitions.push(tokens), - // Some(Err(err)) => return Err(err), - // Some(Ok(None)) => (), - // None => { - // return Err(UnionError::UnknownType { - // ty: (*on).to_string(), - // } - // .into()) - // } - // }; - - // variants.push(quote! { - // #variant_name(#variant_type) - // }) - // } - - // Ok((variants, children_definitions, used_variants)) -} - -impl<'schema> GqlUnion<'schema> { - /// Returns the code to deserialize this union in the response given the query selection. - pub(crate) fn response_for_selection( - &self, - query_context: &QueryContext<'_>, - selection: &Selection<'_>, - prefix: &str, - ) -> Result { - let typename_field = selection.extract_typename(query_context); - - if typename_field.is_none() { - return Err(UnionError::MissingTypename { - union_name: prefix.into(), - } - .into()); - } - - let struct_name = Ident::new(prefix, Span::call_site()); - let derives = query_context.response_derives(); - - let (mut variants, children_definitions, used_variants) = - union_variants(selection, query_context, prefix, &self.name)?; - - for used_variant in used_variants.iter() { - if !self.variants.contains(used_variant) { - return Err(UnionError::UnknownVariant { - ty: self.name.into(), - var: (*used_variant).to_string(), - } - .into()); - } - } - - variants.extend( - self.variants - .iter() - .filter(|v| used_variants.iter().find(|a| a == v).is_none()) - .map(|v| { - let v = Ident::new(v, Span::call_site()); - quote!(#v) - }), - ); - - Ok(quote! { - #(#children_definitions)* - - #derives - #[serde(tag = "__typename")] - pub enum #struct_name { - #(#variants),* - } - }) - } -} - #[cfg(test)] mod tests { use super::*; From 5327bcf9cb642cde26c23f8ce1c8590207edaf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 00:51:14 +0100 Subject: [PATCH 71/93] Move github tests --- graphql_client_codegen/src/lib.rs | 1 - graphql_client_codegen/src/schema.rs | 28 ++----------------- graphql_client_codegen/src/schema/tests.rs | 1 + .../src/{ => schema}/tests/github.rs | 8 +++--- .../{ => schema}/tests/github_schema.graphql | 0 .../src/{ => schema}/tests/github_schema.json | 0 graphql_client_codegen/src/tests/mod.rs | 17 +++++------ 7 files changed, 15 insertions(+), 40 deletions(-) create mode 100644 graphql_client_codegen/src/schema/tests.rs rename graphql_client_codegen/src/{ => schema}/tests/github.rs (92%) rename graphql_client_codegen/src/{ => schema}/tests/github_schema.graphql (100%) rename graphql_client_codegen/src/{ => schema}/tests/github_schema.json (100%) diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index fadbb137f..8312e3ec3 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -1,4 +1,3 @@ -#![recursion_limit = "128"] #![deny(missing_docs)] #![warn(rust_2018_idioms)] diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index b89a0eaf6..3d5a499fd 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -1,6 +1,9 @@ mod graphql_parser_conversion; mod json_conversion; +#[cfg(test)] +mod tests; + use crate::field_type::GraphqlTypeQualifier; use crate::resolution::UsedTypes; use std::collections::HashMap; @@ -238,31 +241,6 @@ impl TypeId { _ => None, } } - - // pub(crate) fn upgrade(self, schema: &Schema) -> TypeRef<'_> { - // match self { - // TypeId::Enum(id) => TypeRef::Enum(EnumRef { - // enum_id: id, - // schema, - // }), - // TypeId::Interface(id) => TypeRef::Interface(InterfaceRef { - // interface_id: id, - // schema, - // }), - // TypeId::Object(id) => TypeRef::Object(ObjectRef { - // object_id: id, - // schema, - // }), - // TypeId::Scalar(id) => TypeRef::Scalar(ScalarRef { - // scalar_id: id, - // schema, - // }), - // TypeId::Union(id) => TypeRef::Union(UnionRef { - // union_id: id, - // schema, - // }), - // } - // } } #[derive(Debug, Clone, PartialEq)] diff --git a/graphql_client_codegen/src/schema/tests.rs b/graphql_client_codegen/src/schema/tests.rs new file mode 100644 index 000000000..6a5a51cbc --- /dev/null +++ b/graphql_client_codegen/src/schema/tests.rs @@ -0,0 +1 @@ +mod github; diff --git a/graphql_client_codegen/src/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs similarity index 92% rename from graphql_client_codegen/src/tests/github.rs rename to graphql_client_codegen/src/schema/tests/github.rs index 78823e1c0..0fb9867e5 100644 --- a/graphql_client_codegen/src/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -1,17 +1,17 @@ use crate::schema::Schema; -use std::collections::HashSet; +// use std::collections::HashSet; +// use std::iter::FromIterator; const SCHEMA_JSON: &str = include_str!("github_schema.json"); const SCHEMA_GRAPHQL: &str = include_str!("github_schema.graphql"); #[test] fn ast_from_graphql_and_json_produce_the_same_schema() { - use std::iter::FromIterator; 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(); - let json = Schema::from(&json); - let gql = Schema::from(&graphql_parser_schema); + let json = Schema::from(json); + let gql = Schema::from(graphql_parser_schema); assert_eq!(json.stored_scalars, gql.stored_scalars); // TODO: reenable this diff --git a/graphql_client_codegen/src/tests/github_schema.graphql b/graphql_client_codegen/src/schema/tests/github_schema.graphql similarity index 100% rename from graphql_client_codegen/src/tests/github_schema.graphql rename to graphql_client_codegen/src/schema/tests/github_schema.graphql diff --git a/graphql_client_codegen/src/tests/github_schema.json b/graphql_client_codegen/src/schema/tests/github_schema.json similarity index 100% rename from graphql_client_codegen/src/tests/github_schema.json rename to graphql_client_codegen/src/schema/tests/github_schema.json diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index 66728129d..4162e643e 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -1,26 +1,23 @@ -mod github; - #[test] fn schema_with_keywords_works() { - use crate::{ - codegen, generated_module, schema::Schema, CodegenMode, GraphQLClientCodegenOptions, - }; + use crate::{generated_module, schema::Schema, CodegenMode, GraphQLClientCodegenOptions}; use graphql_parser; let query_string = include_str!("keywords_query.graphql"); let query = graphql_parser::parse_query(query_string).expect("Parse keywords query"); let schema = graphql_parser::parse_schema(include_str!("keywords_schema.graphql")) .expect("Parse keywords schema"); - let schema = Schema::from(&schema); + let schema = Schema::from(schema); let options = GraphQLClientCodegenOptions::new(CodegenMode::Cli); - let operations = codegen::all_operations(&query); - for operation in &operations { + let query = crate::resolution::resolve(&schema, &query).unwrap(); + + for operation in query.operations(&schema) { let generated_tokens = generated_module::GeneratedModule { query_string, schema: &schema, - query_document: &query, - operation, + operation: operation.name(), + resolved_query: &query, options: &options, } .to_token_stream() From fef85325157a09925d32a1e141ba9a34ca135abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 00:57:26 +0100 Subject: [PATCH 72/93] Remove old .gitignores --- graphql_client_codegen/.gitignore | 1 - graphql_client_web/.gitignore | 2 -- graphql_query_derive/.gitignore | 1 - 3 files changed, 4 deletions(-) delete mode 100644 graphql_client_codegen/.gitignore delete mode 100644 graphql_client_web/.gitignore delete mode 100644 graphql_query_derive/.gitignore diff --git a/graphql_client_codegen/.gitignore b/graphql_client_codegen/.gitignore deleted file mode 100644 index ea8c4bf7f..000000000 --- a/graphql_client_codegen/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/graphql_client_web/.gitignore b/graphql_client_web/.gitignore deleted file mode 100644 index f32d710ca..000000000 --- a/graphql_client_web/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/wasm-pack.log -/bin diff --git a/graphql_query_derive/.gitignore b/graphql_query_derive/.gitignore deleted file mode 100644 index ea8c4bf7f..000000000 --- a/graphql_query_derive/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target From 078940f2801dcef71b49804ef3583d73dd47abe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 01:12:57 +0100 Subject: [PATCH 73/93] Explore gql/json schema parsing mismatch --- .../src/schema/tests/github.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/graphql_client_codegen/src/schema/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs index 0fb9867e5..6c4eaafc7 100644 --- a/graphql_client_codegen/src/schema/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -13,14 +13,16 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { let json = Schema::from(json); let gql = Schema::from(graphql_parser_schema); - assert_eq!(json.stored_scalars, gql.stored_scalars); - // TODO: reenable this - // for (json, gql) in json.objects.iter().zip(gql.objects.iter()) { - // for (j, g) in json.1.fields.iter().zip(gql.1.fields.iter()) { - // assert_eq!(j, g); - // } - // assert_eq!(json, gql) - // } + assert!(vecs_match(&json.stored_scalars, &gql.stored_scalars)); + assert_eq!( + json.stored_objects.len(), + gql.stored_objects.len(), + "Objects count matches." + ); + assert!( + vecs_match(&json.stored_objects, &gql.stored_objects), + format!("{:?}\n{:?}", json.stored_objects, gql.stored_objects) + ); // for (json, gql) in json.unions.iter().zip(gql.unions.iter()) { // assert_eq!(json, gql) // } @@ -44,3 +46,7 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { // ); // } } + +fn vecs_match(a: &Vec, b: &Vec) -> bool { + a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a == b)) +} From e9db28c05e04230614118861c43f30550ee59605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 09:43:14 +0100 Subject: [PATCH 74/93] Rename `resolution` module to `query` --- graphql_client_codegen/src/codegen.rs | 4 ++-- graphql_client_codegen/src/codegen/enums.rs | 4 ++-- graphql_client_codegen/src/codegen/inputs.rs | 2 +- graphql_client_codegen/src/codegen/selection.rs | 17 +++++++---------- graphql_client_codegen/src/generated_module.rs | 4 ++-- graphql_client_codegen/src/lib.rs | 6 +++--- .../src/{resolution.rs => query.rs} | 0 graphql_client_codegen/src/schema.rs | 2 +- .../src/schema/tests/github.rs | 10 ++++++++++ graphql_client_codegen/src/tests/mod.rs | 2 +- 10 files changed, 29 insertions(+), 22 deletions(-) rename graphql_client_codegen/src/{resolution.rs => query.rs} (100%) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index bf781a46b..e0a972373 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -4,7 +4,7 @@ mod selection; use crate::{ field_type::GraphqlTypeQualifier, - resolution::*, + query::*, schema::{InputRef, TypeRef}, GraphQLClientCodegenOptions, }; @@ -132,7 +132,7 @@ fn generate_variable_struct_field( fn generate_scalar_definitions<'a, 'schema: 'a>( operation: &OperationRef<'schema>, - all_used_types: &'a crate::resolution::UsedTypes, + all_used_types: &'a crate::query::UsedTypes, options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { all_used_types diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index b25f2051e..eda90c1ca 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -1,5 +1,5 @@ use crate::{ - codegen::render_derives, codegen_options::GraphQLClientCodegenOptions, resolution::OperationRef, + codegen::render_derives, codegen_options::GraphQLClientCodegenOptions, query::OperationRef, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -13,7 +13,7 @@ use quote::quote; */ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( operation: &OperationRef<'schema>, - all_used_types: &'a crate::resolution::UsedTypes, + all_used_types: &'a crate::query::UsedTypes, options: &'a GraphQLClientCodegenOptions, ) -> impl Iterator + 'a { let derives = render_derives( diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 3fcf6e71e..a8cbb4444 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,5 +1,5 @@ use crate::codegen_options::GraphQLClientCodegenOptions; -use crate::resolution::{OperationRef, UsedTypes}; +use crate::query::{OperationRef, UsedTypes}; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index afc7f00fb..b135d3751 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -1,18 +1,15 @@ //! Code generation for the selection on an operation or a fragment. -use crate::codegen::decorate_type; -use crate::resolution::FragmentRef; -use crate::resolution::ResolvedFragmentId; -use crate::resolution::SelectedField; -use crate::resolution::SelectionRef; -use crate::schema::TypeRef; -use crate::shared::field_rename_annotation; use crate::{ + codegen::decorate_type, deprecation::DeprecationStrategy, field_type::GraphqlTypeQualifier, - resolution::{InlineFragment, OperationRef, ResolvedQuery, Selection, SelectionId}, - schema::{Schema, TypeId}, - shared::keyword_replace, + query::{ + FragmentRef, InlineFragment, OperationRef, ResolvedFragmentId, ResolvedQuery, + SelectedField, Selection, SelectionId, SelectionRef, + }, + schema::{Schema, TypeId, TypeRef}, + shared::{field_rename_annotation, keyword_replace}, GraphQLClientCodegenOptions, }; use heck::*; diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index f9b489b78..c6c960487 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,4 +1,4 @@ -use crate::{codegen_options::*, resolution::OperationRef}; +use crate::{codegen_options::*, query::OperationRef}; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -7,7 +7,7 @@ use quote::quote; pub(crate) struct GeneratedModule<'a> { pub operation: &'a str, pub query_string: &'a str, - pub resolved_query: &'a crate::resolution::ResolvedQuery, + pub resolved_query: &'a crate::query::ResolvedQuery, pub schema: &'a crate::schema::Schema, pub options: &'a crate::GraphQLClientCodegenOptions, } diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 8312e3ec3..7aaacff7a 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -23,7 +23,7 @@ mod generated_module; /// Normalization-related code pub mod normalization; mod operations; -mod resolution; +mod query; mod shared; #[cfg(test)] @@ -92,7 +92,7 @@ pub fn generate_module_token_stream( } }; - let query = resolution::resolve(&schema, &query)?; + let query = crate::query::resolve(&schema, &query)?; // Determine which operation we are generating code for. This will be used in operationName. let operations = options @@ -157,7 +157,7 @@ fn read_file(path: &std::path::Path) -> anyhow::Result { /// In derive mode, build an error when the operation with the same name as the struct is not found. fn derive_operation_not_found_error( ident: Option<&proc_macro2::Ident>, - query: &crate::resolution::ResolvedQuery, + query: &crate::query::ResolvedQuery, schema: &crate::schema::Schema, ) -> anyhow::Error { let operation_name = ident.map(ToString::to_string); diff --git a/graphql_client_codegen/src/resolution.rs b/graphql_client_codegen/src/query.rs similarity index 100% rename from graphql_client_codegen/src/resolution.rs rename to graphql_client_codegen/src/query.rs diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 3d5a499fd..f74f207f8 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -5,7 +5,7 @@ mod json_conversion; mod tests; use crate::field_type::GraphqlTypeQualifier; -use crate::resolution::UsedTypes; +use crate::query::UsedTypes; use std::collections::HashMap; #[derive(Clone, Copy)] diff --git a/graphql_client_codegen/src/schema/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs index 6c4eaafc7..fff3a5e84 100644 --- a/graphql_client_codegen/src/schema/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -14,6 +14,16 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { let gql = Schema::from(graphql_parser_schema); assert!(vecs_match(&json.stored_scalars, &gql.stored_scalars)); + panic!( + "{:?}", + json.stored_objects + .iter() + .filter(|obj| !gql + .stored_objects + .iter() + .any(|other| &obj.name == &other.name)) + .collect::>() + ); assert_eq!( json.stored_objects.len(), gql.stored_objects.len(), diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index 4162e643e..636ccaf3f 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -10,7 +10,7 @@ fn schema_with_keywords_works() { let schema = Schema::from(schema); let options = GraphQLClientCodegenOptions::new(CodegenMode::Cli); - let query = crate::resolution::resolve(&schema, &query).unwrap(); + let query = crate::query::resolve(&schema, &query).unwrap(); for operation in query.operations(&schema) { let generated_tokens = generated_module::GeneratedModule { From 97f0694346a14945df5b05b7511bc1bb459f39c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 12:00:55 +0100 Subject: [PATCH 75/93] Fix remaining problems with schema conversion test --- .../src/introspection_response.rs | 11 +- graphql_client_codegen/src/query.rs | 20 ++- graphql_client_codegen/src/schema.rs | 25 +-- .../src/schema/json_conversion.rs | 64 +++++++- .../src/schema/tests/github.rs | 150 +++++++++++++----- 5 files changed, 195 insertions(+), 75 deletions(-) diff --git a/graphql-introspection-query/src/introspection_response.rs b/graphql-introspection-query/src/introspection_response.rs index 3e3dab4ca..d3daa1814 100644 --- a/graphql-introspection-query/src/introspection_response.rs +++ b/graphql-introspection-query/src/introspection_response.rs @@ -196,19 +196,14 @@ pub struct FullTypePossibleTypes { #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct InputValue { - pub name: Option, + pub name: String, pub description: Option, #[serde(rename = "type")] - pub type_: Option, + pub type_: InputValueType, pub default_value: Option, } -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct InputValueType { - #[serde(flatten)] - pub type_ref: TypeRef, -} +type InputValueType = TypeRef; #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index cec1c4dc0..88456adf0 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -301,7 +301,11 @@ pub(crate) fn resolve( graphql_parser::query::Definition::Operation( graphql_parser::query::OperationDefinition::Mutation(m), ) => { - let on = schema.mutation_type(); + let on = schema.mutation_type().ok_or_else(|| { + anyhow::anyhow!( + "Query contains a mutation operation, but the schema has no mutation type." + ) + })?; let resolved_operation: ResolvedOperation = ResolvedOperation { object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), @@ -327,7 +331,11 @@ pub(crate) fn resolve( graphql_parser::query::Definition::Operation( graphql_parser::query::OperationDefinition::Subscription(s), ) => { - let on = schema.subscription_type(); + let on = schema.subscription_type().ok_or_else(|| { + anyhow::anyhow!( + "Query contains a subscription operation, but the schema has no subscription type." + ) + })?; if s.selection_set.items.len() != 1 { anyhow::bail!("{}", crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR) @@ -558,7 +566,11 @@ fn resolve_operation( ) -> anyhow::Result<()> { match operation { graphql_parser::query::OperationDefinition::Mutation(m) => { - let on = schema.mutation_type(); + let on = schema.mutation_type().ok_or_else(|| { + anyhow::anyhow!( + "Query contains a mutation operation, but the schema has no mutation type." + ) + })?; let (id, _) = query.find_operation(m.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &m.variable_definitions, schema, id); @@ -572,7 +584,7 @@ fn resolve_operation( resolve_object_selection(query, on, &q.selection_set, SelectionParent::Operation(id))?; } graphql_parser::query::OperationDefinition::Subscription(s) => { - let on = schema.subscription_type(); + let on = schema.subscription_type().ok_or_else(|| anyhow::anyhow!("Query contains a subscription operation, but the schema has no subscription type."))?; let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &s.variable_definitions, schema, id); diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index f74f207f8..5478c9764 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -375,23 +375,12 @@ impl Schema { ) } - pub(crate) fn mutation_type(&self) -> ObjectRef<'_> { - ObjectRef( - self.with( - self.mutation_type - .expect("Mutation operation type must be defined"), - ), - ) + pub(crate) fn mutation_type(&self) -> Option> { + self.mutation_type.map(|id| ObjectRef(self.with(id))) } - pub(crate) fn subscription_type(&self) -> ObjectRef<'_> { - ObjectRef( - self.with( - self.subscription_type - // TODO: make this return an option - .expect("Subscription operation type must be defined"), - ), - ) + pub(crate) fn subscription_type(&self) -> Option> { + self.subscription_type.map(|id| ObjectRef(self.with(id))) } fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface { @@ -403,7 +392,9 @@ impl Schema { } fn get_object(&self, object_id: ObjectId) -> &StoredObject { - self.stored_objects.get(object_id.0).unwrap() + self.stored_objects + .get(object_id.0) + .expect("Schema::get_object") } fn get_field(&self, field_id: StoredFieldId) -> &StoredField { @@ -421,7 +412,7 @@ impl Schema { fn get_union(&self, union_id: UnionId) -> &StoredUnion { self.stored_unions .get(union_id.0) - .expect("Schema.get_union") + .expect("Schema::get_union") } fn objects<'a>(&'a self) -> impl Iterator> + 'a { diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 39fb1a313..420e282c7 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -66,6 +66,10 @@ fn convert(src: &mut JsonSchema, schema: &mut Schema) { ingest_union(schema, unn) } + for input in inputs_mut(src) { + ingest_input(schema, input); + } + // Define the root operations. { schema.query_type = src @@ -81,7 +85,7 @@ fn convert(src: &mut JsonSchema, schema: &mut Schema) { .and_then(|n| schema.names.get(n)) .and_then(|id| id.as_object_id()); schema.subscription_type = src - .mutation_type + .subscription_type .as_mut() .and_then(|n| n.name.as_mut()) .and_then(|n| schema.names.get(n)) @@ -224,7 +228,28 @@ fn ingest_object(schema: &mut Schema, object: &mut FullType) { let object = super::StoredObject { name: object.name.take().expect("take object name"), - implements_interfaces: Vec::new(), + implements_interfaces: object + .interfaces + .as_ref() + .map(|ifaces| { + ifaces + .iter() + .map(|iface| { + schema + .names + .get(iface.type_ref.name.as_ref().unwrap()) + .and_then(|type_id| type_id.as_interface_id()) + .ok_or_else(|| { + format!( + "Unknown interface: {}", + iface.type_ref.name.as_ref().unwrap() + ) + }) + .unwrap() + }) + .collect() + }) + .unwrap_or_else(Vec::new), fields: field_ids, }; @@ -255,10 +280,45 @@ fn ingest_union(schema: &mut Schema, union: &mut FullType) { schema.stored_unions.push(un); } +fn ingest_input(schema: &mut Schema, input: &mut FullType) { + let mut fields = Vec::new(); + + for field in input + .input_fields + .as_mut() + .expect("Missing input_fields on input") + .iter_mut() + { + fields.push(( + std::mem::replace(&mut field.input_value.name, String::new()), + resolve_input_field_type(schema, &mut field.input_value.type_), + )); + } + + let input = super::StoredInputType { + fields, + name: input.name.take().expect("Input without a name"), + }; + + schema.stored_inputs.push(input); +} + fn resolve_field_type(schema: &mut Schema, typeref: &mut TypeRef) -> super::StoredFieldType { from_json_type_inner(schema, typeref) } +fn resolve_input_field_type( + schema: &mut Schema, + typeref: &mut TypeRef, +) -> super::StoredInputFieldType { + let field_type = from_json_type_inner(schema, typeref); + + super::StoredInputFieldType { + id: field_type.id, + qualifiers: field_type.qualifiers, + } +} + fn json_type_qualifiers_depth(typeref: &mut TypeRef) -> usize { use graphql_introspection_query::introspection_response::*; diff --git a/graphql_client_codegen/src/schema/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs index fff3a5e84..a9ec1e7c8 100644 --- a/graphql_client_codegen/src/schema/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -1,6 +1,4 @@ use crate::schema::Schema; -// use std::collections::HashSet; -// use std::iter::FromIterator; const SCHEMA_JSON: &str = include_str!("github_schema.json"); const SCHEMA_GRAPHQL: &str = include_str!("github_schema.graphql"); @@ -10,51 +8,115 @@ 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(); - let json = Schema::from(json); - let gql = Schema::from(graphql_parser_schema); + let mut json = Schema::from(json); + let mut gql = Schema::from(graphql_parser_schema); assert!(vecs_match(&json.stored_scalars, &gql.stored_scalars)); - panic!( - "{:?}", - json.stored_objects + + // Root objects + { + assert_eq!(json.query_type().name(), gql.query_type().name()); + assert_eq!( + json.mutation_type().map(|t| t.name()), + gql.mutation_type().map(|t| t.name()), + "Mutation types don't match." + ); + assert_eq!( + json.subscription_type().map(|t| t.name()), + gql.subscription_type().map(|t| 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() - .filter(|obj| !gql - .stored_objects - .iter() - .any(|other| &obj.name == &other.name)) - .collect::>() - ); - assert_eq!( - json.stored_objects.len(), - gql.stored_objects.len(), - "Objects count matches." - ); - assert!( - vecs_match(&json.stored_objects, &gql.stored_objects), - format!("{:?}\n{:?}", json.stored_objects, gql.stored_objects) - ); - // for (json, gql) in json.unions.iter().zip(gql.unions.iter()) { - // assert_eq!(json, gql) - // } - // for (json, gql) in json.interfaces.iter().zip(gql.interfaces.iter()) { - // assert_eq!(json, gql) - // } - // assert_eq!(json.interfaces, gql.interfaces); - // assert_eq!(json.query_type, gql.query_type); - // assert_eq!(json.mutation_type, gql.mutation_type); - // assert_eq!(json.subscription_type, gql.subscription_type); - // for (json, gql) in json.inputs.iter().zip(gql.inputs.iter()) { - // assert_eq!(json, gql); - // } - // assert_eq!(json.inputs, gql.inputs, "inputs differ"); - // for ((json_name, json_value), (gql_name, gql_value)) in json.enums.iter().zip(gql.enums.iter()) - // { - // assert_eq!(json_name, gql_name); - // assert_eq!( - // HashSet::<&str>::from_iter(json_value.variants.iter().map(|v| v.name)), - // HashSet::<&str>::from_iter(gql_value.variants.iter().map(|v| v.name)), - // ); - // } + .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(a: &Vec, b: &Vec) -> bool { From 7ede92db05b43b5c325c0d5e0780399f758b6bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 21 Mar 2020 12:10:54 +0100 Subject: [PATCH 76/93] Make keywords tests pass --- graphql_client_codegen/src/codegen/inputs.rs | 7 +++++-- graphql_client_codegen/src/shared.rs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index a8cbb4444..b80a274b7 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,5 +1,6 @@ use crate::codegen_options::GraphQLClientCodegenOptions; use crate::query::{OperationRef, UsedTypes}; +use crate::shared::keyword_replace; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -12,10 +13,12 @@ pub(super) fn generate_input_object_definitions( .inputs(operation.schema()) .map(|input| { let normalized_name = options.normalization().input_name(input.name()); - let struct_name = Ident::new(normalized_name.as_ref(), Span::call_site()); + let safe_name = keyword_replace(normalized_name); + let struct_name = Ident::new(safe_name.as_ref(), Span::call_site()); let fields = input.fields().map(|field| { - let name_ident = Ident::new(field.name(), Span::call_site()); + let safe_field_name = keyword_replace(field.name()); + let name_ident = Ident::new(safe_field_name.as_ref(), Span::call_site()); let normalized_field_type_name = options.normalization().field_type(field.field_type_name()); let type_name = Ident::new(normalized_field_type_name.as_ref(), Span::call_site()); diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 149e117e7..2d95508a9 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -84,9 +84,10 @@ pub(crate) fn field_rename_annotation(graphql_name: &str, rust_name: &str) -> Op } } +#[cfg(test)] mod tests { #[test] - fn keyword_replace() { + fn keyword_replace_works() { use super::keyword_replace; assert_eq!("fora", keyword_replace("fora")); assert_eq!("in_", keyword_replace("in")); From 14580836f51ed61db2fe13afffc0dec70b06e5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 22 Mar 2020 18:12:04 +0100 Subject: [PATCH 77/93] Start splitting up query.rs --- graphql_client_codegen/src/query.rs | 161 ++---------------- graphql_client_codegen/src/query/fragments.rs | 57 +++++++ .../src/query/operations.rs | 97 +++++++++++ 3 files changed, 166 insertions(+), 149 deletions(-) create mode 100644 graphql_client_codegen/src/query/fragments.rs create mode 100644 graphql_client_codegen/src/query/operations.rs diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 88456adf0..5cb940ac3 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -1,19 +1,24 @@ -//! The responsibility of this module is to resolve and validate a query +//! The responsibility of this module is to bind and validate a query //! against a given schema. -use crate::normalization::Normalization; -use crate::schema::InputRef; -use crate::schema::ScalarRef; +mod fragments; +mod operations; + +pub(crate) use fragments::FragmentRef; +pub(crate) use operations::OperationRef; + use crate::{ constants::TYPENAME_FIELD, field_type::GraphqlTypeQualifier, + normalization::Normalization, schema::{ - resolve_field_type, EnumRef, ObjectId, Schema, StoredFieldId, StoredFieldType, TypeId, - TypeRef, UnionRef, + resolve_field_type, EnumRef, InputRef, ScalarRef, Schema, StoredFieldId, StoredFieldType, + TypeId, TypeRef, UnionRef, }, }; - +use fragments::*; use heck::CamelCase; +use operations::*; use std::collections::{HashMap, HashSet}; /// This is a convenience struct that should stay private, it's an implementation detail for our `Ref` types. @@ -34,9 +39,6 @@ impl<'a, T> QueryWith<'a, T> { } } -pub(crate) struct OperationRef<'a>(QueryWith<'a, OperationId>); -pub(crate) struct FragmentRef<'a>(QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>); - #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) struct SelectionId(u32); #[derive(Debug, Clone, Copy, PartialEq)] @@ -709,102 +711,6 @@ impl ResolvedQuery { } } -#[derive(Debug)] -pub struct ResolvedFragment { - name: String, - on: crate::schema::TypeId, - selection: Vec, -} - -impl<'a> OperationRef<'a> { - pub(crate) fn query(&self) -> &'a ResolvedQuery { - self.0.query - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - fn get(&self) -> &'a ResolvedOperation { - self.0 - .query - .operations - .get(self.0.focus.0 as usize) - .expect("get operation") - } - - fn to_path_segment(&self) -> String { - self.get().name.to_camel_case() - } - - pub(crate) fn all_used_types(&self) -> UsedTypes { - let mut all_used_types = UsedTypes::default(); - - for selection in self.selection() { - selection.collect_used_types(&mut all_used_types); - } - - for variable in self.variables() { - variable.collect_used_types(&mut all_used_types); - } - - all_used_types - } - - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { - let operation = self.get(); - operation.selection.iter().map(move |selection_id| { - SelectionRef( - self.0 - .refocus((*selection_id, self.0.query.get_selection(*selection_id))), - ) - }) - } - - pub(crate) fn selection_ids(&self) -> &[SelectionId] { - &self.get().selection - } - - pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { - self.0 - .query - .variables - .iter() - .enumerate() - .filter(move |(_, variable)| variable.operation_id == self.0.focus) - .map(move |(id, _)| { - self.0 - .query - .get_variable_ref(self.0.schema, VariableId::new(id)) - }) - } - - pub(crate) fn name(&self) -> &'a str { - self.get().name() - } - - pub(crate) fn has_no_variables(&self) -> bool { - self.variables().next().is_none() - } - - pub(crate) fn on_ref(&self) -> TypeRef<'a> { - self.0.schema.type_ref(TypeId::Object(self.get().object_id)) - } -} - -struct ResolvedOperation { - name: String, - _operation_type: crate::operations::OperationType, - selection: Vec, - object_id: ObjectId, -} - -impl ResolvedOperation { - pub(crate) fn name(&self) -> &str { - &self.name - } -} - #[derive(Debug)] struct ResolvedVariable { operation_id: OperationId, @@ -854,49 +760,6 @@ impl<'a> VariableRef<'a> { } } -impl<'a> FragmentRef<'a> { - pub(crate) fn is_recursive(&self) -> bool { - let id = self.0.focus.0; - - self.selection_set() - .any(|selection| selection.contains_fragment(id)) - } - - pub(crate) fn query(&self) -> &'a ResolvedQuery { - self.0.query - } - - pub(crate) fn name(&self) -> &'a str { - &self.0.focus.1.name - } - - pub(crate) fn on(&self) -> TypeId { - self.0.focus.1.on - } - - pub(crate) fn on_ref(&self) -> TypeRef<'a> { - self.0.schema.type_ref(self.0.focus.1.on) - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - pub(crate) fn selection_ids(&self) -> &[SelectionId] { - &self.0.focus.1.selection - } - - pub(crate) fn selection_set<'b>(&'b self) -> impl Iterator> + 'b { - self.selection_ids() - .iter() - .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) - } - - fn to_path_segment(&self) -> String { - self.0.focus.1.name.to_camel_case() - } -} - #[derive(Debug, Default)] pub(crate) struct UsedTypes { pub(crate) types: HashSet, diff --git a/graphql_client_codegen/src/query/fragments.rs b/graphql_client_codegen/src/query/fragments.rs new file mode 100644 index 000000000..af5b2cd0d --- /dev/null +++ b/graphql_client_codegen/src/query/fragments.rs @@ -0,0 +1,57 @@ +use super::{QueryWith, ResolvedFragmentId, ResolvedQuery, SelectionId, SelectionRef}; +use crate::schema::{Schema, TypeId, TypeRef}; +use heck::*; + +#[derive(Debug)] +pub(crate) struct ResolvedFragment { + pub(crate) name: String, + pub(crate) on: crate::schema::TypeId, + pub(crate) selection: Vec, +} + +pub(crate) struct FragmentRef<'a>( + pub(super) QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>, +); + +impl<'a> FragmentRef<'a> { + pub(crate) fn is_recursive(&self) -> bool { + let id = self.0.focus.0; + + self.selection_set() + .any(|selection| selection.contains_fragment(id)) + } + + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.0.query + } + + pub(crate) fn name(&self) -> &'a str { + &self.0.focus.1.name + } + + pub(crate) fn on(&self) -> TypeId { + self.0.focus.1.on + } + + pub(crate) fn on_ref(&self) -> TypeRef<'a> { + self.0.schema.type_ref(self.0.focus.1.on) + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + + pub(crate) fn selection_ids(&self) -> &[SelectionId] { + &self.0.focus.1.selection + } + + pub(crate) fn selection_set<'b>(&'b self) -> impl Iterator> + 'b { + self.selection_ids() + .iter() + .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) + } + + pub(super) fn to_path_segment(&self) -> String { + self.0.focus.1.name.to_camel_case() + } +} diff --git a/graphql_client_codegen/src/query/operations.rs b/graphql_client_codegen/src/query/operations.rs new file mode 100644 index 000000000..1f71ec2f7 --- /dev/null +++ b/graphql_client_codegen/src/query/operations.rs @@ -0,0 +1,97 @@ +use super::{ + OperationId, QueryWith, ResolvedQuery, SelectionId, SelectionRef, UsedTypes, VariableId, + VariableRef, +}; +use crate::schema::{ObjectId, Schema, TypeId, TypeRef}; +use heck::*; + +pub(crate) struct OperationRef<'a>(pub(super) QueryWith<'a, OperationId>); + +impl<'a> OperationRef<'a> { + pub(crate) fn query(&self) -> &'a ResolvedQuery { + self.0.query + } + + pub(crate) fn schema(&self) -> &'a Schema { + self.0.schema + } + + fn get(&self) -> &'a ResolvedOperation { + self.0 + .query + .operations + .get(self.0.focus.0 as usize) + .expect("get operation") + } + + pub(crate) fn to_path_segment(&self) -> String { + self.get().name.to_camel_case() + } + + pub(crate) fn all_used_types(&self) -> UsedTypes { + let mut all_used_types = UsedTypes::default(); + + for selection in self.selection() { + selection.collect_used_types(&mut all_used_types); + } + + for variable in self.variables() { + variable.collect_used_types(&mut all_used_types); + } + + all_used_types + } + + pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { + let operation = self.get(); + operation.selection.iter().map(move |selection_id| { + SelectionRef( + self.0 + .refocus((*selection_id, self.0.query.get_selection(*selection_id))), + ) + }) + } + + pub(crate) fn selection_ids(&self) -> &[SelectionId] { + &self.get().selection + } + + pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { + self.0 + .query + .variables + .iter() + .enumerate() + .filter(move |(_, variable)| variable.operation_id == self.0.focus) + .map(move |(id, _)| { + self.0 + .query + .get_variable_ref(self.0.schema, VariableId::new(id)) + }) + } + + pub(crate) fn name(&self) -> &'a str { + self.get().name() + } + + pub(crate) fn has_no_variables(&self) -> bool { + self.variables().next().is_none() + } + + pub(crate) fn on_ref(&self) -> TypeRef<'a> { + self.0.schema.type_ref(TypeId::Object(self.get().object_id)) + } +} + +pub(crate) struct ResolvedOperation { + pub(crate) name: String, + pub(crate) _operation_type: crate::operations::OperationType, + pub(crate) selection: Vec, + pub(crate) object_id: ObjectId, +} + +impl ResolvedOperation { + pub(crate) fn name(&self) -> &str { + &self.name + } +} From 197f4a9a176a967c5a42f153a4238bb2f9be107b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 22 Mar 2020 18:55:46 +0100 Subject: [PATCH 78/93] Reorganize more modules --- graphql_client_codegen/src/codegen.rs | 7 ++++--- graphql_client_codegen/src/codegen/enums.rs | 4 ++-- graphql_client_codegen/src/codegen/inputs.rs | 8 +++++--- graphql_client_codegen/src/codegen/selection.rs | 8 +++++--- graphql_client_codegen/src/{ => codegen}/shared.rs | 0 graphql_client_codegen/src/lib.rs | 3 +-- graphql_client_codegen/src/query.rs | 2 +- graphql_client_codegen/src/schema.rs | 4 ++-- graphql_client_codegen/src/schema/json_conversion.rs | 2 +- .../src/{field_type.rs => type_qualifiers.rs} | 0 10 files changed, 21 insertions(+), 17 deletions(-) rename graphql_client_codegen/src/{ => codegen}/shared.rs (100%) rename graphql_client_codegen/src/{field_type.rs => type_qualifiers.rs} (100%) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index e0a972373..d351bb441 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,11 +1,12 @@ mod enums; mod inputs; mod selection; +mod shared; use crate::{ - field_type::GraphqlTypeQualifier, query::*, schema::{InputRef, TypeRef}, + type_qualifiers::GraphqlTypeQualifier, GraphQLClientCodegenOptions, }; use heck::SnakeCase; @@ -121,10 +122,10 @@ fn generate_variable_struct_field( ) -> TokenStream { let snake_case_name = variable.name().to_snake_case(); let ident = Ident::new( - &crate::shared::keyword_replace(&snake_case_name), + &shared::keyword_replace(&snake_case_name), Span::call_site(), ); - let annotation = crate::shared::field_rename_annotation(variable.name(), &snake_case_name); + let annotation = shared::field_rename_annotation(variable.name(), &snake_case_name); let r#type = render_variable_field_type(variable, options); quote::quote!(#annotation pub #ident : #r#type) diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index eda90c1ca..2cae6c14a 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -28,7 +28,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( .variants() .iter() .map(|v| { - let safe_name = crate::shared::keyword_replace(v.as_str()); + let safe_name = super::shared::keyword_replace(v.as_str()); let name = normalization.enum_variant(safe_name.as_ref()); let name = Ident::new(&name, Span::call_site()); @@ -47,7 +47,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( .variants() .iter() .map(|v| { - let safe_name = crate::shared::keyword_replace(v); + let safe_name = super::shared::keyword_replace(v); let name = normalization.enum_variant(safe_name.as_ref()); let v = Ident::new(&name, Span::call_site()); diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index b80a274b7..6b3a75b07 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,6 +1,8 @@ -use crate::codegen_options::GraphQLClientCodegenOptions; -use crate::query::{OperationRef, UsedTypes}; -use crate::shared::keyword_replace; +use super::shared::keyword_replace; +use crate::{ + codegen_options::GraphQLClientCodegenOptions, + query::{OperationRef, UsedTypes}, +}; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index b135d3751..ab4c6bc15 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -1,15 +1,17 @@ //! Code generation for the selection on an operation or a fragment. use crate::{ - codegen::decorate_type, + codegen::{ + decorate_type, + shared::{field_rename_annotation, keyword_replace}, + }, deprecation::DeprecationStrategy, - field_type::GraphqlTypeQualifier, query::{ FragmentRef, InlineFragment, OperationRef, ResolvedFragmentId, ResolvedQuery, SelectedField, Selection, SelectionId, SelectionRef, }, schema::{Schema, TypeId, TypeRef}, - shared::{field_rename_annotation, keyword_replace}, + type_qualifiers::GraphqlTypeQualifier, GraphQLClientCodegenOptions, }; use heck::*; diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/codegen/shared.rs similarity index 100% rename from graphql_client_codegen/src/shared.rs rename to graphql_client_codegen/src/codegen/shared.rs diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 7aaacff7a..c11908bfe 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -18,13 +18,12 @@ pub mod deprecation; pub mod schema; mod constants; -mod field_type; mod generated_module; /// Normalization-related code pub mod normalization; mod operations; mod query; -mod shared; +mod type_qualifiers; #[cfg(test)] mod tests; diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 5cb940ac3..7b24fa7ce 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -9,12 +9,12 @@ pub(crate) use operations::OperationRef; use crate::{ constants::TYPENAME_FIELD, - field_type::GraphqlTypeQualifier, normalization::Normalization, schema::{ resolve_field_type, EnumRef, InputRef, ScalarRef, Schema, StoredFieldId, StoredFieldType, TypeId, TypeRef, UnionRef, }, + type_qualifiers::GraphqlTypeQualifier, }; use fragments::*; use heck::CamelCase; diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 5478c9764..0570e4b96 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -4,8 +4,8 @@ mod json_conversion; #[cfg(test)] mod tests; -use crate::field_type::GraphqlTypeQualifier; use crate::query::UsedTypes; +use crate::type_qualifiers::GraphqlTypeQualifier; use std::collections::HashMap; #[derive(Clone, Copy)] @@ -710,7 +710,7 @@ pub(crate) fn resolve_field_type( schema: &Schema, inner: &graphql_parser::schema::Type, ) -> StoredFieldType { - use crate::field_type::graphql_parser_depth; + use crate::type_qualifiers::graphql_parser_depth; use graphql_parser::schema::Type::*; let qualifiers_depth = graphql_parser_depth(inner); diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 420e282c7..9c65efe60 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -331,7 +331,7 @@ fn json_type_qualifiers_depth(typeref: &mut TypeRef) -> usize { } fn from_json_type_inner(schema: &mut Schema, inner: &mut TypeRef) -> super::StoredFieldType { - use crate::field_type::GraphqlTypeQualifier; + use crate::type_qualifiers::GraphqlTypeQualifier; use graphql_introspection_query::introspection_response::*; let qualifiers_depth = json_type_qualifiers_depth(inner); diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/type_qualifiers.rs similarity index 100% rename from graphql_client_codegen/src/field_type.rs rename to graphql_client_codegen/src/type_qualifiers.rs From c5f288646719ab9a938e4e85a96b907bf12de824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 22 Mar 2020 18:58:26 +0100 Subject: [PATCH 79/93] Delete top-level operations module --- graphql_client_codegen/src/lib.rs | 1 - graphql_client_codegen/src/operations.rs | 6 ------ graphql_client_codegen/src/query.rs | 6 +++--- graphql_client_codegen/src/query/operations.rs | 9 ++++++++- 4 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 graphql_client_codegen/src/operations.rs diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index c11908bfe..63878a557 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -21,7 +21,6 @@ mod constants; mod generated_module; /// Normalization-related code pub mod normalization; -mod operations; mod query; mod type_qualifiers; diff --git a/graphql_client_codegen/src/operations.rs b/graphql_client_codegen/src/operations.rs deleted file mode 100644 index 36b17a04a..000000000 --- a/graphql_client_codegen/src/operations.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Debug, Clone)] -pub(crate) enum OperationType { - Query, - Mutation, - Subscription, -} diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 7b24fa7ce..b795020f3 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -311,7 +311,7 @@ pub(crate) fn resolve( let resolved_operation: ResolvedOperation = ResolvedOperation { object_id: on.id(), name: m.name.as_ref().expect("mutation without name").to_owned(), - _operation_type: crate::operations::OperationType::Mutation, + _operation_type: operations::OperationType::Mutation, selection: Vec::with_capacity(m.selection_set.items.len()), }; @@ -323,7 +323,7 @@ pub(crate) fn resolve( let on = schema.query_type(); let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), - _operation_type: crate::operations::OperationType::Query, + _operation_type: operations::OperationType::Query, object_id: on.id(), selection: Vec::with_capacity(q.selection_set.items.len()), }; @@ -349,7 +349,7 @@ pub(crate) fn resolve( .as_ref() .expect("subscription without name") .to_owned(), - _operation_type: crate::operations::OperationType::Subscription, + _operation_type: operations::OperationType::Subscription, object_id: on.id(), selection: Vec::with_capacity(s.selection_set.items.len()), }; diff --git a/graphql_client_codegen/src/query/operations.rs b/graphql_client_codegen/src/query/operations.rs index 1f71ec2f7..47c542f0f 100644 --- a/graphql_client_codegen/src/query/operations.rs +++ b/graphql_client_codegen/src/query/operations.rs @@ -5,6 +5,13 @@ use super::{ use crate::schema::{ObjectId, Schema, TypeId, TypeRef}; use heck::*; +#[derive(Debug, Clone)] +pub(crate) enum OperationType { + Query, + Mutation, + Subscription, +} + pub(crate) struct OperationRef<'a>(pub(super) QueryWith<'a, OperationId>); impl<'a> OperationRef<'a> { @@ -85,7 +92,7 @@ impl<'a> OperationRef<'a> { pub(crate) struct ResolvedOperation { pub(crate) name: String, - pub(crate) _operation_type: crate::operations::OperationType, + pub(crate) _operation_type: OperationType, pub(crate) selection: Vec, pub(crate) object_id: ObjectId, } From 9e574060aec294e39952914055f496932c5aa7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 22 Mar 2020 20:52:28 +0100 Subject: [PATCH 80/93] Properly set variable derives on input type structs --- graphql_client_codegen/src/codegen.rs | 15 +++++++++------ graphql_client_codegen/src/codegen/inputs.rs | 3 ++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index d351bb441..bd263f5c6 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -21,15 +21,20 @@ pub(crate) fn response_for_query( ) -> anyhow::Result { let all_used_types = operation.all_used_types(); let response_derives = render_derives(options.all_response_derives()); + let variable_derives = render_derives(options.all_variable_derives()); let scalar_definitions = generate_scalar_definitions(&operation, &all_used_types, &options); let enum_definitions = enums::generate_enum_definitions(&operation, &all_used_types, options); let fragment_definitions = generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); - let input_object_definitions = - inputs::generate_input_object_definitions(&operation, &all_used_types, options); + let input_object_definitions = inputs::generate_input_object_definitions( + &operation, + &all_used_types, + options, + &variable_derives, + ); - let variables_struct = generate_variables_struct(&operation, options); + let variables_struct = generate_variables_struct(&operation, options, &variable_derives); let definitions = render_response_data_fields(&operation, options).render(&response_derives); @@ -64,10 +69,8 @@ pub(crate) fn response_for_query( fn generate_variables_struct( operation: &OperationRef<'_>, options: &GraphQLClientCodegenOptions, + variable_derives: &impl quote::ToTokens, ) -> TokenStream { - let variable_derives = options.all_variable_derives(); - let variable_derives = render_derives(variable_derives); - if operation.has_no_variables() { return quote!( #variable_derives diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 6b3a75b07..7522e9756 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -10,6 +10,7 @@ pub(super) fn generate_input_object_definitions( operation: &OperationRef<'_>, all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, + variable_derives: &impl quote::ToTokens, ) -> Vec { all_used_types .inputs(operation.schema()) @@ -38,7 +39,7 @@ pub(super) fn generate_input_object_definitions( }); quote! { - #[derive(Serialize)] + #variable_derives pub struct #struct_name { #(#fields,)* } From 77a5490d7fd16465cc9cbb48ea852bb782d2d628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Tue, 24 Mar 2020 09:13:06 +0100 Subject: [PATCH 81/93] Rename ResolvedQuery to Query --- .../src/codegen/selection.rs | 6 ++-- .../src/generated_module.rs | 2 +- graphql_client_codegen/src/lib.rs | 2 +- graphql_client_codegen/src/query.rs | 28 +++++++++---------- graphql_client_codegen/src/query/fragments.rs | 4 +-- .../src/query/operations.rs | 5 ++-- 6 files changed, 23 insertions(+), 24 deletions(-) diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index ab4c6bc15..2d92be67c 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -7,8 +7,8 @@ use crate::{ }, deprecation::DeprecationStrategy, query::{ - FragmentRef, InlineFragment, OperationRef, ResolvedFragmentId, ResolvedQuery, - SelectedField, Selection, SelectionId, SelectionRef, + FragmentRef, InlineFragment, OperationRef, Query, ResolvedFragmentId, SelectedField, + Selection, SelectionId, SelectionRef, }, schema::{Schema, TypeId, TypeRef}, type_qualifiers::GraphqlTypeQualifier, @@ -406,7 +406,7 @@ pub(crate) struct ExpandedType<'a> { } pub(crate) struct ExpandedSelection<'a> { - query: &'a ResolvedQuery, + query: &'a Query, schema: &'a Schema, types: Vec>, fields: Vec>, diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index c6c960487..138a3e0f8 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -7,7 +7,7 @@ use quote::quote; pub(crate) struct GeneratedModule<'a> { pub operation: &'a str, pub query_string: &'a str, - pub resolved_query: &'a crate::query::ResolvedQuery, + pub resolved_query: &'a crate::query::Query, pub schema: &'a crate::schema::Schema, pub options: &'a crate::GraphQLClientCodegenOptions, } diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 63878a557..bf3a3ae9a 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -155,7 +155,7 @@ fn read_file(path: &std::path::Path) -> anyhow::Result { /// In derive mode, build an error when the operation with the same name as the struct is not found. fn derive_operation_not_found_error( ident: Option<&proc_macro2::Ident>, - query: &crate::query::ResolvedQuery, + query: &crate::query::Query, schema: &crate::schema::Schema, ) -> anyhow::Error { let operation_name = ident.map(ToString::to_string); diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index b795020f3..b62f3d676 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -24,7 +24,7 @@ use std::collections::{HashMap, HashSet}; /// This is a convenience struct that should stay private, it's an implementation detail for our `Ref` types. #[derive(Copy, Clone)] struct QueryWith<'a, T> { - query: &'a ResolvedQuery, + query: &'a Query, schema: &'a Schema, focus: T, } @@ -71,7 +71,7 @@ enum SelectionParent { } impl SelectionParent { - fn add_to_selection_set(&self, q: &mut ResolvedQuery, selection_id: SelectionId) { + fn add_to_selection_set(&self, q: &mut Query, selection_id: SelectionId) { match self { SelectionParent::Field(parent_selection_id) | SelectionParent::InlineFragment(parent_selection_id) => { @@ -117,7 +117,7 @@ impl<'a> SelectionRef<'a> { .any(|selection| selection.contains_fragment(fragment_id)), } } - pub(crate) fn query(&self) -> &'a ResolvedQuery { + pub(crate) fn query(&self) -> &'a Query { self.0.query } @@ -285,8 +285,8 @@ impl SelectedField { pub(crate) fn resolve( schema: &Schema, query: &graphql_parser::query::Document, -) -> anyhow::Result { - let mut resolved_query: ResolvedQuery = Default::default(); +) -> anyhow::Result { + let mut resolved_query: Query = Default::default(); // First, give ids to all fragments and operations. // TODO: refactor this into a "create_roots" function. @@ -378,7 +378,7 @@ pub(crate) fn resolve( } fn resolve_fragment( - query: &mut ResolvedQuery, + query: &mut Query, schema: &Schema, fragment_definition: &graphql_parser::query::FragmentDefinition, ) -> anyhow::Result<()> { @@ -401,7 +401,7 @@ fn resolve_fragment( } fn resolve_union_selection( - query: &mut ResolvedQuery, + query: &mut Query, union: UnionRef<'_>, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, @@ -439,7 +439,7 @@ fn resolve_union_selection( } fn resolve_object_selection<'a>( - query: &mut ResolvedQuery, + query: &mut Query, object: impl crate::schema::ObjectRefLike<'a>, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, @@ -497,7 +497,7 @@ fn resolve_object_selection<'a>( } fn resolve_selection( - ctx: &mut ResolvedQuery, + ctx: &mut Query, schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, @@ -529,7 +529,7 @@ fn resolve_selection( } fn resolve_inline_fragment( - query: &mut ResolvedQuery, + query: &mut Query, schema: &Schema, inline_fragment: &graphql_parser::query::InlineFragment, parent: SelectionParent, @@ -562,7 +562,7 @@ fn resolve_inline_fragment( } fn resolve_operation( - query: &mut ResolvedQuery, + query: &mut Query, schema: &Schema, operation: &graphql_parser::query::OperationDefinition, ) -> anyhow::Result<()> { @@ -601,7 +601,7 @@ fn resolve_operation( } #[derive(Default)] -pub(crate) struct ResolvedQuery { +pub(crate) struct Query { fragments: Vec, operations: Vec, selection_parent_idx: HashMap, @@ -609,7 +609,7 @@ pub(crate) struct ResolvedQuery { variables: Vec, } -impl ResolvedQuery { +impl Query { fn push_selection(&mut self, node: Selection, parent: SelectionParent) -> SelectionId { let id = SelectionId(self.selections.len() as u32); self.selections.push(node); @@ -807,7 +807,7 @@ impl UsedTypes { } fn resolve_variables( - query: &mut ResolvedQuery, + query: &mut Query, variables: &[graphql_parser::query::VariableDefinition], schema: &Schema, operation_id: OperationId, diff --git a/graphql_client_codegen/src/query/fragments.rs b/graphql_client_codegen/src/query/fragments.rs index af5b2cd0d..eff6a922f 100644 --- a/graphql_client_codegen/src/query/fragments.rs +++ b/graphql_client_codegen/src/query/fragments.rs @@ -1,4 +1,4 @@ -use super::{QueryWith, ResolvedFragmentId, ResolvedQuery, SelectionId, SelectionRef}; +use super::{Query, QueryWith, ResolvedFragmentId, SelectionId, SelectionRef}; use crate::schema::{Schema, TypeId, TypeRef}; use heck::*; @@ -21,7 +21,7 @@ impl<'a> FragmentRef<'a> { .any(|selection| selection.contains_fragment(id)) } - pub(crate) fn query(&self) -> &'a ResolvedQuery { + pub(crate) fn query(&self) -> &'a Query { self.0.query } diff --git a/graphql_client_codegen/src/query/operations.rs b/graphql_client_codegen/src/query/operations.rs index 47c542f0f..94e2f3e41 100644 --- a/graphql_client_codegen/src/query/operations.rs +++ b/graphql_client_codegen/src/query/operations.rs @@ -1,6 +1,5 @@ use super::{ - OperationId, QueryWith, ResolvedQuery, SelectionId, SelectionRef, UsedTypes, VariableId, - VariableRef, + OperationId, Query, QueryWith, SelectionId, SelectionRef, UsedTypes, VariableId, VariableRef, }; use crate::schema::{ObjectId, Schema, TypeId, TypeRef}; use heck::*; @@ -15,7 +14,7 @@ pub(crate) enum OperationType { pub(crate) struct OperationRef<'a>(pub(super) QueryWith<'a, OperationId>); impl<'a> OperationRef<'a> { - pub(crate) fn query(&self) -> &'a ResolvedQuery { + pub(crate) fn query(&self) -> &'a Query { self.0.query } From e4bc8788c0f63d6727e50b58ec26f11de058981e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Tue, 24 Mar 2020 20:03:40 +0100 Subject: [PATCH 82/93] Simplify schema and query navigation --- graphql_client_codegen/src/codegen.rs | 171 +++--- graphql_client_codegen/src/codegen/enums.rs | 14 +- graphql_client_codegen/src/codegen/inputs.rs | 33 +- .../src/codegen/selection.rs | 171 +++--- .../src/generated_module.rs | 14 +- graphql_client_codegen/src/lib.rs | 15 +- graphql_client_codegen/src/query.rs | 486 ++++++++--------- graphql_client_codegen/src/query/fragments.rs | 59 +-- .../src/query/operations.rs | 90 +--- graphql_client_codegen/src/schema.rs | 496 +++++------------- .../src/schema/graphql_parser_conversion.rs | 6 +- .../src/schema/json_conversion.rs | 4 +- .../src/schema/tests/github.rs | 13 +- graphql_client_codegen/src/tests/mod.rs | 4 +- 14 files changed, 590 insertions(+), 986 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index bd263f5c6..7b0954449 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -5,7 +5,7 @@ mod shared; use crate::{ query::*, - schema::{InputRef, TypeRef}, + schema::{InputId, TypeId}, type_qualifiers::GraphqlTypeQualifier, GraphQLClientCodegenOptions, }; @@ -13,30 +13,34 @@ use heck::SnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use selection::*; +use std::collections::BTreeMap; /// The main code generation function. pub(crate) fn response_for_query( - operation: OperationRef<'_>, + operation_id: OperationId, options: &GraphQLClientCodegenOptions, + query: BoundQuery<'_>, ) -> anyhow::Result { - let all_used_types = operation.all_used_types(); + let all_used_types = all_used_types(operation_id, &query); let response_derives = render_derives(options.all_response_derives()); let variable_derives = render_derives(options.all_variable_derives()); - let scalar_definitions = generate_scalar_definitions(&operation, &all_used_types, &options); - let enum_definitions = enums::generate_enum_definitions(&operation, &all_used_types, options); + let scalar_definitions = generate_scalar_definitions(&all_used_types, options, query); + let enum_definitions = enums::generate_enum_definitions(&all_used_types, options, query); let fragment_definitions = - generate_fragment_definitions(&operation, &all_used_types, &response_derives, options); + generate_fragment_definitions(&all_used_types, &response_derives, options, &query); let input_object_definitions = inputs::generate_input_object_definitions( - &operation, &all_used_types, options, &variable_derives, + &query, ); - let variables_struct = generate_variables_struct(&operation, options, &variable_derives); + let variables_struct = + generate_variables_struct(operation_id, &variable_derives, options, &query); - let definitions = render_response_data_fields(&operation, options).render(&response_derives); + let definitions = + render_response_data_fields(operation_id, options, &query).render(&response_derives); let q = quote! { use serde::{Serialize, Deserialize}; @@ -67,43 +71,46 @@ pub(crate) fn response_for_query( } fn generate_variables_struct( - operation: &OperationRef<'_>, - options: &GraphQLClientCodegenOptions, + operation_id: OperationId, variable_derives: &impl quote::ToTokens, + options: &GraphQLClientCodegenOptions, + query: &BoundQuery<'_>, ) -> TokenStream { - if operation.has_no_variables() { + if operation_has_no_variables(operation_id, query.query) { return quote!( #variable_derives pub struct Variables; ); } - let variable_fields = operation - .variables() - .map(|variable| generate_variable_struct_field(variable, options)); - let variable_defaults = operation.variables().map(|variable| { - let method_name = format!("default_{}", variable.name()); - let method_name = Ident::new(&method_name, Span::call_site()); - let method_return_type = render_variable_field_type(variable, options); - - variable.default().map(|default| { - let value = graphql_parser_value_to_literal( - default, - variable.variable_type(), - variable - .type_qualifiers() - .get(0) - .map(|qual| !qual.is_required()) - .unwrap_or(true), - ); - - quote!( - pub fn #method_name() -> #method_return_type { - #value - } - ) - }) - }); + let variable_fields = walk_operation_variables(operation_id, query.query) + .map(|(_id, variable)| generate_variable_struct_field(variable, options, query)); + let variable_defaults = + walk_operation_variables(operation_id, query.query).map(|(_id, variable)| { + let method_name = format!("default_{}", variable.name); + let method_name = Ident::new(&method_name, Span::call_site()); + let method_return_type = render_variable_field_type(variable, options, query); + + variable.default.as_ref().map(|default| { + let value = graphql_parser_value_to_literal( + default, + variable.r#type.id, + variable + .r#type + .qualifiers + .get(0) + .map(|qual| !qual.is_required()) + .unwrap_or(true), + query, + ); + + quote!( + pub fn #method_name() -> #method_return_type { + #value + } + ) + }) + }); let variables_struct = quote!( #variable_derives @@ -120,33 +127,31 @@ fn generate_variables_struct( } fn generate_variable_struct_field( - variable: VariableRef<'_>, + variable: &ResolvedVariable, options: &GraphQLClientCodegenOptions, + query: &BoundQuery<'_>, ) -> TokenStream { - let snake_case_name = variable.name().to_snake_case(); + let snake_case_name = variable.name.to_snake_case(); let ident = Ident::new( &shared::keyword_replace(&snake_case_name), Span::call_site(), ); - let annotation = shared::field_rename_annotation(variable.name(), &snake_case_name); - let r#type = render_variable_field_type(variable, options); + let annotation = shared::field_rename_annotation(&variable.name, &snake_case_name); + let r#type = render_variable_field_type(variable, options, query); quote::quote!(#annotation pub #ident : #r#type) } fn generate_scalar_definitions<'a, 'schema: 'a>( - operation: &OperationRef<'schema>, all_used_types: &'a crate::query::UsedTypes, options: &'a GraphQLClientCodegenOptions, + query: BoundQuery<'schema>, ) -> impl Iterator + 'a { all_used_types - .scalars(operation.schema()) - .map(move |scalar| { + .scalars(query.schema) + .map(move |(_id, scalar)| { let ident = syn::Ident::new( - options - .normalization() - .scalar_name(scalar.name().into()) - .as_ref(), + options.normalization().scalar_name(&scalar.name).as_ref(), proc_macro2::Span::call_site(), ); @@ -161,13 +166,16 @@ fn render_derives<'a>(derives: impl Iterator) -> impl quote::ToT } fn render_variable_field_type( - variable: VariableRef<'_>, + variable: &ResolvedVariable, options: &GraphQLClientCodegenOptions, + query: &BoundQuery<'_>, ) -> TokenStream { - let normalized_name = options.normalization().input_name(variable.type_name()); + let normalized_name = options + .normalization() + .input_name(variable.type_name(query.schema)); let full_name = Ident::new(normalized_name.as_ref(), Span::call_site()); - decorate_type(&full_name, variable.type_qualifiers()) + decorate_type(&full_name, &variable.r#type.qualifiers) } fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStream { @@ -208,31 +216,23 @@ fn decorate_type(ident: &Ident, qualifiers: &[GraphqlTypeQualifier]) -> TokenStr qualified } -fn generate_fragment_definitions( - operation: &OperationRef<'_>, - all_used_types: &UsedTypes, - response_derives: &impl quote::ToTokens, - options: &GraphQLClientCodegenOptions, -) -> Vec { - let mut fragment_definitions = Vec::with_capacity(all_used_types.fragments_len()); - - let fragments = all_used_types - .fragment_ids() - .map(move |id| operation.query().get_fragment_ref(operation.schema(), id)); - - for fragment in fragments { - fragment_definitions - .push(selection::render_fragment(&fragment, options).render(&response_derives)); - } - - fragment_definitions +fn generate_fragment_definitions<'a>( + all_used_types: &'a UsedTypes, + response_derives: &'a impl quote::ToTokens, + options: &'a GraphQLClientCodegenOptions, + query: &'a BoundQuery<'a>, +) -> impl Iterator + 'a { + all_used_types.fragment_ids().map(move |fragment_id| { + selection::render_fragment(fragment_id, options, query).render(&response_derives) + }) } /// For default value constructors. fn graphql_parser_value_to_literal( value: &graphql_parser::query::Value, - ty: TypeRef<'_>, + ty: TypeId, is_optional: bool, + query: &BoundQuery<'_>, ) -> TokenStream { use graphql_parser::query::Value; @@ -256,7 +256,7 @@ fn graphql_parser_value_to_literal( Value::List(inner) => { let elements = inner .iter() - .map(|val| graphql_parser_value_to_literal(val, ty, false)); + .map(|val| graphql_parser_value_to_literal(val, ty, false, query)); quote! { vec![ #(#elements,)* @@ -264,7 +264,7 @@ fn graphql_parser_value_to_literal( } } Value::Object(obj) => { - render_object_literal(obj, ty.as_input_ref().expect("TODO: error handling")) + render_object_literal(obj, ty.as_input_id().expect("TODO: error handling"), query) } }; @@ -277,22 +277,25 @@ fn graphql_parser_value_to_literal( /// For default value constructors. fn render_object_literal( - object: &std::collections::BTreeMap, - ty: InputRef<'_>, + object_map: &BTreeMap, + input_id: InputId, + query: &BoundQuery<'_>, ) -> TokenStream { - let type_name = ty.name(); - let constructor = Ident::new(&type_name, Span::call_site()); - let fields: Vec = ty - .fields() - .map(|field| { - let field_name = Ident::new(field.name(), Span::call_site()); - let provided_value = object.get(field.name()); + let input = query.schema.get_input(input_id); + let constructor = Ident::new(&input.name, Span::call_site()); + let fields: Vec = input + .fields + .iter() + .map(|(name, r#type)| { + let field_name = Ident::new(&name, Span::call_site()); + let provided_value = object_map.get(name); match provided_value { Some(default_value) => { let value = graphql_parser_value_to_literal( default_value, - field.field_type(), - field.is_optional(), + r#type.id, + r#type.is_optional(), + query, ); quote!(#field_name: #value) } diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index 2cae6c14a..5ab000daa 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -1,5 +1,5 @@ use crate::{ - codegen::render_derives, codegen_options::GraphQLClientCodegenOptions, query::OperationRef, + codegen::render_derives, codegen_options::GraphQLClientCodegenOptions, query::BoundQuery, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -12,9 +12,9 @@ use quote::quote; * Generated serialize line: "AnEnum::where_ => "where"," */ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( - operation: &OperationRef<'schema>, all_used_types: &'a crate::query::UsedTypes, options: &'a GraphQLClientCodegenOptions, + query: BoundQuery<'schema>, ) -> impl Iterator + 'a { let derives = render_derives( options @@ -23,9 +23,9 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( ); let normalization = options.normalization(); - all_used_types.enums(operation.schema()).map(move |r#enum| { + all_used_types.enums(query.schema).map(move |(_id, r#enum)| { let variant_names: Vec = r#enum - .variants() + .variants .iter() .map(|v| { let safe_name = super::shared::keyword_replace(v.as_str()); @@ -41,10 +41,10 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( }) .collect(); let variant_names = &variant_names; - let name_ident = normalization.enum_name(r#enum.name()); + let name_ident = normalization.enum_name(r#enum.name.as_str()); let name_ident = Ident::new(&name_ident, Span::call_site()); let constructors: Vec<_> = r#enum - .variants() + .variants .iter() .map(|v| { let safe_name = super::shared::keyword_replace(v); @@ -55,7 +55,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( }) .collect(); let constructors = &constructors; - let variant_str: Vec<&str> = r#enum.variants().iter().map(|s| s.as_str()).collect(); + let variant_str: Vec<&str> = r#enum.variants.iter().map(|s| s.as_str()).collect(); let variant_str = &variant_str; let name = name_ident; diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 7522e9756..b82cacc12 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,39 +1,42 @@ use super::shared::keyword_replace; use crate::{ codegen_options::GraphQLClientCodegenOptions, - query::{OperationRef, UsedTypes}, + query::{BoundQuery, UsedTypes}, + schema::input_is_recursive_without_indirection, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; pub(super) fn generate_input_object_definitions( - operation: &OperationRef<'_>, all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, variable_derives: &impl quote::ToTokens, + query: &BoundQuery<'_>, ) -> Vec { all_used_types - .inputs(operation.schema()) - .map(|input| { - let normalized_name = options.normalization().input_name(input.name()); + .inputs(query.schema) + .map(|(_input_id, input)| { + let normalized_name = options.normalization().input_name(input.name.as_str()); let safe_name = keyword_replace(normalized_name); let struct_name = Ident::new(safe_name.as_ref(), Span::call_site()); - let fields = input.fields().map(|field| { - let safe_field_name = keyword_replace(field.name()); + let fields = input.fields.iter().map(|(field_name, field_type)| { + let safe_field_name = keyword_replace(field_name); let name_ident = Ident::new(safe_field_name.as_ref(), Span::call_site()); - let normalized_field_type_name = - options.normalization().field_type(field.field_type_name()); + let normalized_field_type_name = options + .normalization() + .field_type(field_type.id.name(query.schema)); let type_name = Ident::new(normalized_field_type_name.as_ref(), Span::call_site()); - let field_type = super::decorate_type(&type_name, field.field_type_qualifiers()); - let field_type = if field - .field_type_as_input() - .map(|input| input.is_recursive_without_indirection()) + let field_type_tokens = super::decorate_type(&type_name, &field_type.qualifiers); + let field_type = if field_type + .id + .as_input_id() + .map(|input_id| input_is_recursive_without_indirection(input_id, query.schema)) .unwrap_or(false) { - quote!(Box<#field_type>) + quote!(Box<#field_type_tokens>) } else { - field_type + field_type_tokens }; quote!(pub #name_ident: #field_type) }); diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 2d92be67c..b203cb413 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -7,10 +7,10 @@ use crate::{ }, deprecation::DeprecationStrategy, query::{ - FragmentRef, InlineFragment, OperationRef, Query, ResolvedFragmentId, SelectedField, - Selection, SelectionId, SelectionRef, + fragment_is_recursive, full_path_prefix, BoundQuery, InlineFragment, OperationId, + ResolvedFragment, ResolvedFragmentId, SelectedField, Selection, SelectionId, }, - schema::{Schema, TypeId, TypeRef}, + schema::{Schema, TypeId}, type_qualifiers::GraphqlTypeQualifier, GraphQLClientCodegenOptions, }; @@ -20,15 +20,16 @@ use quote::quote; use std::borrow::Cow; pub(crate) fn render_response_data_fields<'a>( - operation: &OperationRef<'a>, + operation_id: OperationId, options: &'a GraphQLClientCodegenOptions, + query: &'a BoundQuery<'a>, ) -> ExpandedSelection<'a> { + let operation = query.query.get_operation(operation_id); let mut expanded_selection = ExpandedSelection { - query: operation.query(), - schema: operation.schema(), + query, types: Vec::with_capacity(8), variants: Vec::new(), - fields: Vec::with_capacity(operation.selection_ids().len()), + fields: Vec::with_capacity(operation.selection_set.len()), options, }; @@ -38,9 +39,9 @@ pub(crate) fn render_response_data_fields<'a>( calculate_selection( &mut expanded_selection, - operation.selection_ids(), + &operation.selection_set, response_data_type_id, - operation.on_ref(), + TypeId::Object(operation.object_id), options, ); @@ -48,27 +49,28 @@ pub(crate) fn render_response_data_fields<'a>( } pub(super) fn render_fragment<'a>( - fragment: &FragmentRef<'a>, + fragment_id: ResolvedFragmentId, options: &'a GraphQLClientCodegenOptions, + query: &'a BoundQuery<'a>, ) -> ExpandedSelection<'a> { + let fragment = query.query.get_fragment(fragment_id); let mut expanded_selection = ExpandedSelection { - query: fragment.query(), - schema: fragment.schema(), + query, types: Vec::with_capacity(8), variants: Vec::new(), - fields: Vec::with_capacity(fragment.selection_ids().len()), + fields: Vec::with_capacity(fragment.selection_set.len()), options, }; let response_type_id = expanded_selection.push_type(ExpandedType { - name: fragment.name().into(), + name: fragment.name.as_str().into(), }); calculate_selection( &mut expanded_selection, - fragment.selection_ids(), + &fragment.selection_set, response_type_id, - fragment.on_ref(), + fragment.on, options, ); @@ -78,29 +80,29 @@ pub(super) fn render_fragment<'a>( /// A sub-selection set (spread) on one of the variants of a union or interface. enum VariantSelection<'a> { InlineFragment(&'a InlineFragment), - FragmentSpread(FragmentRef<'a>), + FragmentSpread((ResolvedFragmentId, &'a ResolvedFragment)), } impl<'a> VariantSelection<'a> { /// The second argument is the parent type id, so it can be excluded. fn from_selection( - selection_ref: &SelectionRef<'a>, + selection: &'a Selection, type_id: TypeId, + query: &BoundQuery<'a>, ) -> Option> { - match selection_ref.selection() { + match selection { Selection::InlineFragment(inline_fragment) => { Some(VariantSelection::InlineFragment(inline_fragment)) } Selection::FragmentSpread(fragment_id) => { - let schema = selection_ref.schema(); - let fragment_ref = selection_ref.query().get_fragment_ref(schema, *fragment_id); + let fragment = query.query.get_fragment(*fragment_id); - if fragment_ref.on() == type_id { + if fragment.on == type_id { // The selection is on the type itself. None } else { // The selection is on one of the variants of the type. - Some(VariantSelection::FragmentSpread(fragment_ref)) + Some(VariantSelection::FragmentSpread((*fragment_id, fragment))) } } Selection::Field(_) | Selection::Typename => None, @@ -110,7 +112,7 @@ impl<'a> VariantSelection<'a> { fn variant_type_id(&self) -> TypeId { match self { VariantSelection::InlineFragment(f) => f.type_id, - VariantSelection::FragmentSpread(f) => f.on(), + VariantSelection::FragmentSpread((_id, f)) => f.on, } } } @@ -119,50 +121,56 @@ fn calculate_selection<'a>( context: &mut ExpandedSelection<'a>, selection_set: &[SelectionId], struct_id: ResponseTypeId, - type_ref: TypeRef<'a>, + type_id: TypeId, options: &'a GraphQLClientCodegenOptions, ) { // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_. { - let variants: Option> = match type_ref.type_id() { + let variants: Option> = match type_id { TypeId::Interface(interface_id) => { - let interface = context.schema().interface(interface_id); - - Some(interface.variants().collect()) + let variants = context + .query + .schema + .objects() + .filter(|(_, obj)| obj.implements_interfaces.contains(&interface_id)) + .map(|(id, _)| TypeId::Object(id)); + + Some(variants.collect::>().into()) } TypeId::Union(union_id) => { - let union = context.schema().union(union_id); - Some(union.variants().into()) + let union = context.schema().get_union(union_id); + Some(union.variants.as_slice().into()) } _ => None, }; if let Some(variants) = variants { - let variant_selections: Vec<(SelectionRef<'_>, VariantSelection<'_>)> = selection_set - .iter() - .map(|id| context.get_selection_ref(*id)) - .filter_map(|selection_ref| { - VariantSelection::from_selection(&selection_ref, type_ref.type_id()) - .map(|variant_selection| (selection_ref, variant_selection)) - }) - .collect(); + let variant_selections: Vec<(SelectionId, &Selection, VariantSelection<'_>)> = + selection_set + .iter() + .map(|id| (id, context.query.query.get_selection(*id))) + .filter_map(|(id, selection)| { + VariantSelection::from_selection(&selection, type_id, context.query) + .map(|variant_selection| (*id, selection, variant_selection)) + }) + .collect(); // For each variant, get the corresponding fragment spreads and // inline fragments, or default to an empty variant (one with no // associated data). - for variant in variants.as_ref() { - let variant_schema_type = context.schema().type_ref(*variant); - let variant_name_str = variant_schema_type.name(); + for variant_type_id in variants.as_ref() { + let variant_name_str = variant_type_id.name(context.schema()); let mut variant_selections = variant_selections .iter() - .filter(|(_selection_ref, variant)| { - variant.variant_type_id() == variant_schema_type.type_id() + .filter(|(_id, _selection_ref, variant)| { + variant.variant_type_id() == *variant_type_id }) .peekable(); - if let Some((selection_ref, _variant)) = variant_selections.peek() { - let mut variant_struct_name_str = selection_ref.full_path_prefix(); + if let Some((selection_id, selection, _variant)) = variant_selections.peek() { + let mut variant_struct_name_str = + full_path_prefix(*selection_id, &context.query); variant_struct_name_str.reserve(2 + variant_name_str.len()); variant_struct_name_str.push_str("On"); variant_struct_name_str.push_str(variant_name_str); @@ -179,29 +187,28 @@ fn calculate_selection<'a>( let struct_id = context.push_type(expanded_type); - for (_selection, variant_selection) in variant_selections { + for (_selection_id, _selection, variant_selection) in variant_selections { match variant_selection { VariantSelection::InlineFragment(_) => { calculate_selection( context, - selection_ref.subselection_ids(), + selection.subselection(), struct_id, - variant_schema_type, + *variant_type_id, options, ); } - VariantSelection::FragmentSpread(fragment_ref) => { - context.push_field(ExpandedField { - field_type: fragment_ref.name().into(), + VariantSelection::FragmentSpread((fragment_id, fragment)) => context + .push_field(ExpandedField { + field_type: fragment.name.as_str().into(), field_type_qualifiers: &[GraphqlTypeQualifier::Required], flatten: true, graphql_name: None, - rust_name: fragment_ref.name().to_snake_case().into(), + rust_name: fragment.name.to_snake_case().into(), struct_id, deprecation: None, - boxed: fragment_ref.is_recursive(), - }) - } + boxed: fragment_is_recursive(*fragment_id, context.query.query), + }), } } } else { @@ -216,15 +223,15 @@ fn calculate_selection<'a>( } for id in selection_set { - let selection_ref = context.get_selection_ref(*id); + let selection = context.query.query.get_selection(*id); - match selection_ref.selection() { + match selection { Selection::Field(field) => { let (graphql_name, rust_name) = context.field_name(&field); let schema_field = field.schema_field(context.schema()); - let field_type = schema_field.field_type(); + let field_type_id = schema_field.r#type.id; - match field_type.type_id() { + match field_type_id { TypeId::Enum(enm) => { context.push_field(ExpandedField { graphql_name: Some(graphql_name), @@ -232,9 +239,9 @@ fn calculate_selection<'a>( struct_id, field_type: options .normalization() - .field_type(context.schema().r#enum(enm).name()) + .field_type(&context.schema().get_enum(enm).name) .into(), - field_type_qualifiers: schema_field.type_qualifiers(), + field_type_qualifiers: &schema_field.r#type.qualifiers, flatten: false, deprecation: schema_field.deprecation(), boxed: false, @@ -244,11 +251,12 @@ fn calculate_selection<'a>( context.push_field(ExpandedField { field_type: options .normalization() - .field_type(context.schema().scalar(scalar).name()) + .field_type(context.schema().get_scalar(scalar).name.as_str()) .into(), - field_type_qualifiers: field + field_type_qualifiers: &field .schema_field(context.schema()) - .type_qualifiers(), + .r#type + .qualifiers, graphql_name: Some(graphql_name), struct_id, rust_name, @@ -258,13 +266,13 @@ fn calculate_selection<'a>( }); } TypeId::Object(_) | TypeId::Interface(_) | TypeId::Union(_) => { - let struct_name_string = selection_ref.full_path_prefix(); + let struct_name_string = full_path_prefix(*id, &context.query); context.push_field(ExpandedField { struct_id, graphql_name: Some(graphql_name), rust_name, - field_type_qualifiers: schema_field.type_qualifiers(), + field_type_qualifiers: &schema_field.r#type.qualifiers, field_type: Cow::Owned(struct_name_string.clone()), flatten: false, boxed: false, @@ -277,9 +285,9 @@ fn calculate_selection<'a>( calculate_selection( context, - selection_ref.subselection_ids(), + selection.subselection(), type_id, - field_type, + field_type_id, options, ); } @@ -292,28 +300,28 @@ fn calculate_selection<'a>( // Here we only render fragments that are directly on the type // itself, and not on one of its variants. - let fragment = context.get_fragment_ref(*fragment_id); + let fragment = context.query.query.get_fragment(*fragment_id); // Assuming the query was validated properly, a fragment spread // is either on the field's type itself, or on one of the // variants (union or interfaces). If it's not directly a field // on the struct, it will be handled in the `on` variants. - if fragment.on() != type_ref.type_id() { + if fragment.on != type_id { continue; } - let original_field_name = fragment.name().to_snake_case(); + let original_field_name = fragment.name.to_snake_case(); let final_field_name = keyword_replace(original_field_name); context.push_field(ExpandedField { - field_type: fragment.name().into(), + field_type: fragment.name.as_str().into(), field_type_qualifiers: &[GraphqlTypeQualifier::Required], graphql_name: None, rust_name: final_field_name, struct_id, flatten: true, deprecation: None, - boxed: fragment.is_recursive(), + boxed: fragment_is_recursive(*fragment_id, context.query.query), }); // We stop here, because the structs for the fragments are generated separately, to @@ -406,8 +414,7 @@ pub(crate) struct ExpandedType<'a> { } pub(crate) struct ExpandedSelection<'a> { - query: &'a Query, - schema: &'a Schema, + query: &'a BoundQuery<'a>, types: Vec>, fields: Vec>, variants: Vec>, @@ -416,7 +423,7 @@ pub(crate) struct ExpandedSelection<'a> { impl<'a> ExpandedSelection<'a> { pub(crate) fn schema(&self) -> &'a Schema { - self.schema + self.query.schema } fn push_type(&mut self, tpe: ExpandedType<'a>) -> ResponseTypeId { @@ -434,19 +441,11 @@ impl<'a> ExpandedSelection<'a> { self.variants.push(variant); } - pub(crate) fn get_selection_ref(&self, selection_id: SelectionId) -> SelectionRef<'a> { - self.query.get_selection_ref(self.schema, selection_id) - } - - pub(crate) fn get_fragment_ref(&self, fragment_id: ResolvedFragmentId) -> FragmentRef<'a> { - self.query.get_fragment_ref(self.schema, fragment_id) - } - /// Returns a tuple to be interpreted as (graphql_name, rust_name). pub(crate) fn field_name(&self, field: &'a SelectedField) -> (&'a str, Cow<'a, str>) { let name = field .alias() - .unwrap_or_else(|| field.schema_field(self.schema).name()); + .unwrap_or_else(|| &field.schema_field(self.query.schema).name); let snake_case_name = name.to_snake_case(); let final_name = keyword_replace(snake_case_name); diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 138a3e0f8..cf2a1ac5c 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,4 +1,7 @@ -use crate::{codegen_options::*, query::OperationRef}; +use crate::{ + codegen_options::*, + query::{BoundQuery, OperationId}, +}; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; @@ -18,14 +21,19 @@ impl<'a> GeneratedModule<'a> { Ok(crate::codegen::response_for_query( self.root(), &self.options, + BoundQuery { + query: self.resolved_query, + schema: self.schema, + }, )?) } - fn root(&self) -> OperationRef<'_> { + fn root(&self) -> OperationId { let op_name = self.options.normalization().operation(self.operation); self.resolved_query - .select_operation(self.schema, &op_name, self.options.normalization()) + .select_operation(&op_name, self.options.normalization()) .expect("TODO: handle operation not found") + .0 } /// Generate the module and all the code inside. diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index bf3a3ae9a..5fc0f1403 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -96,19 +96,16 @@ pub fn generate_module_token_stream( let operations = options .operation_name .as_ref() - .and_then(|operation_name| { - query.select_operation(&schema, operation_name, &options.normalization()) - }) + .and_then(|operation_name| query.select_operation(operation_name, &options.normalization())) .map(|op| vec![op]); let operations = match (operations, &options.mode) { (Some(ops), _) => ops, - (None, &CodegenMode::Cli) => query.operations(&schema).collect(), + (None, &CodegenMode::Cli) => query.operations().collect(), (None, &CodegenMode::Derive) => { return Err(derive_operation_not_found_error( options.struct_ident(), &query, - &schema, )); } }; @@ -121,7 +118,7 @@ pub fn generate_module_token_stream( query_string: query_string.as_str(), schema: &schema, resolved_query: &query, - operation: operation.name(), + operation: &operation.1.name, options: &options, } .to_token_stream()?; @@ -156,12 +153,14 @@ fn read_file(path: &std::path::Path) -> anyhow::Result { fn derive_operation_not_found_error( ident: Option<&proc_macro2::Ident>, query: &crate::query::Query, - schema: &crate::schema::Schema, ) -> anyhow::Error { let operation_name = ident.map(ToString::to_string); let struct_ident = operation_name.as_deref().unwrap_or(""); - let available_operations: Vec<&str> = query.operations(schema).map(|op| op.name()).collect(); + let available_operations: Vec<&str> = query + .operations() + .map(|(_id, op)| op.name.as_str()) + .collect(); let available_operations: String = available_operations.join(", "); return format_err!( diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index b62f3d676..275988042 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -4,41 +4,20 @@ mod fragments; mod operations; -pub(crate) use fragments::FragmentRef; -pub(crate) use operations::OperationRef; +pub(crate) use fragments::{fragment_is_recursive, ResolvedFragment}; +pub(crate) use operations::ResolvedOperation; use crate::{ constants::TYPENAME_FIELD, normalization::Normalization, schema::{ - resolve_field_type, EnumRef, InputRef, ScalarRef, Schema, StoredFieldId, StoredFieldType, - TypeId, TypeRef, UnionRef, + resolve_field_type, EnumId, InputId, ScalarId, Schema, StoredEnum, StoredField, + StoredFieldId, StoredFieldType, StoredInputType, StoredScalar, TypeId, UnionId, }, - type_qualifiers::GraphqlTypeQualifier, }; -use fragments::*; use heck::CamelCase; -use operations::*; use std::collections::{HashMap, HashSet}; -/// This is a convenience struct that should stay private, it's an implementation detail for our `Ref` types. -#[derive(Copy, Clone)] -struct QueryWith<'a, T> { - query: &'a Query, - schema: &'a Schema, - focus: T, -} - -impl<'a, T> QueryWith<'a, T> { - fn refocus(&self, new_focus: U) -> QueryWith<'a, U> { - QueryWith { - query: self.query, - schema: self.schema, - focus: new_focus, - } - } -} - #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) struct SelectionId(u32); #[derive(Debug, Clone, Copy, PartialEq)] @@ -56,12 +35,6 @@ pub(crate) struct ResolvedFragmentId(u32); #[derive(Debug, Clone, Copy)] pub(crate) struct VariableId(u32); -impl VariableId { - fn new(idx: usize) -> Self { - VariableId(idx as u32) - } -} - #[derive(Debug, Clone, Copy)] enum SelectionParent { Field(SelectionId), @@ -92,7 +65,7 @@ impl SelectionParent { .get_mut(fragment_id.0 as usize) .expect("get fragment"); - fragment.selection.push(selection_id); + fragment.selection_set.push(selection_id); } SelectionParent::Operation(operation_id) => { let operation = q @@ -100,68 +73,48 @@ impl SelectionParent { .get_mut(operation_id.0 as usize) .expect("get operation"); - operation.selection.push(selection_id); + operation.selection_set.push(selection_id); } } } -} - -pub(crate) struct SelectionRef<'a>(QueryWith<'a, (SelectionId, &'a Selection)>); -impl<'a> SelectionRef<'a> { - pub(crate) fn contains_fragment(&self, fragment_id: ResolvedFragmentId) -> bool { - match self.selection() { - Selection::FragmentSpread(id) => *id == fragment_id, - _ => self - .subselection() - .any(|selection| selection.contains_fragment(fragment_id)), + pub(crate) fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { + match self { + SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { + query.query.get_selection(*id).to_path_segment(query) + } + SelectionParent::Operation(id) => query.query.get_operation(*id).to_path_segment(), + SelectionParent::Fragment(id) => query.query.get_fragment(*id).to_path_segment(), } } - pub(crate) fn query(&self) -> &'a Query { - self.0.query - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - pub(crate) fn selection(&self) -> &'a Selection { - self.0.focus.1 - } - - fn id(&self) -> SelectionId { - self.0.focus.0 - } - - pub(crate) fn subselection<'b>(&'b self) -> impl Iterator> + 'b { - self.0 - .focus - .1 - .subselection() - .iter() - .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) - } +} - pub(crate) fn subselection_ids(&self) -> &'a [SelectionId] { - self.selection().subselection() - } +#[derive(Debug)] +pub(crate) enum Selection { + Field(SelectedField), + InlineFragment(InlineFragment), + FragmentSpread(ResolvedFragmentId), + Typename, +} - pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes) { - let selection = self.selection(); - match selection { +impl Selection { + pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes, query: &BoundQuery<'_>) { + match self { Selection::Field(field) => { - let field_ref = self.0.schema.field(field.field_id); - used_types.types.insert(field_ref.type_id()); + let stored_field = query.schema.get_field(field.field_id); + used_types.types.insert(stored_field.r#type.id); - for item in self.subselection() { - item.collect_used_types(used_types); + for selection_id in self.subselection() { + let selection = query.query.get_selection(*selection_id); + selection.collect_used_types(used_types, query); } } Selection::InlineFragment(inline_fragment) => { used_types.types.insert(inline_fragment.type_id); - for item in self.subselection() { - item.collect_used_types(used_types); + for selection_id in self.subselection() { + let selection = query.query.get_selection(*selection_id); + selection.collect_used_types(used_types, query); } } Selection::FragmentSpread(fragment_id) => { @@ -172,92 +125,53 @@ impl<'a> SelectionRef<'a> { used_types.fragments.insert(*fragment_id); - let fragment_ref = self.0.query.get_fragment_ref(self.0.schema, *fragment_id); + let fragment = query.query.get_fragment(*fragment_id); - for item in fragment_ref.selection_set() { - item.collect_used_types(used_types); + for (_id, selection) in query.query.walk_selection_set(&fragment.selection_set) { + selection.collect_used_types(used_types, query); } } Selection::Typename => (), } } - pub(crate) fn full_path_prefix(&self) -> String { - let mut path = match self.selection() { - Selection::FragmentSpread(_) | Selection::InlineFragment(_) => Vec::new(), - _ => vec![self.to_path_segment()], - }; - - let mut item = self.id(); - - while let Some(parent) = self.0.query.selection_parent_idx.get(&item) { - path.push(self.0.refocus(*parent).to_path_segment()); - - match parent { - SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { - item = *id; - } - _ => break, - } + pub(crate) fn contains_fragment(&self, fragment_id: ResolvedFragmentId, query: &Query) -> bool { + match self { + Selection::FragmentSpread(id) => *id == fragment_id, + _ => self.subselection().iter().any(|selection_id| { + query + .get_selection(*selection_id) + .contains_fragment(fragment_id, query) + }), } + } - path.reverse(); - path.join("") + pub(crate) fn subselection(&self) -> &[SelectionId] { + match self { + Selection::Field(field) => field.selection_set.as_slice(), + Selection::InlineFragment(inline_fragment) => &inline_fragment.selection_set, + _ => &[], + } } - fn to_path_segment(&self) -> String { - match self.selection() { + fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { + match self { Selection::Field(field) => field .alias .as_ref() .map(|alias| alias.to_camel_case()) - .unwrap_or_else(move || self.0.schema.field(field.field_id).name().to_camel_case()), + .unwrap_or_else(move || { + query.schema.get_field(field.field_id).name.to_camel_case() + }), Selection::InlineFragment(inline_fragment) => format!( "On{}", - self.0 - .schema - .type_ref(inline_fragment.type_id) - .name() - .to_camel_case() + inline_fragment.type_id.name(query.schema).to_camel_case() ), other => unreachable!("{:?} in to_path_segment", other), } } } -impl<'a> QueryWith<'a, SelectionParent> { - pub(crate) fn to_path_segment(&self) -> String { - match self.focus { - SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { - SelectionRef(self.refocus((id, self.query.get_selection(id)))).to_path_segment() - } - SelectionParent::Operation(id) => OperationRef(self.refocus(id)).to_path_segment(), - SelectionParent::Fragment(id) => self - .query - .get_fragment_ref(self.schema, id) - .to_path_segment(), - } - } -} - -#[derive(Debug)] -pub(crate) enum Selection { - Field(SelectedField), - InlineFragment(InlineFragment), - FragmentSpread(ResolvedFragmentId), - Typename, -} - -impl Selection { - pub(crate) fn subselection(&self) -> &[SelectionId] { - match self { - Selection::Field(field) => field.selection_set.as_slice(), - Selection::InlineFragment(inline_fragment) => &inline_fragment.selection_set, - _ => &[], - } - } -} - #[derive(Debug)] pub(crate) struct InlineFragment { pub(crate) type_id: TypeId, @@ -277,8 +191,8 @@ impl SelectedField { self.alias.as_ref().map(String::as_str) } - pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> crate::schema::FieldRef<'a> { - schema.field(self.field_id) + pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> &'a StoredField { + schema.get_field(self.field_id) } } @@ -297,7 +211,7 @@ pub(crate) fn resolve( resolved_query.fragments.push(ResolvedFragment { name: fragment.name.clone(), on: schema.find_type(on).expect("TODO: proper error message"), - selection: Vec::new(), + selection_set: Vec::new(), }); } graphql_parser::query::Definition::Operation( @@ -309,10 +223,10 @@ pub(crate) fn resolve( ) })?; let resolved_operation: ResolvedOperation = ResolvedOperation { - object_id: on.id(), + object_id: on, name: m.name.as_ref().expect("mutation without name").to_owned(), _operation_type: operations::OperationType::Mutation, - selection: Vec::with_capacity(m.selection_set.items.len()), + selection_set: Vec::with_capacity(m.selection_set.items.len()), }; resolved_query.operations.push(resolved_operation); @@ -324,8 +238,8 @@ pub(crate) fn resolve( let resolved_operation: ResolvedOperation = ResolvedOperation { name: q.name.as_ref().expect("query without name").to_owned(), _operation_type: operations::OperationType::Query, - object_id: on.id(), - selection: Vec::with_capacity(q.selection_set.items.len()), + object_id: on, + selection_set: Vec::with_capacity(q.selection_set.items.len()), }; resolved_query.operations.push(resolved_operation); @@ -350,8 +264,8 @@ pub(crate) fn resolve( .expect("subscription without name") .to_owned(), _operation_type: operations::OperationType::Subscription, - object_id: on.id(), - selection: Vec::with_capacity(s.selection_set.items.len()), + object_id: on, + selection_set: Vec::with_capacity(s.selection_set.items.len()), }; resolved_query.operations.push(resolved_operation); @@ -391,10 +305,10 @@ fn resolve_fragment( resolve_selection( query, - schema, on, &fragment_definition.selection_set, SelectionParent::Fragment(id), + schema, )?; Ok(()) @@ -402,9 +316,10 @@ fn resolve_fragment( fn resolve_union_selection( query: &mut Query, - union: UnionRef<'_>, + _union_id: UnionId, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, + schema: &Schema, ) -> anyhow::Result<()> { for item in selection_set.items.iter() { match item { @@ -417,8 +332,7 @@ fn resolve_union_selection( } } graphql_parser::query::Selection::InlineFragment(inline_fragment) => { - let selection_id = - resolve_inline_fragment(query, union.schema(), inline_fragment, parent)?; + let selection_id = resolve_inline_fragment(query, schema, inline_fragment, parent)?; parent.add_to_selection_set(query, selection_id); } @@ -440,9 +354,10 @@ fn resolve_union_selection( fn resolve_object_selection<'a>( query: &mut Query, - object: impl crate::schema::ObjectRefLike<'a>, + object: &impl crate::schema::ObjectLike, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, + schema: &'a Schema, ) -> anyhow::Result<()> { for item in selection_set.items.iter() { match item { @@ -453,14 +368,16 @@ fn resolve_object_selection<'a>( continue; } - let field_ref = object.get_field_by_name(&field.name).ok_or_else(|| { - anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) - })?; + let (field_id, schema_field) = object + .get_field_by_name(&field.name, schema) + .ok_or_else(|| { + anyhow::anyhow!("No field named {} on {}", &field.name, object.name()) + })?; let id = query.push_selection( Selection::Field(SelectedField { alias: field.alias.clone(), - field_id: field_ref.field_id(), + field_id, selection_set: Vec::with_capacity(selection_set.items.len()), }), parent, @@ -468,16 +385,16 @@ fn resolve_object_selection<'a>( resolve_selection( query, - object.schema(), - field_ref.type_id(), + schema_field.r#type.id, &field.selection_set, SelectionParent::Field(id), + schema, )?; parent.add_to_selection_set(query, id); } graphql_parser::query::Selection::InlineFragment(inline) => { - let selection_id = resolve_inline_fragment(query, object.schema(), inline, parent)?; + let selection_id = resolve_inline_fragment(query, schema, inline, parent)?; parent.add_to_selection_set(query, selection_id); } @@ -498,23 +415,22 @@ fn resolve_object_selection<'a>( fn resolve_selection( ctx: &mut Query, - schema: &Schema, on: TypeId, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, + schema: &Schema, ) -> anyhow::Result<()> { match on { TypeId::Object(oid) => { - let object = schema.object(oid); - resolve_object_selection(ctx, object, selection_set, parent)?; + let object = schema.get_object(oid); + resolve_object_selection(ctx, object, selection_set, parent, schema)?; } TypeId::Interface(interface_id) => { - let interface = schema.interface(interface_id); - resolve_object_selection(ctx, interface, selection_set, parent)?; + let interface = schema.get_interface(interface_id); + resolve_object_selection(ctx, interface, selection_set, parent, schema)?; } TypeId::Union(union_id) => { - let union = schema.union(union_id); - resolve_union_selection(ctx, union, selection_set, parent)?; + resolve_union_selection(ctx, union_id, selection_set, parent, schema)?; } other => { anyhow::ensure!( @@ -552,10 +468,10 @@ fn resolve_inline_fragment( resolve_selection( query, - schema, type_id, &inline_fragment.selection_set, SelectionParent::InlineFragment(id), + schema, )?; Ok(id) @@ -573,24 +489,45 @@ fn resolve_operation( "Query contains a mutation operation, but the schema has no mutation type." ) })?; + let on = schema.get_object(on); + let (id, _) = query.find_operation(m.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &m.variable_definitions, schema, id); - resolve_object_selection(query, on, &m.selection_set, SelectionParent::Operation(id))?; + resolve_object_selection( + query, + on, + &m.selection_set, + SelectionParent::Operation(id), + schema, + )?; } graphql_parser::query::OperationDefinition::Query(q) => { - let on = schema.query_type(); + let on = schema.get_object(schema.query_type()); let (id, _) = query.find_operation(q.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &q.variable_definitions, schema, id); - resolve_object_selection(query, on, &q.selection_set, SelectionParent::Operation(id))?; + resolve_object_selection( + query, + on, + &q.selection_set, + SelectionParent::Operation(id), + schema, + )?; } graphql_parser::query::OperationDefinition::Subscription(s) => { let on = schema.subscription_type().ok_or_else(|| anyhow::anyhow!("Query contains a subscription operation, but the schema has no subscription type."))?; + let on = schema.get_object(on); let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap(); resolve_variables(query, &s.variable_definitions, schema, id); - resolve_object_selection(query, on, &s.selection_set, SelectionParent::Operation(id))?; + resolve_object_selection( + query, + on, + &s.selection_set, + SelectionParent::Operation(id), + schema, + )?; } graphql_parser::query::OperationDefinition::SelectionSet(_) => { unreachable!("unnamed queries are not supported") @@ -619,14 +556,8 @@ impl Query { id } - pub(crate) fn get_selection_ref<'a>( - &'a self, - schema: &'a Schema, - id: SelectionId, - ) -> SelectionRef<'a> { - let selection = self.get_selection(id); - - SelectionRef(self.with_schema(schema).refocus((id, selection))) + pub fn operations(&self) -> impl Iterator { + walk_operations(self) } pub(crate) fn get_selection(&self, id: SelectionId) -> &Selection { @@ -635,55 +566,25 @@ impl Query { .expect("Query.get_selection") } - fn get_fragment(&self, id: ResolvedFragmentId) -> &ResolvedFragment { + pub(crate) fn get_fragment(&self, id: ResolvedFragmentId) -> &ResolvedFragment { self.fragments .get(id.0 as usize) .expect("Query.get_fragment") } - fn get_variable(&self, id: VariableId) -> &ResolvedVariable { - self.variables + pub(crate) fn get_operation(&self, id: OperationId) -> &ResolvedOperation { + self.operations .get(id.0 as usize) - .expect("Query.get_variable") - } - - pub(crate) fn get_fragment_ref<'a>( - &'a self, - schema: &'a Schema, - id: ResolvedFragmentId, - ) -> FragmentRef<'a> { - let fragment = self.get_fragment(id); - - FragmentRef(self.with_schema(schema).refocus((id, fragment))) - } - - pub(crate) fn get_variable_ref<'a>( - &'a self, - schema: &'a Schema, - id: VariableId, - ) -> VariableRef<'a> { - let variable = self.get_variable(id); - - VariableRef(self.with_schema(schema).refocus((id, variable))) - } - - pub(crate) fn operations<'a>( - &'a self, - schema: &'a Schema, - ) -> impl Iterator> + 'a { - (0..self.operations.len()) - .map(move |idx| OperationRef(self.with_schema(schema).refocus(OperationId(idx as u32)))) + .expect("Query.get_operation") } /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation. pub(crate) fn select_operation<'a>( &'a self, - schema: &'a Schema, name: &str, normalization: &Normalization, - ) -> Option> { - self.operations(schema) - .find(|op| normalization.operation(op.name()) == name) + ) -> Option<(OperationId, &'a ResolvedOperation)> { + walk_operations(self).find(|(_id, op)| normalization.operation(&op.name) == name) } fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> { @@ -702,55 +603,37 @@ impl Query { .map(|(id, op)| (OperationId::new(id), op)) } - fn with_schema<'a>(&'a self, schema: &'a Schema) -> QueryWith<'a, ()> { - QueryWith { - focus: (), - query: self, - schema, - } + fn walk_selection_set<'a>( + &'a self, + selection_ids: &'a [SelectionId], + ) -> impl Iterator + 'a { + selection_ids + .iter() + .map(move |id| (*id, self.get_selection(*id))) } } #[derive(Debug)] -struct ResolvedVariable { - operation_id: OperationId, - name: String, - default: Option, - r#type: StoredFieldType, +pub(crate) struct ResolvedVariable { + pub(crate) operation_id: OperationId, + pub(crate) name: String, + pub(crate) default: Option, + pub(crate) r#type: StoredFieldType, } -#[derive(Copy, Clone)] -pub(crate) struct VariableRef<'a>(QueryWith<'a, (VariableId, &'a ResolvedVariable)>); - -impl<'a> VariableRef<'a> { - pub(crate) fn default(&self) -> Option<&graphql_parser::query::Value> { - self.0.focus.1.default.as_ref() - } - - pub(crate) fn name(&self) -> &'a str { - &self.0.focus.1.name - } - - pub(crate) fn variable_type(&self) -> TypeRef<'a> { - self.0.schema.type_ref(self.0.focus.1.r#type.id) - } - - pub(crate) fn type_name(&self) -> &'a str { - self.0.schema.type_ref(self.0.focus.1.r#type.id).name() +impl ResolvedVariable { + pub(crate) fn type_name<'schema>(&self, schema: &'schema Schema) -> &'schema str { + self.r#type.id.name(schema) } - pub(crate) fn type_qualifiers(&self) -> &[GraphqlTypeQualifier] { - &self.0.focus.1.r#type.qualifiers - } - - fn collect_used_types(&self, used_types: &mut UsedTypes) { - match self.0.focus.1.r#type.id { + fn collect_used_types(&self, used_types: &mut UsedTypes, schema: &Schema) { + match self.r#type.id { TypeId::Input(input_id) => { used_types.types.insert(TypeId::Input(input_id)); - let input = self.0.schema.input(input_id); + let input = schema.get_input(input_id); - input.used_input_ids_recursive(used_types) + input.used_input_ids_recursive(used_types, schema) } type_id @ TypeId::Scalar(_) | type_id @ TypeId::Enum(_) => { used_types.types.insert(type_id); @@ -770,40 +653,36 @@ impl UsedTypes { pub(crate) fn inputs<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator + 's { schema .inputs() - .filter(move |input_ref| self.types.contains(&input_ref.type_id())) + .filter(move |(id, _input)| self.types.contains(&TypeId::Input(*id))) } pub(crate) fn scalars<'s, 'a: 's>( &'s self, schema: &'a Schema, - ) -> impl Iterator> + 's { + ) -> impl Iterator + 's { self.types .iter() .filter_map(TypeId::as_scalar_id) - .map(move |scalar_id| schema.scalar(scalar_id)) - .filter(|scalar| !crate::schema::DEFAULT_SCALARS.contains(&scalar.name())) + .map(move |scalar_id| (scalar_id, schema.get_scalar(scalar_id))) + .filter(|(_id, scalar)| !crate::schema::DEFAULT_SCALARS.contains(&scalar.name.as_str())) } pub(crate) fn enums<'a, 'schema: 'a>( &'a self, schema: &'schema Schema, - ) -> impl Iterator> + 'a { + ) -> impl Iterator + 'a { self.types .iter() .filter_map(TypeId::as_enum_id) - .map(move |enum_id| schema.r#enum(enum_id)) + .map(move |enum_id| (enum_id, schema.get_enum(enum_id))) } pub(crate) fn fragment_ids<'b>(&'b self) -> impl Iterator + 'b { self.fragments.iter().map(|v| *v) } - - pub(crate) fn fragments_len(&self) -> usize { - self.fragments.len() - } } fn resolve_variables( @@ -821,3 +700,76 @@ fn resolve_variables( }); } } + +pub(crate) fn walk_operations( + query: &Query, +) -> impl Iterator { + query + .operations + .iter() + .enumerate() + .map(|(id, op)| (OperationId(id as u32), op)) +} + +pub(crate) fn operation_has_no_variables(operation_id: OperationId, query: &Query) -> bool { + walk_operation_variables(operation_id, query) + .next() + .is_none() +} + +pub(crate) fn walk_operation_variables( + operation_id: OperationId, + query: &Query, +) -> impl Iterator { + query + .variables + .iter() + .enumerate() + .map(|(idx, var)| (VariableId(idx as u32), var)) + .filter(move |(_id, var)| var.operation_id == operation_id) +} + +pub(crate) fn all_used_types(operation_id: OperationId, query: &BoundQuery<'_>) -> UsedTypes { + let mut used_types = UsedTypes::default(); + + let operation = query.query.get_operation(operation_id); + + for (_id, selection) in query.query.walk_selection_set(&operation.selection_set) { + selection.collect_used_types(&mut used_types, query); + } + + for (_id, variable) in walk_operation_variables(operation_id, query.query) { + variable.collect_used_types(&mut used_types, query.schema); + } + + used_types +} + +pub(crate) fn full_path_prefix(selection_id: SelectionId, query: &BoundQuery<'_>) -> String { + let mut path = match query.query.get_selection(selection_id) { + Selection::FragmentSpread(_) | Selection::InlineFragment(_) => Vec::new(), + selection => vec![selection.to_path_segment(query)], + }; + + let mut item = selection_id; + + while let Some(parent) = query.query.selection_parent_idx.get(&item) { + path.push(parent.to_path_segment(query)); + + match parent { + SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { + item = *id; + } + _ => break, + } + } + + path.reverse(); + path.join("") +} + +#[derive(Clone, Copy)] +pub(crate) struct BoundQuery<'a> { + pub(crate) query: &'a Query, + pub(crate) schema: &'a Schema, +} diff --git a/graphql_client_codegen/src/query/fragments.rs b/graphql_client_codegen/src/query/fragments.rs index eff6a922f..2a667696d 100644 --- a/graphql_client_codegen/src/query/fragments.rs +++ b/graphql_client_codegen/src/query/fragments.rs @@ -1,57 +1,24 @@ -use super::{Query, QueryWith, ResolvedFragmentId, SelectionId, SelectionRef}; -use crate::schema::{Schema, TypeId, TypeRef}; +use super::{Query, ResolvedFragmentId, SelectionId}; +use crate::schema::TypeId; use heck::*; #[derive(Debug)] pub(crate) struct ResolvedFragment { pub(crate) name: String, - pub(crate) on: crate::schema::TypeId, - pub(crate) selection: Vec, + pub(crate) on: TypeId, + pub(crate) selection_set: Vec, } -pub(crate) struct FragmentRef<'a>( - pub(super) QueryWith<'a, (ResolvedFragmentId, &'a ResolvedFragment)>, -); - -impl<'a> FragmentRef<'a> { - pub(crate) fn is_recursive(&self) -> bool { - let id = self.0.focus.0; - - self.selection_set() - .any(|selection| selection.contains_fragment(id)) - } - - pub(crate) fn query(&self) -> &'a Query { - self.0.query - } - - pub(crate) fn name(&self) -> &'a str { - &self.0.focus.1.name - } - - pub(crate) fn on(&self) -> TypeId { - self.0.focus.1.on - } - - pub(crate) fn on_ref(&self) -> TypeRef<'a> { - self.0.schema.type_ref(self.0.focus.1.on) - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - pub(crate) fn selection_ids(&self) -> &[SelectionId] { - &self.0.focus.1.selection +impl ResolvedFragment { + pub(super) fn to_path_segment(&self) -> String { + self.name.to_camel_case() } +} - pub(crate) fn selection_set<'b>(&'b self) -> impl Iterator> + 'b { - self.selection_ids() - .iter() - .map(move |id| self.0.query.get_selection_ref(self.0.schema, *id)) - } +pub(crate) fn fragment_is_recursive(fragment_id: ResolvedFragmentId, query: &Query) -> bool { + let fragment = query.get_fragment(fragment_id); - pub(super) fn to_path_segment(&self) -> String { - self.0.focus.1.name.to_camel_case() - } + query + .walk_selection_set(&fragment.selection_set) + .any(|(_id, selection)| selection.contains_fragment(fragment_id, query)) } diff --git a/graphql_client_codegen/src/query/operations.rs b/graphql_client_codegen/src/query/operations.rs index 94e2f3e41..f48a1bbf0 100644 --- a/graphql_client_codegen/src/query/operations.rs +++ b/graphql_client_codegen/src/query/operations.rs @@ -1,7 +1,5 @@ -use super::{ - OperationId, Query, QueryWith, SelectionId, SelectionRef, UsedTypes, VariableId, VariableRef, -}; -use crate::schema::{ObjectId, Schema, TypeId, TypeRef}; +use super::SelectionId; +use crate::schema::ObjectId; use heck::*; #[derive(Debug, Clone)] @@ -11,93 +9,15 @@ pub(crate) enum OperationType { Subscription, } -pub(crate) struct OperationRef<'a>(pub(super) QueryWith<'a, OperationId>); - -impl<'a> OperationRef<'a> { - pub(crate) fn query(&self) -> &'a Query { - self.0.query - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - fn get(&self) -> &'a ResolvedOperation { - self.0 - .query - .operations - .get(self.0.focus.0 as usize) - .expect("get operation") - } - - pub(crate) fn to_path_segment(&self) -> String { - self.get().name.to_camel_case() - } - - pub(crate) fn all_used_types(&self) -> UsedTypes { - let mut all_used_types = UsedTypes::default(); - - for selection in self.selection() { - selection.collect_used_types(&mut all_used_types); - } - - for variable in self.variables() { - variable.collect_used_types(&mut all_used_types); - } - - all_used_types - } - - pub(crate) fn selection<'b>(&'b self) -> impl Iterator> + 'b { - let operation = self.get(); - operation.selection.iter().map(move |selection_id| { - SelectionRef( - self.0 - .refocus((*selection_id, self.0.query.get_selection(*selection_id))), - ) - }) - } - - pub(crate) fn selection_ids(&self) -> &[SelectionId] { - &self.get().selection - } - - pub(crate) fn variables<'b>(&'b self) -> impl Iterator> + 'b { - self.0 - .query - .variables - .iter() - .enumerate() - .filter(move |(_, variable)| variable.operation_id == self.0.focus) - .map(move |(id, _)| { - self.0 - .query - .get_variable_ref(self.0.schema, VariableId::new(id)) - }) - } - - pub(crate) fn name(&self) -> &'a str { - self.get().name() - } - - pub(crate) fn has_no_variables(&self) -> bool { - self.variables().next().is_none() - } - - pub(crate) fn on_ref(&self) -> TypeRef<'a> { - self.0.schema.type_ref(TypeId::Object(self.get().object_id)) - } -} - pub(crate) struct ResolvedOperation { pub(crate) name: String, pub(crate) _operation_type: OperationType, - pub(crate) selection: Vec, + pub(crate) selection_set: Vec, pub(crate) object_id: ObjectId, } impl ResolvedOperation { - pub(crate) fn name(&self) -> &str { - &self.name + pub(crate) fn to_path_segment(&self) -> String { + self.name.to_camel_case() } } diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index 0570e4b96..da049e6c2 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -8,20 +8,8 @@ use crate::query::UsedTypes; use crate::type_qualifiers::GraphqlTypeQualifier; use std::collections::HashMap; -#[derive(Clone, Copy)] -/// This is a helper for the `Ref` types. It should stay private. -struct SchemaWith<'a, T> { - schema: &'a Schema, - focus: T, -} - -#[derive(Clone, Copy)] -pub(crate) struct TypeRef<'a>(SchemaWith<'a, TypeId>); - pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"]; -pub(crate) type SchemaRef<'a> = &'a Schema; - #[derive(Debug, PartialEq, Clone)] struct StoredObjectField { name: String, @@ -29,29 +17,37 @@ struct StoredObjectField { } #[derive(Debug, PartialEq, Clone)] -struct StoredObject { - name: String, - fields: Vec, - implements_interfaces: Vec, +pub(crate) struct StoredObject { + pub(crate) name: String, + pub(crate) fields: Vec, + pub(crate) implements_interfaces: Vec, } #[derive(Debug, PartialEq, Clone)] -struct StoredField { - name: String, - r#type: StoredFieldType, - parent: StoredFieldParent, +pub(crate) struct StoredField { + pub(crate) name: String, + pub(crate) r#type: StoredFieldType, + pub(crate) parent: StoredFieldParent, /// `Some(None)` should be interpreted as "deprecated, without reason" - deprecation: Option>, + pub(crate) deprecation: Option>, +} + +impl StoredField { + pub(crate) fn deprecation(&self) -> Option> { + self.deprecation + .as_ref() + .map(|inner| inner.as_ref().map(String::as_str)) + } } #[derive(Debug, PartialEq, Clone)] -enum StoredFieldParent { +pub(crate) enum StoredFieldParent { Object(ObjectId), Interface(InterfaceId), } #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] -pub(crate) struct ObjectId(usize); +pub(crate) struct ObjectId(u32); #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) struct ObjectFieldId(usize); @@ -69,7 +65,7 @@ pub(crate) struct UnionId(usize); pub(crate) struct EnumId(usize); #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] -pub(crate) struct InputId(usize); +pub(crate) struct InputId(u32); #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct StoredFieldId(usize); @@ -78,7 +74,7 @@ pub(crate) struct StoredFieldId(usize); struct InputFieldId(usize); #[derive(Debug, Clone, PartialEq)] -struct StoredInterface { +pub(crate) struct StoredInterface { name: String, fields: Vec, } @@ -94,14 +90,14 @@ pub(crate) struct StoredFieldType { } #[derive(Debug, Clone, PartialEq)] -struct StoredUnion { - name: String, - variants: Vec, +pub(crate) struct StoredUnion { + pub(crate) name: String, + pub(crate) variants: Vec, } #[derive(Debug, Clone, PartialEq)] -struct StoredScalar { - name: String, +pub(crate) struct StoredScalar { + pub(crate) name: String, } #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] @@ -114,78 +110,6 @@ pub(crate) enum TypeId { Input(InputId), } -impl<'a> TypeRef<'a> { - pub(crate) fn type_id(&self) -> TypeId { - self.0.focus - } - - pub(crate) fn name(&self) -> &'a str { - match self.0.focus { - TypeId::Object(obj) => self.0.schema.object(obj).name(), - TypeId::Scalar(s) => self.0.schema.scalar(s).name(), - TypeId::Interface(s) => self.0.schema.interface(s).name(), - TypeId::Union(s) => self.0.schema.union(s).name(), - TypeId::Enum(s) => self.0.schema.r#enum(s).name(), - TypeId::Input(s) => self.0.schema.input(s).name(), - } - } - - pub(crate) fn as_input_ref(&self) -> Option> { - match self.type_id() { - TypeId::Input(input_id) => Some(self.0.schema.input(input_id)), - _ => None, - } - } -} - -pub(crate) struct ScalarRef<'a>(SchemaWith<'a, ScalarId>); - -impl<'a> ScalarRef<'a> { - fn get(&self) -> &'a StoredScalar { - self.0.schema.get_scalar(self.0.focus) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } -} - -pub(crate) struct UnionRef<'a>(SchemaWith<'a, UnionId>); - -impl<'a> UnionRef<'a> { - fn get(&self) -> &'a StoredUnion { - self.0.schema.get_union(self.0.focus) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn schema(&self) -> &'a Schema { - self.0.schema - } - - pub(crate) fn variants(&self) -> &'a [TypeId] { - &self.get().variants - } -} - -pub(crate) struct EnumRef<'a>(SchemaWith<'a, EnumId>); - -impl<'a> EnumRef<'a> { - fn get(&self) -> &'a StoredEnum { - self.0.schema.get_enum(self.0.focus) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn variants(&self) -> &'a [String] { - &self.get().variants - } -} - impl TypeId { fn r#enum(id: usize) -> Self { TypeId::Enum(EnumId(id)) @@ -199,11 +123,11 @@ impl TypeId { TypeId::Union(UnionId(id)) } - fn object(id: usize) -> Self { + fn object(id: u32) -> Self { TypeId::Object(ObjectId(id)) } - fn input(id: usize) -> Self { + fn input(id: u32) -> Self { TypeId::Input(InputId(id)) } @@ -241,18 +165,29 @@ impl TypeId { _ => None, } } + + pub(crate) fn name<'a>(&self, schema: &'a Schema) -> &'a str { + match self { + TypeId::Object(obj) => schema.get_object(*obj).name.as_str(), + TypeId::Scalar(s) => schema.get_scalar(*s).name.as_str(), + TypeId::Interface(s) => schema.get_interface(*s).name.as_str(), + TypeId::Union(s) => schema.get_union(*s).name.as_str(), + TypeId::Enum(s) => schema.get_enum(*s).name.as_str(), + TypeId::Input(s) => schema.get_input(*s).name.as_str(), + } + } } #[derive(Debug, Clone, PartialEq)] -struct StoredEnum { - name: String, - variants: Vec, +pub(crate) struct StoredEnum { + pub(crate) name: String, + pub(crate) variants: Vec, } #[derive(Debug, Clone, PartialEq)] pub(crate) struct StoredInputFieldType { - id: TypeId, - qualifiers: Vec, + pub(crate) id: TypeId, + pub(crate) qualifiers: Vec, } impl StoredInputFieldType { @@ -274,9 +209,9 @@ impl StoredInputFieldType { } #[derive(Debug, Clone, PartialEq)] -struct StoredInputType { - name: String, - fields: Vec<(String, StoredInputFieldType)>, +pub(crate) struct StoredInputType { + pub(crate) name: String, + pub(crate) fields: Vec<(String, StoredInputFieldType)>, } /// Intermediate representation for a parsed GraphQL schema used during code generation. @@ -328,7 +263,7 @@ impl Schema { } fn push_object(&mut self, object: StoredObject) -> ObjectId { - let id = ObjectId(self.stored_objects.len()); + let id = ObjectId(self.stored_objects.len() as u32); self.stored_objects.push(object); id @@ -366,91 +301,51 @@ impl Schema { id } - pub(crate) fn query_type(&self) -> ObjectRef<'_> { - ObjectRef( - self.with( - self.query_type - .expect("Query operation type must be defined"), - ), - ) + pub(crate) fn query_type(&self) -> ObjectId { + self.query_type + .expect("Query operation type must be defined") } - pub(crate) fn mutation_type(&self) -> Option> { - self.mutation_type.map(|id| ObjectRef(self.with(id))) + pub(crate) fn mutation_type(&self) -> Option { + self.mutation_type } - pub(crate) fn subscription_type(&self) -> Option> { - self.subscription_type.map(|id| ObjectRef(self.with(id))) + pub(crate) fn subscription_type(&self) -> Option { + self.subscription_type } - fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface { + pub(crate) fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface { self.stored_interfaces.get(interface_id.0).unwrap() } - fn get_stored_input(&self, input_id: InputId) -> &StoredInputType { - self.stored_inputs.get(input_id.0).unwrap() + pub(crate) fn get_input(&self, input_id: InputId) -> &StoredInputType { + self.stored_inputs.get(input_id.0 as usize).unwrap() } - fn get_object(&self, object_id: ObjectId) -> &StoredObject { + pub(crate) fn get_object(&self, object_id: ObjectId) -> &StoredObject { self.stored_objects - .get(object_id.0) + .get(object_id.0 as usize) .expect("Schema::get_object") } - fn get_field(&self, field_id: StoredFieldId) -> &StoredField { + pub(crate) fn get_field(&self, field_id: StoredFieldId) -> &StoredField { self.stored_fields.get(field_id.0).unwrap() } - fn get_enum(&self, enum_id: EnumId) -> &StoredEnum { + pub(crate) fn get_enum(&self, enum_id: EnumId) -> &StoredEnum { self.stored_enums.get(enum_id.0).unwrap() } - fn get_scalar(&self, scalar_id: ScalarId) -> &StoredScalar { + pub(crate) fn get_scalar(&self, scalar_id: ScalarId) -> &StoredScalar { self.stored_scalars.get(scalar_id.0).unwrap() } - fn get_union(&self, union_id: UnionId) -> &StoredUnion { + pub(crate) fn get_union(&self, union_id: UnionId) -> &StoredUnion { self.stored_unions .get(union_id.0) .expect("Schema::get_union") } - fn objects<'a>(&'a self) -> impl Iterator> + 'a { - (0..self.stored_objects.len()).map(move |id| self.object(ObjectId(id))) - } - - pub(crate) fn union(&self, id: UnionId) -> UnionRef<'_> { - UnionRef(self.with(id)) - } - - pub(crate) fn object(&self, id: ObjectId) -> ObjectRef<'_> { - ObjectRef(self.with(id)) - } - - pub(crate) fn interface(&self, interface_id: InterfaceId) -> InterfaceRef<'_> { - InterfaceRef(self.with(interface_id)) - } - - pub(crate) fn field(&self, id: StoredFieldId) -> FieldRef<'_> { - FieldRef(self.with((id, self.get_field(id)))) - } - - pub(crate) fn scalar(&self, scalar_id: ScalarId) -> ScalarRef<'_> { - ScalarRef(self.with(scalar_id)) - } - - pub(crate) fn r#enum(&self, enum_id: EnumId) -> EnumRef<'_> { - EnumRef(self.with(enum_id)) - } - - pub(crate) fn type_ref(&self, id: TypeId) -> TypeRef<'_> { - TypeRef(self.with(id)) - } - - pub(crate) fn input(&self, id: InputId) -> InputRef<'_> { - InputRef(self.with(id)) - } - fn find_interface(&self, interface_name: &str) -> InterfaceId { self.find_type_id(interface_name).as_interface_id().unwrap() } @@ -459,6 +354,20 @@ impl Schema { self.names.get(type_name).map(|id| *id) } + pub(crate) fn objects(&self) -> impl Iterator { + self.stored_objects + .iter() + .enumerate() + .map(|(idx, obj)| (ObjectId(idx as u32), obj)) + } + + pub(crate) fn inputs(&self) -> impl Iterator { + self.stored_inputs + .iter() + .enumerate() + .map(|(idx, obj)| (InputId(idx as u32), obj)) + } + fn find_type_id(&self, type_name: &str) -> TypeId { match self.names.get(type_name) { Some(id) => *id, @@ -470,130 +379,19 @@ impl Schema { } } } - - pub(crate) fn inputs<'a>(&'a self) -> impl Iterator> + 'a { - (0..self.stored_inputs.len()).map(move |id| InputRef(self.with(InputId(id)))) - } - - fn with(&self, focus: F) -> SchemaWith<'_, F> { - SchemaWith { - schema: self, - focus, - } - } } -pub(crate) struct InterfaceRef<'a>(SchemaWith<'a, InterfaceId>); - -impl<'a> InterfaceRef<'a> { - fn get(&self) -> &'a StoredInterface { - self.0.schema.get_interface(self.0.focus) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn variants<'b>(&'b self) -> impl Iterator + 'b { - self.0 - .schema - .objects() - .filter(move |object| object.implements_interface(self.0.focus)) - .map(|object| TypeId::Object(object.id())) - } -} - -pub(crate) struct ObjectRef<'a>(SchemaWith<'a, ObjectId>); - -impl<'a> ObjectRef<'a> { - fn get(&self) -> &'a StoredObject { - self.0.schema.get_object(self.0.focus) - } - - fn fields<'b>(&'b self) -> impl Iterator> + 'b { - self.get() - .fields - .iter() - .map(move |field| self.0.schema.field(*field)) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn get_field_by_name(&self, name: &str) -> Option> { - self.fields().find(|field| field.name() == name) - } - - pub(crate) fn id(&self) -> ObjectId { - self.0.focus - } - - pub(crate) fn implements_interface(&self, id: InterfaceId) -> bool { - self.get().implements_interfaces.contains(&id) - } -} - -pub(crate) struct FieldRef<'a>(SchemaWith<'a, (StoredFieldId, &'a StoredField)>); - -impl<'a> FieldRef<'a> { - fn field(&self) -> &'a StoredField { - self.0.focus.1 - } - - pub(crate) fn name(&self) -> &'a str { - &self.field().name - } - - pub(crate) fn field_type(&self) -> TypeRef<'a> { - self.0.schema.type_ref(self.field().r#type.id) - } - - pub(crate) fn type_qualifiers(&self) -> &'a [GraphqlTypeQualifier] { - &self.field().r#type.qualifiers - } - - pub(crate) fn field_id(&self) -> StoredFieldId { - self.0.focus.0 - } - - pub(crate) fn type_id(&self) -> TypeId { - self.field().r#type.id - } - - pub(crate) fn deprecation(&self) -> Option> { - self.field() - .deprecation - .as_ref() - .map(|o| o.as_ref().map(String::as_str)) - } -} - -pub(crate) struct InputRef<'a>(SchemaWith<'a, InputId>); - -impl<'a> InputRef<'a> { - fn get(&self) -> &'a StoredInputType { - self.0.schema.get_stored_input(self.0.focus) - } - - pub(crate) fn type_id(&self) -> TypeId { - TypeId::Input(self.0.focus) - } - - pub(crate) fn name(&self) -> &'a str { - &self.get().name - } - - pub(crate) fn used_input_ids_recursive<'b>(&'b self, used_types: &mut UsedTypes) { - for type_id in self.fields().map(|field| field.field_type_id()) { +impl StoredInputType { + pub(crate) fn used_input_ids_recursive(&self, used_types: &mut UsedTypes, schema: &Schema) { + for type_id in self.fields.iter().map(|(_name, ty)| ty.id) { match type_id { TypeId::Input(input_id) => { if used_types.types.contains(&type_id) { continue; } else { used_types.types.insert(type_id); - let input_ref = self.0.schema.input(input_id); - input_ref.used_input_ids_recursive(used_types); + let input = schema.get_input(input_id); + input.used_input_ids_recursive(used_types, schema); } } TypeId::Enum(_) | TypeId::Scalar(_) => { @@ -604,38 +402,25 @@ impl<'a> InputRef<'a> { } } - pub(crate) fn fields<'b>(&'b self) -> impl Iterator> + 'b { - self.get().fields.iter().map(move |field| { - StoredInputFieldRef(SchemaWith { - schema: self.0.schema, - focus: field, - }) - }) - } - - pub(crate) fn is_recursive_without_indirection(&self) -> bool { - self.contains_type_without_indirection(self.0.focus) - } - - fn contains_type_without_indirection(&self, input_id: InputId) -> bool { + fn contains_type_without_indirection(&self, input_id: InputId, schema: &Schema) -> bool { // The input type is recursive if any of its members contains it, without indirection - self.fields().any(|field| { + self.fields.iter().any(|(_name, field_type)| { // the field is indirected, so no boxing is needed - if field.is_indirected() { + if field_type.is_indirected() { return false; } - let field_input_id = field.field_type_id().as_input_id(); + let field_input_id = field_type.id.as_input_id(); if let Some(field_input_id) = field_input_id { if field_input_id == input_id { return true; } - let input = self.0.schema.input(field_input_id); + let input = schema.get_input(field_input_id); // we check if the other input contains this one (without indirection) - input.contains_type_without_indirection(input_id) + input.contains_type_without_indirection(input_id, schema) } else { // the field is not referring to an input type false @@ -644,50 +429,9 @@ impl<'a> InputRef<'a> { } } -pub(crate) struct StoredInputFieldRef<'a>(SchemaWith<'a, &'a (String, StoredInputFieldType)>); - -impl<'a> StoredInputFieldRef<'a> { - fn field_type_id(&self) -> TypeId { - self.0.focus.1.id - } - - pub(crate) fn field_type_qualifiers(&self) -> &'a [GraphqlTypeQualifier] { - &self.0.focus.1.qualifiers - } - - pub(crate) fn field_type_name(&self) -> &'a str { - TypeRef(SchemaWith { - schema: self.0.schema, - focus: self.field_type_id(), - }) - .name() - } - - pub(crate) fn field_type(&self) -> TypeRef<'a> { - TypeRef(SchemaWith { - schema: self.0.schema, - focus: self.field_type_id(), - }) - } - - pub(crate) fn is_optional(&self) -> bool { - self.0.focus.1.is_optional() - } - - /// This is used for recursion checking. - pub(crate) fn field_type_as_input(&self) -> Option> { - self.field_type_id() - .as_input_id() - .map(|input_id| InputRef(self.0.schema.with(input_id))) - } - - fn is_indirected(&self) -> bool { - self.0.focus.1.is_indirected() - } - - pub(crate) fn name(&self) -> &'a str { - self.0.focus.0.as_str() - } +pub(crate) fn input_is_recursive_without_indirection(input_id: InputId, schema: &Schema) -> bool { + let input = schema.get_input(input_id); + input.contains_type_without_indirection(input_id, schema) } impl std::convert::From for Schema { @@ -738,42 +482,46 @@ pub(crate) fn resolve_field_type( } } -pub(crate) trait ObjectRefLike<'a> { - fn name(&self) -> &'a str; - - fn get_field_by_name(&self, name: &str) -> Option>; +pub(crate) trait ObjectLike { + fn name(&self) -> &str; - fn schema(&self) -> SchemaRef<'a>; + fn get_field_by_name<'a>( + &'a self, + name: &str, + schema: &'a Schema, + ) -> Option<(StoredFieldId, &'a StoredField)>; } -impl<'a> ObjectRefLike<'a> for ObjectRef<'a> { - fn name(&self) -> &'a str { - self.name() +impl ObjectLike for StoredObject { + fn name(&self) -> &str { + &self.name } - fn get_field_by_name(&self, name: &str) -> Option> { - self.get_field_by_name(name) - } - - fn schema(&self) -> SchemaRef<'a> { - self.0.schema + fn get_field_by_name<'a>( + &'a self, + name: &str, + schema: &'a Schema, + ) -> Option<(StoredFieldId, &'a StoredField)> { + self.fields + .iter() + .map(|field_id| (*field_id, schema.get_field(*field_id))) + .find(|(_, f)| f.name == name) } } -impl<'a> ObjectRefLike<'a> for InterfaceRef<'a> { - fn name(&self) -> &'a str { - self.name() +impl ObjectLike for StoredInterface { + fn name(&self) -> &str { + &self.name } - fn get_field_by_name(&self, name: &str) -> Option> { - self.get() - .fields + fn get_field_by_name<'a>( + &'a self, + name: &str, + schema: &'a Schema, + ) -> Option<(StoredFieldId, &'a StoredField)> { + self.fields .iter() - .map(|field_id| self.0.schema.field(*field_id)) - .find(|field| field.name() == name) - } - - fn schema(&self) -> SchemaRef<'a> { - self.0.schema + .map(|field_id| (*field_id, schema.get_field(*field_id))) + .find(|(_, field)| field.name == name) } } diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 418dea736..7c7596134 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -89,7 +89,7 @@ fn populate_names_map(schema: &mut Schema, definitions: &[Definition]) { .for_each(|(idx, object_name)| { schema .names - .insert(object_name.into(), TypeId::r#object(idx)); + .insert(object_name.into(), TypeId::r#object(idx as u32)); }); definitions @@ -128,7 +128,9 @@ fn populate_names_map(schema: &mut Schema, definitions: &[Definition]) { }) .enumerate() .for_each(|(idx, input_name)| { - schema.names.insert(input_name.into(), TypeId::input(idx)); + schema + .names + .insert(input_name.into(), TypeId::input(idx as u32)); }); } diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 9c65efe60..0c4c3c3ed 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -34,14 +34,14 @@ fn build_names_map(src: &mut JsonSchema, schema: &mut Schema) { .map(|obj| obj.name.as_ref().expect("object name")) .enumerate() .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::object(idx)); + names.insert(name.clone(), TypeId::object(idx as u32)); }); inputs_mut(src) .map(|obj| obj.name.as_ref().expect("input name")) .enumerate() .for_each(|(idx, name)| { - names.insert(name.clone(), TypeId::input(idx)); + names.insert(name.clone(), TypeId::input(idx as u32)); }); } diff --git a/graphql_client_codegen/src/schema/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs index a9ec1e7c8..cfd1f2580 100644 --- a/graphql_client_codegen/src/schema/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -15,15 +15,18 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { // Root objects { - assert_eq!(json.query_type().name(), gql.query_type().name()); assert_eq!( - json.mutation_type().map(|t| t.name()), - gql.mutation_type().map(|t| t.name()), + 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| t.name()), - gql.subscription_type().map(|t| t.name()), + 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." ); } diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index 636ccaf3f..9c6b39c49 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -12,11 +12,11 @@ fn schema_with_keywords_works() { let options = GraphQLClientCodegenOptions::new(CodegenMode::Cli); let query = crate::query::resolve(&schema, &query).unwrap(); - for operation in query.operations(&schema) { + for (_id, operation) in query.operations() { let generated_tokens = generated_module::GeneratedModule { query_string, schema: &schema, - operation: operation.name(), + operation: &operation.name, resolved_query: &query, options: &options, } From a7bd81e1eb4c7ff97ad0a14478f0fcbf28a4a168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Tue, 24 Mar 2020 22:50:44 +0100 Subject: [PATCH 83/93] Alias response structs containing a single fragment to just the fragment Squashed: - Flatten type-refining fragments ...when they are the only selection on the variant. --- graphql_client/tests/fragments.rs | 26 ++------ .../tests/type_refining_fragments.rs | 12 +--- graphql_client/tests/union_query.rs | 46 ++++--------- .../src/codegen/selection.rs | 64 ++++++++++++++++++- 4 files changed, 84 insertions(+), 64 deletions(-) diff --git a/graphql_client/tests/fragments.rs b/graphql_client/tests/fragments.rs index 28572b0e7..902ec409e 100644 --- a/graphql_client/tests/fragments.rs +++ b/graphql_client/tests/fragments.rs @@ -24,13 +24,7 @@ fn fragment_reference() { let valid_fragment_reference = serde_json::from_value::(valid_response).unwrap(); - assert_eq!( - valid_fragment_reference - .fragment_reference - .in_fragment - .unwrap(), - "value" - ); + assert_eq!(valid_fragment_reference.in_fragment.unwrap(), "value"); } #[test] @@ -42,13 +36,7 @@ fn fragments_with_snake_case_name() { let valid_fragment_reference = serde_json::from_value::(valid_response).unwrap(); - assert_eq!( - valid_fragment_reference - .snake_case_fragment - .in_fragment - .unwrap(), - "value" - ); + assert_eq!(valid_fragment_reference.in_fragment.unwrap(), "value"); } #[derive(GraphQLQuery)] @@ -64,11 +52,9 @@ fn recursive_fragment() { let _ = RecursiveFragment { head: Some("ABCD".to_string()), - tail: Some(RecursiveFragmentTail { - recursive_fragment: Box::new(RecursiveFragment { - head: Some("EFGH".to_string()), - tail: None, - }), - }), + tail: Some(Box::new(RecursiveFragment { + head: Some("EFGH".to_string()), + tail: None, + })), }; } diff --git a/graphql_client/tests/type_refining_fragments.rs b/graphql_client/tests/type_refining_fragments.rs index b27462024..2e7497fde 100644 --- a/graphql_client/tests/type_refining_fragments.rs +++ b/graphql_client/tests/type_refining_fragments.rs @@ -29,9 +29,7 @@ fn type_refining_fragment_on_union() { last_name: Some("Lorde".to_string()), }), query_on_union::QueryOnUnionNames::Dog(query_on_union::QueryOnUnionNamesOnDog { - dog_name: query_on_union::DogName { - name: "Laïka".to_string(), - }, + name: "Laïka".to_string(), }), query_on_union::QueryOnUnionNames::Organization( query_on_union::QueryOnUnionNamesOnOrganization { @@ -39,9 +37,7 @@ fn type_refining_fragment_on_union() { }, ), query_on_union::QueryOnUnionNames::Dog(query_on_union::QueryOnUnionNamesOnDog { - dog_name: query_on_union::DogName { - name: "Norbert".to_string(), - }, + name: "Norbert".to_string(), }), ]), }; @@ -62,9 +58,7 @@ fn type_refining_fragment_on_interface() { QueryOnInterfaceEverything { name: "Audrey Lorde".to_string(), on: QueryOnInterfaceEverythingOn::Person(QueryOnInterfaceEverythingOnPerson { - birthday_fragment: BirthdayFragment { - birthday: Some("1934-02-18".to_string()), - }, + birthday: Some("1934-02-18".to_string()), }), }, QueryOnInterfaceEverything { diff --git a/graphql_client/tests/union_query.rs b/graphql_client/tests/union_query.rs index 713620f92..87d4c976e 100644 --- a/graphql_client/tests/union_query.rs +++ b/graphql_client/tests/union_query.rs @@ -62,38 +62,20 @@ fn fragment_on_union() { let expected = fragment_on_union::ResponseData { names: Some(vec![ - fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Person( - fragment_on_union::NamesFragmentOnPerson { - first_name: "Audrey".to_string(), - }, - ), - on: fragment_on_union::FragmentOnUnionNamesOn::Person, - }, - fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Dog( - fragment_on_union::NamesFragmentOnDog { - name: "Laïka".to_string(), - }, - ), - on: fragment_on_union::FragmentOnUnionNamesOn::Dog, - }, - fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Organization( - fragment_on_union::NamesFragmentOnOrganization { - title: "Mozilla".to_string(), - }, - ), - on: fragment_on_union::FragmentOnUnionNamesOn::Organization, - }, - fragment_on_union::FragmentOnUnionNames { - names_fragment: fragment_on_union::NamesFragment::Dog( - fragment_on_union::NamesFragmentOnDog { - name: "Norbert".to_string(), - }, - ), - on: fragment_on_union::FragmentOnUnionNamesOn::Dog, - }, + fragment_on_union::NamesFragment::Person(fragment_on_union::NamesFragmentOnPerson { + first_name: "Audrey".to_string(), + }), + fragment_on_union::NamesFragment::Dog(fragment_on_union::NamesFragmentOnDog { + name: "Laïka".to_string(), + }), + fragment_on_union::NamesFragment::Organization( + fragment_on_union::NamesFragmentOnOrganization { + title: "Mozilla".to_string(), + }, + ), + fragment_on_union::NamesFragment::Dog(fragment_on_union::NamesFragmentOnDog { + name: "Norbert".to_string(), + }), ]), }; diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index b203cb413..4a84e8755 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -28,6 +28,7 @@ pub(crate) fn render_response_data_fields<'a>( let mut expanded_selection = ExpandedSelection { query, types: Vec::with_capacity(8), + aliases: Vec::new(), variants: Vec::new(), fields: Vec::with_capacity(operation.selection_set.len()), options, @@ -56,6 +57,7 @@ pub(super) fn render_fragment<'a>( let fragment = query.query.get_fragment(fragment_id); let mut expanded_selection = ExpandedSelection { query, + aliases: Vec::new(), types: Vec::with_capacity(8), variants: Vec::new(), fields: Vec::with_capacity(fragment.selection_set.len()), @@ -124,6 +126,22 @@ fn calculate_selection<'a>( type_id: TypeId, options: &'a GraphQLClientCodegenOptions, ) { + // If the selection only contains a fragment, replace the selection with + // that fragment. + if selection_set.len() == 1 { + if let Selection::FragmentSpread(fragment_id) = + context.query.query.get_selection(selection_set[0]) + { + let fragment = context.query.query.get_fragment(*fragment_id); + context.push_type_alias(TypeAlias { + name: &fragment.name, + struct_id, + boxed: fragment_is_recursive(*fragment_id, context.query.query), + }); + return; + } + } + // If we are on a union or an interface, we need to generate an enum that matches the variants _exhaustively_. { let variants: Option> = match type_id { @@ -161,14 +179,14 @@ fn calculate_selection<'a>( for variant_type_id in variants.as_ref() { let variant_name_str = variant_type_id.name(context.schema()); - let mut variant_selections = variant_selections + let variant_selections: Vec<_> = variant_selections .iter() .filter(|(_id, _selection_ref, variant)| { variant.variant_type_id() == *variant_type_id }) - .peekable(); + .collect(); - if let Some((selection_id, selection, _variant)) = variant_selections.peek() { + if let Some((selection_id, selection, _variant)) = variant_selections.get(0) { let mut variant_struct_name_str = full_path_prefix(*selection_id, &context.query); variant_struct_name_str.reserve(2 + variant_name_str.len()); @@ -187,6 +205,19 @@ fn calculate_selection<'a>( let struct_id = context.push_type(expanded_type); + if variant_selections.len() == 1 { + if let VariantSelection::FragmentSpread((fragment_id, fragment)) = + variant_selections[0].2 + { + context.push_type_alias(TypeAlias { + boxed: fragment_is_recursive(fragment_id, context.query.query), + name: &fragment.name, + struct_id, + }); + continue; + } + } + for (_selection_id, _selection, variant_selection) in variant_selections { match variant_selection { VariantSelection::InlineFragment(_) => { @@ -334,6 +365,12 @@ fn calculate_selection<'a>( #[derive(Clone, Copy, PartialEq)] struct ResponseTypeId(u32); +struct TypeAlias<'a> { + name: &'a str, + struct_id: ResponseTypeId, + boxed: bool, +} + struct ExpandedField<'a> { graphql_name: Option<&'a str>, rust_name: Cow<'a, str>, @@ -418,6 +455,7 @@ pub(crate) struct ExpandedSelection<'a> { types: Vec>, fields: Vec>, variants: Vec>, + aliases: Vec>, options: &'a GraphQLClientCodegenOptions, } @@ -437,6 +475,10 @@ impl<'a> ExpandedSelection<'a> { self.fields.push(field); } + fn push_type_alias(&mut self, alias: TypeAlias<'a>) { + self.aliases.push(alias) + } + fn push_variant(&mut self, variant: ExpandedVariant<'a>) { self.variants.push(variant); } @@ -464,6 +506,22 @@ impl<'a> ExpandedSelection<'a> { for (type_id, ty) in self.types() { let struct_name = Ident::new(&ty.name, Span::call_site()); + + // If the type is aliased, stop here. + if let Some(alias) = self.aliases.iter().find(|alias| alias.struct_id == type_id) { + let fragment_name = Ident::new(&alias.name, Span::call_site()); + let fragment_name = if alias.boxed { + quote!(Box<#fragment_name>) + } else { + quote!(#fragment_name) + }; + let item = quote! { + pub type #struct_name = #fragment_name; + }; + items.push(item); + continue; + } + let mut fields = self .fields .iter() From c61bab3eacfd864b476ace8cc56b37d1b16efce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 08:28:02 +0100 Subject: [PATCH 84/93] Minor error handling improvements --- graphql_client_codegen/src/codegen.rs | 11 ++++++++--- graphql_client_codegen/src/generated_module.rs | 13 +++++++++---- graphql_client_codegen/src/lib.rs | 6 +++--- graphql_client_codegen/src/query.rs | 8 +++++++- graphql_client_codegen/src/unions.rs | 1 + 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 7b0954449..999ecbb25 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -263,9 +263,14 @@ fn graphql_parser_value_to_literal( ] } } - Value::Object(obj) => { - render_object_literal(obj, ty.as_input_id().expect("TODO: error handling"), query) - } + Value::Object(obj) => ty + .as_input_id() + .map(|input_id| render_object_literal(obj, input_id, query)) + .unwrap_or_else(|| { + quote!(compile_error!( + "Object literal on a non-input-object field." + )) + }), }; if is_optional { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index cf2a1ac5c..07888e82c 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -19,7 +19,7 @@ impl<'a> GeneratedModule<'a> { /// Generate the items for the variables and the response that will go inside the module. fn build_impls(&self) -> anyhow::Result { Ok(crate::codegen::response_for_query( - self.root(), + self.root()?, &self.options, BoundQuery { query: self.resolved_query, @@ -28,12 +28,17 @@ impl<'a> GeneratedModule<'a> { )?) } - fn root(&self) -> OperationId { + fn root(&self) -> anyhow::Result { let op_name = self.options.normalization().operation(self.operation); self.resolved_query .select_operation(&op_name, self.options.normalization()) - .expect("TODO: handle operation not found") - .0 + .map(|op| op.0) + .ok_or_else(|| { + anyhow::anyhow!( + "Could not find an operation named {} in the query document.", + op_name + ) + }) } /// Generate the module and all the code inside. diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 5fc0f1403..62a7b5940 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -61,7 +61,7 @@ pub fn generate_module_token_stream( let schema_string = read_file(v.key())?; let schema = match schema_extension { "graphql" | "gql" => { - let s = graphql_parser::schema::parse_schema(&schema_string).expect("TODO: error conversion"); + let s = graphql_parser::schema::parse_schema(&schema_string).map_err(|parser_error| anyhow::anyhow!("Parser error: {}", parser_error))?; schema::Schema::from(s) } "json" => { @@ -83,8 +83,8 @@ pub fn generate_module_token_stream( hash_map::Entry::Occupied(o) => o.get().clone(), hash_map::Entry::Vacant(v) => { let query_string = read_file(v.key())?; - let query = - graphql_parser::parse_query(&query_string).expect("TODO: error conversion"); + let query = graphql_parser::parse_query(&query_string) + .map_err(|err| anyhow::anyhow!("Query parser error: {}", err))?; v.insert((query_string, query)).clone() } } diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 275988042..d9d01ee9a 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -210,7 +210,13 @@ pub(crate) fn resolve( let graphql_parser::query::TypeCondition::On(on) = &fragment.type_condition; resolved_query.fragments.push(ResolvedFragment { name: fragment.name.clone(), - on: schema.find_type(on).expect("TODO: proper error message"), + on: schema.find_type(on).ok_or_else(|| { + anyhow::anyhow!( + "Could not find type {} for fragment {} in schema.", + on, + fragment.name + ) + })?, selection_set: Vec::new(), }); } diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs index 37fabf798..8ae5da31a 100644 --- a/graphql_client_codegen/src/unions.rs +++ b/graphql_client_codegen/src/unions.rs @@ -6,6 +6,7 @@ use quote::quote; use std::cell::Cell; use std::collections::BTreeSet; +// TODO: here! #[derive(Debug, Fail)] #[fail(display = "UnionError")] enum UnionError { From 8920d5a8ba75b52b28ab6a08389a94b3aa0cdb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 21:36:04 +0100 Subject: [PATCH 85/93] Add validations for union and interface selections --- graphql_client_codegen/src/query.rs | 271 ++++++--------- graphql_client_codegen/src/query/selection.rs | 257 ++++++++++++++ graphql_client_codegen/src/unions.rs | 324 ------------------ graphql_query_derive/src/lib.rs | 1 - 4 files changed, 368 insertions(+), 485 deletions(-) create mode 100644 graphql_client_codegen/src/query/selection.rs delete mode 100644 graphql_client_codegen/src/unions.rs diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index d9d01ee9a..d485f09dc 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -3,19 +3,20 @@ mod fragments; mod operations; +mod selection; pub(crate) use fragments::{fragment_is_recursive, ResolvedFragment}; pub(crate) use operations::ResolvedOperation; +pub(crate) use selection::*; use crate::{ constants::TYPENAME_FIELD, normalization::Normalization, schema::{ - resolve_field_type, EnumId, InputId, ScalarId, Schema, StoredEnum, StoredField, - StoredFieldId, StoredFieldType, StoredInputType, StoredScalar, TypeId, UnionId, + resolve_field_type, EnumId, InputId, ScalarId, Schema, StoredEnum, StoredFieldType, + StoredInputType, StoredScalar, TypeId, UnionId, }, }; -use heck::CamelCase; use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] @@ -35,165 +36,73 @@ pub(crate) struct ResolvedFragmentId(u32); #[derive(Debug, Clone, Copy)] pub(crate) struct VariableId(u32); -#[derive(Debug, Clone, Copy)] -enum SelectionParent { - Field(SelectionId), - InlineFragment(SelectionId), - Fragment(ResolvedFragmentId), - Operation(OperationId), -} - -impl SelectionParent { - fn add_to_selection_set(&self, q: &mut Query, selection_id: SelectionId) { - match self { - SelectionParent::Field(parent_selection_id) - | SelectionParent::InlineFragment(parent_selection_id) => { - let parent_selection = q - .selections - .get_mut(parent_selection_id.0 as usize) - .expect("get parent selection"); - - match parent_selection { - Selection::Field(f) => f.selection_set.push(selection_id), - Selection::InlineFragment(inline) => inline.selection_set.push(selection_id), - other => unreachable!("impossible parent selection: {:?}", other), - } - } - SelectionParent::Fragment(fragment_id) => { - let fragment = q - .fragments - .get_mut(fragment_id.0 as usize) - .expect("get fragment"); - - fragment.selection_set.push(selection_id); - } - SelectionParent::Operation(operation_id) => { - let operation = q - .operations - .get_mut(operation_id.0 as usize) - .expect("get operation"); - - operation.selection_set.push(selection_id); - } - } - } - - pub(crate) fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { - match self { - SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { - query.query.get_selection(*id).to_path_segment(query) - } - SelectionParent::Operation(id) => query.query.get_operation(*id).to_path_segment(), - SelectionParent::Fragment(id) => query.query.get_fragment(*id).to_path_segment(), - } - } -} - -#[derive(Debug)] -pub(crate) enum Selection { - Field(SelectedField), - InlineFragment(InlineFragment), - FragmentSpread(ResolvedFragmentId), - Typename, -} - -impl Selection { - pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes, query: &BoundQuery<'_>) { - match self { - Selection::Field(field) => { - let stored_field = query.schema.get_field(field.field_id); - used_types.types.insert(stored_field.r#type.id); - - for selection_id in self.subselection() { - let selection = query.query.get_selection(*selection_id); - selection.collect_used_types(used_types, query); - } - } - Selection::InlineFragment(inline_fragment) => { - used_types.types.insert(inline_fragment.type_id); +// TODO: put this into a validation module. +fn selection_set_contains_type_name( + parent_type_id: TypeId, + selection_set: &[SelectionId], + query: &Query, +) -> bool { + for id in selection_set { + let selection = query.get_selection(*id); - for selection_id in self.subselection() { - let selection = query.query.get_selection(*selection_id); - selection.collect_used_types(used_types, query); - } - } + match selection { + Selection::Typename => return true, Selection::FragmentSpread(fragment_id) => { - // This is necessary to avoid infinite recursion. - if used_types.fragments.contains(fragment_id) { - return; - } - - used_types.fragments.insert(*fragment_id); - - let fragment = query.query.get_fragment(*fragment_id); - - for (_id, selection) in query.query.walk_selection_set(&fragment.selection_set) { - selection.collect_used_types(used_types, query); + let fragment = query.get_fragment(*fragment_id); + if fragment.on == parent_type_id + && selection_set_contains_type_name(fragment.on, &fragment.selection_set, query) + { + return true; } } - Selection::Typename => (), + _ => (), } } - pub(crate) fn contains_fragment(&self, fragment_id: ResolvedFragmentId, query: &Query) -> bool { - match self { - Selection::FragmentSpread(id) => *id == fragment_id, - _ => self.subselection().iter().any(|selection_id| { - query - .get_selection(*selection_id) - .contains_fragment(fragment_id, query) - }), - } - } + false +} - pub(crate) fn subselection(&self) -> &[SelectionId] { - match self { - Selection::Field(field) => field.selection_set.as_slice(), - Selection::InlineFragment(inline_fragment) => &inline_fragment.selection_set, - _ => &[], +fn validate_typename_presence(query: &BoundQuery<'_>) -> anyhow::Result<()> { + for fragment in query.query.fragments.iter() { + let type_id = match fragment.on { + id @ TypeId::Interface(_) | id @ TypeId::Union(_) => id, + _ => continue, + }; + + if !selection_set_contains_type_name(fragment.on, &fragment.selection_set, query.query) { + anyhow::bail!( + "The `{}` fragment uses `{}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", + &fragment.name, + type_id.name(query.schema), + ) } } - fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { - match self { - Selection::Field(field) => field - .alias - .as_ref() - .map(|alias| alias.to_camel_case()) - .unwrap_or_else(move || { - query.schema.get_field(field.field_id).name.to_camel_case() - }), - Selection::InlineFragment(inline_fragment) => format!( - "On{}", - inline_fragment.type_id.name(query.schema).to_camel_case() - ), - other => unreachable!("{:?} in to_path_segment", other), + let union_and_interface_field_selections = + query + .query + .selections() + .filter_map(|(selection_id, selection)| match selection { + Selection::Field(field) => match query.schema.get_field(field.field_id).r#type.id { + id @ TypeId::Interface(_) | id @ TypeId::Union(_) => { + Some((selection_id, id, &field.selection_set)) + } + _ => None, + }, + _ => None, + }); + + for selection in union_and_interface_field_selections { + if !selection_set_contains_type_name(selection.1, selection.2, query.query) { + anyhow::bail!( + "The query uses `{path}` at `{selected_type}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", + path = full_path_prefix(selection.0, query), + selected_type = selection.1.name(query.schema) + ); } } -} -#[derive(Debug)] -pub(crate) struct InlineFragment { - pub(crate) type_id: TypeId, - // TODO: see if we can encode this at the top-level instead, with the selection being a parent. - selection_set: Vec, -} - -#[derive(Debug)] -pub(crate) struct SelectedField { - alias: Option, - field_id: StoredFieldId, - selection_set: Vec, -} - -impl SelectedField { - pub(crate) fn alias(&self) -> Option<&str> { - self.alias.as_ref().map(String::as_str) - } - - pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> &'a StoredField { - schema.get_field(self.field_id) - } + Ok(()) } pub(crate) fn resolve( @@ -294,6 +203,22 @@ pub(crate) fn resolve( } } + // Validation: to be expanded and factored out. + validate_typename_presence(&BoundQuery { + query: &resolved_query, + schema, + })?; + + for (selection_id, _) in resolved_query.selections() { + selection::validate_type_conditions( + selection_id, + &BoundQuery { + query: &resolved_query, + schema, + }, + )? + } + Ok(resolved_query) } @@ -303,11 +228,19 @@ fn resolve_fragment( fragment_definition: &graphql_parser::query::FragmentDefinition, ) -> anyhow::Result<()> { let graphql_parser::query::TypeCondition::On(on) = &fragment_definition.type_condition; - let on = schema.find_type(&on).unwrap(); + let on = schema.find_type(&on).ok_or_else(|| { + anyhow::anyhow!( + "Could not find type `{}` referenced by fragment `{}`", + on, + fragment_definition.name + ) + })?; let (id, _) = query .find_fragment(&fragment_definition.name) - .expect("TODO: fragment resolution"); + .ok_or_else(|| { + anyhow::anyhow!("Could not find fragment `{}`.", fragment_definition.name) + })?; resolve_selection( query, @@ -339,14 +272,17 @@ fn resolve_union_selection( } graphql_parser::query::Selection::InlineFragment(inline_fragment) => { let selection_id = resolve_inline_fragment(query, schema, inline_fragment, parent)?; - parent.add_to_selection_set(query, selection_id); } graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { - // TODO: this is very duplicated. - let (fragment_id, _) = query + let (fragment_id, _fragment) = query .find_fragment(&fragment_spread.fragment_name) - .expect("TODO: fragment resolution"); + .ok_or_else(|| { + anyhow::anyhow!( + "Could not find fragment `{}` referenced by fragment spread.", + fragment_spread.fragment_name + ) + })?; let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent); @@ -360,7 +296,7 @@ fn resolve_union_selection( fn resolve_object_selection<'a>( query: &mut Query, - object: &impl crate::schema::ObjectLike, + object: &dyn crate::schema::ObjectLike, selection_set: &graphql_parser::query::SelectionSet, parent: SelectionParent, schema: &'a Schema, @@ -405,9 +341,14 @@ fn resolve_object_selection<'a>( parent.add_to_selection_set(query, selection_id); } graphql_parser::query::Selection::FragmentSpread(fragment_spread) => { - let (fragment_id, _) = query + let (fragment_id, _fragment) = query .find_fragment(&fragment_spread.fragment_name) - .expect("TODO: fragment resolution"); + .ok_or_else(|| { + anyhow::anyhow!( + "Could not find fragment `{}` referenced by fragment spread.", + fragment_spread.fragment_name + ) + })?; let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent); @@ -460,9 +401,12 @@ fn resolve_inline_fragment( .type_condition .as_ref() .expect("missing type condition on inline fragment"); - let type_id = schema - .find_type(on) - .ok_or_else(|| anyhow::anyhow!("TODO: error message"))?; + let type_id = schema.find_type(on).ok_or_else(|| { + anyhow::anyhow!( + "Could not find type `{}` referenced by inline fragment.", + on + ) + })?; let id = query.push_selection( Selection::InlineFragment(InlineFragment { @@ -609,6 +553,13 @@ impl Query { .map(|(id, op)| (OperationId::new(id), op)) } + fn selections(&self) -> impl Iterator { + self.selections + .iter() + .enumerate() + .map(|(idx, selection)| (SelectionId(idx as u32), selection)) + } + fn walk_selection_set<'a>( &'a self, selection_ids: &'a [SelectionId], diff --git a/graphql_client_codegen/src/query/selection.rs b/graphql_client_codegen/src/query/selection.rs new file mode 100644 index 000000000..b27fb0ea6 --- /dev/null +++ b/graphql_client_codegen/src/query/selection.rs @@ -0,0 +1,257 @@ +use super::{BoundQuery, OperationId, Query, ResolvedFragmentId, SelectionId, UsedTypes}; +use crate::schema::{Schema, StoredField, StoredFieldId, TypeId}; +use heck::CamelCase; + +/// This checks that the `on` clause on fragment spreads and inline fragments +/// are valid in their context. +pub(super) fn validate_type_conditions( + selection_id: SelectionId, + query: &BoundQuery<'_>, +) -> anyhow::Result<()> { + let selection = query.query.get_selection(selection_id); + + let selected_type = match selection { + Selection::FragmentSpread(fragment_id) => query.query.get_fragment(*fragment_id).on, + Selection::InlineFragment(inline_fragment) => inline_fragment.type_id, + _ => return Ok(()), + }; + + let parent_schema_type_id = query + .query + .selection_parent_idx + .get(&selection_id) + .expect("Could not find selection parent") + .schema_type_id(query); + + if parent_schema_type_id == selected_type { + return Ok(()); + } + + match parent_schema_type_id { + TypeId::Union(union_id) => { + let union = query.schema.get_union(union_id); + + anyhow::ensure!( + union + .variants + .iter() + .any(|variant| *variant == selected_type), + "The spread {}... on {} is not valid.", + union.name, + selected_type.name(query.schema), + ) + } + TypeId::Interface(interface_id) => { + let mut variants = query + .schema + .objects() + .filter(|(_, obj)| obj.implements_interfaces.contains(&interface_id)); + + anyhow::ensure!( + variants.any(|(id, _)| TypeId::Object(id) == selected_type), + "The spread {}... on {} is not valid.", + parent_schema_type_id.name(query.schema), + selected_type.name(query.schema), + ) + } + _ => (), + } + + Ok(()) +} + +#[derive(Debug, Clone, Copy)] +pub(super) enum SelectionParent { + Field(SelectionId), + InlineFragment(SelectionId), + Fragment(ResolvedFragmentId), + Operation(OperationId), +} + +impl SelectionParent { + fn schema_type_id(&self, query: &BoundQuery<'_>) -> TypeId { + match self { + SelectionParent::Fragment(fragment_id) => query.query.get_fragment(*fragment_id).on, + SelectionParent::Operation(operation_id) => { + TypeId::Object(query.query.get_operation(*operation_id).object_id) + } + SelectionParent::Field(id) => { + let field_id = query + .query + .get_selection(*id) + .as_selected_field() + .unwrap() + .field_id; + query.schema.get_field(field_id).r#type.id + } + SelectionParent::InlineFragment(id) => { + { query.query.get_selection(*id).as_inline_fragment().unwrap() }.type_id + } + } + } + + pub(super) fn add_to_selection_set(&self, q: &mut Query, selection_id: SelectionId) { + match self { + SelectionParent::Field(parent_selection_id) + | SelectionParent::InlineFragment(parent_selection_id) => { + let parent_selection = q + .selections + .get_mut(parent_selection_id.0 as usize) + .expect("get parent selection"); + + match parent_selection { + Selection::Field(f) => f.selection_set.push(selection_id), + Selection::InlineFragment(inline) => inline.selection_set.push(selection_id), + other => unreachable!("impossible parent selection: {:?}", other), + } + } + SelectionParent::Fragment(fragment_id) => { + let fragment = q + .fragments + .get_mut(fragment_id.0 as usize) + .expect("get fragment"); + + fragment.selection_set.push(selection_id); + } + SelectionParent::Operation(operation_id) => { + let operation = q + .operations + .get_mut(operation_id.0 as usize) + .expect("get operation"); + + operation.selection_set.push(selection_id); + } + } + } + + pub(crate) fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { + match self { + SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => { + query.query.get_selection(*id).to_path_segment(query) + } + SelectionParent::Operation(id) => query.query.get_operation(*id).to_path_segment(), + SelectionParent::Fragment(id) => query.query.get_fragment(*id).to_path_segment(), + } + } +} + +#[derive(Debug)] +pub(crate) enum Selection { + Field(SelectedField), + InlineFragment(InlineFragment), + FragmentSpread(ResolvedFragmentId), + Typename, +} + +impl Selection { + pub(crate) fn as_selected_field(&self) -> Option<&SelectedField> { + match self { + Selection::Field(f) => Some(f), + _ => None, + } + } + + pub(crate) fn as_inline_fragment(&self) -> Option<&InlineFragment> { + match self { + Selection::InlineFragment(f) => Some(f), + _ => None, + } + } + + pub(crate) fn collect_used_types(&self, used_types: &mut UsedTypes, query: &BoundQuery<'_>) { + match self { + Selection::Field(field) => { + let stored_field = query.schema.get_field(field.field_id); + used_types.types.insert(stored_field.r#type.id); + + for selection_id in self.subselection() { + let selection = query.query.get_selection(*selection_id); + selection.collect_used_types(used_types, query); + } + } + Selection::InlineFragment(inline_fragment) => { + used_types.types.insert(inline_fragment.type_id); + + for selection_id in self.subselection() { + let selection = query.query.get_selection(*selection_id); + selection.collect_used_types(used_types, query); + } + } + Selection::FragmentSpread(fragment_id) => { + // This is necessary to avoid infinite recursion. + if used_types.fragments.contains(fragment_id) { + return; + } + + used_types.fragments.insert(*fragment_id); + + let fragment = query.query.get_fragment(*fragment_id); + + for (_id, selection) in query.query.walk_selection_set(&fragment.selection_set) { + selection.collect_used_types(used_types, query); + } + } + Selection::Typename => (), + } + } + + pub(crate) fn contains_fragment(&self, fragment_id: ResolvedFragmentId, query: &Query) -> bool { + match self { + Selection::FragmentSpread(id) => *id == fragment_id, + _ => self.subselection().iter().any(|selection_id| { + query + .get_selection(*selection_id) + .contains_fragment(fragment_id, query) + }), + } + } + + pub(crate) fn subselection(&self) -> &[SelectionId] { + match self { + Selection::Field(field) => field.selection_set.as_slice(), + Selection::InlineFragment(inline_fragment) => &inline_fragment.selection_set, + _ => &[], + } + } + + pub(super) fn to_path_segment(&self, query: &BoundQuery<'_>) -> String { + match self { + Selection::Field(field) => field + .alias + .as_ref() + .map(|alias| alias.to_camel_case()) + .unwrap_or_else(move || { + query.schema.get_field(field.field_id).name.to_camel_case() + }), + Selection::InlineFragment(inline_fragment) => format!( + "On{}", + inline_fragment.type_id.name(query.schema).to_camel_case() + ), + other => unreachable!("{:?} in to_path_segment", other), + } + } +} + +#[derive(Debug)] +pub(crate) struct InlineFragment { + pub(crate) type_id: TypeId, + // TODO: see if we can encode this at the top-level instead, with the selection being a parent. + pub(crate) selection_set: Vec, +} + +#[derive(Debug)] +pub(crate) struct SelectedField { + pub(crate) alias: Option, + pub(crate) field_id: StoredFieldId, + pub(crate) selection_set: Vec, +} + +impl SelectedField { + pub(crate) fn alias(&self) -> Option<&str> { + self.alias.as_ref().map(String::as_str) + } + + pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> &'a StoredField { + schema.get_field(self.field_id) + } +} diff --git a/graphql_client_codegen/src/unions.rs b/graphql_client_codegen/src/unions.rs deleted file mode 100644 index 8ae5da31a..000000000 --- a/graphql_client_codegen/src/unions.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::query::QueryContext; -use crate::selection::Selection; -use anyhow::*; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; -use std::cell::Cell; -use std::collections::BTreeSet; - -// TODO: here! -#[derive(Debug, Fail)] -#[fail(display = "UnionError")] -enum UnionError { - #[fail(display = "Unknown type: {}", ty)] - UnknownType { ty: String }, - #[fail(display = "Unknown variant on union {}: {}", ty, var)] - UnknownVariant { var: String, ty: String }, - #[fail(display = "Missing __typename in selection for {}", union_name)] - MissingTypename { union_name: String }, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::constants::*; - use crate::deprecation::DeprecationStatus; - use crate::field_type::FieldType; - use crate::objects::{GqlObject, GqlObjectField}; - use crate::selection::*; - - #[test] - fn union_response_for_selection_complains_if_typename_is_missing() { - let fields = vec![ - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "User", - fields: Selection::from_vec(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "firstName", - fields: Selection::new_empty(), - })]), - }), - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "Organization", - fields: Selection::from_vec(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "title", - fields: Selection::new_empty(), - })]), - }), - ]; - let selection = Selection::from_vec(fields); - let prefix = "Meow"; - let union = GqlUnion { - name: "MyUnion", - description: None, - variants: BTreeSet::new(), - is_required: false.into(), - }; - - let mut schema = crate::schema::Schema::new(); - - schema.objects.insert( - "User", - GqlObject { - description: None, - name: "User", - fields: vec![ - GqlObjectField { - description: None, - name: "firstName", - type_: FieldType::new("String").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "lastName", - type_: FieldType::new("String").nonnull(), - - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "createdAt", - type_: FieldType::new("Date").nonnull(), - deprecation: DeprecationStatus::Current, - }, - ], - is_required: false.into(), - }, - ); - - schema.objects.insert( - "Organization", - GqlObject { - description: None, - name: "Organization", - fields: vec![ - GqlObjectField { - description: None, - name: "title", - type_: FieldType::new("String").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "created_at", - type_: FieldType::new("Date").nonnull(), - deprecation: DeprecationStatus::Current, - }, - ], - is_required: false.into(), - }, - ); - let context = QueryContext::new_empty(&schema); - - let result = union.response_for_selection(&context, &selection, &prefix); - - assert!(result.is_err()); - - assert_eq!( - format!("{}", result.unwrap_err()), - "Missing __typename in selection for Meow" - ); - } - - #[test] - fn union_response_for_selection_works() { - let fields = vec![ - SelectionItem::Field(SelectionField { - alias: None, - name: "__typename", - fields: Selection::new_empty(), - }), - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "User", - fields: Selection::from_vec(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "firstName", - fields: Selection::new_empty(), - })]), - }), - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "Organization", - fields: Selection::from_vec(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "title", - fields: Selection::new_empty(), - })]), - }), - ]; - let schema = crate::schema::Schema::new(); - let context = QueryContext::new_empty(&schema); - let selection: Selection<'_> = fields.into_iter().collect(); - let prefix = "Meow"; - let mut union_variants = BTreeSet::new(); - union_variants.insert("User"); - union_variants.insert("Organization"); - let union = GqlUnion { - name: "MyUnion", - description: None, - variants: union_variants, - is_required: false.into(), - }; - - let result = union.response_for_selection(&context, &selection, &prefix); - - assert!(result.is_err()); - - let mut schema = crate::schema::Schema::new(); - schema.objects.insert( - "User", - GqlObject { - description: None, - name: "User", - fields: vec![ - GqlObjectField { - description: None, - name: "__typename", - type_: FieldType::new(string_type()).nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "firstName", - type_: FieldType::new(string_type()).nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "lastName", - type_: FieldType::new(string_type()).nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "createdAt", - type_: FieldType::new("Date").nonnull(), - deprecation: DeprecationStatus::Current, - }, - ], - is_required: false.into(), - }, - ); - - schema.objects.insert( - "Organization", - GqlObject { - description: None, - name: "Organization", - fields: vec![ - GqlObjectField { - description: None, - name: "__typename", - type_: FieldType::new(string_type()).nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "title", - type_: FieldType::new("String").nonnull(), - deprecation: DeprecationStatus::Current, - }, - GqlObjectField { - description: None, - name: "createdAt", - type_: FieldType::new("Date").nonnull(), - deprecation: DeprecationStatus::Current, - }, - ], - is_required: false.into(), - }, - ); - - let context = QueryContext::new_empty(&schema); - - let result = union.response_for_selection(&context, &selection, &prefix); - - println!("{:?}", result); - - assert!(result.is_ok()); - - assert_eq!( - result.unwrap().to_string(), - vec![ - "# [ derive ( Deserialize ) ] ", - "pub struct MeowOnOrganization { pub title : String , } ", - "# [ derive ( Deserialize ) ] ", - "pub struct MeowOnUser { # [ serde ( rename = \"firstName\" ) ] pub first_name : String , } ", - "# [ derive ( Deserialize ) ] ", - "# [ serde ( tag = \"__typename\" ) ] ", - "pub enum Meow { Organization ( MeowOnOrganization ) , User ( MeowOnUser ) }", - ].into_iter() - .collect::(), - ); - } - - #[test] - fn union_rejects_selection_on_non_member_type() { - let fields = vec![ - SelectionItem::Field(SelectionField { - alias: None, - name: "__typename", - fields: Selection::new_empty(), - }), - SelectionItem::InlineFragment(SelectionInlineFragment { - on: "SomeNonUnionType", - fields: Selection::from_vec(vec![SelectionItem::Field(SelectionField { - alias: None, - name: "field", - fields: Selection::new_empty(), - })]), - }), - ]; - let schema = crate::schema::Schema::new(); - let context = QueryContext::new_empty(&schema); - let selection: Selection<'_> = fields.into_iter().collect(); - let prefix = "Meow"; - let mut union_variants = BTreeSet::new(); - union_variants.insert("Int"); - union_variants.insert("String"); - let union = GqlUnion { - name: "MyUnion", - description: None, - variants: union_variants, - is_required: false.into(), - }; - - let result = union.response_for_selection(&context, &selection, &prefix); - - assert!(result.is_err()); - - let mut schema = crate::schema::Schema::new(); - schema.unions.insert("MyUnion", union.clone()); - schema.objects.insert( - "SomeNonUnionType", - GqlObject { - description: None, - name: "SomeNonUnionType", - fields: vec![GqlObjectField { - description: None, - name: "field", - type_: FieldType::new(string_type()), - deprecation: DeprecationStatus::Current, - }], - is_required: false.into(), - }, - ); - - let context = QueryContext::new_empty(&schema); - - let result = union.response_for_selection(&context, &selection, &prefix); - - println!("{:?}", result); - - assert!(result.is_err()); - - match result.unwrap_err().downcast::() { - Ok(UnionError::UnknownVariant { var, ty }) => { - assert_eq!(var, "SomeNonUnionType"); - assert_eq!(ty, "MyUnion"); - } - err => panic!("Unexpected error type: {:?}", err), - } - } -} diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index 89a62173e..849c60991 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -30,7 +30,6 @@ fn graphql_query_derive_inner( Ok( generate_module_token_stream(query_path, &schema_path, options) .map(Into::into) - .map_err(|fail| fail.compat()) .context("Code generation failed.")?, ) } From a769176c48705f08f5c7095140ead38702b512fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 22:44:40 +0100 Subject: [PATCH 86/93] Split up query::resolve --- graphql_client_codegen/src/query.rs | 69 +++++++++++-------- graphql_client_codegen/src/query/selection.rs | 1 - 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index d485f09dc..8a001a6a1 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -111,8 +111,45 @@ pub(crate) fn resolve( ) -> anyhow::Result { let mut resolved_query: Query = Default::default(); + create_roots(&mut resolved_query, query, schema)?; + + // Then resolve the selections. + for definition in &query.definitions { + match definition { + graphql_parser::query::Definition::Fragment(fragment) => { + resolve_fragment(&mut resolved_query, schema, fragment)? + } + graphql_parser::query::Definition::Operation(operation) => { + resolve_operation(&mut resolved_query, schema, operation)? + } + } + } + + // Validation: to be expanded and factored out. + validate_typename_presence(&BoundQuery { + query: &resolved_query, + schema, + })?; + + for (selection_id, _) in resolved_query.selections() { + selection::validate_type_conditions( + selection_id, + &BoundQuery { + query: &resolved_query, + schema, + }, + )? + } + + Ok(resolved_query) +} + +fn create_roots( + resolved_query: &mut Query, + query: &graphql_parser::query::Document, + schema: &Schema, +) -> anyhow::Result<()> { // First, give ids to all fragments and operations. - // TODO: refactor this into a "create_roots" function. for definition in &query.definitions { match definition { graphql_parser::query::Definition::Fragment(fragment) => { @@ -191,35 +228,7 @@ pub(crate) fn resolve( } } - // Then resolve the selections. - for definition in &query.definitions { - match definition { - graphql_parser::query::Definition::Fragment(fragment) => { - resolve_fragment(&mut resolved_query, schema, fragment)? - } - graphql_parser::query::Definition::Operation(operation) => { - resolve_operation(&mut resolved_query, schema, operation)? - } - } - } - - // Validation: to be expanded and factored out. - validate_typename_presence(&BoundQuery { - query: &resolved_query, - schema, - })?; - - for (selection_id, _) in resolved_query.selections() { - selection::validate_type_conditions( - selection_id, - &BoundQuery { - query: &resolved_query, - schema, - }, - )? - } - - Ok(resolved_query) + Ok(()) } fn resolve_fragment( diff --git a/graphql_client_codegen/src/query/selection.rs b/graphql_client_codegen/src/query/selection.rs index b27fb0ea6..ce518bd2e 100644 --- a/graphql_client_codegen/src/query/selection.rs +++ b/graphql_client_codegen/src/query/selection.rs @@ -235,7 +235,6 @@ impl Selection { #[derive(Debug)] pub(crate) struct InlineFragment { pub(crate) type_id: TypeId, - // TODO: see if we can encode this at the top-level instead, with the selection being a parent. pub(crate) selection_set: Vec, } From 004aed69ad582f6e3a24fa7942f32d83ae7ce79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 22:47:04 +0100 Subject: [PATCH 87/93] Move validation into separate module --- graphql_client_codegen/src/codegen/enums.rs | 5 -- graphql_client_codegen/src/query.rs | 72 +------------------ .../src/query/validation.rs | 70 ++++++++++++++++++ 3 files changed, 72 insertions(+), 75 deletions(-) create mode 100644 graphql_client_codegen/src/query/validation.rs diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index 5ab000daa..d36992e6c 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -32,11 +32,6 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( let name = normalization.enum_variant(safe_name.as_ref()); let name = Ident::new(&name, Span::call_site()); - // TODO - // let description = &v.description; - // let description = description.as_ref().map(|d| quote!(#[doc = #d])); - - // quote!(#description #name) quote!(#name) }) .collect(); diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 8a001a6a1..aa4f76f5b 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -4,6 +4,7 @@ mod fragments; mod operations; mod selection; +mod validation; pub(crate) use fragments::{fragment_is_recursive, ResolvedFragment}; pub(crate) use operations::ResolvedOperation; @@ -36,75 +37,6 @@ pub(crate) struct ResolvedFragmentId(u32); #[derive(Debug, Clone, Copy)] pub(crate) struct VariableId(u32); -// TODO: put this into a validation module. -fn selection_set_contains_type_name( - parent_type_id: TypeId, - selection_set: &[SelectionId], - query: &Query, -) -> bool { - for id in selection_set { - let selection = query.get_selection(*id); - - match selection { - Selection::Typename => return true, - Selection::FragmentSpread(fragment_id) => { - let fragment = query.get_fragment(*fragment_id); - if fragment.on == parent_type_id - && selection_set_contains_type_name(fragment.on, &fragment.selection_set, query) - { - return true; - } - } - _ => (), - } - } - - false -} - -fn validate_typename_presence(query: &BoundQuery<'_>) -> anyhow::Result<()> { - for fragment in query.query.fragments.iter() { - let type_id = match fragment.on { - id @ TypeId::Interface(_) | id @ TypeId::Union(_) => id, - _ => continue, - }; - - if !selection_set_contains_type_name(fragment.on, &fragment.selection_set, query.query) { - anyhow::bail!( - "The `{}` fragment uses `{}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", - &fragment.name, - type_id.name(query.schema), - ) - } - } - - let union_and_interface_field_selections = - query - .query - .selections() - .filter_map(|(selection_id, selection)| match selection { - Selection::Field(field) => match query.schema.get_field(field.field_id).r#type.id { - id @ TypeId::Interface(_) | id @ TypeId::Union(_) => { - Some((selection_id, id, &field.selection_set)) - } - _ => None, - }, - _ => None, - }); - - for selection in union_and_interface_field_selections { - if !selection_set_contains_type_name(selection.1, selection.2, query.query) { - anyhow::bail!( - "The query uses `{path}` at `{selected_type}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", - path = full_path_prefix(selection.0, query), - selected_type = selection.1.name(query.schema) - ); - } - } - - Ok(()) -} - pub(crate) fn resolve( schema: &Schema, query: &graphql_parser::query::Document, @@ -126,7 +58,7 @@ pub(crate) fn resolve( } // Validation: to be expanded and factored out. - validate_typename_presence(&BoundQuery { + validation::validate_typename_presence(&BoundQuery { query: &resolved_query, schema, })?; diff --git a/graphql_client_codegen/src/query/validation.rs b/graphql_client_codegen/src/query/validation.rs new file mode 100644 index 000000000..1dafe512b --- /dev/null +++ b/graphql_client_codegen/src/query/validation.rs @@ -0,0 +1,70 @@ +use super::{full_path_prefix, BoundQuery, Query, Selection, SelectionId}; +use crate::schema::TypeId; + +pub(super) fn validate_typename_presence(query: &BoundQuery<'_>) -> anyhow::Result<()> { + for fragment in query.query.fragments.iter() { + let type_id = match fragment.on { + id @ TypeId::Interface(_) | id @ TypeId::Union(_) => id, + _ => continue, + }; + + if !selection_set_contains_type_name(fragment.on, &fragment.selection_set, query.query) { + anyhow::bail!( + "The `{}` fragment uses `{}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", + &fragment.name, + type_id.name(query.schema), + ) + } + } + + let union_and_interface_field_selections = + query + .query + .selections() + .filter_map(|(selection_id, selection)| match selection { + Selection::Field(field) => match query.schema.get_field(field.field_id).r#type.id { + id @ TypeId::Interface(_) | id @ TypeId::Union(_) => { + Some((selection_id, id, &field.selection_set)) + } + _ => None, + }, + _ => None, + }); + + for selection in union_and_interface_field_selections { + if !selection_set_contains_type_name(selection.1, selection.2, query.query) { + anyhow::bail!( + "The query uses `{path}` at `{selected_type}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.", + path = full_path_prefix(selection.0, query), + selected_type = selection.1.name(query.schema) + ); + } + } + + Ok(()) +} + +fn selection_set_contains_type_name( + parent_type_id: TypeId, + selection_set: &[SelectionId], + query: &Query, +) -> bool { + for id in selection_set { + let selection = query.get_selection(*id); + + match selection { + Selection::Typename => return true, + Selection::FragmentSpread(fragment_id) => { + let fragment = query.get_fragment(*fragment_id); + if fragment.on == parent_type_id + && selection_set_contains_type_name(fragment.on, &fragment.selection_set, query) + { + return true; + } + } + _ => (), + } + } + + false +} From c075361a42872f4e8eee5b46e09bbf3003782c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 23:09:18 +0100 Subject: [PATCH 88/93] Run prettier --- .../unions/fragment_and_more_response.json | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/graphql_client/tests/unions/fragment_and_more_response.json b/graphql_client/tests/unions/fragment_and_more_response.json index d009dcb76..067cb0ab2 100644 --- a/graphql_client/tests/unions/fragment_and_more_response.json +++ b/graphql_client/tests/unions/fragment_and_more_response.json @@ -1,22 +1,22 @@ { - "names": [ - { - "__typename": "Person", - "firstName": "Larry" - }, - { - "__typename": "Dog", - "name": "Laïka", - "isGoodDog": true - }, - { - "__typename": "Organization", - "title": "Mozilla" - }, - { - "__typename": "Dog", - "name": "Norbert", - "isGoodDog": true - } - ] - } + "names": [ + { + "__typename": "Person", + "firstName": "Larry" + }, + { + "__typename": "Dog", + "name": "Laïka", + "isGoodDog": true + }, + { + "__typename": "Organization", + "title": "Mozilla" + }, + { + "__typename": "Dog", + "name": "Norbert", + "isGoodDog": true + } + ] +} From ac82fd82d5723a0f634121cd3b9e7187086fd570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Wed, 25 Mar 2020 23:20:41 +0100 Subject: [PATCH 89/93] Fix false-positives in deprecations --- .../src/schema/json_conversion.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 0c4c3c3ed..02e2b0f38 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -182,9 +182,11 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { schema, &mut field.type_.as_mut().expect("take field type").type_ref, ), - deprecation: Some(None) - .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) - .map(|_: Option<()>| field.deprecation_reason.clone()), + deprecation: if let Some(true) = field.is_deprecated { + Some(field.deprecation_reason.clone()) + } else { + None + }, }; field_ids.push(schema.push_field(field)); @@ -218,9 +220,11 @@ fn ingest_object(schema: &mut Schema, object: &mut FullType) { schema, &mut field.type_.as_mut().expect("take field type").type_ref, ), - deprecation: Some(None) - .filter(|_: &Option<()>| !field.is_deprecated.unwrap_or(false)) - .map(|_: Option<()>| field.deprecation_reason.clone()), + deprecation: if let Some(true) = field.is_deprecated { + Some(field.deprecation_reason.clone()) + } else { + None + }, }; field_ids.push(schema.push_field(field)); From e5b3916fad4f1f6dd05e2759415d06a734d5ebfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Thu, 26 Mar 2020 00:03:14 +0100 Subject: [PATCH 90/93] Fix a bunch of clippy warnings --- graphql_client_codegen/src/codegen.rs | 2 +- graphql_client_codegen/src/codegen/selection.rs | 8 +++----- graphql_client_codegen/src/codegen_options.rs | 10 ++++------ graphql_client_codegen/src/generated_module.rs | 2 +- graphql_client_codegen/src/lib.rs | 2 +- graphql_client_codegen/src/normalization.rs | 14 +++++++------- graphql_client_codegen/src/query.rs | 4 ++-- graphql_client_codegen/src/query/selection.rs | 2 +- graphql_client_codegen/src/schema.rs | 8 +++----- .../src/schema/json_conversion.rs | 3 +-- 10 files changed, 24 insertions(+), 31 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 999ecbb25..3feece889 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -123,7 +123,7 @@ fn generate_variables_struct( } ); - variables_struct.into() + variables_struct } fn generate_variable_struct_field( diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 4a84e8755..1a6d1bea3 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -270,8 +270,7 @@ fn calculate_selection<'a>( struct_id, field_type: options .normalization() - .field_type(&context.schema().get_enum(enm).name) - .into(), + .field_type(&context.schema().get_enum(enm).name), field_type_qualifiers: &schema_field.r#type.qualifiers, flatten: false, deprecation: schema_field.deprecation(), @@ -282,8 +281,7 @@ fn calculate_selection<'a>( context.push_field(ExpandedField { field_type: options .normalization() - .field_type(context.schema().get_scalar(scalar).name.as_str()) - .into(), + .field_type(context.schema().get_scalar(scalar).name.as_str()), field_type_qualifiers: &field .schema_field(context.schema()) .r#type @@ -550,7 +548,7 @@ impl<'a> ExpandedSelection<'a> { continue; } - let (on_field, on_enum) = if on_variants.len() > 0 { + let (on_field, on_enum) = if !on_variants.is_empty() { let enum_name = Ident::new(&format!("{}On", ty.name), Span::call_site()); let on_field = quote!(#[serde(flatten)] pub on: #enum_name); diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 12c7ce1f4..2d1e83579 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -91,10 +91,9 @@ impl GraphQLClientCodegenOptions { pub fn all_variable_derives(&self) -> impl Iterator { let additional = self .variables_derives - .as_ref() - .map(String::as_str) + .as_deref() .into_iter() - .flat_map(|s| s.split(",")); + .flat_map(|s| s.split(',')); std::iter::once("Serialize").chain(additional) } @@ -112,10 +111,9 @@ impl GraphQLClientCodegenOptions { /// Additional traits we want to derive for responses. pub fn additional_response_derives(&self) -> impl Iterator { self.response_derives - .as_ref() - .map(String::as_str) + .as_deref() .into_iter() - .flat_map(|s| s.split(",")) + .flat_map(|s| s.split(',')) .map(|s| s.trim()) } diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 07888e82c..7319ac234 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -31,7 +31,7 @@ impl<'a> GeneratedModule<'a> { fn root(&self) -> anyhow::Result { let op_name = self.options.normalization().operation(self.operation); self.resolved_query - .select_operation(&op_name, self.options.normalization()) + .select_operation(&op_name, *self.options.normalization()) .map(|op| op.0) .ok_or_else(|| { anyhow::anyhow!( diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 62a7b5940..467b2fa35 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -96,7 +96,7 @@ pub fn generate_module_token_stream( let operations = options .operation_name .as_ref() - .and_then(|operation_name| query.select_operation(operation_name, &options.normalization())) + .and_then(|operation_name| query.select_operation(operation_name, *options.normalization())) .map(|op| vec![op]); let operations = match (operations, &options.mode) { diff --git a/graphql_client_codegen/src/normalization.rs b/graphql_client_codegen/src/normalization.rs index 7f8807575..d8fef5b8b 100644 --- a/graphql_client_codegen/src/normalization.rs +++ b/graphql_client_codegen/src/normalization.rs @@ -18,15 +18,15 @@ impl Normalization { } } - pub(crate) fn operation<'a>(self, op: &'a str) -> Cow<'a, str> { + pub(crate) fn operation(self, op: &str) -> Cow<'_, str> { self.camel_case(op) } - pub(crate) fn enum_variant<'a>(self, enm: &'a str) -> Cow<'a, str> { - self.camel_case(enm.into()) + pub(crate) fn enum_variant(self, enm: &str) -> Cow<'_, str> { + self.camel_case(enm) } - pub(crate) fn enum_name<'a>(self, enm: &'a str) -> Cow<'a, str> { + pub(crate) fn enum_name(self, enm: &str) -> Cow<'_, str> { self.camel_case(enm) } @@ -38,15 +38,15 @@ impl Normalization { } } - pub(crate) fn field_type<'a>(self, fty: &'a str) -> Cow<'a, str> { + pub(crate) fn field_type(self, fty: &str) -> Cow<'_, str> { self.field_type_impl(fty) } - pub(crate) fn input_name<'a>(self, inm: &'a str) -> Cow<'a, str> { + pub(crate) fn input_name(self, inm: &str) -> Cow<'_, str> { self.camel_case(inm) } - pub(crate) fn scalar_name<'a>(self, snm: &'a str) -> Cow<'a, str> { + pub(crate) fn scalar_name(self, snm: &str) -> Cow<'_, str> { self.camel_case(snm) } } diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index aa4f76f5b..36b894a6a 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -473,7 +473,7 @@ impl Query { pub(crate) fn select_operation<'a>( &'a self, name: &str, - normalization: &Normalization, + normalization: Normalization, ) -> Option<(OperationId, &'a ResolvedOperation)> { walk_operations(self).find(|(_id, op)| normalization.operation(&op.name) == name) } @@ -579,7 +579,7 @@ impl UsedTypes { } pub(crate) fn fragment_ids<'b>(&'b self) -> impl Iterator + 'b { - self.fragments.iter().map(|v| *v) + self.fragments.iter().copied() } } diff --git a/graphql_client_codegen/src/query/selection.rs b/graphql_client_codegen/src/query/selection.rs index ce518bd2e..3d5c5130a 100644 --- a/graphql_client_codegen/src/query/selection.rs +++ b/graphql_client_codegen/src/query/selection.rs @@ -247,7 +247,7 @@ pub(crate) struct SelectedField { impl SelectedField { pub(crate) fn alias(&self) -> Option<&str> { - self.alias.as_ref().map(String::as_str) + self.alias.as_deref() } pub(crate) fn schema_field<'a>(&self, schema: &'a Schema) -> &'a StoredField { diff --git a/graphql_client_codegen/src/schema.rs b/graphql_client_codegen/src/schema.rs index da049e6c2..96b99524e 100644 --- a/graphql_client_codegen/src/schema.rs +++ b/graphql_client_codegen/src/schema.rs @@ -34,9 +34,7 @@ pub(crate) struct StoredField { impl StoredField { pub(crate) fn deprecation(&self) -> Option> { - self.deprecation - .as_ref() - .map(|inner| inner.as_ref().map(String::as_str)) + self.deprecation.as_ref().map(|inner| inner.as_deref()) } } @@ -258,7 +256,7 @@ impl Schema { name: (*scalar).to_owned(), }); - self.names.insert(scalar.to_string(), TypeId::Scalar(id)); + self.names.insert((*scalar).to_owned(), TypeId::Scalar(id)); } } @@ -351,7 +349,7 @@ impl Schema { } pub(crate) fn find_type(&self, type_name: &str) -> Option { - self.names.get(type_name).map(|id| *id) + self.names.get(type_name).copied() } pub(crate) fn objects(&self) -> impl Iterator { diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 02e2b0f38..50a0dec20 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -125,8 +125,7 @@ fn inputs_mut(schema: &mut JsonSchema) -> impl Iterator { fn scalars_mut(schema: &mut JsonSchema) -> impl Iterator { types_mut(schema).filter(|t| { t.kind == Some(__TypeKind::SCALAR) - && !super::DEFAULT_SCALARS - .contains(&t.name.as_ref().map(String::as_str).expect("FullType.name")) + && !super::DEFAULT_SCALARS.contains(&t.name.as_deref().expect("FullType.name")) }) } From c8394147c3b2e7f2c935068cca77d2b04a36721b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Thu, 26 Mar 2020 21:33:22 +0100 Subject: [PATCH 91/93] Make clippy pass --- graphql_client_codegen/src/lib.rs | 1 + graphql_client_codegen/src/query/selection.rs | 1 + graphql_client_codegen/src/schema/json_conversion.rs | 2 +- graphql_client_codegen/src/schema/tests/github.rs | 2 +- graphql_client_codegen/src/tests/mod.rs | 1 - graphql_query_derive/src/lib.rs | 2 +- 6 files changed, 5 insertions(+), 4 deletions(-) diff --git a/graphql_client_codegen/src/lib.rs b/graphql_client_codegen/src/lib.rs index 467b2fa35..9f0442162 100644 --- a/graphql_client_codegen/src/lib.rs +++ b/graphql_client_codegen/src/lib.rs @@ -1,5 +1,6 @@ #![deny(missing_docs)] #![warn(rust_2018_idioms)] +#![allow(clippy::option_option)] //! Crate for internal use by other graphql-client crates, for code generation. //! diff --git a/graphql_client_codegen/src/query/selection.rs b/graphql_client_codegen/src/query/selection.rs index 3d5c5130a..2c08f5bb1 100644 --- a/graphql_client_codegen/src/query/selection.rs +++ b/graphql_client_codegen/src/query/selection.rs @@ -68,6 +68,7 @@ pub(super) enum SelectionParent { Operation(OperationId), } +#[allow(clippy::trivially_copy_pass_by_ref)] impl SelectionParent { fn schema_type_id(&self, query: &BoundQuery<'_>) -> TypeId { match self { diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 50a0dec20..98afe5ee4 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -146,7 +146,7 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { .enum_values .as_mut() .expect("enm.enum_values.as_mut()") - .into_iter() + .iter_mut() .map(|v| { std::mem::replace( v.name diff --git a/graphql_client_codegen/src/schema/tests/github.rs b/graphql_client_codegen/src/schema/tests/github.rs index cfd1f2580..8957b775c 100644 --- a/graphql_client_codegen/src/schema/tests/github.rs +++ b/graphql_client_codegen/src/schema/tests/github.rs @@ -122,6 +122,6 @@ fn ast_from_graphql_and_json_produce_the_same_schema() { } } -fn vecs_match(a: &Vec, b: &Vec) -> bool { +fn vecs_match(a: &[T], b: &[T]) -> bool { a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a == b)) } diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index 9c6b39c49..1e24cfbb4 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -1,7 +1,6 @@ #[test] fn schema_with_keywords_works() { use crate::{generated_module, schema::Schema, CodegenMode, GraphQLClientCodegenOptions}; - use graphql_parser; let query_string = include_str!("keywords_query.graphql"); let query = graphql_parser::parse_query(query_string).expect("Parse keywords query"); diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index 849c60991..d9374b84f 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -26,7 +26,7 @@ fn graphql_query_derive_inner( let ast = syn::parse2(input).expect("derive input parsing"); // .context("Derive input parsing.")?; let (query_path, schema_path) = build_query_and_schema_path(&ast)?; - let options = build_graphql_client_derive_options(&ast, query_path.to_path_buf())?; + let options = build_graphql_client_derive_options(&ast, query_path.clone())?; Ok( generate_module_token_stream(query_path, &schema_path, options) .map(Into::into) From ca2ad8ad23baacb7465decd3f5812a1400f52167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sun, 29 Mar 2020 14:31:46 +0200 Subject: [PATCH 92/93] Rebase conflicts --- graphql_client/Cargo.toml | 1 - graphql_client_cli/src/generate.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/graphql_client/Cargo.toml b/graphql_client/Cargo.toml index d42d468b9..282c6db63 100644 --- a/graphql_client/Cargo.toml +++ b/graphql_client/Cargo.toml @@ -16,7 +16,6 @@ thiserror = { version = "1.0", optional = true } graphql_query_derive = { path = "../graphql_query_derive", version = "0.9.0" } serde_json = "1.0" serde = { version = "^1.0.78", features = ["derive"] } -thiserror = "1.0.10" [dependencies.futures] version = "^0.1" diff --git a/graphql_client_cli/src/generate.rs b/graphql_client_cli/src/generate.rs index 80bef36f3..be23e3703 100644 --- a/graphql_client_cli/src/generate.rs +++ b/graphql_client_cli/src/generate.rs @@ -59,7 +59,7 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> Result<()> { options.set_deprecation_strategy(deprecation_strategy); } - let gen = generate_module_token_stream(query_path.clone(), &schema_path, options).map_err(|fail| fail.compat())?; + let gen = generate_module_token_stream(query_path.clone(), &schema_path, options)?; let generated_code = gen.to_string(); let generated_code = if cfg!(feature = "rustfmt") && !no_formatting { From c9029709cf362a7be13bbfb3b011e4a3578471ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Sat, 4 Apr 2020 13:24:01 +0200 Subject: [PATCH 93/93] Replace mem::replace with mem::take where possible --- .../src/schema/graphql_parser_conversion.rs | 20 +++++++++---------- .../src/schema/json_conversion.rs | 10 +++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs index 7c7596134..a351aaeef 100644 --- a/graphql_client_codegen/src/schema/graphql_parser_conversion.rs +++ b/graphql_client_codegen/src/schema/graphql_parser_conversion.rs @@ -136,7 +136,7 @@ fn populate_names_map(schema: &mut Schema, definitions: &[Definition]) { fn ingest_union(schema: &mut Schema, union: &mut UnionType) { let stored_union = super::StoredUnion { - name: std::mem::replace(&mut union.name, String::new()), + name: std::mem::take(&mut union.name), variants: union .types .iter() @@ -153,7 +153,7 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy for field in obj.fields.iter_mut() { let field = super::StoredField { - name: std::mem::replace(&mut field.name, String::new()), + name: std::mem::take(&mut field.name), r#type: resolve_field_type(schema, &field.field_type), parent: super::StoredFieldParent::Object(object_id), deprecation: find_deprecation(&field.directives), @@ -164,7 +164,7 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy // Ingest the object itself let object = super::StoredObject { - name: std::mem::replace(&mut obj.name, String::new()), + name: std::mem::take(&mut obj.name), fields: field_ids, implements_interfaces: obj .implements_interfaces @@ -177,7 +177,7 @@ fn ingest_object(schema: &mut Schema, obj: &mut graphql_parser::schema::ObjectTy } fn ingest_scalar(schema: &mut Schema, scalar: &mut graphql_parser::schema::ScalarType) { - let name = std::mem::replace(&mut scalar.name, String::new()); + let name = std::mem::take(&mut scalar.name); let name_for_names = name.clone(); let scalar = super::StoredScalar { name }; @@ -191,11 +191,11 @@ fn ingest_scalar(schema: &mut Schema, scalar: &mut graphql_parser::schema::Scala fn ingest_enum(schema: &mut Schema, enm: &mut graphql_parser::schema::EnumType) { let enm = super::StoredEnum { - name: std::mem::replace(&mut enm.name, String::new()), + name: std::mem::take(&mut enm.name), variants: enm .values .iter_mut() - .map(|value| std::mem::replace(&mut value.name, String::new())) + .map(|value| std::mem::take(&mut value.name)) .collect(), }; @@ -212,7 +212,7 @@ fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema: for field in interface.fields.iter_mut() { let field = super::StoredField { - name: std::mem::replace(&mut field.name, String::new()), + name: std::mem::take(&mut field.name), r#type: resolve_field_type(schema, &field.field_type), parent: super::StoredFieldParent::Interface(interface_id), deprecation: find_deprecation(&field.directives), @@ -222,7 +222,7 @@ fn ingest_interface(schema: &mut Schema, interface: &mut graphql_parser::schema: } let new_interface = super::StoredInterface { - name: std::mem::replace(&mut interface.name, String::new()), + name: std::mem::take(&mut interface.name), fields: field_ids, }; @@ -247,14 +247,14 @@ fn find_deprecation(directives: &[parser::Directive]) -> Option> fn ingest_input(schema: &mut Schema, input: &mut parser::InputObjectType) { let input = super::StoredInputType { - name: std::mem::replace(&mut input.name, String::new()), + name: std::mem::take(&mut input.name), fields: input .fields .iter_mut() .map(|val| { let field_type = super::resolve_field_type(schema, &val.value_type); ( - std::mem::replace(&mut val.name, String::new()), + std::mem::take(&mut val.name), StoredInputFieldType { qualifiers: field_type.qualifiers, id: field_type.id, diff --git a/graphql_client_codegen/src/schema/json_conversion.rs b/graphql_client_codegen/src/schema/json_conversion.rs index 98afe5ee4..0eb2d0086 100644 --- a/graphql_client_codegen/src/schema/json_conversion.rs +++ b/graphql_client_codegen/src/schema/json_conversion.rs @@ -148,12 +148,11 @@ fn ingest_enum(schema: &mut Schema, enm: &mut FullType) { .expect("enm.enum_values.as_mut()") .iter_mut() .map(|v| { - std::mem::replace( + std::mem::take( v.name .as_mut() .take() .expect("variant.name.as_mut().take()"), - String::new(), ) }) .collect(); @@ -192,10 +191,7 @@ fn ingest_interface(schema: &mut Schema, iface: &mut FullType) { } let interface = super::StoredInterface { - name: std::mem::replace( - iface.name.as_mut().expect("iface.name.as_mut"), - String::new(), - ), + name: std::mem::take(iface.name.as_mut().expect("iface.name.as_mut")), fields: field_ids, }; @@ -293,7 +289,7 @@ fn ingest_input(schema: &mut Schema, input: &mut FullType) { .iter_mut() { fields.push(( - std::mem::replace(&mut field.input_value.name, String::new()), + std::mem::take(&mut field.input_value.name), resolve_input_field_type(schema, &mut field.input_value.type_), )); }