Repository: graphql-rust/juniper (language: rust)

Issue:
ScalarValue custom derive: newtype support
Extend the `(GraphQL)ScalarValue` custom derive to support newtype structs.

This should work similar to how serde does it with [#[serde(transparent)]](https://serde.rs/container-attrs.html#transparent).

(I'm working on this).

Example:

```rust
#[derive(GraphQLScalarValue)]
struct MyCustomId(Uuid);
```


Discussion / hints:
Are you still working on this or should I take a crack at it? 

Fix patch:
diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md
index 35d07bf7c..7ab9a1e67 100644
--- a/docs/book/content/types/scalars.md
+++ b/docs/book/content/types/scalars.md
@@ -6,59 +6,130 @@ but this often requires coordination with the client library intended to consume
 the API you're building.
 
 Since any value going over the wire is eventually transformed into JSON, you're
-also limited in the data types you can use. Typically, you represent your custom
-scalars as strings.
+also limited in the data types you can use. 
 
-In Juniper, you use the `graphql_scalar!` macro to create a custom scalar. In
-this example, we're representing a user ID as a string wrapped in a custom type:
+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.
 
-```rust
-use juniper::Value;
 
-struct UserID(String);
+## Built-in scalars
 
-juniper::graphql_scalar!(UserID where Scalar = <S> {
-    description: "An opaque identifier, represented as a string"
+Juniper has built-in support for:
 
-    resolve(&self) -> Value {
-        Value::scalar(self.0.clone())
-    }
+* `i32` as `Int`
+* `f64` as `Float`
+* `String` and `&str` as `String`
+* `bool` as `Boolean`
+* `juniper::ID` as `ID`. This type is defined [in the
+  spec](http://facebook.github.io/graphql/#sec-ID) as a type that is serialized
+  as a string but can be parsed from both a string and an integer.
 
-    from_input_value(v: &InputValue) -> Option<UserID> {
-        // If there's a parse error here, simply return None. Juniper will
-        // present an error to the client.
-        v.as_scalar_value::<String>().map(|s| UserID(s.to_owned()))
-    }
+**Third party types**:
 
-    from_str<'a>(value: ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
-        <String as juniper::ParseScalarValue<S>>::from_str(value)
-    }
-});
+Juniper has built-in support for a few additional types from common third party
+crates. They are enabled via features that are on by default.
+
+* uuid::Uuid
+* chrono::DateTime
+* url::Url
+
+## newtype pattern
+
+Often, you might need a custom scalar that just wraps an existing type. 
+
+This can be done with the newtype pattern and a custom derive, similar to how
+serde supports this pattern with `#[serde(transparent)]`.
+
+```rust
+#[derive(juniper::GraphQLScalarValue)]
+pub struct UserId(i32);
 
 #[derive(juniper::GraphQLObject)]
 struct User {
-    id: UserID,
-    username: String,
+    id: UserId,
 }
 
 # fn main() {}
 ```
 
-## Built-in scalars
+That's it, you can now user `UserId` in your schema.
 
-Juniper has built-in support for:
+The macro also allows for more customization:
 
-* `i32` as `Int`
-* `f64` as `Float`
-* `String` and `&str` as `String`
-* `bool` as `Boolean`
-* `juniper::ID` as `ID`. This type is defined [in the
-  spec](http://facebook.github.io/graphql/#sec-ID) as a type that is serialized
-  as a string but can be parsed from both a string and an integer.
+```rust
+/// You can use a doc comment to specify a description.
+#[derive(juniper::GraphQLScalarValue)]
+#[graphql(
+    transparent,
+    // Overwrite the GraphQL type name.
+    name = "MyUserId",
+    // Specify a custom description.
+    // A description in the attribute will overwrite a doc comment.
+    description = "My user id description",
+)]
+pub struct UserId(i32);
+
+# fn main() {}
+```
+
+## Custom scalars
+
+For more complex situations where you also need custom parsing or validation, 
+you can use the `graphql_scalar!` macro.
+
+Typically, you represent your custom scalars as strings.
+
+The example below implements a custom scalar for a custom `Date` type.
+
+Note: juniper already has built-in support for the `chrono::DateTime` type 
+via `chrono` feature, which is enabled by default and should be used for this 
+purpose.
 
-### Non-standard scalars
+The example below is used just for illustration.
 
-Juniper has built-in support for UUIDs from the [uuid
-crate](https://doc.rust-lang.org/uuid/uuid/index.html). This support is enabled
-by default, but can be disabled if you want to reduce the number of dependencies
-in your application.
+**Note**: the example assumes that the `Date` type implements
+`std::fmt::Display` and `std::str::FromStr`.
+
+
+```rust
+# mod date { 
+#    pub struct Date; 
+#    impl std::str::FromStr for Date{ 
+#        type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
+#    }
+#    // And we define how to represent date as a string.
+#    impl std::fmt::Display for Date {
+#        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+#            unimplemented!()
+#        }
+#    }
+# }
+
+use juniper::{Value, ParseScalarResult, ParseScalarValue};
+use date::Date;
+
+juniper::graphql_scalar!(Date where Scalar = <S> {
+    description: "Date"
+
+    // Define how to convert your custom scalar into a primitive type.
+    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<Date> {
+        v.as_scalar_value::<String>()
+         .and_then(|s| s.parse().ok())
+    }
+
+    // Define how to parse a string value.
+    from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
+        <String as ParseScalarValue<S>>::from_str(value)
+    }
+});
+
+# fn main() {}
+```
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index 0b245d268..9424ea274 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -1,5 +1,22 @@
 # master
 
+### newtype ScalarValue derive
+
+See [#345](https://github.com/graphql-rust/juniper/pull/345).
+
+The newtype pattern can now be used with the `GraphQLScalarValue` custom derive
+to easily implement custom scalar values that just wrap another scalar, 
+similar to serdes `#[serde(transparent)]` functionality.
+
+Example:
+
+```rust
+#[derive(juniper::GraphQLScalarValue)]
+struct UserId(i32);
+```
+
+### Other Changes
+
 - The `ID` scalar now implements Serde's `Serialize` and `Deserialize`
 
 # [[0.12.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.12.0)
diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs
index f969fb3f4..27bf14e81 100644
--- a/juniper_codegen/src/derive_scalar_value.rs
+++ b/juniper_codegen/src/derive_scalar_value.rs
@@ -3,25 +3,198 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{self, Data, Fields, Ident, Variant};
 
+use crate::util;
+
+#[derive(Debug, Default)]
+struct TransparentAttributes {
+    transparent: Option<bool>,
+    name: Option<String>,
+    description: Option<String>,
+}
+
+impl syn::parse::Parse for TransparentAttributes {
+    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
+        let mut output = Self {
+            transparent: None,
+            name: None,
+            description: None,
+        };
+
+        let mut content;
+        syn::parenthesized!(content in input);
+
+        while !content.is_empty() {
+            let ident: syn::Ident = content.parse()?;
+            match ident.to_string().as_str() {
+                "name" => {
+                    content.parse::<syn::Token![=]>()?;
+                    let val = content.parse::<syn::LitStr>()?;
+                    output.name = Some(val.value());
+                }
+                "description" => {
+                    content.parse::<syn::Token![=]>()?;
+                    let val = content.parse::<syn::LitStr>()?;
+                    output.description = Some(val.value());
+                }
+                "transparent" => {
+                    output.transparent = Some(true);
+                }
+                other => {
+                    return Err(content.error(format!("Unknown attribute: {}", other)));
+                }
+            }
+            if content.lookahead1().peek(syn::Token![,]) {
+                content.parse::<syn::Token![,]>()?;
+            }
+        }
+
+        Ok(output)
+    }
+}
+
+impl TransparentAttributes {
+    fn from_attrs(attrs: &Vec<syn::Attribute>) -> syn::parse::Result<Self> {
+        match util::find_graphql_attr(attrs) {
+            Some(attr) => {
+                let mut parsed: TransparentAttributes = syn::parse(attr.tts.clone().into())?;
+                if parsed.description.is_none() {
+                    parsed.description = util::get_doc_comment(attrs);
+                }
+                Ok(parsed)
+            }
+            None => Ok(Default::default()),
+        }
+    }
+}
+
 pub fn impl_scalar_value(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream {
     let ident = &ast.ident;
 
-    let variants = match ast.data {
-        Data::Enum(ref enum_data) => &enum_data.variants,
+    match ast.data {
+        Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, is_internal),
+        Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, is_internal),
+        Data::Union(_) => {
+            panic!("#[derive(GraphQLScalarValue)] may not be applied to unions");
+        }
+    }
+}
+
+fn impl_scalar_struct(
+    ast: &syn::DeriveInput,
+    data: &syn::DataStruct,
+    is_internal: bool,
+) -> TokenStream {
+    let field = match data.fields {
+        syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
+            fields.unnamed.first().unwrap().into_value()
+        }
         _ => {
-            panic!("#[derive(GraphQLScalarValue)] may only be applied to enums, not to structs");
+            panic!("#[derive(GraphQLScalarValue)] may only be applied to enums or tuple structs with a single field");
         }
     };
+    let ident = &ast.ident;
+    let attrs = match TransparentAttributes::from_attrs(&ast.attrs) {
+        Ok(attrs) => attrs,
+        Err(e) => {
+            panic!("Invalid #[graphql] attribute: {}", e);
+        }
+    };
+    let inner_ty = &field.ty;
+    let name = attrs.name.unwrap_or(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!(),
+    };
+
+    quote!(
+        impl<S> #crate_name::GraphQLType<S> for #ident
+        where
+            S: #crate_name::ScalarValue,
+            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
+        {
+            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
+                for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
+                S: 'r,
+            {
+                registry.build_scalar_type::<Self>(info)
+                    #description
+                    .into_meta()
+            }
+
+            fn resolve(
+                &self,
+                info: &(),
+                selection: Option<&[#crate_name::Selection<S>]>,
+                executor: &#crate_name::Executor<Self::Context, S>,
+            ) -> #crate_name::Value<S> {
+                #crate_name::GraphQLType::resolve(&self.0, info, selection, executor)
+            }
+        }
+
+        impl<S> #crate_name::ToInputValue<S> for #ident
+        where
+            S: #crate_name::ScalarValue,
+            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
+        {
+            fn to_input_value(&self) -> #crate_name::InputValue<S> {
+                #crate_name::ToInputValue::to_input_value(&self.0)
+            }
+        }
+
+        impl<S> #crate_name::FromInputValue<S> for #ident
+        where
+            S: #crate_name::ScalarValue,
+            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
+        {
+            fn from_input_value(v: &#crate_name::InputValue<S>) -> Option<#ident> {
+                let inner: #inner_ty = #crate_name::FromInputValue::from_input_value(v)?;
+                Some(#ident(inner))
+            }
+        }
+
+        impl<S> #crate_name::ParseScalarValue<S> for #ident
+        where
+            S: #crate_name::ScalarValue,
+            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
+        {
+            fn from_str<'a>(
+                value: #crate_name::parser::ScalarToken<'a>,
+            ) -> #crate_name::ParseScalarResult<'a, S> {
+                <#inner_ty as #crate_name::ParseScalarValue<S>>::from_str(value)
+            }
+        }
+    )
+}
 
-    let froms = variants
+fn impl_scalar_enum(ident: &syn::Ident, data: &syn::DataEnum, is_internal: bool) -> TokenStream {
+    let froms = data
+        .variants
         .iter()
         .map(|v| derive_from_variant(v, ident))
         .collect::<Result<Vec<_>, String>>()
         .unwrap_or_else(|s| panic!("{}", s));
 
-    let serialize = derive_serialize(variants.iter(), ident, is_internal);
+    let serialize = derive_serialize(data.variants.iter(), ident, is_internal);
 
-    let display = derive_display(variants.iter(), ident);
+    let display = derive_display(data.variants.iter(), ident);
 
     quote! {
         #(#froms)*
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index 3c9ec907c..f578ce311 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -55,7 +55,49 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
     gen.into()
 }
 
-#[proc_macro_derive(GraphQLScalarValue)]
+/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
+/// derive.
+///
+/// This can be used for two purposes.
+///
+/// ## Transparent Newtype Wrapper
+///
+/// Sometimes, you want to create a custerm scalar type by wrapping
+/// an existing type. In Rust, this is often called the "newtype" pattern.
+/// Thanks to this custom derive, this becomes really easy:
+///
+/// ```rust
+/// // Deriving GraphQLScalar is all that is required.
+/// #[derive(juniper::GraphQLScalarValue)]
+/// struct UserId(String);
+///
+/// #[derive(juniper::GraphQLObject)]
+/// struct User {
+///   id: UserId,
+/// }
+/// ```
+///
+/// The type can also be customized.
+///
+/// ```rust
+/// /// Doc comments are used for the GraphQL type description.
+/// #[derive(juniper::GraphQLScalarValue)]
+/// #[graphql(
+///    transparent,
+///    // Set a custom GraphQL name.
+///    name= "MyUserId",
+///    // A description can also specified in the attribute.
+///    // This will the doc comment, if one exists.
+///    description = "...",
+/// )]
+/// struct UserId(String);
+/// ```
+///
+/// ### Base ScalarValue Enum
+///
+/// TODO: write documentation.
+///
+#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
 pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
     let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
     let gen = derive_scalar_value::impl_scalar_value(&ast, false);


Test patch:
diff --git a/docs/book/tests/Cargo.toml b/docs/book/tests/Cargo.toml
index 8f49bca3b..e642b42a8 100644
--- a/docs/book/tests/Cargo.toml
+++ b/docs/book/tests/Cargo.toml
@@ -9,11 +9,12 @@ build = "build.rs"
 juniper = { path = "../../../juniper" }
 juniper_iron = { path = "../../../juniper_iron" }
 
-iron = "^0.5.0"
-mount = "^0.3.0"
+iron = "0.5.0"
+mount = "0.4.0"
 
 skeptic = "0.13"
 serde_json = "1.0.39"
+uuid = "0.7.4"
 
 [build-dependencies]
 skeptic = "0.13"
diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
index 938446eff..039e99edf 100644
--- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
+++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
@@ -1,4 +1,3 @@
-#[cfg(test)]
 use fnv::FnvHashMap;
 
 use juniper::DefaultScalarValue;
diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs
index ac20e205b..498258bd7 100644
--- a/integration_tests/juniper_tests/src/codegen/mod.rs
+++ b/integration_tests/juniper_tests/src/codegen/mod.rs
@@ -3,3 +3,4 @@ mod util;
 mod derive_enum;
 mod derive_input_object;
 mod derive_object;
+mod scalar_value_transparent;
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
new file mode 100644
index 000000000..886850cd1
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
@@ -0,0 +1,76 @@
+use fnv::FnvHashMap;
+use juniper::{DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue};
+
+#[derive(juniper::GraphQLScalarValue, PartialEq, Eq, Debug)]
+#[graphql(transparent)]
+struct UserId(String);
+
+#[derive(juniper::GraphQLScalarValue, PartialEq, Eq, Debug)]
+#[graphql(transparent, name = "MyUserId", description = "custom description...")]
+struct CustomUserId(String);
+
+/// The doc comment...
+#[derive(juniper::GraphQLScalarValue, PartialEq, Eq, Debug)]
+#[graphql(transparent)]
+struct IdWithDocComment(i32);
+
+#[derive(juniper::GraphQLObject)]
+struct User {
+    id: UserId,
+    id_custom: CustomUserId,
+}
+
+struct User2;
+
+#[juniper::object]
+impl User2 {
+    fn id(&self) -> UserId {
+        UserId("id".to_string())
+    }
+}
+
+#[test]
+fn test_scalar_value_simple() {
+    assert_eq!(<UserId as GraphQLType>::name(&()), Some("UserId"));
+
+    let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
+    let meta = UserId::meta(&(), &mut registry);
+    assert_eq!(meta.name(), Some("UserId"));
+    assert_eq!(meta.description(), None);
+
+    let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
+    let output: UserId = FromInputValue::from_input_value(&input).unwrap();
+    assert_eq!(output, UserId("userId1".into()),);
+
+    let id = UserId("111".into());
+    let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
+    assert_eq!(output, InputValue::scalar("111"),);
+}
+
+#[test]
+fn test_scalar_value_custom() {
+    assert_eq!(<CustomUserId as GraphQLType>::name(&()), Some("MyUserId"));
+
+    let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
+    let meta = CustomUserId::meta(&(), &mut registry);
+    assert_eq!(meta.name(), Some("MyUserId"));
+    assert_eq!(
+        meta.description(),
+        Some(&"custom description...".to_string())
+    );
+
+    let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
+    let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
+    assert_eq!(output, CustomUserId("userId1".into()),);
+
+    let id = CustomUserId("111".into());
+    let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
+    assert_eq!(output, InputValue::scalar("111"),);
+}
+
+#[test]
+fn test_scalar_value_doc_comment() {
+    let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
+    let meta = IdWithDocComment::meta(&(), &mut registry);
+    assert_eq!(meta.description(), Some(&"The doc comment...".to_string()));
+}