diff --git a/examples/github/Cargo.toml b/examples/github/Cargo.toml index 6f27baddd..0423a0a2f 100644 --- a/examples/github/Cargo.toml +++ b/examples/github/Cargo.toml @@ -7,16 +7,16 @@ edition = "2018" [dependencies] failure = "*" graphql_client = { path = "../../graphql_client" } -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -reqwest = "^0.9.0" -prettytable-rs = "0.7.0" -structopt = "0.2.10" -dotenv = "0.13.0" -envy = "0.3.2" -log = "0.4.3" -env_logger = "0.5.10" +serde = "^1.0" +serde_derive = "^1.0" +serde_json = "^1.0" +reqwest = "^0.9" +prettytable-rs = "^0.7" +structopt = "^0.2" +dotenv = "^0.13" +envy = "^0.3" +log = "^0.4" +env_logger = "^0.5" [workspace] members = ["."] diff --git a/graphql_client/Cargo.toml b/graphql_client/Cargo.toml index 3ced39c07..7e1e75c53 100644 --- a/graphql_client/Cargo.toml +++ b/graphql_client/Cargo.toml @@ -10,26 +10,26 @@ categories = ["network-programming", "web-programming", "wasm"] edition = "2018" [dependencies] -failure = "0.1" +failure = "^0.1" graphql_query_derive = { path = "../graphql_query_derive", version = "0.8.0" } -serde = { version = "^1.0.78", features = ["derive"] } -serde_json = "1.0" -doc-comment = "0.3.1" +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +doc-comment = "^0.3" [dependencies.futures] -version = "0.1" +version = "^0.1" optional = true [dependencies.js-sys] -version = "0.3.5" +version = "^0.3" optional = true [dependencies.log] -version = "0.4.6" +version = "^0.4" optional = true [dependencies.web-sys] -version = "0.3.2" +version = "^0.3" optional = true features = [ "Headers", @@ -40,18 +40,18 @@ features = [ ] [dependencies.wasm-bindgen] -version = "0.2.43" +version = "^0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "0.3.2" +version = "^0.3" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.reqwest] -version = "0.9.16" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +reqwest = "^0.9" -[dev-dependencies.wasm-bindgen-test] -version = "0.2.43" +[dev-dependencies] +wasm-bindgen-test = "^0.2" [features] web = [ diff --git a/graphql_client/tests/input_object_variables.rs b/graphql_client/tests/input_object_variables.rs index c31826ac6..15f16516a 100644 --- a/graphql_client/tests/input_object_variables.rs +++ b/graphql_client/tests/input_object_variables.rs @@ -58,14 +58,14 @@ fn recursive_input_objects_can_be_constructed() { RecursiveInput { head: "hello".to_string(), - tail: Box::new(None), + tail: None, }; RecursiveInput { head: "hi".to_string(), - tail: Box::new(Some(RecursiveInput { + tail: Some(Box::new(RecursiveInput { head: "this is crazy".to_string(), - tail: Box::new(None), + tail: None, })), }; } @@ -84,14 +84,14 @@ fn indirectly_recursive_input_objects_can_be_constructed() { IndirectlyRecursiveInput { head: "hello".to_string(), - tail: Box::new(None), + tail: None, }; IndirectlyRecursiveInput { head: "hi".to_string(), - tail: Box::new(Some(IndirectlyRecursiveInputTailPart { + tail: Some(Box::new(IndirectlyRecursiveInputTailPart { name: "this is crazy".to_string(), - recursed_field: Box::new(None), + recursed_field: None, })), }; } diff --git a/graphql_client_cli/Cargo.toml b/graphql_client_cli/Cargo.toml index 2640fe536..8ffa2716e 100644 --- a/graphql_client_cli/Cargo.toml +++ b/graphql_client_cli/Cargo.toml @@ -12,18 +12,18 @@ name = "graphql-client" path = "src/main.rs" [dependencies] -failure = "0.1" -reqwest = "^0.9.0" +failure = "^0.1" +reqwest = "^0.9" graphql_client = { version = "0.8.0", path = "../graphql_client" } graphql_client_codegen = { path = "../graphql_client_codegen/", version = "0.8.0" } -structopt = "0.2" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -syn = "0.15" -log = "0.4.0" -env_logger = "0.6.0" +structopt = "0.2.18" +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +syn = "^1.0" +log = "^0.4" +env_logger = "^0.6" -rustfmt-nightly = { version = "0.99" , optional = true } +rustfmt-nightly = { version = "1.4.5", optional = true } [features] default = [] diff --git a/graphql_client_cli/src/generate.rs b/graphql_client_cli/src/generate.rs index 2d8c65fd4..dcd001df1 100644 --- a/graphql_client_cli/src/generate.rs +++ b/graphql_client_cli/src/generate.rs @@ -82,7 +82,6 @@ fn format(codes: &str) -> String { #[cfg(feature = "rustfmt")] { use rustfmt::{Config, Input, Session}; - use std::default::Default; let mut config = Config::default(); diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index f9045d5c5..c9e5d52ac 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -21,7 +21,7 @@ pub fn introspect_schema( ) -> Result<(), failure::Error> { use std::io::Write; - let out: Box = match output { + let out: Box = match output { Some(path) => Box::new(::std::fs::File::create(path)?), None => Box::new(::std::io::stdout()), }; diff --git a/graphql_client_cli/src/main.rs b/graphql_client_cli/src/main.rs index f6dba1f8e..a19cc8d4f 100644 --- a/graphql_client_cli/src/main.rs +++ b/graphql_client_cli/src/main.rs @@ -36,7 +36,7 @@ enum Cli { /// Path to the GraphQL query file. query_path: PathBuf, /// Name of target query. If you don't set this parameter, cli generate all queries in query file. - #[structopt(short = "o", long = "selected-operation")] + #[structopt(long = "selected-operation")] selected_operation: Option, /// Additional derives that will be added to the generated structs and enums for the response and the variables. /// --additional-derives='Serialize,PartialEq' @@ -59,7 +59,7 @@ enum Cli { /// /// If this option is omitted, the code will be generated next to the .graphql /// file, with the same name and the .rs extension. - #[structopt(short = "out", long = "output-directory")] + #[structopt(short = "o", long = "output-directory")] output_directory: Option, }, } diff --git a/graphql_client_codegen/Cargo.toml b/graphql_client_codegen/Cargo.toml index f0b2c71e5..4fb4836e6 100644 --- a/graphql_client_codegen/Cargo.toml +++ b/graphql_client_codegen/Cargo.toml @@ -8,12 +8,13 @@ repository = "https://github.com/graphql-rust/graphql-client" edition = "2018" [dependencies] -failure = "0.1" -lazy_static = "1.0" -quote = "0.6" -syn = "0.15.20" -proc-macro2 = { version = "0.4", features = [] } -serde = { version = "^1.0.78", features = ["derive"] } -serde_json = "1.0" -heck = "0.3" -graphql-parser = "0.2.2" +failure = "^0.1" +lazy_static = "^1.3" +quote = "^1.0" +syn = "^1.0" +proc-macro2 = { version = "^1.0", features = [] } +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +heck = "^0.3" +graphql-parser = "^0.2" +derivative = "1.0.2" diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 5c31ca28a..ef555720e 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -1,4 +1,5 @@ use crate::deprecation::DeprecationStrategy; +use derivative::*; use proc_macro2::Ident; use std::path::{Path, PathBuf}; use syn::Visibility; @@ -13,7 +14,8 @@ pub enum CodegenMode { } /// Used to configure code generation. -#[derive(Debug)] +#[derive(Derivative)] +#[derivative(Debug)] pub struct GraphQLClientCodegenOptions { /// Which context is this code generation effort taking place. pub mode: CodegenMode, @@ -28,6 +30,7 @@ pub struct GraphQLClientCodegenOptions { /// The deprecation strategy to adopt. deprecation_strategy: Option, /// Target module visibility. + #[derivative(Debug = "ignore")] module_visibility: Option, /// A path to a file to include in the module to force Cargo to take into account changes in /// the query files when recompiling. diff --git a/graphql_client_codegen/src/enums.rs b/graphql_client_codegen/src/enums.rs index d294cc8c0..ad2ecc046 100644 --- a/graphql_client_codegen/src/enums.rs +++ b/graphql_client_codegen/src/enums.rs @@ -19,6 +19,13 @@ 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<'_, '_>, @@ -28,7 +35,8 @@ impl<'schema> GqlEnum<'schema> { .variants .iter() .map(|v| { - let name = Ident::new(&v.name, Span::call_site()); + let rust_safe_field_name = crate::shared::keyword_replace(&v.name); + let name = Ident::new(&rust_safe_field_name, Span::call_site()); let description = &v.description; let description = description.as_ref().map(|d| quote!(#[doc = #d])); quote!(#description #name) @@ -40,7 +48,8 @@ impl<'schema> GqlEnum<'schema> { .variants .iter() .map(|v| { - let v = Ident::new(&v.name, Span::call_site()); + let rust_safe_field_name = crate::shared::keyword_replace(&v.name); + let v = Ident::new(&rust_safe_field_name, Span::call_site()); quote!(#name_ident::#v) }) .collect(); diff --git a/graphql_client_codegen/src/field_type.rs b/graphql_client_codegen/src/field_type.rs index 4d95f9486..97006fe03 100644 --- a/graphql_client_codegen/src/field_type.rs +++ b/graphql_client_codegen/src/field_type.rs @@ -46,8 +46,9 @@ impl<'a> FieldType<'a> { } prefix.to_string() }; - let full_name = Ident::new(&full_name, Span::call_site()); + let rust_safe_field_name = crate::shared::keyword_replace(&full_name); + let full_name = Ident::new(&rust_safe_field_name, Span::call_site()); quote!(#full_name) } FieldType::Optional(inner) => { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 5e6e9dce4..1a2583979 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -28,7 +28,8 @@ impl<'a> GeneratedModule<'a> { let module_name = Ident::new(&self.operation.name.to_snake_case(), Span::call_site()); let module_visibility = &self.options.module_visibility(); let operation_name_literal = &self.operation.name; - let operation_name_ident = Ident::new(&self.operation.name, Span::call_site()); + let operation_name_ident = + Ident::new(&self.operation.name.to_camel_case(), Span::call_site()); // Force cargo to refresh the generated code when the query file changes. let query_include = self @@ -51,7 +52,15 @@ impl<'a> GeneratedModule<'a> { CodegenMode::Derive => quote!(), }; + let variables_type = match self.operation.variables.len() { + 0 => quote!(()), + _ => quote!( + #module_name::Variables + ), + }; + Ok(quote!( + #[allow(dead_code)] #struct_declaration #module_visibility mod #module_name { @@ -66,7 +75,7 @@ impl<'a> GeneratedModule<'a> { } impl graphql_client::GraphQLQuery for #operation_name_ident { - type Variables = #module_name::Variables; + type Variables = #variables_type; type ResponseData = #module_name::ResponseData; fn build_query(variables: Self::Variables) -> ::graphql_client::QueryBody { diff --git a/graphql_client_codegen/src/inputs.rs b/graphql_client_codegen/src/inputs.rs index e0e109d19..76b5b4025 100644 --- a/graphql_client_codegen/src/inputs.rs +++ b/graphql_client_codegen/src/inputs.rs @@ -65,42 +65,96 @@ impl<'schema> GqlInput<'schema> { self.contains_type_without_indirection(context, &self.name) } - pub(crate) fn to_rust( + fn map_field( &self, context: &QueryContext<'_, '_>, - ) -> Result { - let name = Ident::new(&self.name, Span::call_site()); - 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, ""); + field: &GqlObjectField<'_>, + required_fields: &mut Vec, + struct_field_assignments: &mut Vec, + ) -> TokenStream { + let is_recursive = + if let Some(input) = context.schema.inputs.get(field.type_.inner_name_str()) { + input.is_recursive_without_indirection(context) + } else { + false + }; - // 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) { + // If the type is recursive, we have to box it + let ty = if is_recursive { + match &field.type_ { + // If it's an optional field: Wrap the boxed inner type in an Option + crate::field_type::FieldType::Optional(inner) => { + let ty = inner.to_rust(&context, ""); + quote! { Option> } + } + _ => { + let ty = field.type_.to_rust(&context, ""); quote! { Box<#ty> } - } else { - quote!(#ty) } - } else { - quote!(#ty) - }; + } + } else { + let ty = field.type_.to_rust(&context, ""); + quote!(#ty) + }; + + context.schema.require(&field.type_.inner_name_str()); + let rust_safe_field_name = crate::shared::keyword_replace(&field.name.to_snake_case()); + let mut rename = crate::shared::field_rename_annotation(&field.name, &rust_safe_field_name); + let name = Ident::new(&rust_safe_field_name, Span::call_site()); - context.schema.require(&field.type_.inner_name_str()); - let original_name = &field.name; - let snake_case_name = field.name.to_snake_case(); - let rename = crate::shared::field_rename_annotation(&original_name, &snake_case_name); - let name = Ident::new(&snake_case_name, Span::call_site()); + match &field.type_ { + crate::field_type::FieldType::Optional(_) => { + struct_field_assignments.push(quote!(#name: None)); + rename = quote!( + #[serde(skip_serializing_if = "Option::is_none")] + #rename + ) + } + _ => { + required_fields.push(quote!(#name: #ty)); + struct_field_assignments.push(quote!(#name: #name)); + } + }; + quote!(#rename pub #name: #ty) + } - quote!(#rename pub #name: #ty) - }); + pub(crate) fn to_rust( + &self, + context: &QueryContext<'_, '_>, + ) -> Result { + let mut obj_fields: Vec<&GqlObjectField<'_>> = self.fields.values().collect(); + obj_fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); + + let mut fields: Vec = vec![]; + let mut required_fields: Vec = vec![]; + let mut struct_field_assignments: Vec = vec![]; + + for field in obj_fields.iter() { + fields.push(self.map_field( + context, + field, + &mut required_fields, + &mut struct_field_assignments, + )); + } 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 rust_safe_field_name = crate::shared::keyword_replace(&self.name); + let name = Ident::new(&rust_safe_field_name, Span::call_site()); Ok(quote! { #variables_derives pub struct #name { #(#fields,)* } + impl #name { + pub fn new(#(#required_fields),*) -> Self { + Self { + #(#struct_field_assignments,)* + } + } + } }) } } @@ -226,7 +280,8 @@ mod tests { "# [ serde ( rename = \"pawsCount\" ) ] ", "pub paws_count : Float , ", "pub requirements : Option < CatRequirements > , ", - "}", + "} ", + "impl Cat { pub fn new ( offsprings : Vec < Cat > , paws_count : Float ) -> Self { Self { offsprings : offsprings , paws_count : paws_count , requirements : None , } } }" ] .into_iter() .collect(); diff --git a/graphql_client_codegen/src/operations.rs b/graphql_client_codegen/src/operations.rs index 23dc477ec..1684fc46e 100644 --- a/graphql_client_codegen/src/operations.rs +++ b/graphql_client_codegen/src/operations.rs @@ -48,18 +48,23 @@ impl<'query> Operation<'query> { let variables_derives = context.variables_derives(); if variables.is_empty() { - return quote! { - #variables_derives - pub struct Variables; - }; + return quote! {}; } let fields = variables.iter().map(|variable| { - let name = &variable.name; let ty = variable.ty.to_rust(context, ""); - let snake_case_name = name.to_snake_case(); - let rename = crate::shared::field_rename_annotation(&name, &snake_case_name); - let name = Ident::new(&snake_case_name, Span::call_site()); + let rust_safe_field_name = + crate::shared::keyword_replace(&variable.name.to_snake_case()); + let mut rename = + crate::shared::field_rename_annotation(&variable.name, &rust_safe_field_name); + let name = Ident::new(&rust_safe_field_name, Span::call_site()); + + if let crate::field_type::FieldType::Optional(_) = &variable.ty { + rename = quote!( + #[serde(skip_serializing_if = "Option::is_none")] + #rename + ) + } quote!(#rename pub #name: #ty) }); diff --git a/graphql_client_codegen/src/query.rs b/graphql_client_codegen/src/query.rs index 341d41744..9ccfae919 100644 --- a/graphql_client_codegen/src/query.rs +++ b/graphql_client_codegen/src/query.rs @@ -115,7 +115,6 @@ impl<'query, 'schema> QueryContext<'query, 'schema> { pub(crate) fn response_derives(&self) -> TokenStream { let derives: BTreeSet<&Ident> = self.response_derives.iter().collect(); let derives = derives.iter(); - quote! { #[derive( #(#derives),* )] } @@ -130,8 +129,9 @@ impl<'query, 'schema> QueryContext<'query, 'schema> { .response_derives .iter() .filter(|derive| { - !derive.to_string().contains("erialize") - && !derive.to_string().contains("Deserialize") + // 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()); diff --git a/graphql_client_codegen/src/shared.rs b/graphql_client_codegen/src/shared.rs index 6641c4728..c4fe5625c 100644 --- a/graphql_client_codegen/src/shared.rs +++ b/graphql_client_codegen/src/shared.rs @@ -7,9 +7,91 @@ use heck::{CamelCase, SnakeCase}; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; +// List of keywords based on https://doc.rust-lang.org/grammar.html#keywords +const RUST_KEYWORDS: &'static [&'static str] = &[ + "abstract", + "alignof", + "as", + "async", + "await", + "become", + "box", + "break", + "const", + "continue", + "crate", + "do", + "else", + "enum", + "extern crate", + "extern", + "false", + "final", + "fn", + "for", + "for", + "if let", + "if", + "if", + "impl", + "impl", + "in", + "let", + "loop", + "macro", + "match", + "mod", + "move", + "mut", + "offsetof", + "override", + "priv", + "proc", + "pub", + "pure", + "ref", + "return", + "self", + "sizeof", + "static", + "struct", + "super", + "trait", + "true", + "type", + "typeof", + "unsafe", + "unsized", + "use", + "use", + "virtual", + "where", + "while", + "yield", +]; + +pub(crate) fn keyword_replace(needle: &str) -> String { + match RUST_KEYWORDS.binary_search(&needle) { + Ok(index) => [RUST_KEYWORDS[index], "_"].concat(), + Err(_) => needle.to_owned(), + } +} + +mod tests { + #[test] + fn keyword_replace() { + use super::keyword_replace; + assert_eq!("fora", keyword_replace("fora")); + assert_eq!("in_", keyword_replace("in")); + assert_eq!("fn_", keyword_replace("fn")); + assert_eq!("struct_", keyword_replace("struct")); + } +} + pub(crate) fn render_object_field( field_name: &str, field_type: &TokenStream, + field_is_optional: bool, description: Option<&str>, status: &DeprecationStatus, strategy: &DeprecationStrategy, @@ -36,29 +118,16 @@ pub(crate) fn render_object_field( let description = description.map(|s| quote!(#[doc = #s])); - // List of keywords based on https://doc.rust-lang.org/grammar.html#keywords - let reserved = &[ - "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do", - "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop", - "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub", - "pure", "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait", - "true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", - ]; + 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 mut rename = crate::shared::field_rename_annotation(&field_name, &rust_safe_field_name); - if reserved.contains(&field_name) { - let name_ident = Ident::new(&format!("{}_", field_name), Span::call_site()); - return quote! { - #description - #deprecation - #[serde(rename = #field_name)] - pub #name_ident: #field_type - }; + if field_is_optional { + rename = quote!( + #[serde(skip_serializing_if = "Option::is_none")] + #rename + ) } - - let snake_case_name = field_name.to_snake_case(); - let rename = crate::shared::field_rename_annotation(&field_name, &snake_case_name); - let name_ident = Ident::new(&snake_case_name, Span::call_site()); - quote!(#description #deprecation #rename pub #name_ident: #field_type) } @@ -131,6 +200,10 @@ pub(crate) fn response_fields_for_selection( Ok(render_object_field( alias, &ty, + match &schema_field.type_ { + crate::field_type::FieldType::Optional(_) => true, + _ => false, + }, schema_field.description.as_ref().cloned(), &schema_field.deprecation, &context.deprecation_strategy, diff --git a/graphql_client_codegen/src/tests/keywords_query.graphql b/graphql_client_codegen/src/tests/keywords_query.graphql new file mode 100644 index 000000000..24f0bc997 --- /dev/null +++ b/graphql_client_codegen/src/tests/keywords_query.graphql @@ -0,0 +1,7 @@ +query searchQuery($criteria: extern!) { + search { + transactions(criteria:$searchID) { + for,status + } + } +} diff --git a/graphql_client_codegen/src/tests/keywords_schema.graphql b/graphql_client_codegen/src/tests/keywords_schema.graphql new file mode 100644 index 000000000..5f199c8d0 --- /dev/null +++ b/graphql_client_codegen/src/tests/keywords_schema.graphql @@ -0,0 +1,52 @@ +schema { + query: Query + mutation: Mutation +} + +"""This directive allows results to be deferred during execution""" +directive @defer on FIELD + +"""The top-level Query type.""" +type Query { + """Keyword type""" + search: Self +} + +"""Keyword type""" +type Self { + """ + A keyword variable name with a keyword-named input type + """ + transactions(struct: extern!): Result +} + +"""Keyword type""" +type Result { + """Keyword field.""" + for: String + """dummy field with enum""" + status: AnEnum +} + + +"""Keyword input""" +input extern { + """A field""" + id: crate +} + +"""Input fields for searching for specific values.""" +input crate { + """Keyword field.""" + enum: String + + """Keyword field.""" + in: [String!] +} + + +"""Enum with keywords""" +enum AnEnum { + where + self +} diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index 6a5a51cbc..3d7e29b7a 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -1 +1,43 @@ mod github; + +#[test] +fn schema_with_keywords_works() { + use crate::{ + codegen, 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 options = GraphQLClientCodegenOptions::new(CodegenMode::Cli); + let operations = codegen::all_operations(&query); + for operation in &operations { + let generated_tokens = generated_module::GeneratedModule { + query_string: query_string, + schema: &schema, + query_document: &query, + operation, + options: &options, + } + .to_token_stream() + .expect("Generate keywords module"); + let generated_code = generated_tokens.to_string(); + + // Parse generated code. All keywords should be correctly escaped. + let r: syn::parse::Result = syn::parse2(generated_tokens); + match r { + Ok(_) => { + // Rust keywords should be escaped / renamed now + assert!(generated_code.contains("pub in_")); + assert!(generated_code.contains("extern_")); + } + Err(e) => { + panic!("Error: {}\n Generated content: {}\n", e, &generated_code); + } + }; + } +} diff --git a/graphql_client_web/Cargo.toml b/graphql_client_web/Cargo.toml index cbf8ea883..05a07ea91 100644 --- a/graphql_client_web/Cargo.toml +++ b/graphql_client_web/Cargo.toml @@ -15,5 +15,5 @@ path = "../graphql_client" features = ["web"] [dev-dependencies] -serde = { version = "1", features = ["derive"] } -wasm-bindgen-test = "0.2.25" +serde = { version = "^1.0", features = ["derive"] } +wasm-bindgen-test = "0.2.50" diff --git a/graphql_query_derive/Cargo.toml b/graphql_query_derive/Cargo.toml index 3afffb86e..5ad2b754f 100644 --- a/graphql_query_derive/Cargo.toml +++ b/graphql_query_derive/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" proc-macro = true [dependencies] -failure = "0.1" -syn = { version = "0.15.20", features = ["extra-traits"] } -proc-macro2 = { version = "0.4", features = [] } +failure = "^0.1" +syn = { version = "^1.0", features = ["extra-traits"] } +proc-macro2 = { version = "^1.0", features = [] } graphql_client_codegen = { path = "../graphql_client_codegen/", version = "0.8.0" } diff --git a/graphql_query_derive/src/attributes.rs b/graphql_query_derive/src/attributes.rs index 91a8b38ee..0bd4ade59 100644 --- a/graphql_query_derive/src/attributes.rs +++ b/graphql_query_derive/src/attributes.rs @@ -17,16 +17,15 @@ pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result