From f6e436a92e4053920b86379690dbd84789d6856a Mon Sep 17 00:00:00 2001 From: andrisak Date: Wed, 1 Apr 2020 21:37:45 +0200 Subject: [PATCH 1/2] First version of graphql_scalar proc macro #571 Signed-off-by: andrisak --- .../juniper_tests/src/codegen/impl_scalar.rs | 42 ++++ .../juniper_tests/src/codegen/mod.rs | 1 + juniper/src/lib.rs | 4 +- juniper_codegen/src/impl_scalar.rs | 235 ++++++++++++++++++ juniper_codegen/src/lib.rs | 7 + 5 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/impl_scalar.rs create mode 100644 juniper_codegen/src/impl_scalar.rs diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs new file mode 100644 index 000000000..19388c5bd --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -0,0 +1,42 @@ +use fnv::FnvHashMap; +#[cfg(test)] +use juniper::{ + self, parser::ScalarToken, DefaultScalarValue, GraphQLType, InputValue, ParseScalarResult, + ParseScalarValue, Value, +}; + +struct UserId(String); + +// TODO Trait that the macro handles, move to proper location! Naming? +trait ParseCustomScalarValue { + fn resolve(&self) -> Value; + fn from_input_value(value: &InputValue) -> Option; + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S>; +} + +#[juniper::graphql_scalar2(name = "MyCustomName", description = "My custom description")] +impl ParseCustomScalarValue for UserId { + fn resolve(&self) -> Value { + Value::scalar(self.0.to_owned()) + } + + fn from_input_value(value: &InputValue) -> Option { + value.as_string_value().map(|s| UserId(s.to_owned())) + } + + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + >::from_str(value) + } +} + +#[test] +fn test_generated_meta() { + let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default()); + let meta = >::meta(&(), &mut registry); + + assert_eq!(meta.name(), Some("MyCustomName")); + assert_eq!( + meta.description(), + Some(&"My custom description".to_string()) + ); +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 1f5a2bb04..d3e4208ba 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -2,5 +2,6 @@ mod derive_enum; mod derive_input_object; mod derive_object; mod derive_object_with_raw_idents; +mod impl_scalar; mod impl_union; mod scalar_value_transparent; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 77bacb29c..f248669bb 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,8 +115,8 @@ extern crate bson; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_object, graphql_subscription, graphql_union, GraphQLEnum, GraphQLInputObject, - GraphQLObject, GraphQLScalarValue, + graphql_object, graphql_scalar2, graphql_subscription, graphql_union, GraphQLEnum, + GraphQLInputObject, GraphQLObject, GraphQLScalarValue, }; // Internal macros are not exported, // but declared at the root to make them easier to use. diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs new file mode 100644 index 000000000..c6ebe8a34 --- /dev/null +++ b/juniper_codegen/src/impl_scalar.rs @@ -0,0 +1,235 @@ +#![allow(clippy::collapsible_if)] + +use crate::util; +use proc_macro::TokenStream; +use quote::quote; + +#[derive(Debug)] +struct ScalarCodegenInput { + ident: Option, + resolve_body: Option, + from_input_value_arg: Option, + from_input_value_body: Option, + from_input_value_result: Option, + from_str_arg: Option, + from_str_body: Option, + from_str_result: Option, +} + +impl syn::parse::Parse for ScalarCodegenInput { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + let mut ident: Option = None; + let mut resolve_body: Option = None; + let mut from_input_value_arg: Option = None; + let mut from_input_value_body: Option = None; + let mut from_input_value_result: Option = None; + let mut from_str_arg: Option = None; + let mut from_str_body: Option = None; + let mut from_str_result: Option = None; + + let parse_custom_scalar_value_impl: syn::ItemImpl = input.parse()?; + + match *parse_custom_scalar_value_impl.self_ty { + syn::Type::Path(type_path) => match type_path.path.segments.first() { + Some(path_segment) => { + ident = Some(path_segment.ident.clone()); + } + _ => (), + }, + _ => (), + } + + for impl_item in parse_custom_scalar_value_impl.items { + match impl_item { + syn::ImplItem::Method(method) => match method.sig.ident.to_string().as_str() { + "resolve" => { + resolve_body = Some(method.block); + } + "from_input_value" => { + match method.sig.inputs.first() { + Some(fn_arg) => match fn_arg { + syn::FnArg::Typed(pat_type) => match &*pat_type.pat { + syn::Pat::Ident(pat_ident) => { + from_input_value_arg = Some(pat_ident.ident.clone()) + } + _ => (), + }, + _ => (), + }, + _ => (), + } + + match method.sig.output { + syn::ReturnType::Type(_, return_type) => { + from_input_value_result = Some(*return_type); + } + _ => (), + } + + from_input_value_body = Some(method.block); + } + "from_str" => { + match method.sig.inputs.first() { + Some(fn_arg) => match fn_arg { + syn::FnArg::Typed(pat_type) => match &*pat_type.pat { + syn::Pat::Ident(pat_ident) => { + from_str_arg = Some(pat_ident.ident.clone()) + } + _ => (), + }, + _ => (), + }, + _ => (), + } + + match method.sig.output { + syn::ReturnType::Type(_, return_type) => { + from_str_result = Some(*return_type); + } + _ => (), + } + + from_str_body = Some(method.block); + } + _ => (), + }, + _ => (), + }; + } + + Ok(ScalarCodegenInput { + ident, + resolve_body, + from_input_value_arg, + from_input_value_body, + from_input_value_result, + from_str_arg, + from_str_body, + from_str_result, + }) + } +} + +/// Generate code for the juniper::graphql_scalar proc macro. +pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { + let attrs = match syn::parse::(attributes) { + Ok(attrs) => attrs, + Err(e) => { + panic!("Invalid attributes:\n{}", e); + } + }; + + let input = syn::parse_macro_input!(body as ScalarCodegenInput); + let ident = input.ident.unwrap(); + let resolve_body = input.resolve_body.unwrap(); + let from_input_value_arg = input.from_input_value_arg.unwrap(); + let from_input_value_body = input.from_input_value_body.unwrap(); + let from_input_value_result = input.from_input_value_result.unwrap(); + let from_str_arg = input.from_str_arg.unwrap(); + let from_str_body = input.from_str_body.unwrap(); + let from_str_result = input.from_str_result.unwrap(); + + // TODO: Code below copied from derive_scalar_value.rs#impl_scalar_struct. REFACTOR! + + let name = attrs.name.unwrap_or_else(|| ident.to_string()); + + let crate_name = if is_internal { + quote!(crate) + } else { + quote!(juniper) + }; + + let description = match attrs.description { + Some(val) => quote!( .description( #val ) ), + None => quote!(), + }; + + let _async = quote!( + impl<__S> ::#crate_name::GraphQLTypeAsync<__S> for #ident + where + __S: #crate_name::ScalarValue + Send + Sync, + Self: #crate_name::GraphQLType<__S> + Send + Sync, + Self::Context: Send + Sync, + Self::TypeInfo: Send + Sync, + { + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [#crate_name::Selection<__S>]>, + executor: &'a #crate_name::Executor, + ) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> { + use #crate_name::GraphQLType; + use futures::future; + let v = self.resolve(info, selection_set, executor); + Box::pin(future::ready(v)) + } + } + ); + + quote!( + #_async + + impl #crate_name::GraphQLType for #ident + where + S: #crate_name::ScalarValue, + { + type Context = (); + type TypeInfo = (); + + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #crate_name::Registry<'r, S>, + ) -> #crate_name::meta::MetaType<'r, S> + where + S: 'r, + { + registry.build_scalar_type::(info) + #description + .into_meta() + } + + fn resolve( + &self, + info: &(), + selection: Option<&[#crate_name::Selection]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult { + #crate_name::GraphQLType::resolve(&self.0, info, selection, executor) + } + } + + impl #crate_name::ToInputValue for #ident + where + S: #crate_name::ScalarValue, + { + fn to_input_value(&self) -> #crate_name::InputValue { + let v = #resolve_body; + #crate_name::ToInputValue::to_input_value(&v) + } + } + + impl #crate_name::FromInputValue for #ident + where + S: #crate_name::ScalarValue, + { + fn from_input_value(#from_input_value_arg: &#crate_name::InputValue) -> #from_input_value_result { + #from_input_value_body + } + } + + impl #crate_name::ParseScalarValue for #ident + where + S: #crate_name::ScalarValue, + { + fn from_str<'a>( + #from_str_arg: #crate_name::parser::ScalarToken<'a>, + ) -> #from_str_result { + #from_str_body + } + } + ).into() +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 562e4a2f7..14e7e04fc 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -16,6 +16,7 @@ mod derive_input_object; mod derive_object; mod derive_scalar_value; mod impl_object; +mod impl_scalar; mod impl_union; use proc_macro::TokenStream; @@ -385,6 +386,12 @@ pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenSt impl_object::build_object(args, input, true) } +/// A proc macro for defining a custom scalar value. +#[proc_macro_attribute] +pub fn graphql_scalar2(args: TokenStream, input: TokenStream) -> TokenStream { + impl_scalar::build_scalar(args, input, false) +} + /// A proc macro for defining a GraphQL subscription. #[proc_macro_attribute] pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream { From 396691bd9f10857bc8f550b3e71484ade708e504 Mon Sep 17 00:00:00 2001 From: andrisak Date: Sat, 11 Apr 2020 16:28:59 +0200 Subject: [PATCH 2/2] Replaced the old macro with the new proc macro. Updated documentation. Signed-off-by: andrisak --- docs/book/content/types/scalars.md | 20 +- .../juniper_tests/src/codegen/impl_scalar.rs | 258 ++++++++++-- .../juniper_tests/src/custom_scalar.rs | 19 +- juniper/CHANGELOG.md | 2 + juniper/src/ast.rs | 2 +- .../src/executor_tests/introspection/mod.rs | 13 +- juniper/src/executor_tests/variables.rs | 13 +- juniper/src/integrations/bson.rs | 38 +- juniper/src/integrations/chrono.rs | 74 ++-- juniper/src/integrations/url.rs | 19 +- juniper/src/integrations/uuid.rs | 20 +- juniper/src/lib.rs | 7 +- juniper/src/macros/mod.rs | 2 - juniper/src/macros/scalar.rs | 366 ------------------ juniper/src/macros/tests/mod.rs | 1 - juniper/src/macros/tests/scalar.rs | 241 ------------ juniper/src/types/scalars.rs | 154 +++++--- juniper_codegen/src/impl_scalar.rs | 281 +++++++++----- juniper_codegen/src/lib.rs | 59 ++- 19 files changed, 693 insertions(+), 896 deletions(-) delete mode 100644 juniper/src/macros/scalar.rs delete mode 100644 juniper/src/macros/tests/scalar.rs diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 70938fc25..c2d9ca508 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -12,7 +12,7 @@ There are two ways to define custom scalars. * For simple scalars that just wrap a primitive type, you can use the newtype pattern with a custom derive. * For more advanced use cases with custom validation, you can use -the `graphql_scalar!` macro. +the `graphql_scalar` proc macro. ## Built-in scalars @@ -79,7 +79,7 @@ pub struct UserId(i32); ## Custom scalars For more complex situations where you also need custom parsing or validation, -you can use the `graphql_scalar!` macro. +you can use the `graphql_scalar` proc macro. Typically, you represent your custom scalars as strings. @@ -112,26 +112,28 @@ The example below is used just for illustration. use juniper::{Value, ParseScalarResult, ParseScalarValue}; use date::Date; -juniper::graphql_scalar!(Date where Scalar = { - description: "Date" - +#[juniper::graphql_scalar(description = "Date")] +impl GraphQLScalar for Date +where + S: ScalarValue +{ // Define how to convert your custom scalar into a primitive type. - resolve(&self) -> Value { + fn resolve(&self) -> Value { Value::scalar(self.to_string()) } // Define how to parse a primitive type into your custom scalar. - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_scalar_value() .and_then(|v| v.as_str()) .and_then(|s| s.parse().ok()) } // Define how to parse a string value. - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { >::from_str(value) } -}); +} # fn main() {} ``` diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 19388c5bd..dc732b1a6 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -1,42 +1,248 @@ -use fnv::FnvHashMap; -#[cfg(test)] use juniper::{ - self, parser::ScalarToken, DefaultScalarValue, GraphQLType, InputValue, ParseScalarResult, - ParseScalarValue, Value, + DefaultScalarValue, EmptyMutation, EmptySubscription, Object, ParseScalarResult, + ParseScalarValue, RootNode, Value, Variables, }; -struct UserId(String); +struct DefaultName(i32); +struct OtherOrder(i32); +struct Named(i32); +struct ScalarDescription(i32); -// TODO Trait that the macro handles, move to proper location! Naming? -trait ParseCustomScalarValue { - fn resolve(&self) -> Value; - fn from_input_value(value: &InputValue) -> Option; - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S>; +struct Root; + +/* + +Syntax to validate: + +* Default name vs. custom name +* Description vs. no description on the scalar + +*/ + +#[juniper::graphql_scalar] +impl GraphQLScalar for DefaultName +where + S: juniper::ScalarValue, +{ + fn resolve(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input_value(v: &juniper::InputValue) -> Option { + v.as_scalar_value() + .and_then(|s| s.as_int()) + .map(|i| DefaultName(i)) + } + + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, S> { + >::from_str(value) + } } -#[juniper::graphql_scalar2(name = "MyCustomName", description = "My custom description")] -impl ParseCustomScalarValue for UserId { +#[juniper::graphql_scalar] +impl GraphQLScalar for OtherOrder { fn resolve(&self) -> Value { - Value::scalar(self.0.to_owned()) + Value::scalar(self.0) } - fn from_input_value(value: &InputValue) -> Option { - value.as_string_value().map(|s| UserId(s.to_owned())) + fn from_input_value(v: &juniper::InputValue) -> Option { + v.as_scalar_value::().map(|i| OtherOrder(*i)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - >::from_str(value) + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + ::from_str(value) } } -#[test] -fn test_generated_meta() { - let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default()); - let meta = >::meta(&(), &mut registry); - - assert_eq!(meta.name(), Some("MyCustomName")); - assert_eq!( - meta.description(), - Some(&"My custom description".to_string()) +#[juniper::graphql_scalar(name = "ANamedScalar")] +impl GraphQLScalar for Named { + fn resolve(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input_value(v: &juniper::InputValue) -> Option { + v.as_scalar_value::().map(|i| Named(*i)) + } + + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + ::from_str(value) + } +} + +#[juniper::graphql_scalar(description = "A sample scalar, represented as an integer")] +impl GraphQLScalar for ScalarDescription { + fn resolve(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input_value(v: &juniper::InputValue) -> Option { + v.as_scalar_value::().map(|i| ScalarDescription(*i)) + } + + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + ::from_str(value) + } +} + +#[juniper::graphql_object] +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) + } +} + +async fn run_type_info_query(doc: &str, f: F) +where + F: Fn(&Object) -> (), +{ + let schema = RootNode::new( + Root {}, + EmptyMutation::<()>::new(), + EmptySubscription::<()>::new(), ); + + let (result, errs) = juniper::execute(doc, None, &schema, &Variables::new(), &()) + .await + .expect("Execution failed"); + + assert_eq!(errs, []); + + println!("Result: {:#?}", result); + + let type_info = result + .as_object_value() + .expect("Result is not an object") + .get_field_value("__type") + .expect("__type field missing") + .as_object_value() + .expect("__type field not an object value"); + + f(type_info); +} + +#[test] +fn path_in_resolve_return_type() { + struct ResolvePath(i32); + + #[juniper::graphql_scalar] + impl GraphQLScalar for ResolvePath { + fn resolve(&self) -> self::Value { + Value::scalar(self.0) + } + + fn from_input_value(v: &juniper::InputValue) -> Option { + v.as_scalar_value::().map(|i| ResolvePath(*i)) + } + + fn from_str<'a>( + value: juniper::ScalarToken<'a>, + ) -> ParseScalarResult<'a, DefaultScalarValue> { + ::from_str(value) + } + } +} + +#[tokio::test] +async fn default_name_introspection() { + let doc = r#" + { + __type(name: "DefaultName") { + name + description + } + } + "#; + + run_type_info_query(doc, |type_info| { + assert_eq!( + type_info.get_field_value("name"), + Some(&Value::scalar("DefaultName")) + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&Value::null()) + ); + }) + .await; +} + +#[tokio::test] +async fn other_order_introspection() { + let doc = r#" + { + __type(name: "OtherOrder") { + name + description + } + } + "#; + + run_type_info_query(doc, |type_info| { + assert_eq!( + type_info.get_field_value("name"), + Some(&Value::scalar("OtherOrder")) + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&Value::null()) + ); + }) + .await; +} + +#[tokio::test] +async fn named_introspection() { + let doc = r#" + { + __type(name: "ANamedScalar") { + name + description + } + } + "#; + + run_type_info_query(doc, |type_info| { + assert_eq!( + type_info.get_field_value("name"), + Some(&Value::scalar("ANamedScalar")) + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&Value::null()) + ); + }) + .await; +} + +#[tokio::test] +async fn scalar_description_introspection() { + let doc = r#" + { + __type(name: "ScalarDescription") { + name + description + } + } + "#; + + run_type_info_query(doc, |type_info| { + assert_eq!( + type_info.get_field_value("name"), + Some(&Value::scalar("ScalarDescription")) + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&Value::scalar("A sample scalar, represented as an integer")) + ); + }) + .await; } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 775c1f7f8..4d14e1352 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -133,28 +133,29 @@ impl<'de> de::Visitor<'de> for MyScalarValueVisitor { } } -juniper::graphql_scalar!(i64 as "Long" where Scalar = MyScalarValue { - resolve(&self) -> Value { +#[juniper::graphql_scalar(name = "Long")] +impl GraphQLScalar for i64 { + fn resolve(&self) -> Value { Value::scalar(*self) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { InputValue::Scalar(MyScalarValue::Long(i)) => Some(i), _ => None, } } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> { if let ScalarToken::Int(v) = value { - v.parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) - .map(|s: i64| s.into()) + v.parse() + .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map(|s: i64| s.into()) } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) + Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} struct TestType; diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 4278e8584..a007a66f3 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -24,6 +24,8 @@ See [#569](https://github.com/graphql-rust/juniper/pull/569). - remove old `graphql_object!` macro, rename `object` proc macro to `graphql_object` +- remove old `graphql_scalar!` macro, rename `scalar` proc macro to `graphql_scalar` + - Remove deprecated `ScalarValue` custom derive (renamed to GraphQLScalarValue) - `graphql_union!` macro removed, replaced by `#[graphql_union]` proc macro diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index cbcb00914..9ad29d9e6 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -147,7 +147,7 @@ pub type Document<'a, S> = Vec>; /// Parse an unstructured input value into a Rust data type. /// /// The conversion _can_ fail, and must in that case return None. Implemented -/// automatically by the convenience macro `graphql_scalar!` or by deriving GraphQLEnum. +/// automatically by the convenience proc macro `graphql_scalar` or by deriving GraphQLEnum. /// /// Must be implemented manually when manually exposing new enums or scalars. pub trait FromInputValue: Sized { diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 8c0098c02..e3da6c88c 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -11,7 +11,7 @@ use crate::{ executor::Variables, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::{ParseScalarResult, ParseScalarValue, Value}, + value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value}, }; #[derive(GraphQLEnum)] @@ -27,19 +27,20 @@ struct Interface; struct Root; -graphql_scalar!(Scalar as "SampleScalar" { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "SampleScalar")] +impl GraphQLScalar for Scalar { + fn resolve(&self) -> Value { Value::scalar(self.0) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_scalar_value().map(|i: &i32| Scalar(*i)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { ::from_str(value) } -}); +} graphql_interface!(Interface: () as "SampleInterface" |&self| { description: "A sample interface" diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index a04b345f2..7948216f3 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -16,12 +16,13 @@ struct TestComplexScalar; struct TestType; -graphql_scalar!(TestComplexScalar { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal] +impl GraphQLScalar for TestComplexScalar { + fn resolve(&self) -> Value { Value::scalar(String::from("SerializedValue")) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { if let Some(s) = v.as_scalar_value::() { if *s == "SerializedValue" { return Some(TestComplexScalar); @@ -31,10 +32,10 @@ graphql_scalar!(TestComplexScalar { None } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { - >::from_str(value) + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + ::from_str(value) } -}); +} #[derive(GraphQLInputObject, Debug)] #[graphql(scalar = "DefaultScalarValue")] diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index dcf3be394..34f99f1e5 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -7,48 +7,52 @@ use crate::{ Value, }; -graphql_scalar!(ObjectId where Scalar = { - description: "ObjectId" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "ObjectId")] +impl GraphQLScalar for ObjectId +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.to_hex()) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_string_value() - .and_then(|s| ObjectId::with_string(s).ok()) + .and_then(|s| ObjectId::with_string(s).ok()) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); - -graphql_scalar!(UtcDateTime where Scalar = { - description: "UtcDateTime" +} - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "UtcDateTime")] +impl GraphQLScalar for UtcDateTime +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar((*self).to_rfc3339()) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_string_value() - .and_then(|s| (s.parse::>().ok())) - .map(|d| UtcDateTime(d)) + .and_then(|s| (s.parse::>().ok())) + .map(|d| UtcDateTime(d)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} #[cfg(test)] mod test { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index d26c9469d..586967549 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -25,92 +25,100 @@ use crate::{ #[doc(hidden)] pub static RFC3339_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f%:z"; -graphql_scalar!(DateTime as "DateTimeFixedOffset" where Scalar = { - description: "DateTime" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "DateTimeFixedOffset", description = "DateTime")] +impl GraphQLScalar for DateTime +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } - from_input_value(v: &InputValue) -> Option> { + fn from_input_value(v: &InputValue) -> Option> { v.as_string_value() - .and_then(|s| DateTime::parse_from_rfc3339(s).ok()) + .and_then(|s| DateTime::parse_from_rfc3339(s).ok()) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(value) = value { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); - -graphql_scalar!(DateTime as "DateTimeUtc" where Scalar = { - description: "DateTime" +} - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "DateTimeUtc", description = "DateTime")] +impl GraphQLScalar for DateTime +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } - from_input_value(v: &InputValue) -> Option> { + fn from_input_value(v: &InputValue) -> Option> { v.as_string_value() - .and_then(|s| (s.parse::>().ok())) + .and_then(|s| (s.parse::>().ok())) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} // Don't use `Date` as the docs say: // "[Date] should be considered ambiguous at best, due to the " // inherent lack of precision required for the time zone resolution. // For serialization and deserialization uses, it is best to use // `NaiveDate` instead." -graphql_scalar!(NaiveDate where Scalar = { - description: "NaiveDate" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "NaiveDate")] +impl GraphQLScalar for NaiveDate +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.format("%Y-%m-%d").to_string()) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_string_value() - .and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()) + .and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond // datetimes. Values will be truncated to microsecond resolution. -graphql_scalar!(NaiveDateTime where Scalar = { - description: "NaiveDateTime" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "NaiveDateTime")] +impl GraphQLScalar for NaiveDateTime +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.timestamp() as f64) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { v.as_float_value() - .and_then(|f| NaiveDateTime::from_timestamp_opt(f as i64, 0)) + .and_then(|f| NaiveDateTime::from_timestamp_opt(f as i64, 0)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { >::from_str(value) } -}); +} #[cfg(test)] mod test { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 1ee695f33..2a91e06e3 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -5,22 +5,23 @@ use crate::{ Value, }; -graphql_scalar!(Url where Scalar = { - description: "Url" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "Url")] +impl GraphQLScalar for Url +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.as_str().to_owned()) } - from_input_value(v: &InputValue) -> Option { - v.as_string_value() - .and_then(|s| Url::parse(s).ok()) + fn from_input_value(v: &InputValue) -> Option { + v.as_string_value().and_then(|s| Url::parse(s).ok()) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { >::from_str(value) } -}); +} #[cfg(test)] mod test { diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 5b0be2e6c..03d339135 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -8,27 +8,27 @@ use crate::{ Value, }; -graphql_scalar!(Uuid where Scalar = { - description: "Uuid" - - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(description = "Uuid")] +impl GraphQLScalar for Uuid +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.to_string()) } - from_input_value(v: &InputValue) -> Option { - v.as_string_value() - .and_then(|s| Uuid::parse_str(s).ok()) + fn from_input_value(v: &InputValue) -> Option { + v.as_string_value().and_then(|s| Uuid::parse_str(s).ok()) } - - from_str<'a>(value: ScalarToken<'_>) -> ParseScalarResult { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} #[cfg(test)] mod test { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index f248669bb..e78605c49 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,15 +115,16 @@ extern crate bson; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_object, graphql_scalar2, graphql_subscription, graphql_union, GraphQLEnum, + graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, }; // Internal macros are not exported, // but declared at the root to make them easier to use. #[allow(unused_imports)] use juniper_codegen::{ - graphql_object_internal, graphql_subscription_internal, graphql_union_internal, - GraphQLEnumInternal, GraphQLInputObjectInternal, GraphQLScalarValueInternal, + graphql_object_internal, graphql_scalar_internal, graphql_subscription_internal, + graphql_union_internal, GraphQLEnumInternal, GraphQLInputObjectInternal, + GraphQLScalarValueInternal, }; #[macro_use] diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index a572ba7e0..1d4c57006 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -5,8 +5,6 @@ mod common; #[macro_use] mod interface; -#[macro_use] -mod scalar; #[cfg(test)] mod tests; diff --git a/juniper/src/macros/scalar.rs b/juniper/src/macros/scalar.rs deleted file mode 100644 index 239d1361e..000000000 --- a/juniper/src/macros/scalar.rs +++ /dev/null @@ -1,366 +0,0 @@ -/// Expose GraphQL scalars -/// -/// The GraphQL language defines a number of built-in scalars: strings, numbers, and -/// booleans. This macro can be used either to define new types of scalars (e.g. -/// timestamps), or expose other types as one of the built-in scalars (e.g. bigints -/// as numbers or strings). -/// -/// Since the preferred transport protocol for GraphQL responses is JSON, most -/// custom scalars will be transferred as strings. You therefore need to ensure that -/// the client library you are sending data to can parse the custom value into a -/// datatype appropriate for that platform. -/// -/// By default the trait is implemented in terms of the default scalar value -/// representation provided by juniper. If that does not fit your needs it is -/// possible to use the same syntax as on `graphql_object!` to specify a custom -/// representation. -/// -/// ```rust -/// # extern crate juniper; -/// # use juniper::{Value, FieldResult, ParseScalarValue, ParseScalarResult}; -/// struct UserID(String); -/// -/// juniper::graphql_scalar!(UserID { -/// description: "An opaque identifier, represented as a string" -/// -/// resolve(&self) -> Value { -/// Value::string(&self.0) -/// } -/// -/// from_input_value(v: &InputValue) -> Option { -/// v.as_string_value().map(|s| UserID(s.to_owned())) -/// } -/// -/// from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { -/// ::from_str(value) -/// } -/// }); -/// -/// # fn main() { } -/// ``` -/// -/// In addition to implementing `GraphQLType` for the type in question, -/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type -/// usable as arguments and default values. -#[macro_export] -macro_rules! graphql_scalar { - ( @as_expr $e:expr) => { $e }; - - ( - @generate, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - $(description = $descr:tt,)* - }, - resolve = { - self_var = $resolve_self_var:ident, - body = $resolve_body: block, - return_type = $resolve_retun_type: ty, - }, - from_input_value = { - arg = $from_input_value_arg: ident, - result = $from_input_value_result: ty, - body = $from_input_value_body: block, - }, - from_str = { - value_arg = $from_str_arg: ident, - result = $from_str_result: ty, - body = $from_str_body: block, - lifetime = $from_str_lt: tt, - }, - - ) => { - $crate::__juniper_impl_trait!( - impl <$($scalar)+> GraphQLType for $name { - type Context = (); - type TypeInfo = (); - - fn name(_: &Self::TypeInfo) -> Option<&str> { - Some($crate::graphql_scalar!(@as_expr $($outname)+)) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> - ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> - where - $crate::__juniper_insert_generic!($($scalar)+): 'r - { - let meta = registry.build_scalar_type::(info); - $( - let meta = meta.description($descr); - )* - meta.into_meta() - } - - fn resolve( - &$resolve_self_var, - _: &(), - _: Option<&[$crate::Selection<$crate::__juniper_insert_generic!($($scalar)+)>]>, - _: &$crate::Executor< - Self::Context, - $crate::__juniper_insert_generic!($($scalar)+) - >) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)+)> { - Ok($resolve_body) - } - } - ); - - $crate::__juniper_impl_trait!( - impl <$($scalar)+> GraphQLTypeAsync for $name - where ( - $crate::__juniper_insert_generic!($($scalar)+): Send + Sync, - Self: $crate::GraphQLType<$crate::__juniper_insert_generic!($($scalar)+)> + Send + Sync, - Self::Context: Send + Sync, - Self::TypeInfo: Send + Sync, - ) - { - - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [$crate::Selection<$crate::__juniper_insert_generic!($($scalar)+)>]>, - executor: &'a $crate::Executor, - ) -> $crate::BoxFuture<'a, $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)+)>> { - use $crate::GraphQLType; - use futures::future; - let v = self.resolve(info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)+> ToInputValue for $name { - fn to_input_value(&$resolve_self_var) -> $crate::InputValue<$crate::__juniper_insert_generic!($($scalar)+)> { - let v = $resolve_body; - $crate::ToInputValue::to_input_value(&v) - } - } - ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)+> FromInputValue for $name { - fn from_input_value( - $from_input_value_arg: &$crate::InputValue<$crate::__juniper_insert_generic!($($scalar)+)> - ) -> $from_input_value_result { - $from_input_value_body - } - } - ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)+> ParseScalarValue for $name { - fn from_str<$from_str_lt>($from_str_arg: $crate::parser::ScalarToken<$from_str_lt>) -> $from_str_result { - $from_str_body - } - } - ); - }; - - // No more items to parse - ( - @parse_functions, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - $(description = $descr:tt,)* - }, - resolve = {$($resolve_body:tt)+}, - from_input_value = {$($from_input_value_body:tt)+}, - from_str = {$($from_str_body:tt)+}, - rest = - ) => { - $crate::graphql_scalar!( - @generate, - meta = { - name = $name, - outname = {$($outname)+}, - scalar = {$($scalar)+}, - $(description = $descr,)* - }, - resolve = {$($resolve_body)+}, - from_input_value = {$($from_input_value_body)+}, - from_str = {$($from_str_body)+}, - ); - }; - - ( - @parse_functions, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - $(description = $descr:tt,)* - }, - $(from_input_value = {$($from_input_value_body:tt)+})*, - $(from_str = {$($from_str_body:tt)+})*, - rest = - ) => { - compile_error!("Missing resolve function"); - }; - - ( - @parse_functions, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - $(description = $descr:tt,)* - }, - resolve = {$($resolve_body:tt)+}, - $(from_str = {$($from_str_body:tt)+})*, - rest = - ) => { - compile_error!("Missing from_input_value function"); - }; - - ( - @parse_functions, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - $(description = $descr:tt,)* - }, - resolve = {$($resolve_body:tt)+}, - from_input_value = {$($from_input_value_body:tt)+}, - rest = - ) =>{ - compile_error!("Missing from_str function"); - }; - - - // resolve(&self) -> Value { ... } - ( - @parse_functions, - meta = {$($meta:tt)*}, - $(resolve = {$($resolve_body:tt)+},)* - $(from_input_value = {$($from_input_value_body:tt)+},)* - $(from_str = {$($from_str_body:tt)+},)* - rest = resolve(&$selfvar:ident) -> $return_ty:ty $body:block $($rest:tt)* - ) => { - $crate::graphql_scalar!( - @parse_functions, - meta = {$($meta)*}, - resolve = { - self_var = $selfvar, - body = $body, - return_type = $return_ty, - }, - $(from_input_value = {$($from_input_value_body)+},)* - $(from_str = {$($from_str_body)+},)* - rest = $($rest)* - ); - }; - - // from_input_value(arg: &InputValue) -> ... { ... } - ( - @parse_functions, - meta = { $($meta:tt)* }, - $(resolve = {$($resolve_body:tt)+})*, - $(from_input_value = {$($from_input_value_body:tt)+},)* - $(from_str = {$($from_str_body:tt)+},)* - rest = from_input_value($arg:ident: &InputValue) -> $result:ty $body:block $($rest:tt)* - ) => { - $crate::graphql_scalar!( - @parse_functions, - meta = { $($meta)* }, - $(resolve = {$($resolve_body)+},)* - from_input_value = { - arg = $arg, - result = $result, - body = $body, - }, - $(from_str = {$($from_str_body)+},)* - rest = $($rest)* - ); - }; - - // from_str(value: &str) -> Result - ( - @parse_functions, - meta = { $($meta:tt)* }, - $(resolve = {$($resolve_body:tt)+},)* - $(from_input_value = {$($from_input_value_body:tt)+},)* - $(from_str = {$($from_str_body:tt)+},)* - rest = from_str<$from_str_lt: tt>($value_arg:ident: ScalarToken<$ignored_lt2: tt>) -> $result:ty $body:block $($rest:tt)* - ) => { - $crate::graphql_scalar!( - @parse_functions, - meta = { $($meta)* }, - $(resolve = {$($resolve_body)+},)* - $(from_input_value = {$($from_input_value_body)+},)* - from_str = { - value_arg = $value_arg, - result = $result, - body = $body, - lifetime = $from_str_lt, - }, - rest = $($rest)* - ); - }; - - // description: - ( - @parse_functions, - meta = { - name = $name:ty, - outname = {$($outname:tt)+}, - scalar = {$($scalar:tt)+}, - }, - $(resolve = {$($resolve_body:tt)+},)* - $(from_input_value = {$($from_input_value_body:tt)+},)* - $(from_str = {$($from_str_body:tt)+},)* - rest = description: $descr:tt $($rest:tt)* - ) => { - $crate::graphql_scalar!( - @parse_functions, - meta = { - name = $name, - outname = {$($outname)+}, - scalar = {$($scalar)+}, - description = $descr, - }, - $(resolve = {$($resolve_body)+},)* - $(from_input_value = {$($from_input_value_body)+},)* - $(from_str = {$($from_str_body)+},)* - rest = $($rest)* - ); - }; - - ( - @parse, - meta = { - lifetimes = [], - name = $name: ty, - outname = {$($outname:tt)*}, - scalar = {$($scalar:tt)*}, - }, - rest = $($rest:tt)* - ) => { - $crate::graphql_scalar!( - @parse_functions, - meta = { - name = $name, - outname = {$($outname)*}, - scalar = {$($scalar)*}, - }, - rest = $($rest)* - ); - }; - - (@$($stuff:tt)*) => { - compile_error!("Invalid syntax for `graphql_scalar!`"); - }; - - ($($rest:tt)*) => { - $crate::__juniper_parse_object_header!( - callback = graphql_scalar, - rest = $($rest)* - ); - } -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index 6fb9b9073..795133254 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -4,6 +4,5 @@ mod impl_object; mod impl_subscription; mod interface; mod object; -mod scalar; mod union; mod util; diff --git a/juniper/src/macros/tests/scalar.rs b/juniper/src/macros/tests/scalar.rs deleted file mode 100644 index be578c22c..000000000 --- a/juniper/src/macros/tests/scalar.rs +++ /dev/null @@ -1,241 +0,0 @@ -use crate::{ - executor::Variables, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, Value}, -}; - -struct DefaultName(i32); -struct OtherOrder(i32); -struct Named(i32); -struct ScalarDescription(i32); - -struct Root; - -/* - -Syntax to validate: - -* Default name vs. custom name -* Description vs. no description on the scalar - -*/ - -graphql_scalar!(DefaultName where Scalar = { - resolve(&self) -> Value { - Value::scalar(self.0) - } - - from_input_value(v: &InputValue) -> Option { - v.as_scalar_value().and_then(|s| s.as_int()).map(|i| DefaultName(i)) - } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - >::from_str(value) - } -}); - -graphql_scalar!(OtherOrder { - resolve(&self) -> Value { - Value::scalar(self.0) - } - - from_input_value(v: &InputValue) -> Option { - v.as_scalar_value::().map(|i| OtherOrder(*i)) - } - - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { - ::from_str(value) - } -}); - -graphql_scalar!(Named as "ANamedScalar" where Scalar = DefaultScalarValue { - resolve(&self) -> Value { - Value::scalar(self.0) - } - - from_input_value(v: &InputValue) -> Option { - v.as_scalar_value::().map(|i| Named(*i)) - } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { - ::from_str(value) - } -}); - -graphql_scalar!(ScalarDescription { - description: "A sample scalar, represented as an integer" - - resolve(&self) -> Value { - Value::scalar(self.0) - } - - from_input_value(v: &InputValue) -> Option { - v.as_scalar_value::().map(|i| ScalarDescription(*i)) - } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { - ::from_str(value) - } -}); - -#[crate::graphql_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) - } -} - -async fn run_type_info_query(doc: &str, f: F) -where - F: Fn(&Object) -> (), -{ - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - let type_info = result - .as_object_value() - .expect("Result is not an object") - .get_field_value("__type") - .expect("__type field missing") - .as_object_value() - .expect("__type field not an object value"); - - f(type_info); -} - -#[test] -fn path_in_resolve_return_type() { - struct ResolvePath(i32); - - graphql_scalar!(ResolvePath { - resolve(&self) -> self::Value { - Value::scalar(self.0) - } - - from_input_value(v: &InputValue) -> Option { - v.as_scalar_value::().map(|i| ResolvePath(*i)) - } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { - ::from_str(value) - } - }); -} - -#[tokio::test] -async fn default_name_introspection() { - let doc = r#" - { - __type(name: "DefaultName") { - name - description - } - } - "#; - - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&Value::scalar("DefaultName")) - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn other_order_introspection() { - let doc = r#" - { - __type(name: "OtherOrder") { - name - description - } - } - "#; - - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&Value::scalar("OtherOrder")) - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn named_introspection() { - let doc = r#" - { - __type(name: "ANamedScalar") { - name - description - } - } - "#; - - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&Value::scalar("ANamedScalar")) - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn scalar_description_introspection() { - let doc = r#" - { - __type(name: "ScalarDescription") { - name - description - } - } - "#; - - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&Value::scalar("ScalarDescription")) - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&Value::scalar("A sample scalar, represented as an integer")) - ); - }) - .await; -} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index fa260c536..088bc878d 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -37,67 +37,93 @@ impl Deref for ID { } } -graphql_scalar!(ID as "ID" where Scalar = { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "ID")] +impl GraphQLScalar for ID +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.0.clone()) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { - InputValue::Scalar(ref s) => { - s.as_string().or_else(|| s.as_int().map(|i| i.to_string())) - .map(ID) - } - _ => None + InputValue::Scalar(ref s) => s + .as_string() + .or_else(|| s.as_int().map(|i| i.to_string())) + .map(ID), + _ => None, } } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { match value { - ScalarToken::String(value) | ScalarToken::Int(value) => { - Ok(S::from(value.to_owned())) - } + ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())), _ => Err(ParseError::UnexpectedToken(Token::Scalar(value))), } } -}); +} -graphql_scalar!(String as "String" where Scalar = { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "String")] +impl GraphQLScalar for String +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(self.clone()) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { InputValue::Scalar(ref s) => s.as_string(), _ => None, } } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); while let Some(ch) = char_iter.next() { match ch { - '\\' => { - match char_iter.next() { - Some('"') => {ret.push('"');} - Some('/') => {ret.push('/');} - Some('n') => {ret.push('\n');} - Some('r') => {ret.push('\r');} - Some('t') => {ret.push('\t');} - Some('\\') => {ret.push('\\');} - Some('f') => {ret.push('\u{000c}');} - Some('b') => {ret.push('\u{0008}');} - Some('u') => { - ret.push(parse_unicode_codepoint(&mut char_iter)?); - } - Some(s) => return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\{}", s)))), - None => return Err(ParseError::LexerError(LexerError::UnterminatedString)), + '\\' => match char_iter.next() { + Some('"') => { + ret.push('"'); + } + Some('/') => { + ret.push('/'); + } + Some('n') => { + ret.push('\n'); + } + Some('r') => { + ret.push('\r'); + } + Some('t') => { + ret.push('\t'); + } + Some('\\') => { + ret.push('\\'); + } + Some('f') => { + ret.push('\u{000c}'); + } + Some('b') => { + ret.push('\u{0008}'); + } + Some('u') => { + ret.push(parse_unicode_codepoint(&mut char_iter)?); } + Some(s) => { + return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence( + format!("\\{}", s), + ))) + } + None => return Err(ParseError::LexerError(LexerError::UnterminatedString)), }, - ch => {ret.push(ch);} + ch => { + ret.push(ch); + } } } Ok(ret.into()) @@ -105,7 +131,7 @@ graphql_scalar!(String as "String" where Scalar = { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} fn parse_unicode_codepoint<'a, I>(char_iter: &mut I) -> Result> where @@ -218,73 +244,81 @@ where } } -graphql_scalar!(bool as "Boolean" where Scalar = { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "Boolean")] +impl GraphQLScalar for bool +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(*self) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { InputValue::Scalar(ref b) => b.as_boolean(), _ => None, } } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S > { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { // Bools are parsed on it's own. This should not hit this code path Err(ParseError::UnexpectedToken(Token::Scalar(value))) } -}); +} -graphql_scalar!(i32 as "Int" where Scalar = { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "Int")] +impl GraphQLScalar for i32 +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(*self) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { InputValue::Scalar(ref i) => i.as_int(), _ => None, } } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { if let ScalarToken::Int(v) = value { v.parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) - .map(|s: i32| s.into()) + .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map(|s: i32| s.into()) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -}); +} -graphql_scalar!(f64 as "Float" where Scalar = { - resolve(&self) -> Value { +#[crate::graphql_scalar_internal(name = "Float")] +impl GraphQLScalar for f64 +where + S: ScalarValue, +{ + fn resolve(&self) -> Value { Value::scalar(*self) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &InputValue) -> Option { match *v { InputValue::Scalar(ref s) => s.as_float(), _ => None, } } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { match value { - ScalarToken::Int(v) | ScalarToken::Float(v) => { - v.parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) - .map(|s: f64| s.into()) - } - ScalarToken::String(_) => { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } + ScalarToken::Int(v) | ScalarToken::Float(v) => v + .parse() + .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map(|s: f64| s.into()), + ScalarToken::String(_) => Err(ParseError::UnexpectedToken(Token::Scalar(value))), } } -}); +} /// Utillity type to define read-only schemas /// diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index c6ebe8a34..3283d18f4 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -6,7 +6,9 @@ use quote::quote; #[derive(Debug)] struct ScalarCodegenInput { - ident: Option, + impl_for_type: Option, + custom_data_type: Option, + custom_data_type_is_struct: bool, resolve_body: Option, from_input_value_arg: Option, from_input_value_body: Option, @@ -16,9 +18,85 @@ struct ScalarCodegenInput { from_str_result: Option, } +fn get_first_method_arg( + inputs: syn::punctuated::Punctuated, +) -> Option { + if let Some(fn_arg) = inputs.first() { + match fn_arg { + syn::FnArg::Typed(pat_type) => match &*pat_type.pat { + syn::Pat::Ident(pat_ident) => return Some(pat_ident.ident.clone()), + _ => (), + }, + _ => (), + } + } + + None +} + +fn get_method_return_type(output: syn::ReturnType) -> Option { + match output { + syn::ReturnType::Type(_, return_type) => Some(*return_type), + _ => None, + } +} + +// Find the enum type by inspecting the type parameter on the return value +fn get_enum_type(return_type: &Option) -> Option { + if let Some(return_type) = return_type { + match return_type { + syn::Type::Path(type_path) => { + let path_segment = type_path + .path + .segments + .iter() + .find(|ps| match ps.arguments { + syn::PathArguments::AngleBracketed(_) => true, + _ => false, + }); + + if let Some(path_segment) = path_segment { + match &path_segment.arguments { + syn::PathArguments::AngleBracketed(generic_args) => { + let generic_type_arg = + generic_args.args.iter().find(|generic_type_arg| { + match generic_type_arg { + syn::GenericArgument::Type(_) => true, + _ => false, + } + }); + + if let Some(generic_type_arg) = generic_type_arg { + match generic_type_arg { + syn::GenericArgument::Type(the_type) => match the_type { + syn::Type::Path(type_path) => { + if let Some(path_segment) = + type_path.path.segments.first() + { + return Some(path_segment.clone()); + } + } + _ => (), + }, + _ => (), + } + } + } + _ => (), + } + } + } + _ => (), + } + } + + None +} + impl syn::parse::Parse for ScalarCodegenInput { fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { - let mut ident: Option = None; + let mut impl_for_type: Option = None; + let mut enum_data_type: Option = None; let mut resolve_body: Option = None; let mut from_input_value_arg: Option = None; let mut from_input_value_body: Option = None; @@ -28,14 +106,17 @@ impl syn::parse::Parse for ScalarCodegenInput { let mut from_str_result: Option = None; let parse_custom_scalar_value_impl: syn::ItemImpl = input.parse()?; + // To implement a custom scalar for a struct, it's required to + // specify a generic type and a type bound + let custom_data_type_is_struct: bool = + !parse_custom_scalar_value_impl.generics.params.is_empty(); match *parse_custom_scalar_value_impl.self_ty { - syn::Type::Path(type_path) => match type_path.path.segments.first() { - Some(path_segment) => { - ident = Some(path_segment.ident.clone()); + syn::Type::Path(type_path) => { + if let Some(path_segment) = type_path.path.segments.first() { + impl_for_type = Some(path_segment.clone()); } - _ => (), - }, + } _ => (), } @@ -46,47 +127,16 @@ impl syn::parse::Parse for ScalarCodegenInput { resolve_body = Some(method.block); } "from_input_value" => { - match method.sig.inputs.first() { - Some(fn_arg) => match fn_arg { - syn::FnArg::Typed(pat_type) => match &*pat_type.pat { - syn::Pat::Ident(pat_ident) => { - from_input_value_arg = Some(pat_ident.ident.clone()) - } - _ => (), - }, - _ => (), - }, - _ => (), - } - - match method.sig.output { - syn::ReturnType::Type(_, return_type) => { - from_input_value_result = Some(*return_type); - } - _ => (), - } - + from_input_value_arg = get_first_method_arg(method.sig.inputs); + from_input_value_result = get_method_return_type(method.sig.output); from_input_value_body = Some(method.block); } "from_str" => { - match method.sig.inputs.first() { - Some(fn_arg) => match fn_arg { - syn::FnArg::Typed(pat_type) => match &*pat_type.pat { - syn::Pat::Ident(pat_ident) => { - from_str_arg = Some(pat_ident.ident.clone()) - } - _ => (), - }, - _ => (), - }, - _ => (), - } + from_str_arg = get_first_method_arg(method.sig.inputs); + from_str_result = get_method_return_type(method.sig.output); - match method.sig.output { - syn::ReturnType::Type(_, return_type) => { - from_str_result = Some(*return_type); - } - _ => (), + if !custom_data_type_is_struct { + enum_data_type = get_enum_type(&from_str_result); } from_str_body = Some(method.block); @@ -97,8 +147,16 @@ impl syn::parse::Parse for ScalarCodegenInput { }; } + let custom_data_type = if custom_data_type_is_struct { + impl_for_type.clone() + } else { + enum_data_type + }; + Ok(ScalarCodegenInput { - ident, + impl_for_type, + custom_data_type, + custom_data_type_is_struct, resolve_body, from_input_value_arg, from_input_value_body, @@ -120,44 +178,81 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo }; let input = syn::parse_macro_input!(body as ScalarCodegenInput); - let ident = input.ident.unwrap(); - let resolve_body = input.resolve_body.unwrap(); - let from_input_value_arg = input.from_input_value_arg.unwrap(); - let from_input_value_body = input.from_input_value_body.unwrap(); - let from_input_value_result = input.from_input_value_result.unwrap(); - let from_str_arg = input.from_str_arg.unwrap(); - let from_str_body = input.from_str_body.unwrap(); - let from_str_result = input.from_str_result.unwrap(); - - // TODO: Code below copied from derive_scalar_value.rs#impl_scalar_struct. REFACTOR! - - let name = attrs.name.unwrap_or_else(|| ident.to_string()); - - let crate_name = if is_internal { - quote!(crate) - } else { - quote!(juniper) - }; + let impl_for_type = input + .impl_for_type + .expect("Unable to find target for implementation target for `GraphQLScalar`"); + let custom_data_type = input + .custom_data_type + .expect("Unable to find custom scalar data type"); + let resolve_body = input + .resolve_body + .expect("Unable to find body of `resolve` method"); + let from_input_value_arg = input + .from_input_value_arg + .expect("Unable to find argument for `from_input_value` method"); + let from_input_value_body = input + .from_input_value_body + .expect("Unable to find body of `from_input_value` method"); + let from_input_value_result = input + .from_input_value_result + .expect("Unable to find return type of `from_input_value` method"); + let from_str_arg = input + .from_str_arg + .expect("Unable to find argument for `from_str` method"); + let from_str_body = input + .from_str_body + .expect("Unable to find body of `from_str` method"); + let from_str_result = input + .from_str_result + .expect("Unable to find return type of `from_str` method"); + + let name = attrs + .name + .unwrap_or_else(|| impl_for_type.ident.to_string()); + let crate_name = match is_internal { + true => quote!(crate), + _ => quote!(juniper), + }; let description = match attrs.description { - Some(val) => quote!( .description( #val ) ), + Some(val) => quote!(.description(#val)), None => quote!(), }; + let async_generic_type = match input.custom_data_type_is_struct { + true => quote!(__S), + _ => quote!(#custom_data_type), + }; + let async_generic_type_decl = match input.custom_data_type_is_struct { + true => quote!(<#async_generic_type>), + _ => quote!(), + }; + let generic_type = match input.custom_data_type_is_struct { + true => quote!(S), + _ => quote!(#custom_data_type), + }; + let generic_type_decl = match input.custom_data_type_is_struct { + true => quote!(<#generic_type>), + _ => quote!(), + }; + let generic_type_bound = match input.custom_data_type_is_struct { + true => quote!(where #generic_type: #crate_name::ScalarValue,), + _ => quote!(), + }; let _async = quote!( - impl<__S> ::#crate_name::GraphQLTypeAsync<__S> for #ident + impl#async_generic_type_decl #crate_name::GraphQLTypeAsync<#async_generic_type> for #impl_for_type where - __S: #crate_name::ScalarValue + Send + Sync, - Self: #crate_name::GraphQLType<__S> + Send + Sync, + #async_generic_type: #crate_name::ScalarValue + Send + Sync, + Self: #crate_name::GraphQLType<#async_generic_type> + Send + Sync, Self::Context: Send + Sync, Self::TypeInfo: Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, - selection_set: Option<&'a [#crate_name::Selection<__S>]>, - executor: &'a #crate_name::Executor, - ) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> { + selection_set: Option<&'a [#crate_name::Selection<#async_generic_type>]>, + executor: &'a #crate_name::Executor, + ) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<#async_generic_type>> { use #crate_name::GraphQLType; use futures::future; let v = self.resolve(info, selection_set, executor); @@ -169,9 +264,8 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo quote!( #_async - impl #crate_name::GraphQLType for #ident - where - S: #crate_name::ScalarValue, + impl#generic_type_decl #crate_name::GraphQLType<#generic_type> for #impl_for_type + #generic_type_bound { type Context = (); type TypeInfo = (); @@ -182,10 +276,10 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo fn meta<'r>( info: &Self::TypeInfo, - registry: &mut #crate_name::Registry<'r, S>, - ) -> #crate_name::meta::MetaType<'r, S> + registry: &mut #crate_name::Registry<'r, #generic_type>, + ) -> #crate_name::meta::MetaType<'r, #generic_type> where - S: 'r, + #generic_type: 'r, { registry.build_scalar_type::(info) #description @@ -195,39 +289,36 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo fn resolve( &self, info: &(), - selection: Option<&[#crate_name::Selection]>, - executor: &#crate_name::Executor, - ) -> #crate_name::ExecutionResult { - #crate_name::GraphQLType::resolve(&self.0, info, selection, executor) + selection: Option<&[#crate_name::Selection<#generic_type>]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult<#generic_type> { + Ok(#resolve_body) } } - impl #crate_name::ToInputValue for #ident - where - S: #crate_name::ScalarValue, + impl#generic_type_decl #crate_name::ToInputValue<#generic_type> for #impl_for_type + #generic_type_bound { - fn to_input_value(&self) -> #crate_name::InputValue { + fn to_input_value(&self) -> #crate_name::InputValue<#generic_type> { let v = #resolve_body; #crate_name::ToInputValue::to_input_value(&v) } } - impl #crate_name::FromInputValue for #ident - where - S: #crate_name::ScalarValue, + impl#generic_type_decl #crate_name::FromInputValue<#generic_type> for #impl_for_type + #generic_type_bound { - fn from_input_value(#from_input_value_arg: &#crate_name::InputValue) -> #from_input_value_result { + fn from_input_value(#from_input_value_arg: &#crate_name::InputValue<#generic_type>) -> #from_input_value_result { #from_input_value_body } } - impl #crate_name::ParseScalarValue for #ident - where - S: #crate_name::ScalarValue, - { - fn from_str<'a>( - #from_str_arg: #crate_name::parser::ScalarToken<'a>, - ) -> #from_str_result { + impl#generic_type_decl #crate_name::ParseScalarValue<#generic_type> for #impl_for_type + #generic_type_bound + { + fn from_str<'a>( + #from_str_arg: #crate_name::parser::ScalarToken<'a>, + ) -> #from_str_result { #from_str_body } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 14e7e04fc..06a75e718 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -386,12 +386,67 @@ pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenSt impl_object::build_object(args, input, true) } -/// A proc macro for defining a custom scalar value. +/// Expose GraphQL scalars +/// +/// The GraphQL language defines a number of built-in scalars: strings, numbers, and +/// booleans. This macro can be used either to define new types of scalars (e.g. +/// timestamps), or expose other types as one of the built-in scalars (e.g. bigints +/// as numbers or strings). +/// +/// Since the preferred transport protocol for GraphQL responses is JSON, most +/// custom scalars will be transferred as strings. You therefore need to ensure that +/// the client library you are sending data to can parse the custom value into a +/// datatype appropriate for that platform. +/// +/// By default the trait is implemented in terms of the default scalar value +/// representation provided by juniper. If that does not fit your needs it is +/// possible to specify a custom representation. +/// +/// ```rust +/// // The data type +/// struct UserID(String); +/// +/// #[juniper::graphql_scalar( +/// // You can rename the type for GraphQL by specifying the name here. +/// name = "MyName", +/// // You can also specify a description here. +/// // If present, doc comments will be ignored. +/// description = "An opaque identifier, represented as a string")] +/// impl GraphQLScalar for UserID +/// where +/// S: juniper::ScalarValue +/// { +/// fn resolve(&self) -> juniper::Value { +/// juniper::Value::scalar(self.0.to_owned()) +/// } +/// +/// fn from_input_value(value: &juniper::InputValue) -> Option { +/// value.as_string_value().map(|s| UserID(s.to_owned())) +/// } +/// +/// fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> { +/// >::from_str(value) +/// } +/// } +/// +/// # fn main() { } +/// ``` +/// +/// In addition to implementing `GraphQLType` for the type in question, +/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type +/// usable as arguments and default values. #[proc_macro_attribute] -pub fn graphql_scalar2(args: TokenStream, input: TokenStream) -> TokenStream { +pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { impl_scalar::build_scalar(args, input, false) } +/// A proc macro for defining a GraphQL scalar. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn graphql_scalar_internal(args: TokenStream, input: TokenStream) -> TokenStream { + impl_scalar::build_scalar(args, input, true) +} + /// A proc macro for defining a GraphQL subscription. #[proc_macro_attribute] pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream {