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/juniper/src/macros/tests/scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs similarity index 68% rename from juniper/src/macros/tests/scalar.rs rename to integration_tests/juniper_tests/src/codegen/impl_scalar.rs index be578c22c..dc732b1a6 100644 --- a/juniper/src/macros/tests/scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -1,8 +1,6 @@ -use crate::{ - executor::Variables, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, Value}, +use juniper::{ + DefaultScalarValue, EmptyMutation, EmptySubscription, Object, ParseScalarResult, + ParseScalarValue, RootNode, Value, Variables, }; struct DefaultName(i32); @@ -21,66 +19,72 @@ Syntax to validate: */ -graphql_scalar!(DefaultName where Scalar = { - resolve(&self) -> Value { +#[juniper::graphql_scalar] +impl GraphQLScalar for DefaultName +where + S: juniper::ScalarValue, +{ + fn 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)) + fn from_input_value(v: &juniper::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> { + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, S> { >::from_str(value) } -}); +} -graphql_scalar!(OtherOrder { - resolve(&self) -> Value { +#[juniper::graphql_scalar] +impl GraphQLScalar for OtherOrder { + fn resolve(&self) -> Value { Value::scalar(self.0) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &juniper::InputValue) -> Option { v.as_scalar_value::().map(|i| OtherOrder(*i)) } - - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { ::from_str(value) } -}); +} -graphql_scalar!(Named as "ANamedScalar" where Scalar = DefaultScalarValue { - resolve(&self) -> Value { +#[juniper::graphql_scalar(name = "ANamedScalar")] +impl GraphQLScalar for Named { + fn resolve(&self) -> Value { Value::scalar(self.0) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &juniper::InputValue) -> Option { v.as_scalar_value::().map(|i| Named(*i)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { ::from_str(value) } -}); - -graphql_scalar!(ScalarDescription { - description: "A sample scalar, represented as an integer" +} - resolve(&self) -> Value { +#[juniper::graphql_scalar(description = "A sample scalar, represented as an integer")] +impl GraphQLScalar for ScalarDescription { + fn resolve(&self) -> Value { Value::scalar(self.0) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &juniper::InputValue) -> Option { v.as_scalar_value::().map(|i| ScalarDescription(*i)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { + fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { ::from_str(value) } -}); +} -#[crate::graphql_object_internal] +#[juniper::graphql_object] impl Root { fn default_name() -> DefaultName { DefaultName(0) @@ -106,7 +110,7 @@ where EmptySubscription::<()>::new(), ); - let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &()) + let (result, errs) = juniper::execute(doc, None, &schema, &Variables::new(), &()) .await .expect("Execution failed"); @@ -129,19 +133,22 @@ where fn path_in_resolve_return_type() { struct ResolvePath(i32); - graphql_scalar!(ResolvePath { - resolve(&self) -> self::Value { + #[juniper::graphql_scalar] + impl GraphQLScalar for ResolvePath { + fn resolve(&self) -> self::Value { Value::scalar(self.0) } - from_input_value(v: &InputValue) -> Option { + fn from_input_value(v: &juniper::InputValue) -> Option { v.as_scalar_value::().map(|i| ResolvePath(*i)) } - from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> { + fn from_str<'a>( + value: juniper::ScalarToken<'a>, + ) -> ParseScalarResult<'a, DefaultScalarValue> { ::from_str(value) } - }); + } } #[tokio::test] diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 8c3baeb1b..868246a1e 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,5 +3,6 @@ mod derive_input_object; mod derive_object; mod derive_object_with_raw_idents; mod derive_union; +mod impl_scalar; mod impl_union; mod scalar_value_transparent; 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 427c71a63..34092bd0e 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -36,6 +36,8 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618). - 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 30e5edc47..2cf1dd748 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_subscription, graphql_union, GraphQLEnum, GraphQLInputObject, - GraphQLObject, GraphQLScalarValue, GraphQLUnion, + graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum, + GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, }; // 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/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 new file mode 100644 index 000000000..3283d18f4 --- /dev/null +++ b/juniper_codegen/src/impl_scalar.rs @@ -0,0 +1,326 @@ +#![allow(clippy::collapsible_if)] + +use crate::util; +use proc_macro::TokenStream; +use quote::quote; + +#[derive(Debug)] +struct ScalarCodegenInput { + 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, + from_input_value_result: Option, + from_str_arg: Option, + from_str_body: Option, + 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 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; + 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()?; + // 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) => { + if let Some(path_segment) = type_path.path.segments.first() { + impl_for_type = Some(path_segment.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" => { + 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" => { + from_str_arg = get_first_method_arg(method.sig.inputs); + from_str_result = get_method_return_type(method.sig.output); + + if !custom_data_type_is_struct { + enum_data_type = get_enum_type(&from_str_result); + } + + from_str_body = Some(method.block); + } + _ => (), + }, + _ => (), + }; + } + + let custom_data_type = if custom_data_type_is_struct { + impl_for_type.clone() + } else { + enum_data_type + }; + + Ok(ScalarCodegenInput { + impl_for_type, + custom_data_type, + custom_data_type_is_struct, + 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 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)), + 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#async_generic_type_decl #crate_name::GraphQLTypeAsync<#async_generic_type> for #impl_for_type + where + #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<#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); + Box::pin(future::ready(v)) + } + } + ); + + quote!( + #_async + + impl#generic_type_decl #crate_name::GraphQLType<#generic_type> for #impl_for_type + #generic_type_bound + { + type Context = (); + type TypeInfo = (); + + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #crate_name::Registry<'r, #generic_type>, + ) -> #crate_name::meta::MetaType<'r, #generic_type> + where + #generic_type: 'r, + { + registry.build_scalar_type::(info) + #description + .into_meta() + } + + fn resolve( + &self, + info: &(), + selection: Option<&[#crate_name::Selection<#generic_type>]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult<#generic_type> { + Ok(#resolve_body) + } + } + + impl#generic_type_decl #crate_name::ToInputValue<#generic_type> for #impl_for_type + #generic_type_bound + { + fn to_input_value(&self) -> #crate_name::InputValue<#generic_type> { + let v = #resolve_body; + #crate_name::ToInputValue::to_input_value(&v) + } + } + + 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<#generic_type>) -> #from_input_value_result { + #from_input_value_body + } + } + + 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 + } + } + ).into() +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 9a21c4578..3f98cb9de 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -17,6 +17,7 @@ mod derive_object; mod derive_scalar_value; mod derive_union; mod impl_object; +mod impl_scalar; mod impl_union; use proc_macro::TokenStream; @@ -393,6 +394,67 @@ pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenSt impl_object::build_object(args, input, true) } +/// 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_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 {