From 758f3f7d40a4c10a46ccd3b688c4e679ad8e6fc9 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:52:10 +0200 Subject: [PATCH 01/15] (codegen) Implement impl_object macro + unify code with object derive This commit implements a new proc macro `impl_object` that replaces the old graphql_object! macro. The code shares a lot of similarities with the GraphQLObject custom derive, so the code was unified to handle both more generically. Also, doc comment processing was standardized and improved. --- juniper_codegen/Cargo.toml | 5 +- juniper_codegen/src/derive_object.rs | 325 +++---------- juniper_codegen/src/impl_object.rs | 226 +++++++++ juniper_codegen/src/lib.rs | 254 +++++++++- juniper_codegen/src/util.rs | 692 +++++++++++++++++++++++++-- 5 files changed, 1200 insertions(+), 302 deletions(-) create mode 100644 juniper_codegen/src/impl_object.rs diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 8942c461..9077f5e2 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -16,10 +16,13 @@ proc-macro = true [dependencies] proc-macro2 = "0.4" -syn = { version = "0.14", features = ["full", "extra-traits"] } +syn = { version = "0.15.28", features = ["full", "extra-traits", "parsing"] } quote = "0.6" regex = "1" lazy_static = "1.0.0" +[dev-dependencies] +juniper = { version = "0.11", path = "../juniper" } + [badges] travis-ci = { repository = "graphql-rust/juniper" } diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs index 0794b1d4..06e9e426 100644 --- a/juniper_codegen/src/derive_object.rs +++ b/juniper_codegen/src/derive_object.rs @@ -1,143 +1,13 @@ -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use syn::{self, parse_quote, Data, DeriveInput, Field, Fields, Ident}; +use syn::{self, Data, Fields}; -use crate::util::*; +use crate::util; -#[derive(Default, Debug)] -struct ObjAttrs { - name: Option, - description: Option, - context: Option, - scalar: Option, -} - -impl ObjAttrs { - fn from_input(input: &DeriveInput) -> ObjAttrs { - let mut res = ObjAttrs::default(); - - // Check doc comments for description. - res.description = get_doc_comment(&input.attrs); - - // Check attributes for name and description. - if let Some(items) = get_graphql_attr(&input.attrs) { - for item in items { - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "name", AttributeValidation::String) - { - if is_valid_name(&*val) { - res.name = Some(val); - continue; - } else { - panic!( - "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", - &*val - ); - } - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "description", AttributeValidation::String) - { - res.description = Some(val); - continue; - } - if let Some(AttributeValue::String(scalar)) = - keyed_item_value(&item, "scalar", AttributeValidation::String) - { - res.scalar = Some(Ident::new(&scalar as &str, Span::call_site())); - continue; - } - if let Some(AttributeValue::String(ctx)) = - keyed_item_value(&item, "Context", AttributeValidation::String) - { - res.context = Some(Ident::new(&ctx as &str, Span::call_site())); - continue; - } - panic!(format!( - "Unknown struct attribute for #[derive(GraphQLObject)]: {:?}", - item - )); - } - } - res - } -} - -#[derive(Default)] -struct ObjFieldAttrs { - name: Option, - description: Option, - deprecation: Option, - skip: bool, -} - -impl ObjFieldAttrs { - fn from_input(variant: &Field) -> ObjFieldAttrs { - let mut res = ObjFieldAttrs::default(); - - // Check doc comments for description. - res.description = get_doc_comment(&variant.attrs); - - // Check builtin deprecated attribute for deprecation. - res.deprecation = get_deprecated(&variant.attrs); - - // Check attributes. - if let Some(items) = get_graphql_attr(&variant.attrs) { - for item in items { - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "name", AttributeValidation::String) - { - if is_valid_name(&*val) { - res.name = Some(val); - continue; - } else { - panic!( - "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", - &*val - ); - } - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "description", AttributeValidation::String) - { - res.description = Some(val); - continue; - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "deprecation", AttributeValidation::String) - { - res.deprecation = Some(DeprecationAttr { reason: Some(val) }); - continue; - } - match keyed_item_value(&item, "deprecated", AttributeValidation::String) { - Some(AttributeValue::String(val)) => { - res.deprecation = Some(DeprecationAttr { reason: Some(val) }); - continue; - } - Some(AttributeValue::Bare) => { - res.deprecation = Some(DeprecationAttr { reason: None }); - continue; - } - None => {} - } - if let Some(_) = keyed_item_value(&item, "skip", AttributeValidation::Bare) { - res.skip = true; - continue; - } - panic!(format!( - "Unknown field attribute for #[derive(GraphQLObject)]: {:?}", - item - )); - } - } - res - } -} - -pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream { - let fields = match ast.data { - Data::Struct(ref data) => match data.fields { - Fields::Named(ref fields) => fields.named.iter().collect::>(), +pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { + let struct_fields = match ast.data { + Data::Struct(data) => match data.fields { + Fields::Named(fields) => fields.named, _ => { panic!("#[derive(GraphQLObject)] may only be used on regular structs with fields"); } @@ -148,140 +18,63 @@ pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream { }; // Parse attributes. - let ident = &ast.ident; - let generics = &ast.generics; - let ident_name = ident.to_string(); - let attrs = ObjAttrs::from_input(ast); + let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) { + Ok(a) => a, + Err(e) => { + panic!("Invalid #[graphql(...)] attribute: {}", e); + } + }; + if attrs.interfaces.len() > 0 { + panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLObject) does not support 'interfaces'"); + } let name = attrs.name.unwrap_or(ast.ident.to_string()); - let build_description = match attrs.description { - Some(s) => quote! { builder.description(#s) }, - None => quote! { builder }, - }; - let mut meta_fields = TokenStream::new(); - let mut resolvers = TokenStream::new(); + let fields = struct_fields.into_iter().filter_map(|field| { + let field_attrs = match util::FieldAttributes::from_attrs( + field.attrs, + util::FieldAttributeParseMode::Object, + ) { + Ok(attrs) => attrs, + Err(e) => panic!("Invalid #[graphql] attribute: \n{}", e), + }; - for field in fields { - let field_ty = &field.ty; - let field_attrs = ObjFieldAttrs::from_input(field); - let field_ident = field.ident.as_ref().unwrap(); - - // Check if we should skip this field. if field_attrs.skip { - continue; + None + } else { + let field_name = field.ident.unwrap(); + let name = field_attrs + .name + .clone() + .unwrap_or_else(|| util::to_camel_case(&field_name.to_string())); + + let resolver_code = quote!( + &self . #field_name + ); + + Some(util::GraphQLTypeDefinitionField { + name, + _type: field.ty, + args: Vec::new(), + description: field_attrs.description, + deprecation: field_attrs.deprecation, + resolver_code, + }) } + }); - // Build value. - let name = match field_attrs.name { - Some(ref name) => { - // Custom name specified. - name.to_string() - } - None => { - // Note: auto camel casing when no custom name specified. - crate::util::to_camel_case(&field_ident.to_string()) - } - }; - let build_description = match field_attrs.description { - Some(s) => quote! { field.description(#s) }, - None => quote! { field }, - }; - - let build_deprecation = match field_attrs.deprecation { - Some(DeprecationAttr { reason: Some(s) }) => quote! { field.deprecated(Some(#s)) }, - Some(DeprecationAttr { reason: None }) => quote! { field.deprecated(None) }, - None => quote! { field }, - }; - - meta_fields.extend(quote! { - { - let field = registry.field::<#field_ty>(#name, &()); - let field = #build_description; - let field = #build_deprecation; - field - }, - }); - - // Build from_input clause. - - resolvers.extend(quote! { - #name => executor.resolve_with_ctx(&(), &self.#field_ident), - }); - } - - let (_, ty_generics, _) = generics.split_for_impl(); - - let mut generics = generics.clone(); - - if attrs.scalar.is_none() { - generics.params.push(parse_quote!(__S)); - { - let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); - where_clause - .predicates - .push(parse_quote!(__S: juniper::ScalarValue)); - where_clause - .predicates - .push(parse_quote!(for<'__b> &'__b __S: juniper::ScalarRefValue<'__b>)); - } - } - - let scalar = attrs - .scalar - .unwrap_or_else(|| Ident::new("__S", Span::call_site())); - - let ctx = attrs - .context - .map(|ident| quote!( #ident )) - .unwrap_or(quote!(())); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - let body = quote! { - impl#impl_generics juniper::GraphQLType<#scalar> for #ident #ty_generics - #where_clause - { - type Context = #ctx; - type TypeInfo = (); - - fn name(_: &()) -> Option<&str> { - Some(#name) - } - - fn concrete_type_name(&self, _: &Self::Context, _: &()) -> String { - #name.to_string() - } - - fn meta<'r>( - _: &(), - registry: &mut juniper::Registry<'r, #scalar> - ) -> juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r - { - let fields = &[ - #(#meta_fields)* - ]; - let builder = registry.build_object_type::<#ident>(&(), fields); - let builder = #build_description; - builder.into_meta() - } - - fn resolve_field( - &self, - _: &(), - field_name: &str, - _: &juniper::Arguments<#scalar>, - executor: &juniper::Executor - ) -> juniper::ExecutionResult<#scalar> - { - - match field_name { - #(#resolvers)* - _ => panic!("Field {} not found on type {}", field_name, #ident_name), - } - - } - } + let definition = util::GraphQLTypeDefiniton { + name, + _type: syn::parse_str(&ast.ident.to_string()).unwrap(), + context: attrs.context, + scalar: attrs.scalar, + description: attrs.description, + fields: fields.collect(), + generics: ast.generics.clone(), + interfaces: None, + include_type_generics: true, + generic_scalar: true, }; - body + + let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; + definition.into_tokens(juniper_crate_name) } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs new file mode 100644 index 00000000..4e07cf52 --- /dev/null +++ b/juniper_codegen/src/impl_object.rs @@ -0,0 +1,226 @@ +use crate::util; +use proc_macro::TokenStream; +use quote::quote; + +/// Generate code for the juniper::impl_object macro. +pub fn build_impl_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { + let impl_attrs = match syn::parse::(args) { + Ok(attrs) => attrs, + Err(e) => { + panic!("Invalid attributes:\n{}", e); + } + }; + + let item = match syn::parse::(body) { + Ok(item) => item, + Err(err) => { + panic!("Parsing error:\n{}", err); + } + }; + let mut _impl = match item { + syn::Item::Impl(_impl) => _impl, + _ => { + panic!("#[juniper::object] can only be applied to impl blocks"); + } + }; + + match _impl.trait_ { + Some((_, ref path, _)) => { + let name = path + .segments + .iter() + .map(|segment| segment.ident.to_string()) + .collect::>() + .join("."); + if !(name == "GraphQLObject" || name == "juniper.GraphQLObject") { + panic!("The impl block must implement the 'GraphQLObject' trait"); + } + } + None => { + // panic!("The impl block must implement the 'GraphQLObject' trait"); + } + } + + let name = match impl_attrs.name.as_ref() { + Some(type_name) => type_name.clone(), + None => match &*_impl.self_ty { + syn::Type::Path(ref type_path) => type_path + .path + .segments + .iter() + .last() + .unwrap() + .ident + .to_string(), + syn::Type::Reference(ref reference) => match &*reference.elem { + syn::Type::Path(ref type_path) => type_path + .path + .segments + .iter() + .last() + .unwrap() + .ident + .to_string(), + _ => { + panic!("Could not determine a name for the object type: specify one with #[juniper::impl_object(name = \"SomeName\")"); + } + }, + _ => { + panic!("Could not determine a name for the object type: specify one with #[juniper::impl_object(name = \"SomeName\")"); + } + }, + }; + + let target_type = *_impl.self_ty.clone(); + + let description = impl_attrs + .description + .or(util::get_doc_comment(&_impl.attrs)); + + let mut definition = util::GraphQLTypeDefiniton { + name, + _type: target_type.clone(), + context: impl_attrs.context, + scalar: impl_attrs.scalar, + description, + fields: Vec::new(), + generics: _impl.generics.clone(), + interfaces: if impl_attrs.interfaces.len() > 0 { + Some(impl_attrs.interfaces) + } else { + None + }, + include_type_generics: false, + generic_scalar: false, + }; + + for item in _impl.items { + match item { + syn::ImplItem::Method(method) => { + let _type = match &method.sig.decl.output { + syn::ReturnType::Type(_, ref t) => (**t).clone(), + syn::ReturnType::Default => { + panic!( + "Invalid field method {}: must return a value", + method.sig.ident + ); + } + }; + + let attrs = match util::FieldAttributes::from_attrs( + method.attrs, + util::FieldAttributeParseMode::Impl, + ) { + Ok(attrs) => attrs, + Err(err) => panic!( + "Invalid #[graphql(...)] attribute on field {}:\n{}", + method.sig.ident, err + ), + }; + + let mut args = Vec::new(); + let mut resolve_parts = Vec::new(); + + for arg in method.sig.decl.inputs { + match arg { + _self @ syn::FnArg::SelfRef(_) => { + // Can be ignored. + // "self" will already be in scope. + // resolve_args.push(quote!(self)); + } + syn::FnArg::SelfValue(_) => { + panic!( + "Invalid method receiver {}(self, ...): did you mean '&self'?", + method.sig.ident + ); + } + syn::FnArg::Captured(ref captured) => { + let arg_ident = match &captured.pat { + syn::Pat::Ident(ref pat_ident) => &pat_ident.ident, + _ => { + panic!("Invalid token for function argument"); + } + }; + let arg_name = arg_ident.to_string(); + + let context_type = definition.context.as_ref(); + + // Check for executor arguments. + if util::type_is_identifier_ref(&captured.ty, "Executor") { + resolve_parts.push(quote!(let #arg_ident = executor;)); + } + // Make sure executor is specified as a reference. + else if util::type_is_identifier(&captured.ty, "Executor") { + panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?"); + } + // Check for executor arg. + else if context_type + .clone() + .map(|ctx| util::type_is_ref_of(&captured.ty, ctx)) + .unwrap_or(false) + { + resolve_parts.push(quote!( let #arg_ident = executor.context(); )); + } + // Make sure the user does not specify the Context + // without a reference. (&Context) + else if context_type.clone().map(|ctx| ctx == &captured.ty).unwrap_or(false) { + panic!( + "Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?", + quote!(captured.ty), + ); + } + else { + let ty = &captured.ty; + // TODO: respect graphql attribute overwrite. + let final_name = util::to_camel_case(&arg_name); + resolve_parts.push(quote!( + let #arg_ident = args + .get::<#ty>(#final_name) + .expect(&format!("Internal error: missing argument {} - validation must have failed", #final_name)); + )); + args.push(util::GraphQLTypeDefinitionFieldArg { + description: attrs.argument(&arg_name).and_then(|arg| { + arg.description.as_ref().map(|d| d.value()) + }), + default: attrs + .argument(&arg_name) + .and_then(|arg| arg.default.clone()), + _type: ty.clone(), + name: final_name, + }) + } + } + _ => panic!("Invalid argument type in method {}", method.sig.ident), + } + } + + let body = &method.block; + let return_ty = &method.sig.decl.output; + let resolver_code = quote!( + (|| #return_ty { + #( #resolve_parts )* + #body + })() + ); + + let name = attrs + .name + .unwrap_or(util::to_camel_case(&method.sig.ident.to_string())); + + definition.fields.push(util::GraphQLTypeDefinitionField { + name, + _type, + args, + description: attrs.description, + deprecation: attrs.deprecation, + resolver_code, + }); + } + _ => { + panic!("Invalid item for GraphQL Object: only type declarations and methods are allowed"); + } + } + } + let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; + definition.into_tokens(juniper_crate_name).into() +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 7717d1e1..95c7826a 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -6,12 +6,15 @@ #![recursion_limit = "1024"] +extern crate self as juniper; + extern crate proc_macro; mod derive_enum; mod derive_input_object; mod derive_object; mod derive_scalar_value; +mod impl_object; mod util; use proc_macro::TokenStream; @@ -49,7 +52,7 @@ pub fn derive_input_object_internal(input: TokenStream) -> TokenStream { #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - let gen = derive_object::impl_object(&ast); + let gen = derive_object::build_derive_object(ast, false); gen.into() } @@ -73,3 +76,252 @@ pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream { let gen = derive_scalar_value::impl_scalar_value(&ast, true); gen.into() } + +/** +The `impl_object` proc macro is the primary way of defining GraphQL resolvers +that can not be implemented with the GraphQLObject derive. + +It enables you to write GraphQL field resolvers for a type by declaring a +regular Rust `impl` block. Under the hood, the procedural macro implements +the GraphQLType trait. + +`impl_object` comes with many features that allow customization of +your fields, all of which are detailed below. + +### Getting Started + +This simple example will show you the most basic use of `impl_object`. +More advanced use cases are introduced step by step. + +``` +// So we can declare it as a plain struct without any members. +struct Query; + +// We prefix the impl Block with the procedural macro. +#[juniper::impl_object] +impl Query { + + // A **warning**: only GraphQL fields can be specified in this impl block. + // If you want to define normal methods on the struct, + // you have to do so in a separate, normal `impl` block. + + + // This defines a simple, static field which does not require any context. + // You can return any value that implements the `GraphQLType` trait. + // This trait is implemented for: + // - basic scalar types like bool, &str, String, i32, f64 + // - GraphQL compatible wrappers like Option<_>, Vec<_>. + // - types which use the `#derive[juniper::GraphQLObject]` + // - `impl_object` structs. + // + // An important note regarding naming: + // By default, field names will be converted to camel case. + // For your GraphQL queries, the field will be available as `apiVersion`. + // + // You can also manually customize the field name if required. (See below) + fn api_version() -> &'static str { + "0.1" + } + + // This field takes two arguments. + // GraphQL arguments are just regular function parameters. + // **Note**: in Juniper, arguments are non-nullable by default. + // for optional arguments, you have to specify them with Option. + fn add(a: f64, b: f64, c: Option) -> f64 { + a + b + c.unwrap_or(0.0) + } +} +``` + +## Accessing self + +``` +struct Person { + first_name: String, + last_name: String, +} + +impl Person { + // The full name method is useful outside of GraphQL, + // so we define it as a normal method. + fn build_full_name(&self) -> String { + format!("{} {}", self.first_name, self.last_name) + } +} + +#[juniper::impl_object] +impl Person { + fn first_name(&self) -> &str { + &self.first_name + } + + fn last_name(&self) -> &str { + &self.last_name + } + + fn full_name(&self) -> String { + self.build_full_name() + } +} +``` + +## Context (+ Executor) + +You can specify a context that will be available across +all your resolvers during query execution. + +The Context can be injected into your resolvers by just +specifying an argument with the same type as the context +(but as a reference). + +``` + +# #[derive(juniper::GraphQLObject)] struct User { id: i32 } +# struct DbPool; +# impl DbPool { fn user(&self, id: i32) -> Option { unimplemented!() } } + +struct Context { + db: DbPool, +} + +// Mark our struct for juniper. +impl juniper::Context for Context {} + +struct Query; + +#[juniper::impl_object( + // Here we specify the context type for this object. + Context = Context, +)] +impl Query { + // Context is injected by specifying a argument + // as a reference to the Context. + fn user(context: &Context, id: i32) -> Option { + context.db.user(id) + } + + // You can also gain access to the executor, which + // allows you to do look aheads. + fn with_executor(executor: &Executor) -> bool { + let info = executor.look_ahead(); + // ... + true + } +} + +``` + +## Customization (Documentation, Renaming, ...) + +``` +struct InternalQuery; + +// Doc comments can be used to specify graphql documentation. +/// GRAPHQL DOCUMENTATION. +/// More info for GraphQL users.... +#[juniper::impl_object( + // You can rename the type for GraphQL by specifying the name here. + name = "Query", + // You can also specify a description here. + // If present, doc comments will be ignored. + description = "...", +)] +impl InternalQuery { + // Documentation doc comments also work on fields. + /// GraphQL description... + fn field_with_description() -> bool { true } + + // Fields can also be customized with the #[graphql] attribute. + #[graphql( + // overwrite the public name + name = "actualFieldName", + // Can be used instead of doc comments. + description = "field description", + )] + fn internal_name() -> bool { true } + + // Fields can be deprecated too. + #[graphql( + deprecated = "deprecatin info...", + // Note: just "deprecated," without a description works too. + )] + fn deprecated_field_simple() -> bool { true } + + + // Customizing field arguments is a little awkward right now. + // This will improve once [RFC 2564](https://github.com/rust-lang/rust/issues/60406) + // is implemented, which will allow attributes on function parameters. + + #[graphql( + arguments( + arg1( + // You can specify default values. + // A default can be any valid expression that yields the right type. + default = true, + description = "Argument description....", + ), + arg2( + default = false, + description = "arg2 description...", + ), + ), + )] + fn args(arg1: bool, arg2: bool) -> bool { + arg1 && arg2 + } +} +``` + +## Lifetimes, Generics and custom Scalars + +Lifetimes work just like you'd expect. + + +``` +struct WithLifetime<'a> { + value: &'a str, +} + +#[juniper::impl_object] +impl<'a> WithLifetime<'a> { + fn value(&self) -> &str { + self.value + } +} + +``` + +Juniper has support for custom scalars. +Mostly you will only need the default scalar type juniper::DefaultScalarValue. + +You can easily specify a custom scalar though. + + +``` + +# type MyCustomScalar = juniper::DefaultScalarValue; + +struct Query; + +#[juniper::impl_object( + Scalar = MyCustomScalar, +)] +impl Query { + // ... +} +``` + +*/ +#[proc_macro_attribute] +pub fn impl_object(args: TokenStream, input: TokenStream) -> TokenStream { + let gen = impl_object::build_impl_object(args, input, false); + gen.into() +} + +/// A proc macro for defining a GraphQL object. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn impl_object_internal(args: TokenStream, input: TokenStream) -> TokenStream { + let gen = impl_object::build_impl_object(args, input, true); + gen.into() +} diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index 808bdc27..930c0129 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -1,9 +1,44 @@ +use quote::quote; use regex::Regex; -use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta}; +use std::collections::HashMap; +use syn::{ + parse, parse_quote, punctuated::Punctuated, Attribute, Lit, Meta, MetaList, MetaNameValue, + NestedMeta, Token, +}; + +/// Compares a path to a one-segment string value, +/// return true if equal. +pub fn path_eq_single(path: &syn::Path, value: &str) -> bool { + path.segments.len() == 1 && path.segments[0].ident == value +} + +/// Check if a type is a reference to another type. +pub fn type_is_ref_of(ty: &syn::Type, target: &syn::Type) -> bool { + match ty { + syn::Type::Reference(_ref) => &*_ref.elem == target, + _ => false, + } +} + +/// Check if a Type is a simple identifier. +pub fn type_is_identifier(ty: &syn::Type, name: &str) -> bool { + match ty { + syn::Type::Path(ref type_path) => path_eq_single(&type_path.path, name), + _ => false, + } +} + +/// Check if a Type is a reference to a given identifier. +pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool { + match ty { + syn::Type::Reference(_ref) => type_is_identifier(&*_ref.elem, name), + _ => false, + } +} pub enum AttributeValidation { Any, - Bare, + // Bare, String, } @@ -12,10 +47,17 @@ pub enum AttributeValue { String(String), } +#[derive(Debug)] pub struct DeprecationAttr { pub reason: Option, } +pub fn find_graphql_attr(attrs: &Vec) -> Option<&Attribute> { + attrs + .iter() + .find(|attr| path_eq_single(&attr.path, "graphql")) +} + pub fn get_deprecated(attrs: &Vec) -> Option { for attr in attrs { match attr.interpret_meta() { @@ -60,35 +102,57 @@ pub fn get_doc_comment(attrs: &Vec) -> Option { // Concatenates doc strings into one string. fn join_doc_strings(docs: &Vec) -> String { - let s: String = docs - .iter() - // Trim any extra spaces. - .map(|x| x.trim().to_string()) - // Convert empty comments to newlines. - .map(|x| if x == "" { "\n".to_string() } else { x.clone() }) - .collect::>() - .join(" "); - // Clean up spacing on empty lines. - s.replace(" \n ", "\n") + // Note: this is guaranteed since this function is only called + // from get_doc_strings(). + debug_assert!(docs.len() > 0); + + let last_index = docs.len() - 1; + docs.iter() + .map(|s| s.as_str().trim_end()) + // Trim leading space. + .map(|s| { + if s.chars().next() == Some(' ') { + &s[1..] + } else { + s + } + }) + // Add newline, exept when string ends in a continuation backslash or is the last line. + .enumerate() + .fold(String::new(), |mut buffer, (index, s)| { + if index == last_index { + buffer.push_str(s); + } else if s.ends_with('\\') { + buffer.push_str(s.trim_end_matches('\\')); + buffer.push(' '); + } else { + buffer.push_str(s); + buffer.push('\n'); + } + buffer + }) } // Gets doc strings from doc comment attributes. fn get_doc_strings(items: &Vec) -> Option> { - let mut docs = Vec::new(); - for item in items { - if item.ident == "doc" { - match item.lit { - Lit::Str(ref strlit) => { - docs.push(strlit.value().to_string()); + let comments = items + .iter() + .filter_map(|item| { + if item.ident == "doc" { + match item.lit { + Lit::Str(ref strlit) => Some(strlit.value().to_string()), + _ => panic!("doc attributes only have string literal"), } - _ => panic!("doc attributes only have string literal"), + } else { + None } - } + }) + .collect::>(); + if comments.len() > 0 { + Some(comments) + } else { + None } - if !docs.is_empty() { - return Some(docs); - } - None } // Gets doc comment attributes. @@ -130,12 +194,12 @@ pub fn keyed_item_value( match &nameval.lit { // We have a string attribute value. &Lit::Str(ref strlit) => match validation { - AttributeValidation::Bare => { - panic!(format!( - "Invalid format for attribute \"{:?}\": expected a bare attribute without a value", - item - )); - } + // AttributeValidation::Bare => { + // panic!(format!( + // "Invalid format for attribute \"{:?}\": expected a bare attribute without a value", + // item + // )); + // } _ => Some(AttributeValue::String(strlit.value())), }, _ => None, @@ -212,6 +276,560 @@ pub fn is_valid_name(field_name: &str) -> bool { GRAPHQL_NAME_SPEC.is_match(field_name) } +#[derive(Default, Debug)] +pub struct ObjectAttributes { + pub name: Option, + pub description: Option, + pub context: Option, + pub scalar: Option, + pub interfaces: Vec, +} + +impl syn::parse::Parse for ObjectAttributes { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + let mut output = Self { + name: None, + description: None, + context: None, + scalar: None, + interfaces: Vec::new(), + }; + + // Skip potential parantheses which are present for regular attributes but not for proc macro + // attributes. + let inner = (|| { + let mut content; + syn::parenthesized!(content in input); + Ok(content) + })(); + let input = match inner.as_ref() { + Ok(content) => content, + Err(_) => input, + }; + + while !input.is_empty() { + let ident: syn::Ident = input.parse()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let val = input.parse::()?; + output.name = Some(val.value()); + } + "description" => { + input.parse::()?; + let val = input.parse::()?; + output.description = Some(val.value()); + } + "context" | "Context" => { + input.parse::()?; + // TODO: remove legacy support for string based Context. + let ctx = if let Ok(val) = input.parse::() { + eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated"); + eprintln!("Use a normal type instead - example: 'Context = MyContextType'"); + syn::parse_str::(&val.value())? + } else { + input.parse::()? + }; + output.context = Some(ctx); + } + "scalar" | "Scalar" => { + input.parse::()?; + let val = input.parse::()?; + output.scalar = Some(val); + } + "interfaces" => { + input.parse::()?; + let mut content; + syn::bracketed!(content in input); + output.interfaces = + syn::punctuated::Punctuated::::parse_terminated( + &content, + )? + .into_iter() + .collect(); + } + other => { + return Err(input.error(format!("Unknown attribute: {}", other))); + } + } + if input.lookahead1().peek(syn::Token![,]) { + input.parse::()?; + } + } + + Ok(output) + } +} + +impl ObjectAttributes { + pub fn from_attrs(attrs: &Vec) -> syn::parse::Result { + let attr_opt = find_graphql_attr(attrs); + if let Some(attr) = attr_opt { + // Need to unwrap outer (), which are not present for proc macro attributes, + // but are present for regular ones. + + let mut a = syn::parse::(attr.tts.clone().into())?; + if a.description.is_none() { + a.description = get_doc_comment(attrs); + } + Ok(a) + } else { + let mut a = Self::default(); + a.description = get_doc_comment(attrs); + Ok(a) + } + } +} + +#[derive(Debug)] +pub struct FieldAttributeArgument { + pub name: syn::Ident, + pub default: Option, + pub description: Option, +} + +impl parse::Parse for FieldAttributeArgument { + fn parse(input: parse::ParseStream) -> parse::Result { + let name = input.parse()?; + + let mut arg = Self { + name, + default: None, + description: None, + }; + + let mut content; + syn::parenthesized!(content in input); + + while !content.is_empty() { + let name = content.parse::()?; + content.parse::()?; + + match name.to_string().as_str() { + "description" => { + arg.description = Some(content.parse()?); + } + "default" => { + arg.default = Some(content.parse()?); + } + other => { + return Err(content.error(format!("Invalid attribute argument key {}", other))); + } + } + + // Discard trailing comma. + content.parse::().ok(); + } + + Ok(arg) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum FieldAttributeParseMode { + Object, + Impl, +} + +enum FieldAttribute { + Name(syn::LitStr), + Description(syn::LitStr), + Deprecation(Option), + Skip(syn::Ident), + Arguments(HashMap), +} + +impl parse::Parse for FieldAttribute { + fn parse(input: parse::ParseStream) -> parse::Result { + let ident = input.parse::()?; + + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let lit = input.parse::()?; + let raw = lit.value(); + if !is_valid_name(&raw) { + Err(input.error(format!( + "Invalid #[graphql(name = ...)] attribute: \n\ + '{}' is not a valid field name\nNames must \ + match /^[_a-zA-Z][_a-zA-Z0-9]*$/", + raw, + ))) + } else { + Ok(FieldAttribute::Name(lit)) + } + } + "description" => { + input.parse::()?; + Ok(FieldAttribute::Description(input.parse()?)) + } + "deprecated" | "deprecation" => { + let reason = if input.peek(Token![=]) { + input.parse::()?; + Some(input.parse()?) + } else { + None + }; + Ok(FieldAttribute::Deprecation(reason)) + } + "skip" => Ok(FieldAttribute::Skip(ident)), + "arguments" => { + let mut arg_content; + syn::parenthesized!(arg_content in input); + let args = Punctuated::::parse_terminated( + &arg_content, + )?; + let map = args + .into_iter() + .map(|arg| (arg.name.to_string(), arg)) + .collect(); + Ok(FieldAttribute::Arguments(map)) + } + other => Err(input.error(format!("Unknown attribute: {}", other))), + } + } +} + +#[derive(Default)] +pub struct FieldAttributes { + pub name: Option, + pub description: Option, + pub deprecation: Option, + // Only relevant for GraphQLObject derive. + pub skip: bool, + /// Only relevant for impl_object macro. + pub arguments: HashMap, +} + +impl parse::Parse for FieldAttributes { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + // Remove wrapping parantheses. + let mut content; + syn::parenthesized!(content in input); + + let items = Punctuated::::parse_terminated(&content)?; + + let mut output = Self { + name: None, + description: None, + deprecation: None, + skip: false, + arguments: Default::default(), + }; + + for item in items { + match item { + FieldAttribute::Name(name) => { + output.name = Some(name.value()); + } + FieldAttribute::Description(name) => { + output.description = Some(name.value()); + } + FieldAttribute::Deprecation(reason_opt) => { + output.deprecation = Some(DeprecationAttr { + reason: reason_opt.map(|val| val.value()), + }); + } + FieldAttribute::Skip(_) => { + output.skip = true; + } + FieldAttribute::Arguments(args) => { + output.arguments = args; + } + } + } + + if !content.is_empty() { + Err(content.error("Unexpected input")) + } else { + Ok(output) + } + } +} + +impl FieldAttributes { + pub fn from_attrs( + attrs: Vec, + _mode: FieldAttributeParseMode, + ) -> syn::parse::Result { + let doc_comment = get_doc_comment(&attrs); + + let attr_opt = attrs + .into_iter() + .find(|attr| path_eq_single(&attr.path, "graphql")); + + let mut output = match attr_opt { + Some(attr) => syn::parse(attr.tts.into())?, + None => Self::default(), + }; + if output.description.is_none() { + output.description = doc_comment; + } + Ok(output) + } + + pub fn argument(&self, name: &str) -> Option<&FieldAttributeArgument> { + self.arguments.get(name) + } +} + +#[derive(Debug)] +pub struct GraphQLTypeDefinitionFieldArg { + pub name: String, + pub description: Option, + pub default: Option, + pub _type: syn::Type, +} + +#[derive(Debug)] +pub struct GraphQLTypeDefinitionField { + pub name: String, + pub _type: syn::Type, + pub description: Option, + pub deprecation: Option, + pub args: Vec, + pub resolver_code: proc_macro2::TokenStream, +} + +/// Definition of a graphql type based on information extracted +/// by various macros. +/// The definition can be rendered to Rust code. +#[derive(Debug)] +pub struct GraphQLTypeDefiniton { + pub name: String, + pub _type: syn::Type, + pub context: Option, + pub scalar: Option, + pub description: Option, + pub fields: Vec, + pub generics: syn::Generics, + pub interfaces: Option>, + // Due to syn parsing differences, + // when parsing an impl the type generics are included in the type + // directly, but in syn::DeriveInput, the type generics are + // in the generics field. + // This flag signifies if the type generics need to be + // included manually. + pub include_type_generics: bool, + // This flag indicates if the generated code should always be + // generic over the ScalarValue. + // If false, the scalar is only generic if a generic parameter + // is specified manually. + pub generic_scalar: bool, +} + +impl GraphQLTypeDefiniton { + pub fn into_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream { + let juniper_crate_name = syn::parse_str::(juniper_crate_name).unwrap(); + + let name = &self.name; + let ty = &self._type; + let context = self + .context + .as_ref() + .map(|ctx| quote!( #ctx )) + .unwrap_or(quote!(())); + + let field_definitions = self.fields.iter().map(|field| { + let args = field.args.iter().map(|arg| { + let arg_type = &arg._type; + let arg_name = &arg.name; + + let description = match arg.description.as_ref() { + Some(value) => quote!( .description( #value ) ), + None => quote!(), + }; + + let code = match arg.default.as_ref() { + Some(value) => quote!( + .argument( + registry.arg_with_default::<#arg_type>(#arg_name, &#value, info) + #description + ) + ), + None => quote!( + .argument( + registry.arg::<#arg_type>(#arg_name, info) + #description + ) + ), + }; + code + }); + + let description = match field.description.as_ref() { + Some(description) => quote!( .description(#description) ), + None => quote!(), + }; + + let deprecation = match field.deprecation.as_ref() { + Some(deprecation) => { + if let Some(reason) = deprecation.reason.as_ref() { + quote!( .deprecated(Some(#reason)) ) + } else { + quote!( .deprecated(None) ) + } + } + None => quote!(), + }; + + let field_name = &field.name; + + let _type = &field._type; + quote! { + registry + .field_convert::<#_type, _, Self::Context>(#field_name, info) + #(#args)* + #description + #deprecation + } + }); + + let resolve_matches = self.fields.iter().map(|field| { + let name = &field.name; + let code = &field.resolver_code; + + quote!( + #name => { + let res = { #code }; + #juniper_crate_name::IntoResolvable::into( + res, + executor.context() + ) + .and_then(|res| { + match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(#juniper_crate_name::Value::null()), + } + }) + }, + ) + }); + + let description = self + .description + .as_ref() + .map(|description| quote!( .description(#description) )); + + let interfaces = self.interfaces.as_ref().map(|items| { + quote!( + .interfaces(&[ + #( registry.get_type::< #items >(&()) ,)* + ]) + ) + }); + + let scalar = self + .scalar + .as_ref() + .map(|s| quote!( #s )) + .unwrap_or_else(|| { + if self.generic_scalar { + // If generic_scalar is true, we always insert a generic scalar. + // See more comments below. + quote!(__S) + } else { + quote!(#juniper_crate_name::DefaultScalarValue) + } + }); + + // Preserve the original type_generics before modification, + // since alteration makes them invalid if self.generic_scalar + // is specified. + let (_, type_generics, _) = self.generics.split_for_impl(); + + let mut generics = self.generics.clone(); + + if self.scalar.is_some() { + // A custom scalar type was specified. + // Therefore, we always insert a where clause that marks the scalar as + // compatible with ScalarValueRef. + // This is done to prevent the user from having to specify this + // manually. + let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); + where_clause.predicates.push( + parse_quote!(for<'__b> &'__b #scalar: #juniper_crate_name::ScalarRefValue<'__b>), + ); + } else if self.generic_scalar { + // No custom scalar specified, but always generic specified. + // Therefore we inject the generic scalar. + + generics.params.push(parse_quote!(__S)); + + let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); + // Insert ScalarValue constraint. + where_clause + .predicates + .push(parse_quote!(__S: #juniper_crate_name::ScalarValue)); + // Insert a where clause that marks the scalar as + // compatible with ScalarValueRef. + // Same as in branch above. + where_clause.predicates.push( + parse_quote!(for<'__b> &'__b __S: #juniper_crate_name::ScalarRefValue<'__b>), + ); + } + + let type_generics_tokens = if self.include_type_generics { + Some(type_generics) + } else { + None + }; + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let output = quote!( + impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #juniper_crate_name::Registry<'r, #scalar> + ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> + where #scalar : 'r, + for<'z> &'z #scalar: #juniper_crate_name::ScalarRefValue<'z>, + { + let fields = vec![ + #( #field_definitions ),* + ]; + let meta = registry.build_object_type::<#ty>( info, &fields ) + #description + #interfaces; + meta.into_meta() + } + + #[allow(unused_variables)] + #[allow(unused_mut)] + fn resolve_field( + &self, + _info: &(), + field: &str, + args: &#juniper_crate_name::Arguments<#scalar>, + executor: &#juniper_crate_name::Executor, + ) -> #juniper_crate_name::ExecutionResult<#scalar> { + match field { + #( #resolve_matches )* + _ => { + panic!("Field {} not found on type {}", field, "Mutation"); + } + } + } + + fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { + #name.to_string() + } + + } + ); + output + } +} + #[cfg(test)] mod test { use super::*; @@ -296,25 +914,31 @@ mod test { #[test] fn test_multiple() { let result = join_doc_strings(&strs_to_strings(vec!["foo", "bar"])); - assert_eq!(&result, "foo bar"); + assert_eq!(&result, "foo\nbar"); } #[test] fn test_trims_spaces() { let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"])); - assert_eq!(&result, "foo bar baz"); + assert_eq!(&result, "foo\nbar\nbaz"); } #[test] fn test_empty() { let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"])); - assert_eq!(&result, "foo\nbar"); + assert_eq!(&result, "foo\n\nbar"); } #[test] fn test_newline_spaces() { let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"])); - assert_eq!(&result, "foo\nbar"); + assert_eq!(&result, "foo\n\nbar"); + } + + #[test] + fn test_continuation_backslash() { + let result = join_doc_strings(&strs_to_strings(vec!["foo\\", "x\\", "y", "bar"])); + assert_eq!(&result, "foo x y\nbar"); } } From a993c16b85c198261b70f761d8282fc652d52d16 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:54:16 +0200 Subject: [PATCH 02/15] Deprecate graphql_object! and replace with impl_object This commit deprecates the graphql_object macro and replaces all of it's uses with the new impl_object proc macro. (Except for the old macro tests). This commit also adds new integration tests for impl_object. --- juniper/CHANGELOG.md | 21 +- juniper/Cargo.toml | 1 - juniper/src/executor_tests/directives.rs | 9 +- juniper/src/executor_tests/enums.rs | 9 +- juniper/src/executor_tests/executor.rs | 229 ++++++++++----- .../src/executor_tests/interfaces_unions.rs | 74 +++-- .../src/executor_tests/introspection/enums.rs | 29 +- .../introspection/input_object.rs | 7 +- .../src/executor_tests/introspection/mod.rs | 26 +- juniper/src/executor_tests/variables.rs | 38 ++- juniper/src/integrations/chrono.rs | 18 +- juniper/src/macros/interface.rs | 17 +- juniper/src/macros/object.rs | 7 +- juniper/src/macros/tests/args.rs | 276 +++++++++++------- juniper/src/macros/tests/field.rs | 78 +++-- juniper/src/macros/tests/impl_object.rs | 269 +++++++++++++++++ juniper/src/macros/tests/interface.rs | 57 ++-- juniper/src/macros/tests/mod.rs | 2 + juniper/src/macros/tests/object.rs | 36 +-- juniper/src/macros/tests/scalar.rs | 21 +- juniper/src/macros/tests/union.rs | 46 ++- juniper/src/macros/tests/util.rs | 54 ++++ juniper/src/parser/tests/value.rs | 18 +- juniper/src/schema/schema.rs | 270 ++++++++++------- juniper/src/tests/schema.rs | 95 +++--- juniper/src/tests/schema_introspection.rs | 19 +- juniper/src/types/base.rs | 18 +- 27 files changed, 1218 insertions(+), 526 deletions(-) create mode 100644 juniper/src/macros/tests/impl_object.rs create mode 100644 juniper/src/macros/tests/util.rs diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index a4d62c3f..610e824a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,10 +1,27 @@ # master -- Refactored all crates to the 2018 edition. [#345](https://github.com/graphql-rust/juniper/pull/345) +### impl_object macro + +The `graphql_object!` macro is deprecated and will be removed in the future. +It is replaced by the new [impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) procedural macro. + +[#333](https://github.com/graphql-rust/juniper/pull/333) + +### 2018 Edition + +All crates were refactored to the Rust 2018 edition. + +This should not have any impact on your code, since juniper already was 2018 compatible. + +[#345](https://github.com/graphql-rust/juniper/pull/345) + +### Other changes + - The minimum required Rust version is now `1.31.0`. - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - Added built-in support for the canonical schema introspection query via - `juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307) + `juniper::introspect()`. + [#307](https://github.com/graphql-rust/juniper/issues/307) - Fix introspection query validity The DirectiveLocation::InlineFragment had an invalid literal value, which broke third party tools like apollo cli. diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index a9123338..51bc7ad8 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -23,7 +23,6 @@ harness = false path = "benches/bench.rs" [features] -nightly = [] expose-test-schema = [] default = [ "chrono", diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs index ed35e6bd..e5648f3e 100644 --- a/juniper/src/executor_tests/directives.rs +++ b/juniper/src/executor_tests/directives.rs @@ -5,15 +5,16 @@ use crate::value::{DefaultScalarValue, Object, Value}; struct TestType; -graphql_object!(TestType: () |&self| { - field a() -> &str { +#[crate::impl_object_internal] +impl TestType { + fn a() -> &str { "a" } - field b() -> &str { + fn b() -> &str { "b" } -}); +} fn run_variable_query(query: &str, vars: Variables, f: F) where diff --git a/juniper/src/executor_tests/enums.rs b/juniper/src/executor_tests/enums.rs index 62f23447..6738f531 100644 --- a/juniper/src/executor_tests/enums.rs +++ b/juniper/src/executor_tests/enums.rs @@ -17,15 +17,16 @@ enum Color { } struct TestType; -graphql_object!(TestType: () |&self| { - field to_string(color: Color) -> String { +#[crate::impl_object_internal] +impl TestType { + fn to_string(color: Color) -> String { format!("Color::{:?}", color) } - field a_color() -> Color { + fn a_color() -> Color { Color::Red } -}); +} fn run_variable_query(query: &str, vars: Variables, f: F) where diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index 6d3feb30..e8fd0870 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -7,34 +7,57 @@ mod field_execution { struct DataType; struct DeepDataType; - graphql_object!(DataType: () |&self| { - field a() -> &str { "Apple" } - field b() -> &str { "Banana" } - field c() -> &str { "Cookie" } - field d() -> &str { "Donut" } - field e() -> &str { "Egg" } - field f() -> &str { "Fish" } + #[crate::impl_object_internal] + impl DataType { + fn a() -> &str { + "Apple" + } + fn b() -> &str { + "Banana" + } + fn c() -> &str { + "Cookie" + } + fn d() -> &str { + "Donut" + } + fn e() -> &str { + "Egg" + } + fn f() -> &str { + "Fish" + } - field pic(size: Option) -> String { + fn pic(size: Option) -> String { format!("Pic of size: {}", size.unwrap_or(50)) } - field deep() -> DeepDataType { + fn deep() -> DeepDataType { DeepDataType } - }); + } - graphql_object!(DeepDataType: () |&self| { - field a() -> &str { "Already Been Done" } - field b() -> &str { "Boring" } - field c() -> Vec> { vec![Some("Contrived"), None, Some("Confusing")] } + #[crate::impl_object_internal] + impl DeepDataType { + fn a() -> &str { + "Already Been Done" + } + fn b() -> &str { + "Boring" + } + fn c() -> Vec> { + vec![Some("Contrived"), None, Some("Confusing")] + } - field deeper() -> Vec> { vec![Some(DataType), None, Some(DataType) ] } - }); + fn deeper() -> Vec> { + vec![Some(DataType), None, Some(DataType)] + } + } #[test] fn test() { - let schema = RootNode::new(DataType, EmptyMutation::<()>::new()); + let schema = + RootNode::<_, _, crate::DefaultScalarValue>::new(DataType, EmptyMutation::<()>::new()); let doc = r" query Example($size: Int) { a, @@ -139,12 +162,21 @@ mod merge_parallel_fragments { struct Type; - graphql_object!(Type: () |&self| { - field a() -> &str { "Apple" } - field b() -> &str { "Banana" } - field c() -> &str { "Cherry" } - field deep() -> Type { Type } - }); + #[crate::impl_object_internal] + impl Type { + fn a() -> &str { + "Apple" + } + fn b() -> &str { + "Banana" + } + fn c() -> &str { + "Cherry" + } + fn deep() -> Type { + Type + } + } #[test] fn test() { @@ -214,21 +246,43 @@ mod merge_parallel_inline_fragments { struct Type; struct Other; - graphql_object!(Type: () |&self| { - field a() -> &str { "Apple" } - field b() -> &str { "Banana" } - field c() -> &str { "Cherry" } - field deep() -> Type { Type } - field other() -> Vec { vec![Other, Other] } - }); + #[crate::impl_object_internal] + impl Type { + fn a() -> &str { + "Apple" + } + fn b() -> &str { + "Banana" + } + fn c() -> &str { + "Cherry" + } + fn deep() -> Type { + Type + } + fn other() -> Vec { + vec![Other, Other] + } + } - graphql_object!(Other: () |&self| { - field a() -> &str { "Apple" } - field b() -> &str { "Banana" } - field c() -> &str { "Cherry" } - field deep() -> Type { Type } - field other() -> Vec { vec![Other, Other] } - }); + #[crate::impl_object_internal] + impl Other { + fn a() -> &str { + "Apple" + } + fn b() -> &str { + "Banana" + } + fn c() -> &str { + "Cherry" + } + fn deep() -> Type { + Type + } + fn other() -> Vec { + vec![Other, Other] + } + } #[test] fn test() { @@ -342,9 +396,14 @@ mod threads_context_correctly { impl Context for TestContext {} - graphql_object!(Schema: TestContext |&self| { - field a(&executor) -> String { executor.context().value.clone() } - }); + #[crate::impl_object_internal( + Context = TestContext, + )] + impl Schema { + fn a(context: &TestContext) -> String { + context.value.clone() + } + } #[test] fn test() { @@ -403,36 +462,42 @@ mod dynamic_context_switching { struct ItemRef; - graphql_object!(Schema: OuterContext |&self| { - field item_opt(&executor, key: i32) -> Option<(&InnerContext, ItemRef)> { + #[crate::impl_object_internal(Context = OuterContext)] + impl Schema { + fn item_opt(context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> { executor.context().items.get(&key).map(|c| (c, ItemRef)) } - field item_res(&executor, key: i32) -> FieldResult<(&InnerContext, ItemRef)> { - let res = executor.context().items.get(&key) + fn item_res(context: &OuterContext, key: i32) -> FieldResult<(&InnerContext, ItemRef)> { + let res = context + .items + .get(&key) .ok_or(format!("Could not find key {}", key)) .map(|c| (c, ItemRef))?; Ok(res) } - field item_res_opt(&executor, key: i32) -> FieldResult> { + fn item_res_opt( + context: &OuterContext, + key: i32, + ) -> FieldResult> { if key > 100 { Err(format!("Key too large: {}", key))?; } - Ok(executor.context().items.get(&key) - .map(|c| (c, ItemRef))) + Ok(context.items.get(&key).map(|c| (c, ItemRef))) } - field item_always(&executor, key: i32) -> (&InnerContext, ItemRef) { - executor.context().items.get(&key) - .map(|c| (c, ItemRef)) - .unwrap() + fn item_always(context: &OuterContext, key: i32) -> (&InnerContext, ItemRef) { + context.items.get(&key).map(|c| (c, ItemRef)).unwrap() } - }); + } - graphql_object!(ItemRef: InnerContext |&self| { - field value(&executor) -> String { executor.context().value.clone() } - }); + #[crate::impl_object_internal(Context = InnerContext)] + impl ItemRef { + fn value(context: &InnerContext) -> String { + context.value.clone() + } + } #[test] fn test_opt() { @@ -736,19 +801,37 @@ mod propagates_errors_to_nullable_fields { } } - graphql_object!(Schema: () |&self| { - field inner() -> Inner { Inner } - field inners() -> Vec { (0..5).map(|_| Inner).collect() } - field nullable_inners() -> Vec> { (0..5).map(|_| Some(Inner)).collect() } - }); + #[crate::impl_object_internal] + impl Schema { + fn inner() -> Inner { + Inner + } + fn inners() -> Vec { + (0..5).map(|_| Inner).collect() + } + fn nullable_inners() -> Vec> { + (0..5).map(|_| Some(Inner)).collect() + } + } - graphql_object!(Inner: () |&self| { - field nullable_field() -> Option { Some(Inner) } - field non_nullable_field() -> Inner { Inner } - field nullable_error_field() -> FieldResult> { Err("Error for nullableErrorField")? } - field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? } - field custom_error_field() -> Result<&str, CustomError> { Err(CustomError::NotFound) } - }); + #[crate::impl_object_internal] + impl Inner { + fn nullable_field() -> Option { + Some(Inner) + } + fn non_nullable_field() -> Inner { + Inner + } + fn nullable_error_field() -> FieldResult> { + Err("Error for nullableErrorField")? + } + fn non_nullable_error_field() -> FieldResult<&str> { + Err("Error for nonNullableErrorField")? + } + fn custom_error_field() -> Result<&str, CustomError> { + Err(CustomError::NotFound) + } + } #[test] fn nullable_first_level() { @@ -985,13 +1068,17 @@ mod named_operations { struct Schema; - graphql_object!(Schema: () |&self| { - field a() -> &str { "b" } - }); + #[crate::impl_object_internal] + impl Schema { + fn a() -> &str { + "b" + } + } #[test] fn uses_inline_operation_if_no_name_provided() { - let schema = RootNode::new(Schema, EmptyMutation::<()>::new()); + let schema = + RootNode::<_, _, crate::DefaultScalarValue>::new(Schema, EmptyMutation::<()>::new()); let doc = r"{ a }"; let vars = vec![].into_iter().collect(); diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index a0ea1401..851d7197 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -37,12 +37,17 @@ mod interface { } } - graphql_object!(Dog: () |&self| { - field name() -> &str { &self.name } - field woofs() -> bool { self.woofs } - - interfaces: [&Pet] - }); + #[crate::impl_object_internal( + interfaces = [&Pet] + )] + impl Dog { + fn name(&self) -> &str { + &self.name + } + fn woofs(&self) -> bool { + self.woofs + } + } struct Cat { name: String, @@ -58,22 +63,28 @@ mod interface { } } - graphql_object!(Cat: () |&self| { - field name() -> &str { &self.name } - field meows() -> bool { self.meows } - - interfaces: [&Pet] - }); + #[crate::impl_object_internal( + interfaces = [&Pet] + )] + impl Cat { + fn name(&self) -> &str { + &self.name + } + fn meows(&self) -> bool { + self.meows + } + } struct Schema { pets: Vec>, } - graphql_object!(Schema: () |&self| { - field pets() -> Vec<&Pet> { + #[crate::impl_object_internal] + impl Schema { + fn pets(&self) -> Vec<&Pet> { self.pets.iter().map(|p| p.as_ref()).collect() } - }); + } #[test] fn test() { @@ -177,10 +188,15 @@ mod union { } } - graphql_object!(Dog: () |&self| { - field name() -> &str { &self.name } - field woofs() -> bool { self.woofs } - }); + #[crate::impl_object_internal] + impl Dog { + fn name(&self) -> &str { + &self.name + } + fn woofs(&self) -> bool { + self.woofs + } + } struct Cat { name: String, @@ -193,20 +209,26 @@ mod union { } } - graphql_object!(Cat: () |&self| { - field name() -> &str { &self.name } - field meows() -> bool { self.meows } - }); + #[crate::impl_object_internal] + impl Cat { + fn name(&self) -> &str { + &self.name + } + fn meows(&self) -> bool { + self.meows + } + } struct Schema { pets: Vec>, } - graphql_object!(Schema: () |&self| { - field pets() -> Vec<&Pet> { + #[crate::impl_object_internal] + impl Schema { + fn pets(&self) -> Vec<&Pet> { self.pets.iter().map(|p| p.as_ref()).collect() } - }); + } #[test] fn test() { diff --git a/juniper/src/executor_tests/introspection/enums.rs b/juniper/src/executor_tests/introspection/enums.rs index c80967e5..c9e88b6a 100644 --- a/juniper/src/executor_tests/introspection/enums.rs +++ b/juniper/src/executor_tests/introspection/enums.rs @@ -64,14 +64,27 @@ enum EnumDeprecation { struct Root; -graphql_object!(Root: () |&self| { - field default_name() -> DefaultName { DefaultName::Foo } - field named() -> Named { Named::Foo } - field no_trailing_comma() -> NoTrailingComma { NoTrailingComma::Foo } - field enum_description() -> EnumDescription { EnumDescription::Foo } - field enum_value_description() -> EnumValueDescription { EnumValueDescription::Foo } - field enum_deprecation() -> EnumDeprecation { EnumDeprecation::Foo } -}); +#[crate::impl_object_internal] +impl Root { + fn default_name() -> DefaultName { + DefaultName::Foo + } + fn named() -> Named { + Named::Foo + } + fn no_trailing_comma() -> NoTrailingComma { + NoTrailingComma::Foo + } + fn enum_description() -> EnumDescription { + EnumDescription::Foo + } + fn enum_value_description() -> EnumValueDescription { + EnumValueDescription::Foo + } + fn enum_deprecation() -> EnumDeprecation { + EnumDeprecation::Foo + } +} fn run_type_info_query(doc: &str, f: F) where diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index 68bc2ce9..388fa556 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -79,8 +79,9 @@ struct FieldWithDefaults { field_two: i32, } -graphql_object!(Root: () |&self| { - field test_field( +#[crate::impl_object_internal] +impl Root { + fn test_field( a1: DefaultName, a2: NoTrailingComma, a3: Derive, @@ -95,7 +96,7 @@ graphql_object!(Root: () |&self| { ) -> i32 { 0 } -}); +} fn run_type_info_query(doc: &str, f: F) where diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 3c2b3506..b93fccd2 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -51,22 +51,26 @@ graphql_interface!(Interface: () as "SampleInterface" |&self| { } }); -graphql_object!(Root: () |&self| { - description: "The root query object in the schema" - - interfaces: [Interface] - - field sample_enum() -> Sample { +/// The root query object in the schema +#[crate::impl_object_internal( + interfaces = [&Interface] + Scalar = crate::DefaultScalarValue, +)] +impl Root { + fn sample_enum() -> Sample { Sample::One } - field sample_scalar( - first: i32 as "The first number", - second = 123: i32 as "The second number" - ) -> Scalar as "A sample scalar field on the object" { + #[graphql(arguments( + first(description = "The first number",), + second(description = "The second number", default = 123,), + ))] + + /// A sample scalar field on the object + fn sample_scalar(first: i32, second: i32) -> Scalar { Scalar(first + second) } -}); +} #[test] fn test_execution() { diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index f8effac6..0bf99854 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -62,59 +62,67 @@ struct InputWithDefaults { a: i32, } -graphql_object!(TestType: () |&self| { - field field_with_object_input(input: Option) -> String { +#[crate::impl_object_internal] +impl TestType { + fn field_with_object_input(input: Option) -> String { format!("{:?}", input) } - field field_with_nullable_string_input(input: Option) -> String { + fn field_with_nullable_string_input(input: Option) -> String { format!("{:?}", input) } - field field_with_non_nullable_string_input(input: String) -> String { + fn field_with_non_nullable_string_input(input: String) -> String { format!("{:?}", input) } - field field_with_default_argument_value(input = ("Hello World".to_owned()): String) -> String { + #[graphql( + arguments( + input( + default = "Hello World".to_string(), + ) + ) + )] + fn field_with_default_argument_value(input: String) -> String { format!("{:?}", input) } - field field_with_nested_object_input(input: Option) -> String { + fn field_with_nested_object_input(input: Option) -> String { format!("{:?}", input) } - field list(input: Option>>) -> String { + fn list(input: Option>>) -> String { format!("{:?}", input) } - field nn_list(input: Vec>) -> String { + fn nn_list(input: Vec>) -> String { format!("{:?}", input) } - field list_nn(input: Option>) -> String { + fn list_nn(input: Option>) -> String { format!("{:?}", input) } - field nn_list_nn(input: Vec) -> String { + fn nn_list_nn(input: Vec) -> String { format!("{:?}", input) } - field example_input(arg: ExampleInputObject) -> String { + fn example_input(arg: ExampleInputObject) -> String { format!("a: {:?}, b: {:?}", arg.a, arg.b) } - field input_with_defaults(arg: InputWithDefaults) -> String { + fn input_with_defaults(arg: InputWithDefaults) -> String { format!("a: {:?}", arg.a) } - field integer_input(value: i32) -> String { + fn integer_input(value: i32) -> String { format!("value: {}", value) } - field float_input(value: f64) -> String { + fn float_input(value: f64) -> String { format!("value: {}", value) } -}); +} fn run_variable_query(query: &str, vars: Variables, f: F) where diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 40d64820..d52bc2dc 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -209,20 +209,22 @@ mod integration_test { #[test] fn test_serialization() { struct Root; - graphql_object!(Root: () |&self| { - field exampleNaiveDate() -> NaiveDate { + + #[crate::impl_object_internal] + impl Root { + fn exampleNaiveDate() -> NaiveDate { NaiveDate::from_ymd(2015, 3, 14) } - field exampleNaiveDateTime() -> NaiveDateTime { + fn exampleNaiveDateTime() -> NaiveDateTime { NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) } - field exampleDateTimeFixedOffset() -> DateTime { - DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() + fn exampleDateTimeFixedOffset() -> DateTime { + DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() } - field exampleDateTimeUtc() -> DateTime { - Utc.timestamp(61, 0) + fn exampleDateTimeUtc() -> DateTime { + Utc.timestamp(61, 0) } - }); + } let doc = r#" { diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs index 7de478fc..0dfeed42 100644 --- a/juniper/src/macros/interface.rs +++ b/juniper/src/macros/interface.rs @@ -61,13 +61,18 @@ impl Character for Droid { fn id(&self) -> &str { &self.id } } -juniper::graphql_object!(Human: Database as "Human" |&self| { - field id() -> &str { &self.id } -}); +#[juniper::impl_object(Context = Database)] +impl Human { + fn id(&self) -> &str { &self.id } +} -juniper::graphql_object!(Droid: Database as "Droid" |&self| { - field id() -> &str { &self.id } -}); +#[juniper::impl_object( + name = "Droid", + Context = Database, +)] +impl Droid { + fn id(&self) -> &str { &self.id } +} // You can introduce lifetimes or generic parameters by < > before the name. juniper::graphql_interface!(<'a> &'a Character: Database as "Character" |&self| { diff --git a/juniper/src/macros/object.rs b/juniper/src/macros/object.rs index 72207c8a..713f371f 100644 --- a/juniper/src/macros/object.rs +++ b/juniper/src/macros/object.rs @@ -1,6 +1,12 @@ /** +## DEPRECATION WARNING + +The `graphql_object!` macro is deprecated and will be removed soon. +Use the new[impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) macro instead. + Expose GraphQL objects + This is a short-hand macro that implements the `GraphQLType` trait for a given type. By using this macro instead of implementing it manually, you gain type safety and reduce repetitive declarations. @@ -308,7 +314,6 @@ arg_name: ArgType ``` [1]: struct.Executor.html - */ #[macro_export] macro_rules! graphql_object { diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 91818a35..9874249a 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -26,63 +26,111 @@ struct Point { x: i32, } -graphql_object!(Root: () |&self| { - field simple() -> i32 { 0 } - field exec_arg(&executor) -> i32 { 0 } - field exec_arg_and_more(&executor, arg: i32) -> i32 { 0 } +#[crate::impl_object_internal] +impl Root { + fn simple() -> i32 { + 0 + } + fn exec_arg(executor: &Executor) -> i32 { + 0 + } + fn exec_arg_and_more(executor: &Executor, arg: i32) -> i32 { + 0 + } - field single_arg(arg: i32) -> i32 { 0 } - field multi_args( - arg1: i32, - arg2: i32 - ) -> i32 { 0 } - field multi_args_trailing_comma( - arg1: i32, - arg2: i32, - ) -> i32 { 0 } + fn single_arg(arg: i32) -> i32 { + 0 + } - field single_arg_descr(arg: i32 as "The arg") -> i32 { 0 } - field multi_args_descr( - arg1: i32 as "The first arg", - arg2: i32 as "The second arg" - ) -> i32 { 0 } - field multi_args_descr_trailing_comma( - arg1: i32 as "The first arg", - arg2: i32 as "The second arg", - ) -> i32 { 0 } + fn multi_args(arg1: i32, arg2: i32) -> i32 { + 0 + } - field attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 } - field attr_arg_descr_collapse( - #[doc = "The arg"] - #[doc = "and more details"] - arg: i32, - ) -> i32 { 0 } + fn multi_args_trailing_comma(arg1: i32, arg2: i32) -> i32 { + 0 + } - field arg_with_default(arg = 123: i32) -> i32 { 0 } - field multi_args_with_default( - arg1 = 123: i32, - arg2 = 456: i32 - ) -> i32 { 0 } - field multi_args_with_default_trailing_comma( - arg1 = 123: i32, - arg2 = 456: i32, - ) -> i32 { 0 } + #[graphql(arguments(arg(description = "The arg")))] + fn single_arg_descr(arg: i32) -> i32 { + 0 + } - field arg_with_default_descr(arg = 123: i32 as "The arg") -> i32 { 0 } - field multi_args_with_default_descr( - arg1 = 123: i32 as "The first arg", - arg2 = 456: i32 as "The second arg" - ) -> i32 { 0 } - field multi_args_with_default_trailing_comma_descr( - arg1 = 123: i32 as "The first arg", - arg2 = 456: i32 as "The second arg", - ) -> i32 { 0 } + #[graphql(arguments( + arg1(description = "The first arg",), + arg2(description = "The second arg") + ))] + fn multi_args_descr(arg1: i32, arg2: i32) -> i32 { + 0 + } - field args_with_complex_default( - arg1 = ("test".to_owned()): String as "A string default argument", - arg2 = (Point { x: 1 }): Point as "An input object default argument", - ) -> i32 { 0 } -}); + #[graphql(arguments( + arg1(description = "The first arg",), + arg2(description = "The second arg") + ))] + fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 { + 0 + } + + // TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented + // fn attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 } + // fn attr_arg_descr_collapse( + // #[doc = "The arg"] + // #[doc = "and more details"] + // arg: i32, + // ) -> i32 { 0 } + + #[graphql(arguments(arg(default = 123,),))] + fn arg_with_default(arg: i32) -> i32 { + 0 + } + + #[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))] + fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 { + 0 + } + + #[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))] + fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 { + 0 + } + + #[graphql(arguments(arg(default = 123, description = "The arg")))] + fn arg_with_default_descr(arg: i32) -> i32 { + 0 + } + + #[graphql(arguments( + arg1(default = 123, description = "The first arg"), + arg2(default = 456, description = "The second arg") + ))] + fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 { + 0 + } + + #[graphql(arguments( + arg1(default = 123, description = "The first arg",), + arg2(default = 456, description = "The second arg",) + ))] + fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 { + 0 + } + + #[graphql( + arguments( + arg1( + default = "test".to_string(), + description = "A string default argument", + ), + arg2( + default = Point{ x: 1 }, + description = "An input object default argument", + ) + ), + )] + fn args_with_complex_default(arg1: String, arg2: Point) -> i32 { + 0 + } +} fn run_args_info_query(field_name: &str, f: F) where @@ -509,71 +557,73 @@ fn introspect_field_multi_args_descr_trailing_comma() { }); } -#[test] -fn introspect_field_attr_arg_descr() { - run_args_info_query("attrArgDescr", |args| { - assert_eq!(args.len(), 1); +// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented +// #[test] +// fn introspect_field_attr_arg_descr() { +// run_args_info_query("attrArgDescr", |args| { +// assert_eq!(args.len(), 1); - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }); -} +// assert!(args.contains(&Value::object( +// vec![ +// ("name", Value::scalar("arg")), +// ("description", Value::scalar("The arg")), +// ("defaultValue", Value::null()), +// ( +// "type", +// Value::object( +// vec![ +// ("name", Value::null()), +// ( +// "ofType", +// Value::object( +// vec![("name", Value::scalar("Int"))].into_iter().collect(), +// ), +// ), +// ] +// .into_iter() +// .collect(), +// ), +// ), +// ] +// .into_iter() +// .collect(), +// ))); +// }); +// } -#[test] -fn introspect_field_attr_arg_descr_collapse() { - run_args_info_query("attrArgDescrCollapse", |args| { - assert_eq!(args.len(), 1); +// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented +// #[test] +// fn introspect_field_attr_arg_descr_collapse() { +// run_args_info_query("attrArgDescrCollapse", |args| { +// assert_eq!(args.len(), 1); - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg\nand more details")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }); -} +// assert!(args.contains(&Value::object( +// vec![ +// ("name", Value::scalar("arg")), +// ("description", Value::scalar("The arg\nand more details")), +// ("defaultValue", Value::null()), +// ( +// "type", +// Value::object( +// vec![ +// ("name", Value::null()), +// ( +// "ofType", +// Value::object( +// vec![("name", Value::scalar("Int"))].into_iter().collect(), +// ), +// ), +// ] +// .into_iter() +// .collect(), +// ), +// ), +// ] +// .into_iter() +// .collect(), +// ))); +// }); +// } #[test] fn introspect_field_arg_with_default() { diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index 9002769c..eea4c110 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -20,49 +20,77 @@ Syntax to validate: */ -graphql_object!(Root: () |&self| { - field simple() -> i32 { 0 } - - field description() -> i32 as "Field description" { 0 } - - field deprecated "Deprecation reason" - deprecated() -> i32 { 0 } - - field deprecated "Deprecation reason" - deprecated_descr() -> i32 as "Field description" { 0 } +#[crate::impl_object_internal( + interfaces = [&Interface], +)] +impl Root { + fn simple() -> i32 { + 0 + } /// Field description - field attr_description() -> i32 { 0 } + fn description() -> i32 { + 0 + } + + #[graphql(deprecated = "Deprecation reason")] + fn deprecated() -> i32 { + 0 + } + + #[graphql(deprecated = "Deprecation reason", description = "Field description")] + fn deprecated_descr() -> i32 { + 0 + } + + /// Field description + fn attr_description() -> i32 { + 0 + } /// Field description /// with `collapse_docs` behavior - field attr_description_collapse() -> i32 { 0 } + fn attr_description_collapse() -> i32 { + 0 + } /// Get the i32 representation of 0. /// /// - This comment is longer. /// - These two lines are rendered as bullets by GraphiQL. /// - subsection - field attr_description_long() -> i32 { 0 } + fn attr_description_long() -> i32 { + 0 + } - #[deprecated] - field attr_deprecated() -> i32 { 0 } + #[graphql(deprecated)] + fn attr_deprecated() -> i32 { + 0 + } - #[deprecated(note = "Deprecation reason")] - field attr_deprecated_reason() -> i32 { 0 } + #[graphql(deprecated = "Deprecation reason")] + fn attr_deprecated_reason() -> i32 { + 0 + } /// Field description - #[deprecated(note = "Deprecation reason")] - field attr_deprecated_descr() -> i32 { 0 } + #[graphql(deprecated = "Deprecation reason")] + fn attr_deprecated_descr() -> i32 { + 0 + } - field with_field_result() -> FieldResult { Ok(0) } + fn with_field_result() -> FieldResult { + Ok(0) + } - field with_return() -> i32 { return 0; } + fn with_return() -> i32 { + return 0; + } - field with_return_field_result() -> FieldResult { return Ok(0); } - - interfaces: [Interface] -}); + fn with_return_field_result() -> FieldResult { + return Ok(0); + } +} graphql_interface!(Interface: () |&self| { field simple() -> i32 { 0 } diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs new file mode 100644 index 00000000..72ae6ef5 --- /dev/null +++ b/juniper/src/macros/tests/impl_object.rs @@ -0,0 +1,269 @@ +use super::util; +use crate::{graphql_value, EmptyMutation, RootNode}; + +#[derive(Default)] +struct Context { + flag1: bool, +} + +impl crate::Context for Context {} + +struct WithLifetime<'a> { + value: &'a str, +} + +#[crate::impl_object_internal(Context=Context)] +impl<'a> WithLifetime<'a> { + fn value(&'a self) -> &'a str { + self.value + } +} + +struct WithContext; + +#[crate::impl_object_internal(Context=Context)] +impl WithContext { + fn ctx(ctx: &Context) -> bool { + ctx.flag1 + } +} + +#[derive(Default)] +struct Query { + b: bool, +} + +#[crate::impl_object_internal( + scalar = crate::DefaultScalarValue, + name = "Query", + context = Context, +)] +/// Query Description. +impl<'a> Query { + #[graphql(description = "With Self Description")] + fn with_self(&self) -> bool { + self.b + } + + fn independent() -> i32 { + 100 + } + + fn with_executor(_exec: &Executor) -> bool { + true + } + + fn with_executor_and_self(&self, _exec: &Executor) -> bool { + true + } + + fn with_context(_context: &Context) -> bool { + true + } + + fn with_context_and_self(&self, _context: &Context) -> bool { + true + } + + #[graphql(name = "renamed")] + fn has_custom_name() -> bool { + true + } + + #[graphql(description = "attr")] + fn has_description_attr() -> bool { + true + } + + /// Doc description + fn has_description_doc_comment() -> bool { + true + } + + fn has_argument(arg1: bool) -> bool { + arg1 + } + + #[graphql(arguments(default_arg(default = true)))] + fn default_argument(default_arg: bool) -> bool { + default_arg + } + + #[graphql(arguments(arg(description = "my argument description")))] + fn arg_with_description(arg: bool) -> bool { + arg + } + + fn with_context_child(&self) -> WithContext { + WithContext + } + + fn with_lifetime_child(&self) -> WithLifetime<'a> { + WithLifetime { value: "blub" } + } +} + +#[derive(Default)] +struct Mutation; + +#[crate::impl_object_internal(context = Context)] +impl Mutation { + fn empty() -> bool { + true + } +} + +#[test] +fn impl_object_introspect() { + let res = util::run_info_query::("Query"); + assert_eq!( + res, + crate::graphql_value!({ + "name": "Query", + "description": "Query Description.", + "fields": [ + { + "name": "withSelf", + "description": "With Self Description", + "args": [], + }, + { + "name": "independent", + "description": None, + "args": [], + }, + { + "name": "withExecutor", + "description": None, + "args": [], + }, + { + "name": "withExecutorAndSelf", + "description": None, + "args": [], + }, + { + "name": "withContext", + "description": None, + "args": [], + }, + { + "name": "withContextAndSelf", + "description": None, + "args": [], + }, + { + "name": "renamed", + "description": None, + "args": [], + }, + { + "name": "hasDescriptionAttr", + "description": "attr", + "args": [], + }, + { + "name": "hasDescriptionDocComment", + "description": "Doc description", + "args": [], + }, + { + "name": "hasArgument", + "description": None, + "args": [ + { + "name": "arg1", + "description": None, + "type": { + "name": None, + }, + } + ], + }, + { + "name": "defaultArgument", + "description": None, + "args": [ + { + "name": "defaultArg", + "description": None, + "type": { + "name": "Boolean", + }, + } + ], + }, + { + "name": "argWithDescription", + "description": None, + "args": [ + { + "name": "arg", + "description": "my argument description", + "type": { + "name": None + }, + } + ], + }, + { + "name": "withContextChild", + "description": None, + "args": [], + }, + { + "name": "withLifetimeChild", + "description": None, + "args": [], + }, + ] + }) + ); +} + +#[test] +fn impl_object_query() { + let doc = r#" + query { + withSelf + independent + withExecutor + withExecutorAndSelf + withContext + withContextAndSelf + renamed + hasArgument(arg1: true) + defaultArgument + argWithDescription(arg: true) + withContextChild { + ctx + } + withLifetimeChild { + value + } + } + "#; + let schema = RootNode::new(Query { b: true }, EmptyMutation::::new()); + let vars = std::collections::HashMap::new(); + + let (result, errs) = crate::execute(doc, None, &schema, &vars, &Context { flag1: true }) + .expect("Execution failed"); + assert_eq!(errs, []); + assert_eq!( + result, + graphql_value!({ + "withSelf": true, + "independent": 100, + "withExecutor": true, + "withExecutorAndSelf": true, + "withContext": true, + "withContextAndSelf": true, + "renamed": true, + "hasArgument": true, + "defaultArgument": true, + "argWithDescription": true, + "withContextChild": { "ctx": true }, + "withLifetimeChild": { "value": "blub" }, + }) + ); +} diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index b2b3148f..c09a09f5 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -42,9 +42,12 @@ struct ResolversWithTrailingComma; struct Root; -graphql_object!(Concrete: () |&self| { - field simple() -> i32 { 0 } -}); +#[crate::impl_object_internal] +impl Concrete { + fn simple() -> i32 { + 0 + } +} graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| { field simple() -> i32 { 0 } @@ -108,24 +111,40 @@ graphql_interface!(ResolversWithTrailingComma: () |&self| { field simple() -> i32 { 0 } }); -graphql_object!(<'a> Root: () as "Root" |&self| { - field custom_name() -> CustomName { CustomName {} } - - field with_lifetime() -> WithLifetime<'a> { WithLifetime { data: PhantomData } } - field with_generics() -> WithGenerics { WithGenerics { data: 123 } } - - field description_first() -> DescriptionFirst { DescriptionFirst {} } - field fields_first() -> FieldsFirst { FieldsFirst {} } - field interfaces_first() -> InterfacesFirst { InterfacesFirst {} } - - field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} } - field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} } - - field resolvers_with_trailing_comma() -> ResolversWithTrailingComma { - ResolversWithTrailingComma {} +#[crate::impl_object_internal] +impl<'a> Root { + fn custom_name() -> CustomName { + CustomName {} } -}); + fn with_lifetime() -> WithLifetime<'a> { + WithLifetime { data: PhantomData } + } + fn with_generics() -> WithGenerics { + WithGenerics { data: 123 } + } + + fn description_first() -> DescriptionFirst { + DescriptionFirst {} + } + fn fields_first() -> FieldsFirst { + FieldsFirst {} + } + fn interfaces_first() -> InterfacesFirst { + InterfacesFirst {} + } + + fn commas_with_trailing() -> CommasWithTrailing { + CommasWithTrailing {} + } + fn commas_on_meta() -> CommasOnMeta { + CommasOnMeta {} + } + + fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma { + ResolversWithTrailingComma {} + } +} fn run_type_info_query(type_name: &str, f: F) where diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index 82f642e8..c630db6f 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,6 +1,8 @@ mod args; mod field; +mod impl_object; mod interface; mod object; mod scalar; mod union; +mod util; diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs index fcc6a527..2fb14524 100644 --- a/juniper/src/macros/tests/object.rs +++ b/juniper/src/macros/tests/object.rs @@ -18,41 +18,29 @@ Syntax to validate: */ -struct Interface; - struct CustomName; +graphql_object!(CustomName: () as "ACustomNamedType" |&self| { + field simple() -> i32 { 0 } +}); #[allow(dead_code)] struct WithLifetime<'a> { data: PhantomData<&'a i32>, } +graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { + field simple() -> i32 { 0 } +}); #[allow(dead_code)] struct WithGenerics { data: T, } - -struct DescriptionFirst; -struct FieldsFirst; -struct InterfacesFirst; - -struct CommasWithTrailing; -struct CommasOnMeta; - -struct Root; - -graphql_object!(CustomName: () as "ACustomNamedType" |&self| { - field simple() -> i32 { 0 } -}); - -graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { - field simple() -> i32 { 0 } -}); - graphql_object!( WithGenerics: () as "WithGenerics" |&self| { field simple() -> i32 { 0 } }); +struct Interface; +struct DescriptionFirst; graphql_interface!(Interface: () |&self| { field simple() -> i32 { 0 } @@ -60,7 +48,6 @@ graphql_interface!(Interface: () |&self| { DescriptionFirst => Some(DescriptionFirst {}), } }); - graphql_object!(DescriptionFirst: () |&self| { description: "A description" @@ -69,6 +56,7 @@ graphql_object!(DescriptionFirst: () |&self| { interfaces: [Interface] }); +struct FieldsFirst; graphql_object!(FieldsFirst: () |&self| { field simple() -> i32 { 0 } @@ -77,6 +65,7 @@ graphql_object!(FieldsFirst: () |&self| { interfaces: [Interface] }); +struct InterfacesFirst; graphql_object!(InterfacesFirst: ()|&self| { interfaces: [Interface] @@ -85,6 +74,7 @@ graphql_object!(InterfacesFirst: ()|&self| { description: "A description" }); +struct CommasWithTrailing; graphql_object!(CommasWithTrailing: () |&self| { interfaces: [Interface], @@ -93,6 +83,8 @@ graphql_object!(CommasWithTrailing: () |&self| { description: "A description", }); +struct CommasOnMeta; + graphql_object!(CommasOnMeta: () |&self| { interfaces: [Interface], description: "A description", @@ -100,6 +92,8 @@ graphql_object!(CommasOnMeta: () |&self| { field simple() -> i32 { 0 } }); +struct Root; + struct InnerContext; impl Context for InnerContext {} diff --git a/juniper/src/macros/tests/scalar.rs b/juniper/src/macros/tests/scalar.rs index a823f992..48a9af2e 100644 --- a/juniper/src/macros/tests/scalar.rs +++ b/juniper/src/macros/tests/scalar.rs @@ -78,12 +78,21 @@ graphql_scalar!(ScalarDescription { } }); -graphql_object!(Root: () |&self| { - field default_name() -> DefaultName { DefaultName(0) } - field other_order() -> OtherOrder { OtherOrder(0) } - field named() -> Named { Named(0) } - field scalar_description() -> ScalarDescription { ScalarDescription(0) } -}); +#[crate::impl_object_internal] +impl Root { + fn default_name() -> DefaultName { + DefaultName(0) + } + fn other_order() -> OtherOrder { + OtherOrder(0) + } + fn named() -> Named { + Named(0) + } + fn scalar_description() -> ScalarDescription { + ScalarDescription(0) + } +} fn run_type_info_query(doc: &str, f: F) where diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index da8e9086..24b3a692 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -46,23 +46,26 @@ enum ResolversWithTrailingComma { struct Root; -graphql_object!(Concrete: () |&self| { - field simple() -> i32 { 123 } -}); +#[crate::impl_object_internal] +impl Concrete { + fn simple() -> i32 { + 123 + } +} -graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| { +graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| { instance_resolvers: |&_| { &Concrete => match *self { CustomName::Concrete(ref c) => Some(c) } } }); -graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { +graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { instance_resolvers: |&_| { Concrete => match *self { WithLifetime::Int(_) => Some(Concrete) } } }); -graphql_union!( WithGenerics: () as "WithGenerics" |&self| { +graphql_union!( WithGenerics: () as "WithGenerics" |&self| { instance_resolvers: |&_| { Concrete => match *self { WithGenerics::Generic(_) => Some(Concrete) } } @@ -96,17 +99,30 @@ graphql_union!(ResolversWithTrailingComma: () |&self| { description: "A description" }); -graphql_object!(<'a> Root: () as "Root" |&self| { - field custom_name() -> CustomName { CustomName::Concrete(Concrete) } - field with_lifetime() -> WithLifetime<'a> { WithLifetime::Int(PhantomData) } - field with_generics() -> WithGenerics { WithGenerics::Generic(123) } - field description_first() -> DescriptionFirst { DescriptionFirst::Concrete(Concrete) } - field resolvers_first() -> ResolversFirst { ResolversFirst::Concrete(Concrete) } - field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing::Concrete(Concrete) } - field resolvers_with_trailing_comma() -> ResolversWithTrailingComma { +#[crate::impl_object_internal] +impl<'a> Root { + fn custom_name() -> CustomName { + CustomName::Concrete(Concrete) + } + fn with_lifetime() -> WithLifetime<'a> { + WithLifetime::Int(PhantomData) + } + fn with_generics() -> WithGenerics { + WithGenerics::Generic(123) + } + fn description_first() -> DescriptionFirst { + DescriptionFirst::Concrete(Concrete) + } + fn resolvers_first() -> ResolversFirst { + ResolversFirst::Concrete(Concrete) + } + fn commas_with_trailing() -> CommasWithTrailing { + CommasWithTrailing::Concrete(Concrete) + } + fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma { ResolversWithTrailingComma::Concrete(Concrete) } -}); +} fn run_type_info_query(type_name: &str, f: F) where diff --git a/juniper/src/macros/tests/util.rs b/juniper/src/macros/tests/util.rs new file mode 100644 index 00000000..db08950f --- /dev/null +++ b/juniper/src/macros/tests/util.rs @@ -0,0 +1,54 @@ +use crate::{DefaultScalarValue, GraphQLType, RootNode, Value, Variables}; +use std::default::Default; + +pub fn run_query(query: &str) -> Value +where + Query: GraphQLType + Default, + Mutation: GraphQLType + Default, + Context: Default, +{ + let schema = RootNode::new(Query::default(), Mutation::default()); + let (result, errs) = + crate::execute(query, None, &schema, &Variables::new(), &Context::default()) + .expect("Execution failed"); + + assert_eq!(errs, []); + result +} + +pub fn run_info_query(type_name: &str) -> Value +where + Query: GraphQLType + Default, + Mutation: GraphQLType + Default, + Context: Default, +{ + let query = format!( + r#" + {{ + __type(name: "{}") {{ + name, + description, + fields {{ + name + description + args {{ + name + description + type {{ + name + }} + }} + }} + }} + }} + "#, + type_name + ); + let result = run_query::(&query); + result + .as_object_value() + .expect("Result is not an object") + .get_field_value("__type") + .expect("__type field missing") + .clone() +} diff --git a/juniper/src/parser/tests/value.rs b/juniper/src/parser/tests/value.rs index 3915e522..8606b800 100644 --- a/juniper/src/parser/tests/value.rs +++ b/juniper/src/parser/tests/value.rs @@ -31,27 +31,31 @@ struct Foo { struct Query; -graphql_object!(Query: () where Scalar = |&self| { - field int_field() -> i32 { +#[crate::impl_object_internal(Scalar = S)] +impl<'a, S> Query +where + S: crate::ScalarValue + 'a, +{ + fn int_field() -> i32 { 42 } - field float_field() -> f64 { + fn float_field() -> f64 { 3.14 } - field string_field() -> String { + fn string_field() -> String { "".into() } - field bool_field() -> bool { + fn bool_field() -> bool { true } - field enum_field(_foo: Foo) -> Enum { + fn enum_field(_foo: Foo) -> Enum { Enum::EnumValue } -}); +} fn scalar_meta(name: &'static str) -> MetaType where diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index a7073938..deaa006b 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -73,52 +73,68 @@ where } } -graphql_object!(<'a> SchemaType<'a, S>: SchemaType<'a, S> as "__Schema" - where Scalar = |&self| +#[crate::impl_object_internal( + name = "__Schema" + Context = SchemaType<'a, S>, + Scalar = S, +)] +impl<'a, S> SchemaType<'a, S> +where + S: crate::ScalarValue + 'a, { - field types() -> Vec> { + fn types(&self) -> Vec> { self.type_list() .into_iter() - .filter(|t| t.to_concrete().map(|t| t.name() != Some("_EmptyMutation")).unwrap_or(false)) - .collect() + .filter(|t| { + t.to_concrete() + .map(|t| t.name() != Some("_EmptyMutation")) + .unwrap_or(false) + }) + .collect::>() } - field query_type() -> TypeType { + fn query_type(&self) -> TypeType { self.query_type() } - field mutation_type() -> Option> { + fn mutation_type(&self) -> Option> { self.mutation_type() } // Included for compatibility with the introspection query in GraphQL.js - field subscription_type() -> Option> { + fn subscription_type(&self) -> Option> { None } - field directives() -> Vec<&DirectiveType> { + fn directives(&self) -> Vec<&DirectiveType> { self.directive_list() } -}); +} -graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type" - where Scalar = |&self| +#[crate::impl_object_internal( + name = "__Type" + Context = SchemaType<'a, S>, + Scalar = S, +)] +impl<'a, S> TypeType<'a, S> +where + S: crate::ScalarValue + 'a, { - field name() -> Option<&str> { + fn name(&self) -> Option<&str> { match *self { TypeType::Concrete(t) => t.name(), _ => None, } } - field description() -> Option<&String> { + fn description(&self) -> Option<&String> { match *self { TypeType::Concrete(t) => t.description(), _ => None, } } - field kind() -> TypeKind { + fn kind(&self) -> TypeKind { match *self { TypeType::Concrete(t) => t.type_kind(), TypeType::List(_) => TypeKind::List, @@ -126,190 +142,242 @@ graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type" } } - field fields(include_deprecated = false: bool) -> Option>> { + #[graphql(arguments(include_deprecated(default = false)))] + fn fields(&self, include_deprecated: bool) -> Option>> { match *self { - TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | - TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => - Some(fields - .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) - .filter(|f| !f.name.starts_with("__")) - .collect()), + TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) + | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => { + Some( + fields + .iter() + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .filter(|f| !f.name.starts_with("__")) + .collect(), + ) + } _ => None, } } - field of_type() -> Option<&Box>> { + fn of_type(&self) -> Option<&Box>> { match *self { TypeType::Concrete(_) => None, TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l), } } - field input_fields() -> Option<&Vec>> { + fn input_fields(&self) -> Option<&Vec>> { match *self { - TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, .. })) => - Some(input_fields), + TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { + ref input_fields, + .. + })) => Some(input_fields), _ => None, } } - field interfaces(&executor) -> Option>> { + fn interfaces(&self, schema: &SchemaType<'a, S>) -> Option>> { match *self { - TypeType::Concrete(&MetaType::Object(ObjectMeta { ref interface_names, .. })) => { - let schema = executor.context(); - Some(interface_names + TypeType::Concrete(&MetaType::Object(ObjectMeta { + ref interface_names, + .. + })) => Some( + interface_names .iter() .filter_map(|n| schema.type_by_name(n)) - .collect()) - } + .collect(), + ), _ => None, } } - field possible_types(&executor) -> Option>> { - let schema = executor.context(); + fn possible_types(&self, schema: &SchemaType<'a, S>) -> Option>> { match *self { - TypeType::Concrete(&MetaType::Union(UnionMeta { ref of_type_names, .. })) => { - Some(of_type_names + TypeType::Concrete(&MetaType::Union(UnionMeta { + ref of_type_names, .. + })) => Some( + of_type_names .iter() .filter_map(|tn| schema.type_by_name(tn)) - .collect()) - } - TypeType::Concrete(&MetaType::Interface(InterfaceMeta{name: ref iface_name, .. })) => { - Some(schema.concrete_type_list() + .collect(), + ), + TypeType::Concrete(&MetaType::Interface(InterfaceMeta { + name: ref iface_name, + .. + })) => Some( + schema + .concrete_type_list() .iter() - .filter_map(|&ct| - if let MetaType::Object(ObjectMeta{ + .filter_map(|&ct| { + if let MetaType::Object(ObjectMeta { ref name, ref interface_names, .. - }) = *ct { + }) = *ct + { if interface_names.contains(&iface_name.to_string()) { schema.type_by_name(name) - } else { None } - } else { None } - ) - .collect()) + } else { + None + } + } else { + None + } + }) + .collect(), + ), + _ => None, + } + } + + #[graphql(arguments(include_deprecated(default = false)))] + fn enum_values(&self, include_deprecated: bool) -> Option> { + match *self { + TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => { + Some( + values + .iter() + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .collect(), + ) } _ => None, } } +} - field enum_values(include_deprecated = false: bool) -> Option> { - match *self { - TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => - Some(values - .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) - .collect()), - _ => None, - } - } -}); - -graphql_object!(<'a> Field<'a, S>: SchemaType<'a, S> as "__Field" - where Scalar = |&self| +#[crate::impl_object_internal( + name = "__Field", + Context = SchemaType<'a, S>, + Scalar = S, +)] +impl<'a, S> Field<'a, S> +where + S: crate::ScalarValue + 'a, { - field name() -> &String { + fn name(&self) -> &String { &self.name } - field description() -> &Option { + fn description(&self) -> &Option { &self.description } - field args() -> Vec<&Argument> { - self.arguments.as_ref().map_or_else(Vec::new, |v| v.iter().collect()) + fn args(&self) -> Vec<&Argument> { + self.arguments + .as_ref() + .map_or_else(Vec::new, |v| v.iter().collect()) } - field type(&executor) -> TypeType { - executor.context().make_type(&self.field_type) + #[graphql(name = "type")] + fn _type(&self, context: &SchemaType<'a, S>) -> TypeType { + context.make_type(&self.field_type) } - field is_deprecated() -> bool { + fn is_deprecated(&self) -> bool { self.deprecation_status.is_deprecated() } - field deprecation_reason() -> Option<&String> { + fn deprecation_reason(&self) -> Option<&String> { self.deprecation_status.reason() } -}); +} -graphql_object!(<'a> Argument<'a, S>: SchemaType<'a, S> as "__InputValue" - where Scalar = |&self| +#[crate::impl_object_internal( + name = "__InputValue", + Context = SchemaType<'a, S>, + Scalar = S, +)] +impl<'a, S> Argument<'a, S> +where + S: crate::ScalarValue + 'a, { - field name() -> &String { + fn name(&self) -> &String { &self.name } - field description() -> &Option { + fn description(&self) -> &Option { &self.description } - field type(&executor) -> TypeType { - executor.context().make_type(&self.arg_type) + #[graphql(name = "type")] + fn _type(&self, context: &SchemaType<'a, S>) -> TypeType { + context.make_type(&self.arg_type) } - field default_value() -> Option { + fn default_value(&self) -> Option { self.default_value.as_ref().map(|v| format!("{}", v)) } -}); +} -graphql_object!(EnumValue: () as "__EnumValue" where Scalar = |&self| { - field name() -> &String { +#[crate::impl_object_internal( + name = "__EnumValue", + Scalar = S, +)] +impl<'a, S> EnumValue +where + S: crate::ScalarValue + 'a, +{ + fn name(&self) -> &String { &self.name } - field description() -> &Option { + fn description(&self) -> &Option { &self.description } - field is_deprecated() -> bool { + fn is_deprecated(&self) -> bool { self.deprecation_status.is_deprecated() } - field deprecation_reason() -> Option<&String> { + fn deprecation_reason(&self) -> Option<&String> { self.deprecation_status.reason() } -}); +} -graphql_object!(<'a> DirectiveType<'a, S>: SchemaType<'a, S> as "__Directive" - where Scalar = |&self| +#[crate::impl_object_internal( + name = "__Directive", + Context = SchemaType<'a, S>, + Scalar = S, +)] +impl<'a, S> DirectiveType<'a, S> +where + S: crate::ScalarValue + 'a, { - field name() -> &String { + fn name(&self) -> &String { &self.name } - field description() -> &Option { + fn description(&self) -> &Option { &self.description } - field locations() -> &Vec { + fn locations(&self) -> &Vec { &self.locations } - field args() -> &Vec> { + fn args(&self) -> &Vec> { &self.arguments } // Included for compatibility with the introspection query in GraphQL.js - field deprecated "Use the locations array instead" - on_operation() -> bool { + #[graphql(deprecated = "Use the locations array instead")] + fn on_operation(&self) -> bool { self.locations.contains(&DirectiveLocation::Query) } // Included for compatibility with the introspection query in GraphQL.js - field deprecated "Use the locations array instead" - on_fragment() -> bool { - self.locations.contains(&DirectiveLocation::FragmentDefinition) || - self.locations.contains(&DirectiveLocation::InlineFragment) || - self.locations.contains(&DirectiveLocation::FragmentSpread) + #[graphql(deprecated = "Use the locations array instead")] + fn on_fragment(&self) -> bool { + self.locations + .contains(&DirectiveLocation::FragmentDefinition) + || self.locations.contains(&DirectiveLocation::InlineFragment) + || self.locations.contains(&DirectiveLocation::FragmentSpread) } // Included for compatibility with the introspection query in GraphQL.js - field deprecated "Use the locations array instead" - on_field() -> bool { + #[graphql(deprecated = "Use the locations array instead")] + fn on_field(&self) -> bool { self.locations.contains(&DirectiveLocation::Field) } -}); +} diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index fc8890bc..d88faa82 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -29,80 +29,93 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| { } }); -graphql_object!(<'a> &'a Human: Database as "Human" |&self| { - description: "A humanoid creature in the Star Wars universe." - - interfaces: [&Character] - - field id() -> &str as "The id of the human"{ +#[crate::impl_object_internal( + Context = Database, + Scalar = crate::DefaultScalarValue, + interfaces = [&dyn Character], +)] +/// A humanoid creature in the Star Wars universe. +impl<'a> &'a Human { + /// The id of the human + fn id(&self) -> &str { self.id() } - field name() -> Option<&str> as "The name of the human" { + /// The name of the human + fn name(&self) -> Option<&str> { Some(self.name()) } - field friends(&executor) -> Vec<&Character> - as "The friends of the human" { - executor.context().get_friends(self.as_character()) + /// The friends of the human + fn friends(&self, ctx: &Database) -> Vec<&Character> { + ctx.get_friends(self.as_character()) } - field appears_in() -> &[Episode] as "Which movies they appear in" { + /// Which movies they appear in + fn appears_in(&self) -> &[Episode] { self.appears_in() } - field home_planet() -> &Option as "The home planet of the human" { + /// The home planet of the human + fn home_planet(&self) -> &Option { self.home_planet() } -}); +} -graphql_object!(<'a> &'a Droid: Database as "Droid" |&self| { - description: "A mechanical creature in the Star Wars universe." - - interfaces: [&Character] - - field id() -> &str as "The id of the droid" { +#[crate::impl_object_internal( + Context = Database, + Scalar = crate::DefaultScalarValue, + interfaces = [&dyn Character], +)] +/// A mechanical creature in the Star Wars universe. +impl<'a> &'a Droid { + /// The id of the droid + fn id(&self) -> &str { self.id() } - field name() -> Option<&str> as "The name of the droid" { + /// The name of the droid + fn name(&self) -> Option<&str> { Some(self.name()) } - field friends(&executor) -> Vec<&Character> - as "The friends of the droid" { - executor.context().get_friends(self.as_character()) + /// The friends of the droid + fn friends(&self, ctx: &Database) -> Vec<&Character> { + ctx.get_friends(self.as_character()) } - field appears_in() -> &[Episode] as "Which movies they appear in" { + /// Which movies they appear in + fn appears_in(&self) -> &[Episode] { self.appears_in() } - field primary_function() -> &Option as "The primary function of the droid" { + /// The primary function of the droid + fn primary_function(&self) -> &Option { self.primary_function() } -}); +} -graphql_object!(Database: Database as "Query" |&self| { - description: "The root query object of the schema" - - field human( - id: String as "id of the human" - ) -> Option<&Human> { +#[crate::impl_object_internal( + name = "Query", + Context = Database, + Scalar = crate::DefaultScalarValue, +)] +/// The root query object of the schema +impl Database { + #[graphql(arguments(id(description = "id of the human")))] + fn human(&self, id: String) -> Option<&Human> { self.get_human(&id) } - field droid( - id: String as "id of the droid" - ) -> Option<&Droid> { + #[graphql(arguments(id(description = "id of the droid")))] + fn droid(&self, id: String) -> Option<&Droid> { self.get_droid(&id) } - field hero( - episode: Option as - "If omitted, returns the hero of the whole saga. If provided, returns \ - the hero of that particular episode" - ) -> Option<&Character> { + #[graphql(arguments(episode( + description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" + )))] + fn hero(&self, episode: Option) -> Option<&Character> { Some(self.get_hero(episode).as_character()) } -}); +} diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 42d24a3b..e161c6ab 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -344,56 +344,56 @@ pub(crate) fn schema_introspection_result() -> value::Value { { "kind": "ENUM", "name": "__TypeKind", - "description": "GraphQL type kind\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", + "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", "fields": Null, "inputFields": Null, "interfaces": Null, "enumValues": [ { "name": "SCALAR", - "description": "## Scalar types\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", + "description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", "isDeprecated": false, "deprecationReason": Null }, { "name": "OBJECT", - "description": "## Object types\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", + "description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", "isDeprecated": false, "deprecationReason": Null }, { "name": "INTERFACE", - "description": "## Interface types\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", + "description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", "isDeprecated": false, "deprecationReason": Null }, { "name": "UNION", - "description": "## Union types\nUnions are similar to interfaces but can not contain any fields on their own.", + "description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.", "isDeprecated": false, "deprecationReason": Null }, { "name": "ENUM", - "description": "## Enum types\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", + "description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", "isDeprecated": false, "deprecationReason": Null }, { "name": "INPUT_OBJECT", - "description": "## Input objects\nRepresents complex values provided in queries _into_ the system.", + "description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.", "isDeprecated": false, "deprecationReason": Null }, { "name": "LIST", - "description": "## List types\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", + "description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", "isDeprecated": false, "deprecationReason": Null }, { "name": "NON_NULL", - "description": "## Non-null types\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", + "description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", "isDeprecated": false, "deprecationReason": Null } @@ -827,6 +827,7 @@ pub(crate) fn schema_introspection_result() -> value::Value { "args": [ { "name": "id", + "description": Null, "description": "id of the droid", "type": { "kind": "NON_NULL", diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 93bb0dc7..d131e3af 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -12,33 +12,33 @@ use crate::schema::meta::{Argument, MetaType}; /// GraphQL type kind /// -/// The GraphQL specification defines a number of type kinds - the meta type +/// The GraphQL specification defines a number of type kinds - the meta type\ /// of a type. #[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)] #[graphql(name = "__TypeKind")] pub enum TypeKind { /// ## Scalar types /// - /// Scalar types appear as the leaf nodes of GraphQL queries. Strings, - /// numbers, and booleans are the built in types, and while it's possible + /// Scalar types appear as the leaf nodes of GraphQL queries. Strings,\ + /// numbers, and booleans are the built in types, and while it's possible\ /// to define your own, it's relatively uncommon. Scalar, /// ## Object types /// - /// The most common type to be implemented by users. Objects have fields + /// The most common type to be implemented by users. Objects have fields\ /// and can implement interfaces. Object, /// ## Interface types /// - /// Interface types are used to represent overlapping fields between + /// Interface types are used to represent overlapping fields between\ /// multiple types, and can be queried for their concrete type. Interface, /// ## Union types /// - /// Unions are similar to interfaces but can not contain any fields on + /// Unions are similar to interfaces but can not contain any fields on\ /// their own. Union, @@ -55,14 +55,14 @@ pub enum TypeKind { /// ## List types /// - /// Represent lists of other types. This library provides implementations - /// for vectors and slices, but other Rust types can be extended to serve + /// Represent lists of other types. This library provides implementations\ + /// for vectors and slices, but other Rust types can be extended to serve\ /// as GraphQL lists. List, /// ## Non-null types /// - /// In GraphQL, nullable types are the default. By putting a `!` after a + /// In GraphQL, nullable types are the default. By putting a `!` after a\ /// type, it becomes non-nullable. #[graphql(name = "NON_NULL")] NonNull, From bf50c3eb86a56de24fbdc7c36b83a9508697444f Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:55:46 +0200 Subject: [PATCH 03/15] Integration tests: impl_object refactor --- integration_tests/juniper_tests/Cargo.toml | 12 +-- .../juniper_tests/src/codegen/derive_enum.rs | 4 +- .../src/codegen/derive_input_object.rs | 4 +- .../src/codegen/derive_object.rs | 77 ++++++++++--------- .../juniper_tests/src/codegen/mod.rs | 2 + .../juniper_tests/src/codegen/util.rs | 54 +++++++++++++ .../juniper_tests/src/custom_scalar.rs | 11 ++- integration_tests/juniper_tests/src/lib.rs | 9 +-- 8 files changed, 109 insertions(+), 64 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/util.rs diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml index bedc80d1..7b72245c 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/integration_tests/juniper_tests/Cargo.toml @@ -4,15 +4,7 @@ version = "0.1.0" publish = false edition = "2018" -[dependencies] -juniper = { version = "0.11.0", path = "../../juniper" } -serde_json = { version = "1" } - [dev-dependencies] +juniper = { path = "../../juniper" } +serde_json = { version = "1" } fnv = "1.0.3" -indexmap = "1.0" - -[[test]] -name = "integration_tests" -path = "src/lib.rs" -harness = true diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index fd5dbd3c..27c5e8dd 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -19,7 +19,7 @@ enum DocEnum { Foo, } -/// Doc 1. +/// Doc 1.\ /// Doc 2. /// /// Doc 4. @@ -85,7 +85,7 @@ fn test_multi_doc_comment() { let meta = MultiDocEnum::meta(&(), &mut registry); assert_eq!( meta.description(), - Some(&"Doc 1. Doc 2.\nDoc 4.".to_string()) + Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string()) ); } diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index 16173000..938446ef 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -27,7 +27,7 @@ struct DocComment { regular_field: bool, } -/// Doc 1. +/// Doc 1.\ /// Doc 2. /// /// Doc 4. @@ -151,7 +151,7 @@ fn test_multi_doc_comment() { let meta = MultiDocComment::meta(&(), &mut registry); assert_eq!( meta.description(), - Some(&"Doc 1. Doc 2.\nDoc 4.".to_string()) + Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string()) ); } diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index 1a2d9936..ac78b521 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -12,20 +12,20 @@ use juniper::{self, execute, EmptyMutation, GraphQLType, RootNode, Value, Variab #[graphql( name = "MyObj", description = "obj descr", - scalar = "DefaultScalarValue" + scalar = DefaultScalarValue )] struct Obj { regular_field: bool, #[graphql( name = "renamedField", description = "descr", - deprecation = "field descr" + deprecated = "field deprecation" )] c: i32, } #[derive(GraphQLObject, Debug, PartialEq)] -#[graphql(scalar = "DefaultScalarValue")] +#[graphql(scalar = DefaultScalarValue)] struct Nested { obj: Obj, } @@ -39,7 +39,7 @@ struct DocComment { regular_field: bool, } -/// Doc 1. +/// Doc 1.\ /// Doc 2. /// /// Doc 4. @@ -75,56 +75,57 @@ struct Context; impl juniper::Context for Context {} #[derive(GraphQLObject, Debug)] -#[graphql(Context = "Context")] +#[graphql(Context = Context)] struct WithCustomContext { a: bool, } -juniper::graphql_object!(Query: () |&self| { - field obj() -> Obj { - Obj{ - regular_field: true, - c: 22, - } - } - - field nested() -> Nested { - Nested{ - obj: Obj{ - regular_field: false, - c: 333, - } +#[juniper::impl_object] +impl Query { + fn obj() -> Obj { + Obj { + regular_field: true, + c: 22, } } - field doc() -> DocComment { - DocComment{ - regular_field: true, - } + fn nested() -> Nested { + Nested { + obj: Obj { + regular_field: false, + c: 333, + }, + } } - field multi_doc() -> MultiDocComment { - MultiDocComment{ - regular_field: true, - } + fn doc() -> DocComment { + DocComment { + regular_field: true, + } } - field override_doc() -> OverrideDocComment { - OverrideDocComment{ - regular_field: true, - } + fn multi_doc() -> MultiDocComment { + MultiDocComment { + regular_field: true, + } } - field skipped_field_obj() -> SkippedFieldObj { - SkippedFieldObj{ + fn override_doc() -> OverrideDocComment { + OverrideDocComment { + regular_field: true, + } + } + + fn skipped_field_obj() -> SkippedFieldObj { + SkippedFieldObj { regular_field: false, skipped: 42, } } -}); +} #[test] -fn test_doc_comment() { +fn test_doc_comment_simple() { let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default()); let meta = DocComment::meta(&(), &mut registry); assert_eq!(meta.description(), Some(&"Object comment.".to_string())); @@ -143,14 +144,14 @@ fn test_multi_doc_comment() { let meta = MultiDocComment::meta(&(), &mut registry); assert_eq!( meta.description(), - Some(&"Doc 1. Doc 2.\nDoc 4.".to_string()) + Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string()) ); check_descriptions( "MultiDocComment", - &Value::scalar("Doc 1. Doc 2.\nDoc 4."), + &Value::scalar("Doc 1. Doc 2.\n\nDoc 4."), "regularField", - &Value::scalar("Field 1. Field 2."), + &Value::scalar("Field 1.\nField 2."), ); } diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 596af99f..ac20e205 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,3 +1,5 @@ +mod util; + mod derive_enum; mod derive_input_object; mod derive_object; diff --git a/integration_tests/juniper_tests/src/codegen/util.rs b/integration_tests/juniper_tests/src/codegen/util.rs new file mode 100644 index 00000000..59112ea0 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/util.rs @@ -0,0 +1,54 @@ +use juniper::{DefaultScalarValue, GraphQLType, RootNode, Value, Variables}; +use std::default::Default; + +pub fn run_query(query: &str) -> Value +where + Query: GraphQLType + Default, + Mutation: GraphQLType + Default, + Context: Default, +{ + let schema = RootNode::new(Query::default(), Mutation::default()); + let (result, errs) = + juniper::execute(query, None, &schema, &Variables::new(), &Context::default()) + .expect("Execution failed"); + + assert_eq!(errs, []); + result +} + +pub fn run_info_query(type_name: &str) -> Value +where + Query: GraphQLType + Default, + Mutation: GraphQLType + Default, + Context: Default, +{ + let query = format!( + r#" + {{ + __type(name: "{}") {{ + name, + description, + fields {{ + name + description + args {{ + name + description + type {{ + name + }} + }} + }} + }} + }} + "#, + type_name + ); + let result = run_query::(&query); + result + .as_object_value() + .expect("Result is not an object") + .get_field_value("__type") + .expect("__type field missing") + .clone() +} diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index e3d3732f..07c37f72 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -151,15 +151,18 @@ juniper::graphql_scalar!(i64 as "Long" where Scalar = MyScalarValue { struct TestType; -juniper::graphql_object!(TestType: () where Scalar = MyScalarValue |&self| { - field long_field() -> i64 { +#[juniper::impl_object( + Scalar = MyScalarValue +)] +impl TestType { + fn long_field() -> i64 { (::std::i32::MAX as i64) + 1 } - field long_with_arg(long_arg: i64) -> i64 { + fn long_with_arg(long_arg: i64) -> i64 { long_arg } -}); +} #[cfg(test)] fn run_variable_query(query: &str, vars: Variables, f: F) diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index fa3742a3..022c238a 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -1,11 +1,4 @@ -extern crate juniper; #[cfg(test)] -extern crate serde_json; - -#[cfg(test)] -extern crate fnv; -#[cfg(test)] -extern crate indexmap; - mod codegen; +#[cfg(test)] mod custom_scalar; From 693405afa5a86df3a2277065696e7c42306ff630 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:56:06 +0200 Subject: [PATCH 04/15] (book) impl_object refactor --- docs/book/content/advanced/introspection.md | 12 ++-- .../content/advanced/non_struct_objects.md | 9 +-- .../content/advanced/objects_and_generics.md | 22 ++++--- docs/book/content/quickstart.md | 61 ++++++++++++------- .../content/schema/schemas_and_mutations.md | 16 ++--- docs/book/content/servers/iron.md | 22 +++---- docs/book/content/types/input_objects.md | 16 +++-- docs/book/content/types/interfaces.md | 6 +- .../content/types/objects/complex_fields.md | 34 ++++++----- .../content/types/objects/defining_objects.md | 2 +- .../content/types/objects/error_handling.md | 17 +++--- .../content/types/objects/using_contexts.md | 43 ++++++++----- docs/book/content/types/unions.md | 10 +-- docs/book/tests/Cargo.toml | 5 +- 14 files changed, 164 insertions(+), 111 deletions(-) diff --git a/docs/book/content/advanced/introspection.md b/docs/book/content/advanced/introspection.md index 5b23f2f7..66949761 100644 --- a/docs/book/content/advanced/introspection.md +++ b/docs/book/content/advanced/introspection.md @@ -30,9 +30,6 @@ result can then be converted to JSON for use with tools and libraries such as [graphql-client](https://github.com/graphql-rust/graphql-client): ```rust -# // Only needed due to 2018 edition because the macro is not accessible. -# extern crate juniper; -# extern crate serde_json; use juniper::{EmptyMutation, FieldResult, IntrospectionFormat}; // Define our schema. @@ -47,11 +44,14 @@ impl juniper::Context for Context {} struct Query; -juniper::graphql_object!(Query: Context |&self| { - field example(&executor, id: String) -> FieldResult { +#[juniper::impl_object( + Context = Context, +)] +impl Query { + fn example(id: String) -> FieldResult { unimplemented!() } -}); +} type Schema = juniper::RootNode<'static, Query, EmptyMutation>; diff --git a/docs/book/content/advanced/non_struct_objects.md b/docs/book/content/advanced/non_struct_objects.md index c24dd3da..af6b7342 100644 --- a/docs/book/content/advanced/non_struct_objects.md +++ b/docs/book/content/advanced/non_struct_objects.md @@ -23,21 +23,22 @@ enum SignUpResult { Error(Vec), } -juniper::graphql_object!(SignUpResult: () |&self| { - field user() -> Option<&User> { +#[juniper::impl_object] +impl SignUpResult { + fn user(&self) -> Option<&User> { match *self { SignUpResult::Ok(ref user) => Some(user), SignUpResult::Error(_) => None, } } - field error() -> Option<&Vec> { + fn error(&self) -> Option<&Vec> { match *self { SignUpResult::Ok(_) => None, SignUpResult::Error(ref errors) => Some(errors) } } -}); +} # fn main() {} ``` diff --git a/docs/book/content/advanced/objects_and_generics.md b/docs/book/content/advanced/objects_and_generics.md index dbde8cfd..9c91734b 100644 --- a/docs/book/content/advanced/objects_and_generics.md +++ b/docs/book/content/advanced/objects_and_generics.md @@ -25,25 +25,31 @@ struct ValidationError { # #[allow(dead_code)] struct MutationResult(Result>); -juniper::graphql_object!(MutationResult: () as "UserResult" |&self| { - field user() -> Option<&User> { +#[juniper::impl_object( + name = "UserResult", +)] +impl MutationResult { + fn user(&self) -> Option<&User> { self.0.as_ref().ok() } - field error() -> Option<&Vec> { + fn error(&self) -> Option<&Vec> { self.0.as_ref().err() } -}); +} -juniper::graphql_object!(MutationResult: () as "ForumPostResult" |&self| { - field forum_post() -> Option<&ForumPost> { +#[juniper::impl_object( + name = "ForumPostResult", +)] +impl MutationResult { + fn forum_post(&self) -> Option<&ForumPost> { self.0.as_ref().ok() } - field error() -> Option<&Vec> { + fn error(&self) -> Option<&Vec> { self.0.as_ref().err() } -}); +} # fn main() {} ``` diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md index 20e438b4..c5e2c69b 100644 --- a/docs/book/content/quickstart.md +++ b/docs/book/content/quickstart.md @@ -20,7 +20,7 @@ naturally map to GraphQL features, such as `Option`, `Vec`, `Box`, For more advanced mappings, Juniper provides multiple macros to map your Rust types to a GraphQL schema. The most important one is the -[graphql_object!][jp_obj_macro] macro that is used for declaring an object with +[impl_object][jp_impl_object] procedural macro that is used for declaring an object with resolvers, which you will use for the `Query` and `Mutation` roots. ```rust @@ -60,7 +60,7 @@ struct NewHuman { } // Now, we create our root Query and Mutation types with resolvers by using the -// graphql_object! macro. +// impl_object macro. // Objects can have contexts that allow accessing shared state like a database // pool. @@ -74,17 +74,23 @@ impl juniper::Context for Context {} struct Query; -juniper::graphql_object!(Query: Context |&self| { +#[juniper::impl_object( + // Here we specify the context type for the object. + // We need to do this in every type that + // needs access to the context. + Context = Context, +)] +impl Query { - field apiVersion() -> &str { + fn apiVersion() -> &str { "1.0" } // Arguments to resolvers can either be simple types or input objects. - // The executor is a special (optional) argument that allows accessing the context. - field human(&executor, id: String) -> FieldResult { - // Get the context from the executor. - let context = executor.context(); + // To gain access to the context, we specify a argument + // that is a reference to the Context type. + // Juniper automatically injects the correct context here. + fn human(context: &Context, id: String) -> FieldResult { // Get a db connection. let connection = context.pool.get_connection()?; // Execute a db query. @@ -93,18 +99,23 @@ juniper::graphql_object!(Query: Context |&self| { // Return the result. Ok(human) } -}); +} + +// Now, we do the same for our Mutation type. struct Mutation; -juniper::graphql_object!(Mutation: Context |&self| { +#[juniper::impl_object( + Context = Context, +)] +impl Mutation { - field createHuman(&executor, new_human: NewHuman) -> FieldResult { + fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult { let db = executor.context().pool.get_connection()?; let human: Human = db.insert_human(&new_human)?; Ok(human) } -}); +} // A root schema consists of a query and a mutation. // Request queries can be executed against a RootNode. @@ -130,6 +141,7 @@ You can invoke `juniper::execute` directly to run a GraphQL query: # #[macro_use] extern crate juniper; use juniper::{FieldResult, Variables, EmptyMutation}; + #[derive(juniper::GraphQLEnum, Clone, Copy)] enum Episode { NewHope, @@ -137,18 +149,23 @@ enum Episode { Jedi, } -struct Query; - -juniper::graphql_object!(Query: Ctx |&self| { - field favoriteEpisode(&executor) -> FieldResult { - // Use the special &executor argument to fetch our fav episode. - Ok(executor.context().0) - } -}); - // Arbitrary context data. struct Ctx(Episode); +impl juniper::Context for Ctx {} + +struct Query; + +#[juniper::impl_object( + Context = Ctx, +)] +impl Query { + fn favoriteEpisode(context: &Ctx) -> FieldResult { + Ok(context.0) + } +} + + // A root schema consists of a query and a mutation. // Request queries can be executed against a RootNode. type Schema = juniper::RootNode<'static, Query, EmptyMutation>; @@ -181,4 +198,4 @@ fn main() { [rocket]: servers/rocket.md [iron]: servers/iron.md [tutorial]: ./tutorial.html -[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.graphql_object.html +[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.impl_object.html diff --git a/docs/book/content/schema/schemas_and_mutations.md b/docs/book/content/schema/schemas_and_mutations.md index db9aa1e7..c2a90417 100644 --- a/docs/book/content/schema/schemas_and_mutations.md +++ b/docs/book/content/schema/schemas_and_mutations.md @@ -20,19 +20,20 @@ object somewhere but never references it, it will not be exposed in a schema. ## The query root The query root is just a GraphQL object. You define it like any other GraphQL -object in Juniper, most commonly using the `graphql_object!` macro: +object in Juniper, most commonly using the `impl_object` proc macro: ```rust # use juniper::FieldResult; # #[derive(juniper::GraphQLObject)] struct User { name: String } struct Root; -juniper::graphql_object!(Root: () |&self| { - field userWithUsername(username: String) -> FieldResult> { +#[juniper::impl_object] +impl Root { + fn userWithUsername(username: String) -> FieldResult> { // Look up user in database... # unimplemented!() } -}); +} # fn main() { } ``` @@ -47,12 +48,13 @@ usually performs some mutating side-effect, such as updating a database. # #[derive(juniper::GraphQLObject)] struct User { name: String } struct Mutations; -juniper::graphql_object!(Mutations: () |&self| { - field signUpUser(name: String, email: String) -> FieldResult { +#[juniper::impl_object] +impl Mutations { + fn signUpUser(name: String, email: String) -> FieldResult { // Validate inputs and save user in database... # unimplemented!() } -}); +} # fn main() { } ``` diff --git a/docs/book/content/servers/iron.md b/docs/book/content/servers/iron.md index 3218543b..ec428889 100644 --- a/docs/book/content/servers/iron.md +++ b/docs/book/content/servers/iron.md @@ -47,11 +47,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> { struct Root; -juniper::graphql_object!(Root: () |&self| { - field foo() -> String { +#[juniper::impl_object] +impl Root { + fn foo() -> String { "Bar".to_owned() } -}); +} # #[allow(unreachable_code, unused_variables)] fn main() { @@ -98,13 +99,14 @@ fn context_factory(req: &mut Request) -> IronResult { struct Root; -juniper::graphql_object!(Root: Context |&self| { - field my_addr(&executor) -> String { - let context = executor.context(); - +#[juniper::impl_object( + Context = Context, +)] +impl Root { + field my_addr(context: &Context) -> String { format!("Hello, you're coming from {}", context.remote_addr) } -}); +} # fn main() { # let _graphql_endpoint = juniper_iron::GraphQLHandler::new( @@ -115,10 +117,6 @@ juniper::graphql_object!(Root: Context |&self| { # } ``` -## Accessing global data - -FIXME: Show how the `persistent` crate works with contexts using e.g. `r2d2`. - [iron]: http://ironframework.io [graphiql]: https://github.com/graphql/graphiql [mount]: https://github.com/iron/mount diff --git a/docs/book/content/types/input_objects.md b/docs/book/content/types/input_objects.md index 48d78a34..837a3aed 100644 --- a/docs/book/content/types/input_objects.md +++ b/docs/book/content/types/input_objects.md @@ -14,12 +14,14 @@ struct Coordinate { struct Root; # #[derive(juniper::GraphQLObject)] struct User { name: String } -juniper::graphql_object!(Root: () |&self| { - field users_at_location(coordinate: Coordinate, radius: f64) -> Vec { +#[juniper::impl_object] +impl Root { + fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec { // Send coordinate to database + // ... # unimplemented!() } -}); +} # fn main() {} ``` @@ -43,12 +45,14 @@ struct WorldCoordinate { struct Root; # #[derive(juniper::GraphQLObject)] struct User { name: String } -juniper::graphql_object!(Root: () |&self| { - field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec { +#[juniper::impl_object] +impl Root { + fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec { // Send coordinate to database + // ... # unimplemented!() } -}); +} # fn main() {} ``` diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index b128e86c..b9d459d2 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -49,7 +49,7 @@ impl Character for Droid { fn as_droid(&self) -> Option<&Droid> { Some(&self) } } -juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = |&self| { +juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = |&self| { field id() -> &str { self.id() } instance_resolvers: |_| { @@ -79,14 +79,14 @@ we'll use two hashmaps, but this could be two tables and some SQL calls instead: ```rust # use std::collections::HashMap; #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Human { id: String, home_planet: String, } #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Droid { id: String, primary_function: String, diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md index e7bfcc81..81399756 100644 --- a/docs/book/content/types/objects/complex_fields.md +++ b/docs/book/content/types/objects/complex_fields.md @@ -2,8 +2,8 @@ If you've got a struct that can't be mapped directly to GraphQL, that contains computed fields or circular structures, you have to use a more powerful tool: -the `graphql_object!` macro. This macro lets you define GraphQL objects similar -to how you define methods in a Rust `impl` block for a type. Continuing with the +the `impl_object` procedural macro. This macro lets you define GraphQL object +fields in a Rust `impl` block for a type. Continuing with the example from the last chapter, this is how you would define `Person` using the macro: @@ -14,15 +14,16 @@ struct Person { age: i32, } -juniper::graphql_object!(Person: () |&self| { - field name() -> &str { +#[juniper::impl_object] +impl Person { + fn name(&self) -> &str { self.name.as_str() } - field age() -> i32 { + fn age(&self) -> i32 { self.age } -}); +} # fn main() { } ``` @@ -42,12 +43,13 @@ struct House { inhabitants: Vec, } -juniper::graphql_object!(House: () |&self| { +#[juniper::impl_object] +impl House { // Creates the field inhabitantWithName(name), returning a nullable person - field inhabitant_with_name(name: String) -> Option<&Person> { + fn inhabitant_with_name(&self, name: String) -> Option<&Person> { self.inhabitants.iter().find(|p| p.name == name) } -}); +} # fn main() {} ``` @@ -68,15 +70,19 @@ struct Person { website_url: String, } -juniper::graphql_object!(Person: () as "PersonObject" |&self| { - field name() -> &str { +#[juniper::impl_object( + // With this attribtue you can change the public GraphQL name of the type. + name = "PersonObject", +)] +impl Person { + fn name(&self) -> &str { self.name.as_str() } - field websiteURL() -> &str { + fn websiteURL(&self) -> &str { self.website_url.as_str() } -}); +} # fn main() { } ``` @@ -90,4 +96,4 @@ GraphQL fields expose more features than Rust's standard method syntax gives us: * Per-argument descriptions These, and more features, are described more thorougly in [the reference -documentation](https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html). +documentation](https://docs.rs/juniper/latest/juniper/macro.impl_object.html). diff --git a/docs/book/content/types/objects/defining_objects.md b/docs/book/content/types/objects/defining_objects.md index 0f4c0b9c..06507d5c 100644 --- a/docs/book/content/types/objects/defining_objects.md +++ b/docs/book/content/types/objects/defining_objects.md @@ -154,7 +154,7 @@ attribute: struct Person { name: String, age: i32, - #[graphql(deprecation="Please use the name field instead")] + #[graphql(deprecated = "Please use the name field instead")] first_name: String, } diff --git a/docs/book/content/types/objects/error_handling.md b/docs/book/content/types/objects/error_handling.md index a2921ec0..8e4b047d 100644 --- a/docs/book/content/types/objects/error_handling.md +++ b/docs/book/content/types/objects/error_handling.md @@ -25,14 +25,16 @@ struct Example { filename: PathBuf, } -juniper::graphql_object!(Example: () |&self| { - field contents() -> FieldResult { +#[juniper::impl_object] +impl Example { + fn contents() -> FieldResult { let mut file = File::open(&self.filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } - field foo() -> FieldResult> { + + fn foo() -> FieldResult> { // Some invalid bytes. let invalid = vec![128, 223]; @@ -41,7 +43,7 @@ juniper::graphql_object!(Example: () |&self| { Err(e) => Err(e)?, } } -}); +} # fn main() {} ``` @@ -141,14 +143,15 @@ struct Example { whatever: Option, } -juniper::graphql_object!(Example: () |&self| { - field whatever() -> Result { +#[juniper::impl_object] +impl Example { + fn whatever() -> Result { if let Some(value) = self.whatever { return Ok(value); } Err(CustomError::WhateverNotSet) } -}); +} # fn main() {} ``` diff --git a/docs/book/content/types/objects/using_contexts.md b/docs/book/content/types/objects/using_contexts.md index c9747bba..ef210c8b 100644 --- a/docs/book/content/types/objects/using_contexts.md +++ b/docs/book/content/types/objects/using_contexts.md @@ -31,42 +31,57 @@ We would like a `friends` field on `User` that returns a list of `User` objects. In order to write such a field though, the database must be queried. To solve this, we mark the `Database` as a valid context type and assign it to -the user object. Then, we use the special `&executor` argument to access the -current context object: +the user object. + +To gain access to the context, we need to specify an argument with the same +type as the specified `Context` for the type: + ```rust # use std::collections::HashMap; extern crate juniper; +// This struct represents our context. struct Database { users: HashMap, } +// Mark the Database as a valid context type for Juniper +impl juniper::Context for Database {} + struct User { id: i32, name: String, friend_ids: Vec, } -// 1. Mark the Database as a valid context type for Juniper -impl juniper::Context for Database {} -// 2. Assign Database as the context type for User -juniper::graphql_object!(User: Database |&self| { - // 3. Use the special executor argument - field friends(&executor) -> Vec<&User> { - // 4. Use the executor to access the context object - let database = executor.context(); +// Assign Database as the context type for User +#[juniper::impl_object( + Context = Database, +)] +impl User { + // 3. Inject the context by specifying an argument + // with the context type. + // Note: + // - the type must be a reference + // - the name of the argument SHOULD be context + fn friends(&self, context: &Database) -> Vec<&User> { // 5. Use the database to lookup users self.friend_ids.iter() - .map(|id| database.users.get(id).expect("Could not find user with ID")) + .map(|id| context.users.get(id).expect("Could not find user with ID")) .collect() } - field name() -> &str { self.name.as_str() } - field id() -> i32 { self.id } -}); + fn name(&self) -> &str { + self.name.as_str() + } + + fn id(&self) -> i32 { + self.id + } +} # fn main() { } ``` diff --git a/docs/book/content/types/unions.md b/docs/book/content/types/unions.md index 0908f03a..f38a356c 100644 --- a/docs/book/content/types/unions.md +++ b/docs/book/content/types/unions.md @@ -42,7 +42,7 @@ impl Character for Droid { fn as_droid(&self) -> Option<&Droid> { Some(&self) } } -juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = |&self| { +juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = |&self| { instance_resolvers: |_| { // The left hand side indicates the concrete type T, the right hand // side should be an expression returning Option @@ -61,14 +61,14 @@ FIXME: This example does not compile at the moment ```rust # use std::collections::HashMap; #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Human { id: String, home_planet: String, } #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Droid { id: String, primary_function: String, @@ -108,14 +108,14 @@ juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar ```rust # use std::collections::HashMap; #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Human { id: String, home_planet: String, } #[derive(juniper::GraphQLObject)] -#[graphql(Context = "Database")] +#[graphql(Context = Database)] struct Droid { id: String, primary_function: String, diff --git a/docs/book/tests/Cargo.toml b/docs/book/tests/Cargo.toml index 395d6fb2..8f49bca3 100644 --- a/docs/book/tests/Cargo.toml +++ b/docs/book/tests/Cargo.toml @@ -6,13 +6,14 @@ edition = "2018" build = "build.rs" [dependencies] -juniper = { version = "0.11", path = "../../../juniper" } -juniper_iron = { version = "0.3", path = "../../../juniper_iron" } +juniper = { path = "../../../juniper" } +juniper_iron = { path = "../../../juniper_iron" } iron = "^0.5.0" mount = "^0.3.0" skeptic = "0.13" +serde_json = "1.0.39" [build-dependencies] skeptic = "0.13" From 1b30e012aeab0c952b2ff054307defd0ea5ff0f3 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:56:20 +0200 Subject: [PATCH 05/15] (iron) impl_object refactor --- juniper_iron/src/lib.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 4aa3cd73..ea546a77 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -37,27 +37,29 @@ use juniper::{Context, EmptyMutation}; # struct QueryRoot; # struct Database { users: HashMap } # -# juniper::graphql_object!(User: Database |&self| { -# field id() -> FieldResult<&String> { +# #[juniper::impl_object( Context = Database )] +# impl User { +# fn id(&self) -> FieldResult<&String> { # Ok(&self.id) # } # -# field name() -> FieldResult<&String> { +# fn name(&self) -> FieldResult<&String> { # Ok(&self.name) # } # -# field friends(&executor) -> FieldResult> { +# fn friends(context: &Database) -> FieldResult> { # Ok(self.friend_ids.iter() # .filter_map(|id| executor.context().users.get(id)) # .collect()) # } -# }); +# } # -# juniper::graphql_object!(QueryRoot: Database |&self| { -# field user(&executor, id: String) -> FieldResult> { +# #[juniper::impl_object( Context = Database )] +# impl QueryRoot { +# fn user(context: &Database, id: String) -> FieldResult> { # Ok(executor.context().users.get(&id)) # } -# }); +# } // This function is executed for every request. Here, we would realistically // provide a database connection or similar. For this example, we'll be From f72f808be31e7488cf853d09e08c89cb3723d090 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 10:56:33 +0200 Subject: [PATCH 06/15] (warp) impl_object refactor --- juniper_warp/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 458fb42e..9a458d78 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -134,13 +134,18 @@ where /// /// struct QueryRoot; /// -/// juniper::graphql_object! (QueryRoot: ExampleContext |&self| { -/// field say_hello(&executor) -> String { -/// let context = executor.context(); -/// -/// format!("good morning {}, the app state is {:?}", context.1, context.0) +/// #[juniper::impl_object( +/// Context = ExampleContext +/// )] +/// impl QueryRoot { +/// fn say_hello(context: &ExampleContext) -> String { +/// format!( +/// "good morning {}, the app state is {:?}", +/// context.1, +/// context.0 +/// ) /// } -/// }); +/// } /// /// let schema = RootNode::new(QueryRoot, EmptyMutation::new()); /// From 52d7af2b8ed09c719ee83ae1117082330c83f004 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 11:16:14 +0200 Subject: [PATCH 07/15] Remove internal macros from export + improve macro doc system. Remove the internal macros from re-export. This was a mistake. Also, import each item from juniper_codegen manually to enable rustdoc integration. --- juniper/src/lib.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 65f8e9df..5b955218 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -108,8 +108,23 @@ extern crate uuid; // Depend on juniper_codegen and re-export everything in it. // This allows users to just depend on juniper and get the derive // functionality automatically. -#[doc(hidden)] -pub use juniper_codegen::*; +pub use juniper_codegen::{ + GraphQLEnum, + GraphQLInputObject, + GraphQLObject, + GraphQLScalarValue, + ScalarValue, + impl_object, +}; +// Internal macros are not exported, +// but declared at the root to make them easier to use. +#[allow(unused_imports)] +use juniper_codegen::{ + GraphQLScalarValueInternal, + GraphQLEnumInternal, + GraphQLInputObjectInternal, + impl_object_internal, +}; #[macro_use] mod value; From 67b28c5e898659b540672d4526d50ae979b1ad31 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 11:44:35 +0200 Subject: [PATCH 08/15] (codegen) Remove (extern crate self) declaration --- juniper_codegen/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 95c7826a..a086bc28 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -6,8 +6,6 @@ #![recursion_limit = "1024"] -extern crate self as juniper; - extern crate proc_macro; mod derive_enum; From 3cc142bfbc7e87f710da021d5115e2fd07c7c2df Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 12:20:48 +0200 Subject: [PATCH 09/15] Bump minimum rust version to 1.34 --- .travis.yml | 4 ++-- _build/azure-pipelines-template.yml | 9 +++++---- juniper/CHANGELOG.md | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ef163b0..c90e8605 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,9 @@ rust: - beta - nightly + # TODO: re-enable once new versions are released. # Prevent accidentally breaking older Rust versions - - 1.32.0 - - 1.31.0 + # - 1.33.0 matrix: include: diff --git a/_build/azure-pipelines-template.yml b/_build/azure-pipelines-template.yml index 8b023b1c..c7e6531c 100644 --- a/_build/azure-pipelines-template.yml +++ b/_build/azure-pipelines-template.yml @@ -10,10 +10,11 @@ jobs: rustup_toolchain: beta nightly: rustup_toolchain: nightly - minimum_supported_version_plus_one: - rustup_toolchain: 1.32.0 - minimum_supported_version: - rustup_toolchain: 1.31.0 + # TODO: re-enable once new versions are released. + # minimum_supported_version_plus_one: + # rustup_toolchain: 1.32.0 + #minimum_supported_version: + # rustup_toolchain: 1.33.0 steps: - ${{ if ne(parameters.name, 'Windows') }}: # Linux and macOS. diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 610e824a..0a0119fa 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -17,7 +17,7 @@ This should not have any impact on your code, since juniper already was 2018 com ### Other changes -- The minimum required Rust version is now `1.31.0`. +- The minimum required Rust version is now `1.34.0`. - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - Added built-in support for the canonical schema introspection query via `juniper::introspect()`. From de12e0eba56514ec7c6c4719cdb842520c63ec2c Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 7 May 2019 11:23:46 +0200 Subject: [PATCH 10/15] Formatting... --- juniper/src/lib.rs | 13 +++---------- juniper/src/macros/object.rs | 2 +- juniper/src/schema/schema.rs | 30 +++++++++++++----------------- juniper_codegen/src/impl_object.rs | 13 ++++++++----- juniper_codegen/src/lib.rs | 20 ++++++++++---------- juniper_codegen/src/util.rs | 10 +++++----- 6 files changed, 40 insertions(+), 48 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 5b955218..9841e3ce 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -109,21 +109,14 @@ extern crate uuid; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - GraphQLEnum, - GraphQLInputObject, - GraphQLObject, - GraphQLScalarValue, - ScalarValue, - impl_object, + impl_object, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, ScalarValue, }; -// Internal macros are not exported, +// Internal macros are not exported, // but declared at the root to make them easier to use. #[allow(unused_imports)] use juniper_codegen::{ + impl_object_internal, GraphQLEnumInternal, GraphQLInputObjectInternal, GraphQLScalarValueInternal, - GraphQLEnumInternal, - GraphQLInputObjectInternal, - impl_object_internal, }; #[macro_use] diff --git a/juniper/src/macros/object.rs b/juniper/src/macros/object.rs index 713f371f..02c27d31 100644 --- a/juniper/src/macros/object.rs +++ b/juniper/src/macros/object.rs @@ -1,7 +1,7 @@ /** ## DEPRECATION WARNING -The `graphql_object!` macro is deprecated and will be removed soon. +The `graphql_object!` macro is deprecated and will be removed soon. Use the new[impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) macro instead. Expose GraphQL objects diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index deaa006b..1ff43a13 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -146,15 +146,13 @@ where fn fields(&self, include_deprecated: bool) -> Option>> { match *self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) - | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => { - Some( - fields - .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) - .filter(|f| !f.name.starts_with("__")) - .collect(), - ) - } + | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( + fields + .iter() + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .filter(|f| !f.name.starts_with("__")) + .collect(), + ), _ => None, } } @@ -233,14 +231,12 @@ where #[graphql(arguments(include_deprecated(default = false)))] fn enum_values(&self, include_deprecated: bool) -> Option> { match *self { - TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => { - Some( - values - .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) - .collect(), - ) - } + TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( + values + .iter() + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .collect(), + ), _ => None, } } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 4e07cf52..baff884b 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -148,7 +148,7 @@ pub fn build_impl_object(args: TokenStream, body: TokenStream, is_internal: bool // Check for executor arguments. if util::type_is_identifier_ref(&captured.ty, "Executor") { resolve_parts.push(quote!(let #arg_ident = executor;)); - } + } // Make sure executor is specified as a reference. else if util::type_is_identifier(&captured.ty, "Executor") { panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?"); @@ -160,16 +160,19 @@ pub fn build_impl_object(args: TokenStream, body: TokenStream, is_internal: bool .unwrap_or(false) { resolve_parts.push(quote!( let #arg_ident = executor.context(); )); - } + } // Make sure the user does not specify the Context // without a reference. (&Context) - else if context_type.clone().map(|ctx| ctx == &captured.ty).unwrap_or(false) { + else if context_type + .clone() + .map(|ctx| ctx == &captured.ty) + .unwrap_or(false) + { panic!( "Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?", quote!(captured.ty), ); - } - else { + } else { let ty = &captured.ty; // TODO: respect graphql attribute overwrite. let final_name = util::to_camel_case(&arg_name); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index a086bc28..66e334bb 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -79,16 +79,16 @@ pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream { The `impl_object` proc macro is the primary way of defining GraphQL resolvers that can not be implemented with the GraphQLObject derive. -It enables you to write GraphQL field resolvers for a type by declaring a +It enables you to write GraphQL field resolvers for a type by declaring a regular Rust `impl` block. Under the hood, the procedural macro implements the GraphQLType trait. -`impl_object` comes with many features that allow customization of +`impl_object` comes with many features that allow customization of your fields, all of which are detailed below. ### Getting Started -This simple example will show you the most basic use of `impl_object`. +This simple example will show you the most basic use of `impl_object`. More advanced use cases are introduced step by step. ``` @@ -105,17 +105,17 @@ impl Query { // This defines a simple, static field which does not require any context. - // You can return any value that implements the `GraphQLType` trait. + // You can return any value that implements the `GraphQLType` trait. // This trait is implemented for: // - basic scalar types like bool, &str, String, i32, f64 // - GraphQL compatible wrappers like Option<_>, Vec<_>. // - types which use the `#derive[juniper::GraphQLObject]` // - `impl_object` structs. - // - // An important note regarding naming: + // + // An important note regarding naming: // By default, field names will be converted to camel case. // For your GraphQL queries, the field will be available as `apiVersion`. - // + // // You can also manually customize the field name if required. (See below) fn api_version() -> &'static str { "0.1" @@ -169,7 +169,7 @@ You can specify a context that will be available across all your resolvers during query execution. The Context can be injected into your resolvers by just -specifying an argument with the same type as the context +specifying an argument with the same type as the context (but as a reference). ``` @@ -198,11 +198,11 @@ impl Query { context.db.user(id) } - // You can also gain access to the executor, which + // You can also gain access to the executor, which // allows you to do look aheads. fn with_executor(executor: &Executor) -> bool { let info = executor.look_ahead(); - // ... + // ... true } } diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index 930c0129..44a65fbb 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -611,7 +611,7 @@ pub struct GraphQLTypeDefiniton { // This flag signifies if the type generics need to be // included manually. pub include_type_generics: bool, - // This flag indicates if the generated code should always be + // This flag indicates if the generated code should always be // generic over the ScalarValue. // If false, the scalar is only generic if a generic parameter // is specified manually. @@ -744,7 +744,7 @@ impl GraphQLTypeDefiniton { // A custom scalar type was specified. // Therefore, we always insert a where clause that marks the scalar as // compatible with ScalarValueRef. - // This is done to prevent the user from having to specify this + // This is done to prevent the user from having to specify this // manually. let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); where_clause.predicates.push( @@ -764,9 +764,9 @@ impl GraphQLTypeDefiniton { // Insert a where clause that marks the scalar as // compatible with ScalarValueRef. // Same as in branch above. - where_clause.predicates.push( - parse_quote!(for<'__b> &'__b __S: #juniper_crate_name::ScalarRefValue<'__b>), - ); + where_clause + .predicates + .push(parse_quote!(for<'__b> &'__b __S: #juniper_crate_name::ScalarRefValue<'__b>)); } let type_generics_tokens = if self.include_type_generics { From 552b4d01ac27ec1c4238370a1c628d66827213fe Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Sun, 12 May 2019 11:02:43 +0200 Subject: [PATCH 11/15] (codegen) Fix test dependency version to be exact. Needed for releasing automation. --- juniper_codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 9077f5e2..27291c10 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -22,7 +22,7 @@ regex = "1" lazy_static = "1.0.0" [dev-dependencies] -juniper = { version = "0.11", path = "../juniper" } +juniper = { version = "0.11.1", path = "../juniper" } [badges] travis-ci = { repository = "graphql-rust/juniper" } From ffe00b9fa34cec824554508d551ca26466b516b7 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Sun, 12 May 2019 21:31:15 +0200 Subject: [PATCH 12/15] Fix release tooling for juniper_codegen Patch juniper_codegen dev dependency on juniper. --- juniper/release.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/juniper/release.toml b/juniper/release.toml index 689c2096..1ad707d0 100644 --- a/juniper/release.toml +++ b/juniper/release.toml @@ -6,6 +6,8 @@ upload-doc = false pre-release-replacements = [ # Juniper's changelog {file="CHANGELOG.md", search="# master", replace="# master\n\n- No changes yet\n\n# [[{{version}}] {{date}}](https://github.com/graphql-rust/juniper/releases/tag/{{crate_name}}-{{version}})"}, + # codegen + {file="../juniper_codegen/Cargo.toml", search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, # Tests. {file="../integration_tests/juniper_tests/Cargo.toml", search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, # Hyper From db0d5952dd504b8cc1b5620a49c05cc2bdb003a8 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 13 May 2019 12:35:14 +0200 Subject: [PATCH 13/15] Formatting --- juniper/src/macros/tests/object.rs | 1 - juniper/src/macros/tests/union.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs index 2fb14524..c1597a0c 100644 --- a/juniper/src/macros/tests/object.rs +++ b/juniper/src/macros/tests/object.rs @@ -84,7 +84,6 @@ graphql_object!(CommasWithTrailing: () |&self| { }); struct CommasOnMeta; - graphql_object!(CommasOnMeta: () |&self| { interfaces: [Interface], description: "A description", diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index 24b3a692..92a0ec97 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -53,7 +53,7 @@ impl Concrete { } } -graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| { +graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| { instance_resolvers: |&_| { &Concrete => match *self { CustomName::Concrete(ref c) => Some(c) } } From 520cac29a0debb0ce466acb0a242aa2776553082 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 13 May 2019 12:37:22 +0200 Subject: [PATCH 14/15] (codegen) Allow #[deprecated] on field in impl_object --- juniper/src/macros/tests/field.rs | 48 +++++++++++++++++++++++++++++++ juniper_codegen/src/util.rs | 44 +++++++++++++++++++--------- 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index eea4c110..d023a768 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -33,6 +33,16 @@ impl Root { 0 } + #[deprecated] + fn deprecated_outer() -> bool { + true + } + + #[deprecated(note = "Deprecation Reason")] + fn deprecated_outer_with_reason() -> bool { + true + } + #[graphql(deprecated = "Deprecation reason")] fn deprecated() -> i32 { 0 @@ -275,6 +285,44 @@ fn introspect_interface_field_description() { }); } +#[test] +fn introspect_object_field_deprecated_outer() { + run_field_info_query("Root", "deprecatedOuter", |field| { + assert_eq!( + field.get_field_value("name"), + Some(&Value::scalar("deprecatedOuter")) + ); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!( + field.get_field_value("isDeprecated"), + Some(&Value::scalar(true)) + ); + assert_eq!( + field.get_field_value("deprecationReason"), + Some(&Value::null()), + ); + }); +} + +#[test] +fn introspect_object_field_deprecated_outer_with_reason() { + run_field_info_query("Root", "deprecatedOuterWithReason", |field| { + assert_eq!( + field.get_field_value("name"), + Some(&Value::scalar("deprecatedOuterWithReason")) + ); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!( + field.get_field_value("isDeprecated"), + Some(&Value::scalar(true)) + ); + assert_eq!( + field.get_field_value("deprecationReason"), + Some(&Value::scalar("Deprecation Reason")), + ); + }); +} + #[test] fn introspect_object_field_deprecated() { run_field_info_query("Root", "deprecated", |field| { diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index 44a65fbb..7c8f8632 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -76,14 +76,23 @@ pub fn get_deprecated(attrs: &Vec) -> Option { fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr { for meta in &list.nested { match meta { - &NestedMeta::Meta(Meta::NameValue(ref nv)) if nv.ident == "note" => match &nv.lit { - &Lit::Str(ref strlit) => { - return DeprecationAttr { - reason: Some(strlit.value().to_string()), - }; + &NestedMeta::Meta(Meta::NameValue(ref nv)) => { + if nv.ident == "note" { + match &nv.lit { + &Lit::Str(ref strlit) => { + return DeprecationAttr { + reason: Some(strlit.value()), + }; + } + _ => panic!("deprecated attribute note value only has string literal"), + } + } else { + panic!( + "Unrecognized setting on #[deprecated(..)] attribute: {}", + nv.ident + ); } - _ => panic!("deprecated attribute note value only has string literal"), - }, + } _ => {} } } @@ -434,7 +443,7 @@ pub enum FieldAttributeParseMode { enum FieldAttribute { Name(syn::LitStr), Description(syn::LitStr), - Deprecation(Option), + Deprecation(DeprecationAttr), Skip(syn::Ident), Arguments(HashMap), } @@ -466,11 +475,13 @@ impl parse::Parse for FieldAttribute { "deprecated" | "deprecation" => { let reason = if input.peek(Token![=]) { input.parse::()?; - Some(input.parse()?) + Some(input.parse::()?.value()) } else { None }; - Ok(FieldAttribute::Deprecation(reason)) + Ok(FieldAttribute::Deprecation(DeprecationAttr { + reason: reason, + })) } "skip" => Ok(FieldAttribute::Skip(ident)), "arguments" => { @@ -525,10 +536,8 @@ impl parse::Parse for FieldAttributes { FieldAttribute::Description(name) => { output.description = Some(name.value()); } - FieldAttribute::Deprecation(reason_opt) => { - output.deprecation = Some(DeprecationAttr { - reason: reason_opt.map(|val| val.value()), - }); + FieldAttribute::Deprecation(attr) => { + output.deprecation = Some(attr); } FieldAttribute::Skip(_) => { output.skip = true; @@ -553,6 +562,7 @@ impl FieldAttributes { _mode: FieldAttributeParseMode, ) -> syn::parse::Result { let doc_comment = get_doc_comment(&attrs); + let deprecation = get_deprecated(&attrs); let attr_opt = attrs .into_iter() @@ -562,9 +572,15 @@ impl FieldAttributes { Some(attr) => syn::parse(attr.tts.into())?, None => Self::default(), }; + + // Check for regular doc comment. if output.description.is_none() { output.description = doc_comment; } + if output.deprecation.is_none() { + output.deprecation = deprecation; + } + Ok(output) } From 29025e6cae4a249fa56017dcf16b95ee4e89363e Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 13 May 2019 18:33:45 +0200 Subject: [PATCH 15/15] Rename impl_object to object. --- docs/book/content/advanced/introspection.md | 2 +- .../content/advanced/non_struct_objects.md | 2 +- .../content/advanced/objects_and_generics.md | 4 +-- docs/book/content/quickstart.md | 12 ++++---- .../content/schema/schemas_and_mutations.md | 6 ++-- docs/book/content/servers/iron.md | 4 +-- docs/book/content/types/input_objects.md | 4 +-- .../content/types/objects/complex_fields.md | 10 +++---- .../content/types/objects/error_handling.md | 4 +-- .../content/types/objects/using_contexts.md | 2 +- .../src/codegen/derive_object.rs | 2 +- .../juniper_tests/src/custom_scalar.rs | 2 +- juniper/CHANGELOG.md | 4 +-- juniper/src/executor_tests/directives.rs | 2 +- juniper/src/executor_tests/enums.rs | 2 +- juniper/src/executor_tests/executor.rs | 22 +++++++-------- .../src/executor_tests/interfaces_unions.rs | 12 ++++---- .../src/executor_tests/introspection/enums.rs | 2 +- .../introspection/input_object.rs | 2 +- .../src/executor_tests/introspection/mod.rs | 2 +- juniper/src/executor_tests/variables.rs | 2 +- juniper/src/integrations/chrono.rs | 2 +- juniper/src/lib.rs | 5 ++-- juniper/src/macros/interface.rs | 4 +-- juniper/src/macros/object.rs | 2 +- juniper/src/macros/tests/args.rs | 2 +- juniper/src/macros/tests/field.rs | 2 +- juniper/src/macros/tests/impl_object.rs | 12 ++++---- juniper/src/macros/tests/interface.rs | 4 +-- juniper/src/macros/tests/scalar.rs | 2 +- juniper/src/macros/tests/union.rs | 4 +-- juniper/src/parser/tests/value.rs | 2 +- juniper/src/schema/schema.rs | 12 ++++---- juniper/src/tests/schema.rs | 6 ++-- juniper_codegen/src/impl_object.rs | 8 +++--- juniper_codegen/src/lib.rs | 28 +++++++++---------- juniper_codegen/src/util.rs | 2 +- juniper_iron/src/lib.rs | 4 +-- juniper_warp/src/lib.rs | 2 +- 39 files changed, 103 insertions(+), 104 deletions(-) diff --git a/docs/book/content/advanced/introspection.md b/docs/book/content/advanced/introspection.md index 66949761..a8675b03 100644 --- a/docs/book/content/advanced/introspection.md +++ b/docs/book/content/advanced/introspection.md @@ -44,7 +44,7 @@ impl juniper::Context for Context {} struct Query; -#[juniper::impl_object( +#[juniper::object( Context = Context, )] impl Query { diff --git a/docs/book/content/advanced/non_struct_objects.md b/docs/book/content/advanced/non_struct_objects.md index af6b7342..bb23c28a 100644 --- a/docs/book/content/advanced/non_struct_objects.md +++ b/docs/book/content/advanced/non_struct_objects.md @@ -23,7 +23,7 @@ enum SignUpResult { Error(Vec), } -#[juniper::impl_object] +#[juniper::object] impl SignUpResult { fn user(&self) -> Option<&User> { match *self { diff --git a/docs/book/content/advanced/objects_and_generics.md b/docs/book/content/advanced/objects_and_generics.md index 9c91734b..87b98437 100644 --- a/docs/book/content/advanced/objects_and_generics.md +++ b/docs/book/content/advanced/objects_and_generics.md @@ -25,7 +25,7 @@ struct ValidationError { # #[allow(dead_code)] struct MutationResult(Result>); -#[juniper::impl_object( +#[juniper::object( name = "UserResult", )] impl MutationResult { @@ -38,7 +38,7 @@ impl MutationResult { } } -#[juniper::impl_object( +#[juniper::object( name = "ForumPostResult", )] impl MutationResult { diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md index c5e2c69b..21917716 100644 --- a/docs/book/content/quickstart.md +++ b/docs/book/content/quickstart.md @@ -20,7 +20,7 @@ naturally map to GraphQL features, such as `Option`, `Vec`, `Box`, For more advanced mappings, Juniper provides multiple macros to map your Rust types to a GraphQL schema. The most important one is the -[impl_object][jp_impl_object] procedural macro that is used for declaring an object with +[object][jp_object] procedural macro that is used for declaring an object with resolvers, which you will use for the `Query` and `Mutation` roots. ```rust @@ -60,7 +60,7 @@ struct NewHuman { } // Now, we create our root Query and Mutation types with resolvers by using the -// impl_object macro. +// object macro. // Objects can have contexts that allow accessing shared state like a database // pool. @@ -74,7 +74,7 @@ impl juniper::Context for Context {} struct Query; -#[juniper::impl_object( +#[juniper::object( // Here we specify the context type for the object. // We need to do this in every type that // needs access to the context. @@ -105,7 +105,7 @@ impl Query { struct Mutation; -#[juniper::impl_object( +#[juniper::object( Context = Context, )] impl Mutation { @@ -156,7 +156,7 @@ impl juniper::Context for Ctx {} struct Query; -#[juniper::impl_object( +#[juniper::object( Context = Ctx, )] impl Query { @@ -198,4 +198,4 @@ fn main() { [rocket]: servers/rocket.md [iron]: servers/iron.md [tutorial]: ./tutorial.html -[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.impl_object.html +[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.object.html diff --git a/docs/book/content/schema/schemas_and_mutations.md b/docs/book/content/schema/schemas_and_mutations.md index c2a90417..543bc899 100644 --- a/docs/book/content/schema/schemas_and_mutations.md +++ b/docs/book/content/schema/schemas_and_mutations.md @@ -20,14 +20,14 @@ object somewhere but never references it, it will not be exposed in a schema. ## The query root The query root is just a GraphQL object. You define it like any other GraphQL -object in Juniper, most commonly using the `impl_object` proc macro: +object in Juniper, most commonly using the `object` proc macro: ```rust # use juniper::FieldResult; # #[derive(juniper::GraphQLObject)] struct User { name: String } struct Root; -#[juniper::impl_object] +#[juniper::object] impl Root { fn userWithUsername(username: String) -> FieldResult> { // Look up user in database... @@ -48,7 +48,7 @@ usually performs some mutating side-effect, such as updating a database. # #[derive(juniper::GraphQLObject)] struct User { name: String } struct Mutations; -#[juniper::impl_object] +#[juniper::object] impl Mutations { fn signUpUser(name: String, email: String) -> FieldResult { // Validate inputs and save user in database... diff --git a/docs/book/content/servers/iron.md b/docs/book/content/servers/iron.md index ec428889..da76ba75 100644 --- a/docs/book/content/servers/iron.md +++ b/docs/book/content/servers/iron.md @@ -47,7 +47,7 @@ fn context_factory(_: &mut Request) -> IronResult<()> { struct Root; -#[juniper::impl_object] +#[juniper::object] impl Root { fn foo() -> String { "Bar".to_owned() @@ -99,7 +99,7 @@ fn context_factory(req: &mut Request) -> IronResult { struct Root; -#[juniper::impl_object( +#[juniper::object( Context = Context, )] impl Root { diff --git a/docs/book/content/types/input_objects.md b/docs/book/content/types/input_objects.md index 837a3aed..0d5aa45b 100644 --- a/docs/book/content/types/input_objects.md +++ b/docs/book/content/types/input_objects.md @@ -14,7 +14,7 @@ struct Coordinate { struct Root; # #[derive(juniper::GraphQLObject)] struct User { name: String } -#[juniper::impl_object] +#[juniper::object] impl Root { fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec { // Send coordinate to database @@ -45,7 +45,7 @@ struct WorldCoordinate { struct Root; # #[derive(juniper::GraphQLObject)] struct User { name: String } -#[juniper::impl_object] +#[juniper::object] impl Root { fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec { // Send coordinate to database diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md index 81399756..771fba12 100644 --- a/docs/book/content/types/objects/complex_fields.md +++ b/docs/book/content/types/objects/complex_fields.md @@ -2,7 +2,7 @@ If you've got a struct that can't be mapped directly to GraphQL, that contains computed fields or circular structures, you have to use a more powerful tool: -the `impl_object` procedural macro. This macro lets you define GraphQL object +the `object` procedural macro. This macro lets you define GraphQL object fields in a Rust `impl` block for a type. Continuing with the example from the last chapter, this is how you would define `Person` using the macro: @@ -14,7 +14,7 @@ struct Person { age: i32, } -#[juniper::impl_object] +#[juniper::object] impl Person { fn name(&self) -> &str { self.name.as_str() @@ -43,7 +43,7 @@ struct House { inhabitants: Vec, } -#[juniper::impl_object] +#[juniper::object] impl House { // Creates the field inhabitantWithName(name), returning a nullable person fn inhabitant_with_name(&self, name: String) -> Option<&Person> { @@ -70,7 +70,7 @@ struct Person { website_url: String, } -#[juniper::impl_object( +#[juniper::object( // With this attribtue you can change the public GraphQL name of the type. name = "PersonObject", )] @@ -96,4 +96,4 @@ GraphQL fields expose more features than Rust's standard method syntax gives us: * Per-argument descriptions These, and more features, are described more thorougly in [the reference -documentation](https://docs.rs/juniper/latest/juniper/macro.impl_object.html). +documentation](https://docs.rs/juniper/latest/juniper/macro.object.html). diff --git a/docs/book/content/types/objects/error_handling.md b/docs/book/content/types/objects/error_handling.md index 8e4b047d..fe136fba 100644 --- a/docs/book/content/types/objects/error_handling.md +++ b/docs/book/content/types/objects/error_handling.md @@ -25,7 +25,7 @@ struct Example { filename: PathBuf, } -#[juniper::impl_object] +#[juniper::object] impl Example { fn contents() -> FieldResult { let mut file = File::open(&self.filename)?; @@ -143,7 +143,7 @@ struct Example { whatever: Option, } -#[juniper::impl_object] +#[juniper::object] impl Example { fn whatever() -> Result { if let Some(value) = self.whatever { diff --git a/docs/book/content/types/objects/using_contexts.md b/docs/book/content/types/objects/using_contexts.md index ef210c8b..676dc6a0 100644 --- a/docs/book/content/types/objects/using_contexts.md +++ b/docs/book/content/types/objects/using_contexts.md @@ -57,7 +57,7 @@ struct User { // Assign Database as the context type for User -#[juniper::impl_object( +#[juniper::object( Context = Database, )] impl User { diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index ac78b521..88c5cdc1 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -80,7 +80,7 @@ struct WithCustomContext { a: bool, } -#[juniper::impl_object] +#[juniper::object] impl Query { fn obj() -> Obj { Obj { diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 07c37f72..a08c5746 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -151,7 +151,7 @@ juniper::graphql_scalar!(i64 as "Long" where Scalar = MyScalarValue { struct TestType; -#[juniper::impl_object( +#[juniper::object( Scalar = MyScalarValue )] impl TestType { diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 0a0119fa..d3e3660a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,9 +1,9 @@ # master -### impl_object macro +### object macro The `graphql_object!` macro is deprecated and will be removed in the future. -It is replaced by the new [impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) procedural macro. +It is replaced by the new [object](https://docs.rs/juniper/latest/juniper/macro.object.html) procedural macro. [#333](https://github.com/graphql-rust/juniper/pull/333) diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs index e5648f3e..04727687 100644 --- a/juniper/src/executor_tests/directives.rs +++ b/juniper/src/executor_tests/directives.rs @@ -5,7 +5,7 @@ use crate::value::{DefaultScalarValue, Object, Value}; struct TestType; -#[crate::impl_object_internal] +#[crate::object_internal] impl TestType { fn a() -> &str { "a" diff --git a/juniper/src/executor_tests/enums.rs b/juniper/src/executor_tests/enums.rs index 6738f531..7e9cec1b 100644 --- a/juniper/src/executor_tests/enums.rs +++ b/juniper/src/executor_tests/enums.rs @@ -17,7 +17,7 @@ enum Color { } struct TestType; -#[crate::impl_object_internal] +#[crate::object_internal] impl TestType { fn to_string(color: Color) -> String { format!("Color::{:?}", color) diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index e8fd0870..524c7402 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -7,7 +7,7 @@ mod field_execution { struct DataType; struct DeepDataType; - #[crate::impl_object_internal] + #[crate::object_internal] impl DataType { fn a() -> &str { "Apple" @@ -37,7 +37,7 @@ mod field_execution { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl DeepDataType { fn a() -> &str { "Already Been Done" @@ -162,7 +162,7 @@ mod merge_parallel_fragments { struct Type; - #[crate::impl_object_internal] + #[crate::object_internal] impl Type { fn a() -> &str { "Apple" @@ -246,7 +246,7 @@ mod merge_parallel_inline_fragments { struct Type; struct Other; - #[crate::impl_object_internal] + #[crate::object_internal] impl Type { fn a() -> &str { "Apple" @@ -265,7 +265,7 @@ mod merge_parallel_inline_fragments { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl Other { fn a() -> &str { "Apple" @@ -396,7 +396,7 @@ mod threads_context_correctly { impl Context for TestContext {} - #[crate::impl_object_internal( + #[crate::object_internal( Context = TestContext, )] impl Schema { @@ -462,7 +462,7 @@ mod dynamic_context_switching { struct ItemRef; - #[crate::impl_object_internal(Context = OuterContext)] + #[crate::object_internal(Context = OuterContext)] impl Schema { fn item_opt(context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> { executor.context().items.get(&key).map(|c| (c, ItemRef)) @@ -492,7 +492,7 @@ mod dynamic_context_switching { } } - #[crate::impl_object_internal(Context = InnerContext)] + #[crate::object_internal(Context = InnerContext)] impl ItemRef { fn value(context: &InnerContext) -> String { context.value.clone() @@ -801,7 +801,7 @@ mod propagates_errors_to_nullable_fields { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl Schema { fn inner() -> Inner { Inner @@ -814,7 +814,7 @@ mod propagates_errors_to_nullable_fields { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl Inner { fn nullable_field() -> Option { Some(Inner) @@ -1068,7 +1068,7 @@ mod named_operations { struct Schema; - #[crate::impl_object_internal] + #[crate::object_internal] impl Schema { fn a() -> &str { "b" diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 851d7197..d5ab086b 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -37,7 +37,7 @@ mod interface { } } - #[crate::impl_object_internal( + #[crate::object_internal( interfaces = [&Pet] )] impl Dog { @@ -63,7 +63,7 @@ mod interface { } } - #[crate::impl_object_internal( + #[crate::object_internal( interfaces = [&Pet] )] impl Cat { @@ -79,7 +79,7 @@ mod interface { pets: Vec>, } - #[crate::impl_object_internal] + #[crate::object_internal] impl Schema { fn pets(&self) -> Vec<&Pet> { self.pets.iter().map(|p| p.as_ref()).collect() @@ -188,7 +188,7 @@ mod union { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl Dog { fn name(&self) -> &str { &self.name @@ -209,7 +209,7 @@ mod union { } } - #[crate::impl_object_internal] + #[crate::object_internal] impl Cat { fn name(&self) -> &str { &self.name @@ -223,7 +223,7 @@ mod union { pets: Vec>, } - #[crate::impl_object_internal] + #[crate::object_internal] impl Schema { fn pets(&self) -> Vec<&Pet> { self.pets.iter().map(|p| p.as_ref()).collect() diff --git a/juniper/src/executor_tests/introspection/enums.rs b/juniper/src/executor_tests/introspection/enums.rs index c9e88b6a..0ffee96c 100644 --- a/juniper/src/executor_tests/introspection/enums.rs +++ b/juniper/src/executor_tests/introspection/enums.rs @@ -64,7 +64,7 @@ enum EnumDeprecation { struct Root; -#[crate::impl_object_internal] +#[crate::object_internal] impl Root { fn default_name() -> DefaultName { DefaultName::Foo diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index 388fa556..1cf1294e 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -79,7 +79,7 @@ struct FieldWithDefaults { field_two: i32, } -#[crate::impl_object_internal] +#[crate::object_internal] impl Root { fn test_field( a1: DefaultName, diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index b93fccd2..ff9f28f6 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -52,7 +52,7 @@ graphql_interface!(Interface: () as "SampleInterface" |&self| { }); /// The root query object in the schema -#[crate::impl_object_internal( +#[crate::object_internal( interfaces = [&Interface] Scalar = crate::DefaultScalarValue, )] diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 0bf99854..7b8dd791 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -62,7 +62,7 @@ struct InputWithDefaults { a: i32, } -#[crate::impl_object_internal] +#[crate::object_internal] impl TestType { fn field_with_object_input(input: Option) -> String { format!("{:?}", input) diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index d52bc2dc..dada374b 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -210,7 +210,7 @@ mod integration_test { fn test_serialization() { struct Root; - #[crate::impl_object_internal] + #[crate::object_internal] impl Root { fn exampleNaiveDate() -> NaiveDate { NaiveDate::from_ymd(2015, 3, 14) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 9841e3ce..1a2943d4 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -109,14 +109,13 @@ extern crate uuid; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - impl_object, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, ScalarValue, + object, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, ScalarValue, }; // Internal macros are not exported, // but declared at the root to make them easier to use. #[allow(unused_imports)] use juniper_codegen::{ - impl_object_internal, GraphQLEnumInternal, GraphQLInputObjectInternal, - GraphQLScalarValueInternal, + object_internal, GraphQLEnumInternal, GraphQLInputObjectInternal, GraphQLScalarValueInternal, }; #[macro_use] diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs index 0dfeed42..44d1c398 100644 --- a/juniper/src/macros/interface.rs +++ b/juniper/src/macros/interface.rs @@ -61,12 +61,12 @@ impl Character for Droid { fn id(&self) -> &str { &self.id } } -#[juniper::impl_object(Context = Database)] +#[juniper::object(Context = Database)] impl Human { fn id(&self) -> &str { &self.id } } -#[juniper::impl_object( +#[juniper::object( name = "Droid", Context = Database, )] diff --git a/juniper/src/macros/object.rs b/juniper/src/macros/object.rs index 02c27d31..7d6733e8 100644 --- a/juniper/src/macros/object.rs +++ b/juniper/src/macros/object.rs @@ -2,7 +2,7 @@ ## DEPRECATION WARNING The `graphql_object!` macro is deprecated and will be removed soon. -Use the new[impl_object](https://docs.rs/juniper/latest/juniper/macro.impl_object.html) macro instead. +Use the new[object](https://docs.rs/juniper/latest/juniper/macro.object.html) macro instead. Expose GraphQL objects diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 9874249a..1abda753 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -26,7 +26,7 @@ struct Point { x: i32, } -#[crate::impl_object_internal] +#[crate::object_internal] impl Root { fn simple() -> i32 { 0 diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index d023a768..29b9e3b6 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -20,7 +20,7 @@ Syntax to validate: */ -#[crate::impl_object_internal( +#[crate::object_internal( interfaces = [&Interface], )] impl Root { diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 72ae6ef5..81427cd7 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -12,7 +12,7 @@ struct WithLifetime<'a> { value: &'a str, } -#[crate::impl_object_internal(Context=Context)] +#[crate::object_internal(Context=Context)] impl<'a> WithLifetime<'a> { fn value(&'a self) -> &'a str { self.value @@ -21,7 +21,7 @@ impl<'a> WithLifetime<'a> { struct WithContext; -#[crate::impl_object_internal(Context=Context)] +#[crate::object_internal(Context=Context)] impl WithContext { fn ctx(ctx: &Context) -> bool { ctx.flag1 @@ -33,7 +33,7 @@ struct Query { b: bool, } -#[crate::impl_object_internal( +#[crate::object_internal( scalar = crate::DefaultScalarValue, name = "Query", context = Context, @@ -106,7 +106,7 @@ impl<'a> Query { #[derive(Default)] struct Mutation; -#[crate::impl_object_internal(context = Context)] +#[crate::object_internal(context = Context)] impl Mutation { fn empty() -> bool { true @@ -114,7 +114,7 @@ impl Mutation { } #[test] -fn impl_object_introspect() { +fn object_introspect() { let res = util::run_info_query::("Query"); assert_eq!( res, @@ -222,7 +222,7 @@ fn impl_object_introspect() { } #[test] -fn impl_object_query() { +fn object_query() { let doc = r#" query { withSelf diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index c09a09f5..3d083d16 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -42,7 +42,7 @@ struct ResolversWithTrailingComma; struct Root; -#[crate::impl_object_internal] +#[crate::object_internal] impl Concrete { fn simple() -> i32 { 0 @@ -111,7 +111,7 @@ graphql_interface!(ResolversWithTrailingComma: () |&self| { field simple() -> i32 { 0 } }); -#[crate::impl_object_internal] +#[crate::object_internal] impl<'a> Root { fn custom_name() -> CustomName { CustomName {} diff --git a/juniper/src/macros/tests/scalar.rs b/juniper/src/macros/tests/scalar.rs index 48a9af2e..febdb1bc 100644 --- a/juniper/src/macros/tests/scalar.rs +++ b/juniper/src/macros/tests/scalar.rs @@ -78,7 +78,7 @@ graphql_scalar!(ScalarDescription { } }); -#[crate::impl_object_internal] +#[crate::object_internal] impl Root { fn default_name() -> DefaultName { DefaultName(0) diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index 92a0ec97..fdc8c905 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -46,7 +46,7 @@ enum ResolversWithTrailingComma { struct Root; -#[crate::impl_object_internal] +#[crate::object_internal] impl Concrete { fn simple() -> i32 { 123 @@ -99,7 +99,7 @@ graphql_union!(ResolversWithTrailingComma: () |&self| { description: "A description" }); -#[crate::impl_object_internal] +#[crate::object_internal] impl<'a> Root { fn custom_name() -> CustomName { CustomName::Concrete(Concrete) diff --git a/juniper/src/parser/tests/value.rs b/juniper/src/parser/tests/value.rs index 8606b800..a59c3825 100644 --- a/juniper/src/parser/tests/value.rs +++ b/juniper/src/parser/tests/value.rs @@ -31,7 +31,7 @@ struct Foo { struct Query; -#[crate::impl_object_internal(Scalar = S)] +#[crate::object_internal(Scalar = S)] impl<'a, S> Query where S: crate::ScalarValue + 'a, diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 1ff43a13..5ca0fdd6 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -73,7 +73,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__Schema" Context = SchemaType<'a, S>, Scalar = S, @@ -111,7 +111,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__Type" Context = SchemaType<'a, S>, Scalar = S, @@ -242,7 +242,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__Field", Context = SchemaType<'a, S>, Scalar = S, @@ -279,7 +279,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__InputValue", Context = SchemaType<'a, S>, Scalar = S, @@ -306,7 +306,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__EnumValue", Scalar = S, )] @@ -331,7 +331,7 @@ where } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "__Directive", Context = SchemaType<'a, S>, Scalar = S, diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index d88faa82..a39abd59 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -29,7 +29,7 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| { } }); -#[crate::impl_object_internal( +#[crate::object_internal( Context = Database, Scalar = crate::DefaultScalarValue, interfaces = [&dyn Character], @@ -62,7 +62,7 @@ impl<'a> &'a Human { } } -#[crate::impl_object_internal( +#[crate::object_internal( Context = Database, Scalar = crate::DefaultScalarValue, interfaces = [&dyn Character], @@ -95,7 +95,7 @@ impl<'a> &'a Droid { } } -#[crate::impl_object_internal( +#[crate::object_internal( name = "Query", Context = Database, Scalar = crate::DefaultScalarValue, diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index baff884b..4479d89a 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -2,8 +2,8 @@ use crate::util; use proc_macro::TokenStream; use quote::quote; -/// Generate code for the juniper::impl_object macro. -pub fn build_impl_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { +/// Generate code for the juniper::object macro. +pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { let impl_attrs = match syn::parse::(args) { Ok(attrs) => attrs, Err(e) => { @@ -62,11 +62,11 @@ pub fn build_impl_object(args: TokenStream, body: TokenStream, is_internal: bool .ident .to_string(), _ => { - panic!("Could not determine a name for the object type: specify one with #[juniper::impl_object(name = \"SomeName\")"); + panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")"); } }, _ => { - panic!("Could not determine a name for the object type: specify one with #[juniper::impl_object(name = \"SomeName\")"); + panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")"); } }, }; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 66e334bb..d9be341d 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -76,19 +76,19 @@ pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream { } /** -The `impl_object` proc macro is the primary way of defining GraphQL resolvers +The `object` proc macro is the primary way of defining GraphQL resolvers that can not be implemented with the GraphQLObject derive. It enables you to write GraphQL field resolvers for a type by declaring a regular Rust `impl` block. Under the hood, the procedural macro implements the GraphQLType trait. -`impl_object` comes with many features that allow customization of +`object` comes with many features that allow customization of your fields, all of which are detailed below. ### Getting Started -This simple example will show you the most basic use of `impl_object`. +This simple example will show you the most basic use of `object`. More advanced use cases are introduced step by step. ``` @@ -96,7 +96,7 @@ More advanced use cases are introduced step by step. struct Query; // We prefix the impl Block with the procedural macro. -#[juniper::impl_object] +#[juniper::object] impl Query { // A **warning**: only GraphQL fields can be specified in this impl block. @@ -110,7 +110,7 @@ impl Query { // - basic scalar types like bool, &str, String, i32, f64 // - GraphQL compatible wrappers like Option<_>, Vec<_>. // - types which use the `#derive[juniper::GraphQLObject]` - // - `impl_object` structs. + // - `object` structs. // // An important note regarding naming: // By default, field names will be converted to camel case. @@ -147,7 +147,7 @@ impl Person { } } -#[juniper::impl_object] +#[juniper::object] impl Person { fn first_name(&self) -> &str { &self.first_name @@ -187,7 +187,7 @@ impl juniper::Context for Context {} struct Query; -#[juniper::impl_object( +#[juniper::object( // Here we specify the context type for this object. Context = Context, )] @@ -217,7 +217,7 @@ struct InternalQuery; // Doc comments can be used to specify graphql documentation. /// GRAPHQL DOCUMENTATION. /// More info for GraphQL users.... -#[juniper::impl_object( +#[juniper::object( // You can rename the type for GraphQL by specifying the name here. name = "Query", // You can also specify a description here. @@ -280,7 +280,7 @@ struct WithLifetime<'a> { value: &'a str, } -#[juniper::impl_object] +#[juniper::object] impl<'a> WithLifetime<'a> { fn value(&self) -> &str { self.value @@ -301,7 +301,7 @@ You can easily specify a custom scalar though. struct Query; -#[juniper::impl_object( +#[juniper::object( Scalar = MyCustomScalar, )] impl Query { @@ -311,15 +311,15 @@ impl Query { */ #[proc_macro_attribute] -pub fn impl_object(args: TokenStream, input: TokenStream) -> TokenStream { - let gen = impl_object::build_impl_object(args, input, false); +pub fn object(args: TokenStream, input: TokenStream) -> TokenStream { + let gen = impl_object::build_object(args, input, false); gen.into() } /// A proc macro for defining a GraphQL object. #[doc(hidden)] #[proc_macro_attribute] -pub fn impl_object_internal(args: TokenStream, input: TokenStream) -> TokenStream { - let gen = impl_object::build_impl_object(args, input, true); +pub fn object_internal(args: TokenStream, input: TokenStream) -> TokenStream { + let gen = impl_object::build_object(args, input, true); gen.into() } diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index 7c8f8632..e1eb7cf8 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -508,7 +508,7 @@ pub struct FieldAttributes { pub deprecation: Option, // Only relevant for GraphQLObject derive. pub skip: bool, - /// Only relevant for impl_object macro. + /// Only relevant for object macro. pub arguments: HashMap, } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index ea546a77..f97f6a5e 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -37,7 +37,7 @@ use juniper::{Context, EmptyMutation}; # struct QueryRoot; # struct Database { users: HashMap } # -# #[juniper::impl_object( Context = Database )] +# #[juniper::object( Context = Database )] # impl User { # fn id(&self) -> FieldResult<&String> { # Ok(&self.id) @@ -54,7 +54,7 @@ use juniper::{Context, EmptyMutation}; # } # } # -# #[juniper::impl_object( Context = Database )] +# #[juniper::object( Context = Database )] # impl QueryRoot { # fn user(context: &Database, id: String) -> FieldResult> { # Ok(executor.context().users.get(&id)) diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 9a458d78..3c1d442a 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -134,7 +134,7 @@ where /// /// struct QueryRoot; /// -/// #[juniper::impl_object( +/// #[juniper::object( /// Context = ExampleContext /// )] /// impl QueryRoot {