From 0ebd19af5a7afa91d2d721808a12f07c5597aac2 Mon Sep 17 00:00:00 2001
From: ilslv <47687266+ilslv@users.noreply.github.com>
Date: Mon, 28 Feb 2022 12:34:38 +0300
Subject: [PATCH] Redesign `#[derive(GraphQLScalar)]` and `#[graphql_scalar]`
 macros (#1017)

- `#[derive(GraphQLScalar)]`:
    - support generic scalars
    - support structs with single named field
    - support for overriding resolvers
- `#[graphql_scalar]`:
    - support `transparent` argument

Co-authored-by: Kai Ren <tyranron@gmail.com>
---
 docs/book/content/types/scalars.md            |   24 +-
 .../scalar/derive_input/attr_invalid_url.rs   |    6 +
 .../derive_input/attr_invalid_url.stderr      |    5 +
 .../derive_input/attr_transparent_and_with.rs |    6 +
 .../attr_transparent_and_with.stderr          |    5 +
 .../attr_transparent_multiple_named_fields.rs |    9 +
 ...r_transparent_multiple_named_fields.stderr |    8 +
 ...ttr_transparent_multiple_unnamed_fields.rs |    6 +
 ...transparent_multiple_unnamed_fields.stderr |    5 +
 .../attr_transparent_unit_struct.rs           |    6 +
 .../attr_transparent_unit_struct.stderr       |    5 +
 .../scalar/derive_input/derive_invalid_url.rs |    7 +
 .../derive_input/derive_invalid_url.stderr    |    5 +
 .../derive_transparent_and_with.rs            |    7 +
 .../derive_transparent_and_with.stderr        |    5 +
 ...erive_transparent_multiple_named_fields.rs |   10 +
 ...e_transparent_multiple_named_fields.stderr |    9 +
 ...ive_transparent_multiple_unnamed_fields.rs |    7 +
 ...transparent_multiple_unnamed_fields.stderr |    6 +
 .../derive_transparent_unit_struct.rs         |    7 +
 .../derive_transparent_unit_struct.stderr     |    6 +
 .../scalar/derive_input/impl_invalid_url.rs   |   16 -
 .../derive_input/impl_invalid_url.stderr      |   17 -
 ...mpl_invalid_url.rs => attr_invalid_url.rs} |    0
 ...lid_url.stderr => attr_invalid_url.stderr} |    2 +-
 ...vers.rs => attr_with_not_all_resolvers.rs} |    0
 ...err => attr_with_not_all_resolvers.stderr} |    4 +-
 ...resolvers.rs => attr_without_resolvers.rs} |    0
 ...s.stderr => attr_without_resolvers.stderr} |    4 +-
 .../juniper_tests/src/codegen/mod.rs          |    1 +
 .../src/codegen/scalar_attr_derive_input.rs   |  135 +-
 .../src/codegen/scalar_attr_type_alias.rs     |    2 +
 .../src/codegen/scalar_derive.rs              | 1114 +++++++++++++++++
 .../src/executor_tests/introspection/mod.rs   |   20 +-
 juniper/src/executor_tests/variables.rs       |    9 +-
 juniper/src/lib.rs                            |    2 +-
 juniper/src/types/scalars.rs                  |    8 +-
 juniper_codegen/src/graphql_scalar/attr.rs    |  107 +-
 juniper_codegen/src/graphql_scalar/derive.rs  |  123 ++
 juniper_codegen/src/graphql_scalar/mod.rs     |   38 +-
 juniper_codegen/src/lib.rs                    |  349 +++++-
 juniper_codegen/src/result.rs                 |    2 -
 42 files changed, 1922 insertions(+), 185 deletions(-)
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs
 create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr
 delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs
 delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_invalid_url.rs => attr_invalid_url.rs} (100%)
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_invalid_url.stderr => attr_invalid_url.stderr} (71%)
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_with_not_all_resolvers.rs => attr_with_not_all_resolvers.rs} (100%)
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_with_not_all_resolvers.stderr => attr_with_not_all_resolvers.stderr} (65%)
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_without_resolvers.rs => attr_without_resolvers.rs} (100%)
 rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_without_resolvers.stderr => attr_without_resolvers.stderr} (66%)
 create mode 100644 integration_tests/juniper_tests/src/codegen/scalar_derive.rs
 create mode 100644 juniper_codegen/src/graphql_scalar/derive.rs

diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md
index 66bb45cc..ccd35a6e 100644
--- a/docs/book/content/types/scalars.md
+++ b/docs/book/content/types/scalars.md
@@ -52,7 +52,7 @@ 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,ignore
+```rust
 # extern crate juniper;
 #
 #[derive(juniper::GraphQLScalar)]
@@ -70,7 +70,7 @@ struct User {
 `#[derive(GraphQLScalar)]` is mostly interchangeable with `#[graphql_scalar]` 
 attribute:
 
-```rust,ignore
+```rust
 # extern crate juniper;
 # use juniper::graphql_scalar;
 #
@@ -91,7 +91,7 @@ That's it, you can now use `UserId` in your schema.
 
 The macro also allows for more customization:
 
-```rust,ignore
+```rust
 # extern crate juniper;
 /// You can use a doc comment to specify a description.
 #[derive(juniper::GraphQLScalar)]
@@ -112,7 +112,7 @@ All the methods used from newtype's field can be replaced with attributes:
 
 ### `#[graphql(to_output_with = <fn>)]` attribute
 
-```rust,ignore
+```rust
 # use juniper::{GraphQLScalar, ScalarValue, Value};
 #
 #[derive(GraphQLScalar)]
@@ -129,7 +129,7 @@ fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
 
 ### `#[graphql(from_input_with = <fn>)]` attribute
 
-```rust,ignore
+```rust
 # use juniper::{GraphQLScalar, InputValue, ScalarValue};
 #
 #[derive(GraphQLScalar)]
@@ -164,7 +164,7 @@ impl UserId {
 
 ### `#[graphql(parse_token_with = <fn>]` or `#[graphql(parse_token(<types>)]` attributes
 
-```rust,ignore
+```rust
 # use juniper::{
 #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, 
 #     ScalarValue, ScalarToken, Value
@@ -226,7 +226,7 @@ Instead of providing all custom resolvers, you can provide path to the `to_outpu
 Path can be simply `with = Self` (default path where macro expects resolvers to be), 
 in case there is an impl block with custom resolvers:
 
-```rust,ignore
+```rust
 # use juniper::{
 #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
 #     ScalarValue, ScalarToken, Value
@@ -253,7 +253,7 @@ impl StringOrInt {
     {
         v.as_string_value()
             .map(|s| Self::String(s.to_owned()))
-            .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+            .or_else(|| v.as_int_value().map(Self::Int))
             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
     }
   
@@ -271,7 +271,7 @@ impl StringOrInt {
 
 Or it can be path to a module, where custom resolvers are located.
 
-```rust,ignore
+```rust
 # use juniper::{
 #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, 
 #     ScalarValue, ScalarToken, Value
@@ -303,7 +303,7 @@ mod string_or_int {
     {
         v.as_string_value()
             .map(|s| StringOrInt::String(s.to_owned()))
-            .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
+            .or_else(|| v.as_int_value().map(StringOrInt::Int))
             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
     }
   
@@ -321,7 +321,7 @@ mod string_or_int {
 
 Also, you can partially override `#[graphql(with)]` attribute with other custom scalars.
 
-```rust,ignore
+```rust
 # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value};
 #
 #[derive(GraphQLScalar)]
@@ -348,7 +348,7 @@ impl StringOrInt {
     {
         v.as_string_value()
             .map(|s| Self::String(s.to_owned()))
-            .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+            .or_else(|| v.as_int_value().map(Self::Int))
             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
     }
 }
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs
new file mode 100644
index 00000000..b28cb1fc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_scalar;
+
+#[graphql_scalar(specified_by_url = "not an url", transparent)]
+struct ScalarSpecifiedByUrl(i32);
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr
new file mode 100644
index 00000000..b1bd1210
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr
@@ -0,0 +1,5 @@
+error: Invalid URL: relative URL without a base
+ --> fail/scalar/derive_input/attr_invalid_url.rs:3:37
+  |
+3 | #[graphql_scalar(specified_by_url = "not an url", transparent)]
+  |                                     ^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs
new file mode 100644
index 00000000..3b319084
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_scalar;
+
+#[graphql_scalar(with = Self, transparent)]
+struct Scalar;
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr
new file mode 100644
index 00000000..829c42c7
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar `with = <path>` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones.
+ --> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25
+  |
+3 | #[graphql_scalar(with = Self, transparent)]
+  |                         ^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs
new file mode 100644
index 00000000..56658b54
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs
@@ -0,0 +1,9 @@
+use juniper::graphql_scalar;
+
+#[graphql_scalar(transparent)]
+struct Scalar {
+    id: i32,
+    another: i32,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr
new file mode 100644
index 00000000..5a6f9406
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr
@@ -0,0 +1,8 @@
+error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute
+ --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1
+  |
+4 | / struct Scalar {
+5 | |     id: i32,
+6 | |     another: i32,
+7 | | }
+  | |_^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs
new file mode 100644
index 00000000..bbf96936
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_scalar;
+
+#[graphql_scalar(transparent)]
+struct Scalar(i32, i32);
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr
new file mode 100644
index 00000000..dfdcff7c
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute
+ --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1
+  |
+4 | struct Scalar(i32, i32);
+  | ^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs
new file mode 100644
index 00000000..9f8d7568
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_scalar;
+
+#[graphql_scalar(transparent)]
+struct ScalarSpecifiedByUrl;
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr
new file mode 100644
index 00000000..11b661f9
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute
+ --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1
+  |
+4 | struct ScalarSpecifiedByUrl;
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs
new file mode 100644
index 00000000..ae85bfd9
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs
@@ -0,0 +1,7 @@
+use juniper::GraphQLScalar;
+
+#[derive(GraphQLScalar)]
+#[graphql(specified_by_url = "not an url", transparent)]
+struct ScalarSpecifiedByUrl(i64);
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr
new file mode 100644
index 00000000..ee830d6a
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr
@@ -0,0 +1,5 @@
+error: Invalid URL: relative URL without a base
+ --> fail/scalar/derive_input/derive_invalid_url.rs:4:30
+  |
+4 | #[graphql(specified_by_url = "not an url", transparent)]
+  |                              ^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs
new file mode 100644
index 00000000..494ff668
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs
@@ -0,0 +1,7 @@
+use juniper::GraphQLScalar;
+
+#[derive(GraphQLScalar)]
+#[graphql(with = Self, transparent)]
+struct Scalar;
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr
new file mode 100644
index 00000000..860d5391
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr
@@ -0,0 +1,5 @@
+error: GraphQL scalar `with = <path>` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones.
+ --> fail/scalar/derive_input/derive_transparent_and_with.rs:4:18
+  |
+4 | #[graphql(with = Self, transparent)]
+  |                  ^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs
new file mode 100644
index 00000000..3388e1fc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs
@@ -0,0 +1,10 @@
+use juniper::GraphQLScalar;
+
+#[derive(GraphQLScalar)]
+#[graphql(transparent)]
+struct Scalar {
+    id: i32,
+    another: i32,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr
new file mode 100644
index 00000000..d5258cef
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr
@@ -0,0 +1,9 @@
+error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute
+ --> fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs:4:1
+  |
+4 | / #[graphql(transparent)]
+5 | | struct Scalar {
+6 | |     id: i32,
+7 | |     another: i32,
+8 | | }
+  | |_^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs
new file mode 100644
index 00000000..d7710fc8
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs
@@ -0,0 +1,7 @@
+use juniper::GraphQLScalar;
+
+#[derive(GraphQLScalar)]
+#[graphql(transparent)]
+struct Scalar(i32, i32);
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr
new file mode 100644
index 00000000..a832801b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr
@@ -0,0 +1,6 @@
+error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute
+ --> fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs:4:1
+  |
+4 | / #[graphql(transparent)]
+5 | | struct Scalar(i32, i32);
+  | |________________________^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs
new file mode 100644
index 00000000..09712baa
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs
@@ -0,0 +1,7 @@
+use juniper::GraphQLScalar;
+
+#[derive(GraphQLScalar)]
+#[graphql(transparent)]
+struct ScalarSpecifiedByUrl;
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr
new file mode 100644
index 00000000..3fc185ed
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr
@@ -0,0 +1,6 @@
+error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute
+ --> fail/scalar/derive_input/derive_transparent_unit_struct.rs:4:1
+  |
+4 | / #[graphql(transparent)]
+5 | | struct ScalarSpecifiedByUrl;
+  | |____________________________^
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs
deleted file mode 100644
index 4d93bc1f..00000000
--- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
-
-#[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
-struct ScalarSpecifiedByUrl(i32);
-
-impl ScalarSpecifiedByUrl {
-    fn to_output<S: ScalarValue>(&self) -> Value<S> {
-        Value::scalar(0)
-    }
-
-    fn from_input<S: ScalarValue>(_: &InputValue<S>) -> Result<Self, String> {
-        Ok(Self)
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr
deleted file mode 100644
index 1b6edaeb..00000000
--- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr
+++ /dev/null
@@ -1,17 +0,0 @@
-error: Invalid URL: relative URL without a base
- --> fail/scalar/derive_input/impl_invalid_url.rs:3:37
-  |
-3 | #[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
-  |                                     ^^^^^^^^^^^^
-
-error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope
- --> fail/scalar/derive_input/impl_invalid_url.rs:6:6
-  |
-6 | impl ScalarSpecifiedByUrl {
-  |      ^^^^^^^^^^^^^^^^^^^^ not found in this scope
-
-error: the `Self` constructor can only be used with tuple or unit structs
-  --> fail/scalar/derive_input/impl_invalid_url.rs:12:12
-   |
-12 |         Ok(Self)
-   |            ^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs
similarity index 100%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr
similarity index 71%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr
index b000693c..4ecefa21 100644
--- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr
@@ -1,5 +1,5 @@
 error: Invalid URL: relative URL without a base
- --> fail/scalar/type_alias/impl_invalid_url.rs:6:24
+ --> fail/scalar/type_alias/attr_invalid_url.rs:6:24
   |
 6 |     specified_by_url = "not an url",
   |                        ^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs
similarity index 100%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.rs
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr
similarity index 65%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr
index 9502bb06..2bb56a2b 100644
--- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_not_all_resolvers.stderr
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr
@@ -1,5 +1,5 @@
-error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
- --> fail/scalar/type_alias/impl_with_not_all_resolvers.rs:6:1
+error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments
+ --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1
   |
 6 | type CustomScalar = Scalar;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs
similarity index 100%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs
diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr
similarity index 66%
rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr
rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr
index 93f6eb7a..626f34a3 100644
--- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr
+++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr
@@ -1,5 +1,5 @@
-error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
- --> fail/scalar/type_alias/impl_without_resolvers.rs:6:1
+error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments
+ --> fail/scalar/type_alias/attr_without_resolvers.rs:6:1
   |
 6 | type CustomScalar = Scalar;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs
index f87b4a3c..2c617724 100644
--- a/integration_tests/juniper_tests/src/codegen/mod.rs
+++ b/integration_tests/juniper_tests/src/codegen/mod.rs
@@ -6,6 +6,7 @@ mod object_attr;
 mod object_derive;
 mod scalar_attr_derive_input;
 mod scalar_attr_type_alias;
+mod scalar_derive;
 mod subscription_attr;
 mod union_attr;
 mod union_derive;
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs
index d50cc58c..ccfa3575 100644
--- a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs
+++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs
@@ -1,3 +1,7 @@
+//! Tests for `#[graphql_scalar]` macro placed on [`DeriveInput`].
+//!
+//! [`DeriveInput`]: syn::DeriveInput
+
 use std::fmt;
 
 use chrono::{DateTime, TimeZone, Utc};
@@ -87,6 +91,135 @@ mod trivial {
     }
 }
 
+mod transparent {
+    use super::*;
+
+    #[graphql_scalar(transparent)]
+    struct Counter(i32);
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod transparent_with_resolver {
+    use super::*;
+
+    #[graphql_scalar(
+        transparent,
+        to_output_with = Self::to_output,
+    )]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0 + 1)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 1}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
 mod all_custom_resolvers {
     use super::*;
 
@@ -333,7 +466,7 @@ mod multiple_delegated_parse_token {
         fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
             v.as_string_value()
                 .map(|s| Self::String(s.to_owned()))
-                .or_else(|| v.as_int_value().map(|i| Self::Int(i)))
+                .or_else(|| v.as_int_value().map(Self::Int))
                 .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
         }
     }
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs
index 3f8854c3..9dfd8f6c 100644
--- a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs
+++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs
@@ -1,3 +1,5 @@
+//! Tests for `#[graphql_scalar]` macro placed on a type alias.
+
 use std::fmt;
 
 use chrono::{DateTime, TimeZone, Utc};
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_derive.rs b/integration_tests/juniper_tests/src/codegen/scalar_derive.rs
new file mode 100644
index 00000000..a538095f
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/scalar_derive.rs
@@ -0,0 +1,1114 @@
+//! Tests for `#[derive(GraphQLScalar)]` macro.
+
+use std::fmt;
+
+use chrono::{DateTime, TimeZone, Utc};
+use juniper::{
+    execute, graphql_object, graphql_value, graphql_vars, GraphQLScalar, InputValue,
+    ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value,
+};
+
+use crate::{
+    custom_scalar::MyScalarValue,
+    util::{schema, schema_with_scalar},
+};
+
+mod trivial {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(t)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod transparent {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(transparent)]
+    struct Counter(i32);
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod transparent_with_resolver {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(transparent, to_output_with = Self::to_output)]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0 + 1)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 1}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod all_custom_resolvers {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(
+        to_output_with = to_output,
+        from_input_with = from_input,
+    )]
+    #[graphql(parse_token_with = parse_token)]
+    struct Counter(i32);
+
+    fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
+        Value::scalar(v.0)
+    }
+
+    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
+        v.as_int_value()
+            .map(Counter)
+            .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+    }
+
+    fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+        <i32 as ParseScalarValue<S>>::from_str(value)
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod explicit_name {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(name = "Counter")]
+    struct CustomCounter(i32);
+
+    impl CustomCounter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(value)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: CustomCounter) -> CustomCounter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod delegated_parse_token {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod multiple_delegated_parse_token {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(parse_token(String, i32))]
+    enum StringOrInt {
+        String(String),
+        Int(i32),
+    }
+
+    impl StringOrInt {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            match self {
+                Self::String(str) => Value::scalar(str.to_owned()),
+                Self::Int(i) => Value::scalar(*i),
+            }
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_string_value()
+                .map(|s| Self::String(s.to_owned()))
+                .or_else(|| v.as_int_value().map(Self::Int))
+                .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn string_or_int(value: StringOrInt) -> StringOrInt {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_string() {
+        const DOC: &str = r#"{ stringOrInt(value: "test") }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": "test"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_int() {
+        const DOC: &str = r#"{ stringOrInt(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"stringOrInt": 0}), vec![])),
+        );
+    }
+}
+
+mod where_attribute {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(
+        to_output_with = to_output,
+        from_input_with = from_input,
+        parse_token(String),
+        where(Tz: From<Utc>, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
+
+    fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        Value::scalar(v.0.to_rfc3339())
+    }
+
+    fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+    where
+        S: ScalarValue,
+        Tz: From<Utc> + TimeZone,
+        Tz::Offset: fmt::Display,
+    {
+        v.as_string_value()
+            .ok_or_else(|| format!("Expected `String`, found: {}", v))
+            .and_then(|s| {
+                DateTime::parse_from_rfc3339(s)
+                    .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
+                    .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+            })
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod with_self {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(with = Self)]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+
+        fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+            <i32 as ParseScalarValue<S>>::from_str(value)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+}
+
+mod with_module {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(
+        with = custom_date_time,
+        parse_token(String),
+        where(Tz: From<Utc>, Tz::Offset: fmt::Display),
+        specified_by_url = "https://tools.ietf.org/html/rfc3339",
+    )]
+    struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
+
+    mod custom_date_time {
+        use super::*;
+
+        pub(super) fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            Value::scalar(v.0.to_rfc3339())
+        }
+
+        pub(super) fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
+        where
+            S: ScalarValue,
+            Tz: From<Utc> + TimeZone,
+            Tz::Offset: fmt::Display,
+        {
+            v.as_string_value()
+                .ok_or_else(|| format!("Expected `String`, found: {}", v))
+                .and_then(|s| {
+                    DateTime::parse_from_rfc3339(s)
+                        .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
+                        .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
+                })
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_custom_date_time() {
+        const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_specified_by_url() {
+        const DOC: &str = r#"{
+            __type(name: "CustomDateTime") {
+                specifiedByUrl
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    /// Description
+    #[derive(GraphQLScalar)]
+    #[graphql(parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_attribute {
+    use super::*;
+
+    /// Doc comment
+    #[derive(GraphQLScalar)]
+    #[graphql(description = "Description from attribute", parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description from attribute"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod custom_scalar {
+    use super::*;
+
+    /// Description
+    #[derive(GraphQLScalar)]
+    #[graphql(scalar = MyScalarValue, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic_scalar {
+    use super::*;
+
+    /// Description
+    #[derive(GraphQLScalar)]
+    #[graphql(scalar = S: ScalarValue, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `Counter`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                description
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"description": "Description"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    #[derive(GraphQLScalar)]
+    #[graphql(scalar = S: ScalarValue + Clone, parse_token(i32))]
+    struct Counter(i32);
+
+    impl Counter {
+        fn to_output<S: ScalarValue>(&self) -> Value<S> {
+            Value::scalar(self.0)
+        }
+
+        fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
+            v.as_int_value()
+                .map(Self)
+                .ok_or_else(|| format!("Expected `String`, found: {}", v))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn counter(value: Counter) -> Counter {
+            value
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_scalar() {
+        const DOC: &str = r#"{
+            __type(name: "Counter") {
+                kind
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_counter() {
+        const DOC: &str = r#"{ counter(value: 0) }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"counter": 0}), vec![])),
+        );
+    }
+}
diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs
index 8f140cf7..25eff845 100644
--- a/juniper/src/executor_tests/introspection/mod.rs
+++ b/juniper/src/executor_tests/introspection/mod.rs
@@ -6,10 +6,10 @@ mod input_object;
 use self::input_object::{NamedPublic, NamedPublicWithDescription};
 
 use crate::{
-    graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
+    graphql_interface, graphql_object, graphql_value, graphql_vars,
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
-    GraphQLEnum, InputValue, ScalarValue, Value,
+    GraphQLEnum, GraphQLScalar,
 };
 
 #[derive(GraphQLEnum)]
@@ -19,22 +19,10 @@ enum Sample {
     Two,
 }
 
-// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
-#[graphql_scalar(name = "SampleScalar", parse_token(i32))]
+#[derive(GraphQLScalar)]
+#[graphql(name = "SampleScalar", transparent)]
 struct Scalar(i32);
 
-impl Scalar {
-    fn to_output<S: ScalarValue>(&self) -> Value<S> {
-        Value::scalar(self.0)
-    }
-
-    fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
-        v.as_int_value()
-            .map(Self)
-            .ok_or_else(|| format!("Expected `Int`, found: {}", v))
-    }
-}
-
 /// A sample interface
 #[graphql_interface(name = "SampleInterface", for = Root)]
 trait Interface {
diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs
index 22bb8b49..257f26c4 100644
--- a/juniper/src/executor_tests/variables.rs
+++ b/juniper/src/executor_tests/variables.rs
@@ -1,18 +1,17 @@
 use crate::{
     executor::Variables,
-    graphql_object, graphql_scalar, graphql_value, graphql_vars,
+    graphql_object, graphql_value, graphql_vars,
     parser::SourcePosition,
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
     validation::RuleError,
     value::{DefaultScalarValue, Object},
     GraphQLError::ValidationError,
-    GraphQLInputObject, InputValue, ScalarValue, Value,
+    GraphQLInputObject, GraphQLScalar, InputValue, ScalarValue, Value,
 };
 
-// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
-#[derive(Debug)]
-#[graphql_scalar(parse_token(String))]
+#[derive(Debug, GraphQLScalar)]
+#[graphql(parse_token(String))]
 struct TestComplexScalar;
 
 impl TestComplexScalar {
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index bd3dadd0..ff430e32 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -115,7 +115,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture};
 // functionality automatically.
 pub use juniper_codegen::{
     graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
-    GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLUnion,
+    GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLUnion,
 };
 
 #[doc(hidden)]
diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs
index 52a21dc0..2ba5e5ea 100644
--- a/juniper/src/types/scalars.rs
+++ b/juniper/src/types/scalars.rs
@@ -17,14 +17,14 @@ use crate::{
         subscriptions::GraphQLSubscriptionValue,
     },
     value::{ParseScalarResult, ScalarValue, Value},
+    GraphQLScalar,
 };
 
 /// An ID as defined by the GraphQL specification
 ///
 /// Represented as a string, but can be converted _to_ from an integer as well.
-// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
-#[graphql_scalar(parse_token(String, i32))]
+#[derive(Clone, Debug, Deserialize, Eq, GraphQLScalar, PartialEq, Serialize)]
+#[graphql(parse_token(String, i32))]
 pub struct ID(String);
 
 impl ID {
@@ -35,7 +35,7 @@ impl ID {
     fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
         v.as_string_value()
             .map(str::to_owned)
-            .or_else(|| v.as_int_value().map(|i| i.to_string()))
+            .or_else(|| v.as_int_value().as_ref().map(ToString::to_string))
             .map(Self)
             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
     }
diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs
index 1405b236..b9ff5b58 100644
--- a/juniper_codegen/src/graphql_scalar/attr.rs
+++ b/juniper_codegen/src/graphql_scalar/attr.rs
@@ -1,7 +1,7 @@
 //! Code generation for `#[graphql_scalar]` macro.
 
 use proc_macro2::{Span, TokenStream};
-use quote::{quote, ToTokens};
+use quote::quote;
 use syn::{parse_quote, spanned::Spanned};
 
 use crate::{
@@ -10,7 +10,7 @@ use crate::{
     GraphQLScope,
 };
 
-use super::{Attr, Definition, GraphQLScalarMethods, ParseToken};
+use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken};
 
 const ERR: GraphQLScope = GraphQLScope::ScalarAttr;
 
@@ -39,36 +39,14 @@ fn expand_on_type_alias(
     ast: syn::ItemType,
 ) -> syn::Result<TokenStream> {
     let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
+    if attr.transparent {
+        return Err(ERR.custom_error(
+            ast.span(),
+            "`transparent` attribute argument isn't applicable to type aliases",
+        ));
+    }
 
-    let field = match (
-        attr.to_output.as_deref().cloned(),
-        attr.from_input.as_deref().cloned(),
-        attr.parse_token.as_deref().cloned(),
-        attr.with.as_deref().cloned(),
-    ) {
-        (Some(to_output), Some(from_input), Some(parse_token), None) => {
-            GraphQLScalarMethods::Custom {
-                to_output,
-                from_input,
-                parse_token,
-            }
-        }
-        (to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom {
-            to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
-            from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
-            parse_token: parse_token
-                .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
-        },
-        _ => {
-            return Err(ERR.custom_error(
-                ast.span(),
-                "all custom resolvers have to be provided via `with` or \
-                 combination of `to_output_with`, `from_input_with`, \
-                 `parse_token_with` attribute arguments",
-            ));
-        }
-    };
-
+    let methods = parse_type_alias_methods(&ast, &attr)?;
     let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
     let def = Definition {
@@ -77,7 +55,7 @@ fn expand_on_type_alias(
             .where_clause
             .map_or_else(Vec::new, |cl| cl.into_inner()),
         generics: ast.generics.clone(),
-        methods: field,
+        methods,
         name: attr
             .name
             .as_deref()
@@ -86,8 +64,7 @@ fn expand_on_type_alias(
         description: attr.description.as_deref().cloned(),
         specified_by_url: attr.specified_by_url.as_deref().cloned(),
         scalar,
-    }
-    .to_token_stream();
+    };
 
     Ok(quote! {
         #ast
@@ -95,38 +72,13 @@ fn expand_on_type_alias(
     })
 }
 
-// TODO: Support `#[graphql(transparent)]`.
-/// Expands `#[graphql_scalar]` macro placed on a struct/enum/union.
+/// Expands `#[graphql_scalar]` macro placed on a struct, enum or union.
 fn expand_on_derive_input(
     attrs: Vec<syn::Attribute>,
     ast: syn::DeriveInput,
 ) -> syn::Result<TokenStream> {
     let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
-
-    let field = match (
-        attr.to_output.as_deref().cloned(),
-        attr.from_input.as_deref().cloned(),
-        attr.parse_token.as_deref().cloned(),
-        attr.with.as_deref().cloned(),
-    ) {
-        (Some(to_output), Some(from_input), Some(parse_token), None) => {
-            GraphQLScalarMethods::Custom {
-                to_output,
-                from_input,
-                parse_token,
-            }
-        }
-        (to_output, from_input, parse_token, module) => {
-            let module = module.unwrap_or_else(|| parse_quote! { Self });
-            GraphQLScalarMethods::Custom {
-                to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
-                from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
-                parse_token: parse_token
-                    .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
-            }
-        }
-    };
-
+    let methods = parse_derived_methods(&ast, &attr)?;
     let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
     let def = Definition {
@@ -135,7 +87,7 @@ fn expand_on_derive_input(
             .where_clause
             .map_or_else(Vec::new, |cl| cl.into_inner()),
         generics: ast.generics.clone(),
-        methods: field,
+        methods,
         name: attr
             .name
             .as_deref()
@@ -144,11 +96,38 @@ fn expand_on_derive_input(
         description: attr.description.as_deref().cloned(),
         specified_by_url: attr.specified_by_url.as_deref().cloned(),
         scalar,
-    }
-    .to_token_stream();
+    };
 
     Ok(quote! {
         #ast
         #def
     })
 }
+
+/// Parses [`Methods`] from the provided [`Attr`] for the specified type alias.
+fn parse_type_alias_methods(ast: &syn::ItemType, attr: &Attr) -> syn::Result<Methods> {
+    match (
+        attr.to_output.as_deref().cloned(),
+        attr.from_input.as_deref().cloned(),
+        attr.parse_token.as_deref().cloned(),
+        attr.with.as_deref().cloned(),
+    ) {
+        (Some(to_output), Some(from_input), Some(parse_token), None) => Ok(Methods::Custom {
+            to_output,
+            from_input,
+            parse_token,
+        }),
+        (to_output, from_input, parse_token, Some(module)) => Ok(Methods::Custom {
+            to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
+            from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
+            parse_token: parse_token
+                .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
+        }),
+        _ => Err(ERR.custom_error(
+            ast.span(),
+            "all the resolvers have to be provided via `with` attribute \
+             argument or a combination of `to_output_with`, `from_input_with`, \
+             `parse_token_with`/`parse_token` attribute arguments",
+        )),
+    }
+}
diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs
new file mode 100644
index 00000000..e16bcd7d
--- /dev/null
+++ b/juniper_codegen/src/graphql_scalar/derive.rs
@@ -0,0 +1,123 @@
+//! Code generation for `#[derive(GraphQLScalar)]` macro.
+
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::{parse_quote, spanned::Spanned};
+
+use crate::{common::scalar, result::GraphQLScope};
+
+use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent};
+
+/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro.
+const ERR: GraphQLScope = GraphQLScope::ScalarDerive;
+
+/// Expands `#[derive(GraphQLScalar)]` macro into generated code.
+pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
+    let ast = syn::parse2::<syn::DeriveInput>(input)?;
+    let attr = Attr::from_attrs("graphql", &ast.attrs)?;
+    let methods = parse_derived_methods(&ast, &attr)?;
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    Ok(Definition {
+        ty: TypeOrIdent::Ident(ast.ident.clone()),
+        where_clause: attr
+            .where_clause
+            .map_or_else(Vec::new, |cl| cl.into_inner()),
+        generics: ast.generics.clone(),
+        methods,
+        name: attr
+            .name
+            .as_deref()
+            .cloned()
+            .unwrap_or_else(|| ast.ident.to_string()),
+        description: attr.description.as_deref().cloned(),
+        specified_by_url: attr.specified_by_url.as_deref().cloned(),
+        scalar,
+    }
+    .to_token_stream())
+}
+
+/// Parses [`Methods`] from the provided [`Attr`] for the specified
+/// [`syn::DeriveInput`].
+pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn::Result<Methods> {
+    match (
+        attr.to_output.as_deref().cloned(),
+        attr.from_input.as_deref().cloned(),
+        attr.parse_token.as_deref().cloned(),
+        attr.with.as_deref().cloned(),
+        attr.transparent,
+    ) {
+        (Some(to_output), Some(from_input), Some(parse_token), None, false) => {
+            Ok(Methods::Custom {
+                to_output,
+                from_input,
+                parse_token,
+            })
+        }
+        (to_output, from_input, parse_token, module, false) => {
+            let module = module.unwrap_or_else(|| parse_quote! { Self });
+            Ok(Methods::Custom {
+                to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
+                from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
+                parse_token: parse_token
+                    .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
+            })
+        }
+        (to_output, from_input, parse_token, None, true) => {
+            let data = if let syn::Data::Struct(data) = &ast.data {
+                data
+            } else {
+                return Err(ERR.custom_error(
+                    ast.span(),
+                    "`transparent` attribute argument requires exactly 1 field",
+                ));
+            };
+            let field = match &data.fields {
+                syn::Fields::Unit => Err(ERR.custom_error(
+                    ast.span(),
+                    "`transparent` attribute argument requires exactly 1 field",
+                )),
+                syn::Fields::Unnamed(fields) => fields
+                    .unnamed
+                    .first()
+                    .filter(|_| fields.unnamed.len() == 1)
+                    .cloned()
+                    .map(Field::Unnamed)
+                    .ok_or_else(|| {
+                        ERR.custom_error(
+                            ast.span(),
+                            "`transparent` attribute argument requires \
+                             exactly 1 field",
+                        )
+                    }),
+                syn::Fields::Named(fields) => fields
+                    .named
+                    .first()
+                    .filter(|_| fields.named.len() == 1)
+                    .cloned()
+                    .map(Field::Named)
+                    .ok_or_else(|| {
+                        ERR.custom_error(
+                            ast.span(),
+                            "`transparent` attribute argument requires \
+                             exactly 1 field",
+                        )
+                    }),
+            }?;
+            Ok(Methods::Delegated {
+                to_output,
+                from_input,
+                parse_token,
+                field: Box::new(field),
+            })
+        }
+        (_, _, _, Some(module), true) => Err(ERR.custom_error(
+            module.span(),
+            "`with = <path>` attribute argument cannot be combined with \
+             `transparent`. \
+             You can specify custom resolvers with `to_output_with`, \
+             `from_input_with`, `parse_token`/`parse_token_with` attribute \
+             arguments and still use `transparent` for unspecified ones.",
+        )),
+    }
+}
diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs
index 08dad125..a7de21bc 100644
--- a/juniper_codegen/src/graphql_scalar/mod.rs
+++ b/juniper_codegen/src/graphql_scalar/mod.rs
@@ -26,6 +26,7 @@ use crate::{
 };
 
 pub mod attr;
+pub mod derive;
 
 /// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when
 /// generating code for [GraphQL scalar][1].
@@ -86,6 +87,10 @@ struct Attr {
 
     /// Explicit where clause added to [`syn::WhereClause`].
     where_clause: Option<SpanContainer<Vec<syn::WherePredicate>>>,
+
+    /// Indicator for single-field structs allowing to delegate implmemntations
+    /// of non-provided resolvers to that field.
+    transparent: bool,
 }
 
 impl Parse for Attr {
@@ -184,10 +189,7 @@ impl Parse for Attr {
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "where" => {
-                    let (span, parsed_predicates) = if input.parse::<token::Eq>().is_ok() {
-                        let pred = input.parse::<syn::WherePredicate>()?;
-                        (pred.span(), vec![pred])
-                    } else {
+                    let (span, parsed_predicates) = {
                         let predicates;
                         let _ = syn::parenthesized!(predicates in input);
                         let parsed_predicates = predicates
@@ -196,7 +198,7 @@ impl Parse for Attr {
                         if parsed_predicates.is_empty() {
                             return Err(syn::Error::new(
                                 ident.span(),
-                                "expected at least 1 where predicate.",
+                                "expected at least 1 where predicate",
                             ));
                         }
 
@@ -214,6 +216,9 @@ impl Parse for Attr {
                         ))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "transparent" => {
+                    out.transparent = true;
+                }
                 name => {
                     return Err(err::unknown_arg(&ident, name));
                 }
@@ -238,6 +243,7 @@ impl Attr {
             parse_token: try_merge_opt!(parse_token: self, another),
             with: try_merge_opt!(with: self, another),
             where_clause: try_merge_opt!(where_clause: self, another),
+            transparent: self.transparent || another.transparent,
         })
     }
 
@@ -293,7 +299,7 @@ struct Definition {
     /// [`GraphQLScalarMethods`] representing [GraphQL scalar][1].
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Scalars
-    methods: GraphQLScalarMethods,
+    methods: Methods,
 
     /// Description of this [GraphQL scalar][1] to put into GraphQL schema.
     ///
@@ -535,10 +541,10 @@ impl Definition {
             #[automatically_derived]
             impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty
                 #where_clause
-           {
-               fn from_str(
+            {
+                fn from_str(
                     token: ::juniper::parser::ScalarToken<'_>,
-               ) -> ::juniper::ParseScalarResult<'_, #scalar> {
+                ) -> ::juniper::ParseScalarResult<'_, #scalar> {
                     #from_str
                 }
             }
@@ -676,8 +682,7 @@ impl VisitMut for ModifyLifetimes {
 /// Methods representing [GraphQL scalar][1].
 ///
 /// [1]: https://spec.graphql.org/October2021#sec-Scalars
-#[allow(dead_code)]
-enum GraphQLScalarMethods {
+enum Methods {
     /// [GraphQL scalar][1] represented with only custom resolvers.
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Scalars
@@ -713,7 +718,7 @@ enum GraphQLScalarMethods {
     },
 }
 
-impl GraphQLScalarMethods {
+impl Methods {
     /// Expands [`GraphQLValue::resolve`] method.
     ///
     /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve
@@ -755,7 +760,9 @@ impl GraphQLScalarMethods {
                 }
             }
             Self::Delegated { field, .. } => {
-                quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) }
+                quote! {
+                    ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field)
+                }
             }
         }
     }
@@ -798,7 +805,9 @@ impl GraphQLScalarMethods {
             }
             Self::Delegated { field, .. } => {
                 let field_ty = field.ty();
-                quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }
+                quote! {
+                    <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token)
+                }
             }
         }
     }
@@ -848,7 +857,6 @@ impl ParseToken {
 }
 
 /// Struct field to resolve not provided methods.
-#[allow(dead_code)]
 enum Field {
     /// Named [`Field`].
     Named(syn::Field),
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index 2be16385..6208a9a3 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -142,19 +142,48 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
     }
 }
 
-/// `#[graphql_scalar]` is interchangeable with `#[derive(`[`GraphQLScalar`]`)]`
-/// macro:
+/// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][0]
+/// implementation.
 ///
-/// ```rust,ignore
+/// # Transparent delegation
+///
+/// Sometimes, you want to create a custom [GraphQL scalar][0] type by just
+/// wrapping an existing one, inheriting all its behavior. In Rust, this is
+/// often called as ["`Newtype` pattern"][1]. This may be achieved by providing
+/// a `#[graphql(transparent)]` attribute to the definition:
+/// ```rust
+/// # use juniper::{GraphQLObject, GraphQLScalar};
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(transparent)]
+/// struct UserId(String);
+///
+/// #[derive(GraphQLScalar)]
+/// #[graphql(transparent)]
+/// struct DroidId {
+///     value: String,
+/// }
+///
+/// #[derive(GraphQLObject)]
+/// struct Pair {
+///   user_id: UserId,
+///   droid_id: DroidId,
+/// }
+/// ```
+///
+/// The inherited behaviour may also be customized:
+/// ```rust
+/// # use juniper::GraphQLScalar;
+/// #
 /// /// Doc comments are used for the GraphQL type description.
-/// #[derive(juniper::GraphQLScalar)]
+/// #[derive(GraphQLScalar)]
 /// #[graphql(
-///     // Set a custom GraphQL name.
+///     // Custom GraphQL name.
 ///     name = "MyUserId",
-///     // A description can also specified in the attribute.
+///     // Description can also specified in the attribute.
 ///     // This will the doc comment, if one exists.
 ///     description = "...",
-///     // A specification URL.
+///     // Optional specification URL.
 ///     specified_by_url = "https://tools.ietf.org/html/rfc4122",
 ///     // Explicit generic scalar.
 ///     scalar = S: juniper::ScalarValue,
@@ -163,17 +192,284 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
 /// struct UserId(String);
 /// ```
 ///
-/// Is transformed into:
+/// All of the methods inherited from `Newtype`'s field may also be overridden
+/// with the attributes described below.
 ///
-/// ```rust,ignore
+/// # Custom resolving
+///
+/// Customization of a [GraphQL scalar][0] type resolving is possible via
+/// `#[graphql(to_output_with = <fn path>)]` attribute:
+/// ```rust
+/// # use juniper::{GraphQLScalar, ScalarValue, Value};
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(to_output_with = to_output, transparent)]
+/// struct Incremented(i32);
+///
+/// /// Increments [`Incremented`] before converting into a [`Value`].
+/// fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
+///     let inc = v.0 + 1;
+///     Value::from(inc)
+/// }
+/// ```
+///
+/// # Custom parsing
+///
+/// Customization of a [GraphQL scalar][0] type parsing is possible via
+/// `#[graphql(from_input_with = <fn path>)]` attribute:
+/// ```rust
+/// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue};
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(from_input_with = Self::from_input, transparent)]
+/// struct UserId(String);
+///
+/// impl UserId {
+///     /// Checks whether [`InputValue`] is `String` beginning with `id: ` and
+///     /// strips it.
+///     fn from_input<S: ScalarValue>(
+///         input: &InputValue<S>,
+///     ) -> Result<Self, String> {
+///         //            ^^^^^^ must implement `IntoFieldError`
+///         input.as_string_value()
+///             .ok_or_else(|| format!("Expected `String`, found: {}", input))
+///             .and_then(|str| {
+///                 str.strip_prefix("id: ")
+///                     .ok_or_else(|| {
+///                         format!(
+///                             "Expected `UserId` to begin with `id: `, \
+///                              found: {}",
+///                             input,
+///                         )
+///                     })
+///             })
+///             .map(|id| Self(id.to_owned()))
+///     }
+/// }
+/// ```
+///
+/// # Custom token parsing
+///
+/// Customization of which tokens a [GraphQL scalar][0] type should be parsed is
+/// possible via `#[graphql(parse_token_with = <fn path>)]` or
+/// `#[graphql(parse_token(<types>)]` attributes:
+/// ```rust
+/// # use juniper::{
+/// #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
+/// #     ScalarValue, ScalarToken, Value,
+/// # };
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(
+///     to_output_with = to_output,
+///     from_input_with = from_input,
+///     parse_token_with = parse_token,
+/// )]
+/// //  ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)`, which
+/// //                   tries to parse as `String` first, and then as `i32` if
+/// //                   prior fails.
+/// enum StringOrInt {
+///     String(String),
+///     Int(i32),
+/// }
+///
+/// fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
+///     match v {
+///         StringOrInt::String(str) => Value::scalar(str.to_owned()),
+///         StringOrInt::Int(i) => Value::scalar(*i),
+///     }
+/// }
+///
+/// fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
+///     v.as_string_value()
+///         .map(|s| StringOrInt::String(s.to_owned()))
+///         .or_else(|| v.as_int_value().map(StringOrInt::Int))
+///         .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+/// }
+///
+/// fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+///     <String as ParseScalarValue<S>>::from_str(value)
+///         .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
+/// }
+/// ```
+/// > __NOTE:__ Once we provide all 3 custom functions, there is no sense to
+/// >           follow [`Newtype` pattern][1] anymore.
+///
+/// # All at once
+///
+/// Instead of providing all custom functions separately, it's possible to
+/// provide a module holding the appropriate `to_output()`, `from_input()` and
+/// `parse_token()` functions:
+/// ```rust
+/// # use juniper::{
+/// #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
+/// #     ScalarValue, ScalarToken, Value,
+/// # };
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(with = string_or_int)]
+/// enum StringOrInt {
+///     String(String),
+///     Int(i32),
+/// }
+///
+/// mod string_or_int {
+///     use super::*;
+///
+///     pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
+///         match v {
+///             StringOrInt::String(str) => Value::scalar(str.to_owned()),
+///             StringOrInt::Int(i) => Value::scalar(*i),
+///         }
+///     }
+///
+///     pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
+///         v.as_string_value()
+///             .map(|s| StringOrInt::String(s.to_owned()))
+///             .or_else(|| v.as_int_value().map(StringOrInt::Int))
+///             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+///     }
+///
+///     pub(super) fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
+///         <String as ParseScalarValue<S>>::from_str(t)
+///             .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(t))
+///     }
+/// }
+/// #
+/// # fn main() {}
+/// ```
+///
+/// A regular `impl` block is also suitable for that:
+/// ```rust
+/// # use juniper::{
+/// #     GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
+/// #     ScalarValue, ScalarToken, Value,
+/// # };
+/// #
+/// #[derive(GraphQLScalar)]
+/// // #[graphql(with = Self)] <- default behaviour, so can be omitted
+/// enum StringOrInt {
+///     String(String),
+///     Int(i32),
+/// }
+///
+/// impl StringOrInt {
+///     fn to_output<S: ScalarValue>(&self) -> Value<S> {
+///         match self {
+///             Self::String(str) => Value::scalar(str.to_owned()),
+///             Self::Int(i) => Value::scalar(*i),
+///         }
+///     }
+///
+///     fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
+///     where
+///         S: ScalarValue
+///     {
+///         v.as_string_value()
+///             .map(|s| Self::String(s.to_owned()))
+///             .or_else(|| v.as_int_value().map(Self::Int))
+///             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+///     }
+///
+///     fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
+///     where
+///         S: ScalarValue
+///     {
+///         <String as ParseScalarValue<S>>::from_str(value)
+///             .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
+///     }
+/// }
+/// #
+/// # fn main() {}
+/// ```
+///
+/// At the same time, any custom function still may be specified separately:
+/// ```rust
+/// # use juniper::{
+/// #     GraphQLScalar, InputValue, ParseScalarResult, ScalarValue,
+/// #     ScalarToken, Value
+/// # };
+/// #
+/// #[derive(GraphQLScalar)]
+/// #[graphql(
+///     with = string_or_int,
+///     parse_token(String, i32)
+/// )]
+/// enum StringOrInt {
+///     String(String),
+///     Int(i32),
+/// }
+///
+/// mod string_or_int {
+///     use super::*;
+///
+///     pub(super) fn to_output<S>(v: &StringOrInt) -> Value<S>
+///     where
+///         S: ScalarValue,
+///     {
+///         match v {
+///             StringOrInt::String(str) => Value::scalar(str.to_owned()),
+///             StringOrInt::Int(i) => Value::scalar(*i),
+///         }
+///     }
+///
+///     pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
+///     where
+///         S: ScalarValue,
+///     {
+///         v.as_string_value()
+///             .map(|s| StringOrInt::String(s.to_owned()))
+///             .or_else(|| v.as_int_value().map(StringOrInt::Int))
+///             .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
+///     }
+///
+///     // No need in `parse_token()` function.
+/// }
+/// #
+/// # fn main() {}
+/// ```
+///
+/// # Custom `ScalarValue`
+///
+/// By default, this macro generates code, which is generic over a
+/// [`ScalarValue`] type. Concrete [`ScalarValue`] type may be specified via
+/// `#[graphql(scalar = <type>)]` attribute.
+///
+/// It also may be used to provide additional bounds to the [`ScalarValue`]
+/// generic, like the following: `#[graphql(scalar = S: Trait)]`.
+///
+/// # Additional arbitrary trait bounds
+///
+/// [GraphQL scalar][0] type implementation may be bound with any additional
+/// trait bounds via `#[graphql(where(<bounds>))]` attribute, like the
+/// following: `#[graphql(where(S: Trait, Self: fmt::Debug + fmt::Display))]`.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
+/// [1]: https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html
+/// [`ScalarValue`]: juniper::ScalarValue
+#[proc_macro_error]
+#[proc_macro_derive(GraphQLScalar, attributes(graphql))]
+pub fn derive_scalar(input: TokenStream) -> TokenStream {
+    graphql_scalar::derive::expand(input.into())
+        .unwrap_or_abort()
+        .into()
+}
+
+/// `#[graphql_scalar]` macro.is interchangeable with
+/// `#[derive(`[`GraphQLScalar`]`)]` macro, and is used for deriving a
+/// [GraphQL scalar][0] implementation.
+///
+/// ```rust
+/// # use juniper::graphql_scalar;
+/// #
 /// /// Doc comments are used for the GraphQL type description.
-/// #[juniper::graphql_scalar(
-///     // Set a custom GraphQL name.
+/// #[graphql_scalar(
+///     // Custom GraphQL name.
 ///     name = "MyUserId",
-///     // A description can also specified in the attribute.
+///     // Description can also specified in the attribute.
 ///     // This will the doc comment, if one exists.
 ///     description = "...",
-///     // A specification URL.
+///     // Optional specification URL.
 ///     specified_by_url = "https://tools.ietf.org/html/rfc4122",
 ///     // Explicit generic scalar.
 ///     scalar = S: juniper::ScalarValue,
@@ -182,30 +478,30 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
 /// struct UserId(String);
 /// ```
 ///
-/// In addition to that `#[graphql_scalar]` can be used in case
-/// [`GraphQLScalar`] isn't applicable because type located in other crate and
-/// you don't want to wrap it in a newtype. This is done by placing
-/// `#[graphql_scalar]` on a type alias.
+/// # Foreign types
 ///
-/// All attributes are mirroring [`GraphQLScalar`] derive macro.
+/// Additionally, `#[graphql_scalar]` can be used directly on foreign types via
+/// type alias, without using [`Newtype` pattern][1].
 ///
 /// > __NOTE:__ To satisfy [orphan rules] you should provide local
 /// >           [`ScalarValue`] implementation.
 ///
 /// ```rust
 /// # mod date {
+/// #    use std::{fmt, str::FromStr};
+/// #
 /// #    pub struct Date;
 /// #
-/// #    impl std::str::FromStr for Date {
+/// #    impl FromStr for Date {
 /// #        type Err = String;
 /// #
-/// #        fn from_str(_value: &str) -> Result<Self, Self::Err> {
+/// #        fn from_str(_: &str) -> Result<Self, Self::Err> {
 /// #            unimplemented!()
 /// #        }
 /// #    }
 /// #
-/// #    impl std::fmt::Display for Date {
-/// #        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+/// #    impl fmt::Display for Date {
+/// #        fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
 /// #            unimplemented!()
 /// #        }
 /// #    }
@@ -218,21 +514,18 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
 ///     with = date_scalar,
 ///     parse_token(String),
 ///     scalar = CustomScalarValue,
-/// //           ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
 /// )]
+/// //           ^^^^^^^^^^^^^^^^^ local `ScalarValue` implementation
 /// type Date = date::Date;
-/// //          ^^^^^^^^^^ Type from another crate.
+/// //          ^^^^^^^^^^ type from another crate
 ///
 /// mod date_scalar {
 ///     use super::*;
 ///
-///     // Define how to convert your custom scalar into a primitive type.
 ///     pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
 ///         Value::scalar(v.to_string())
 ///     }
 ///
-///     // Define how to parse a primitive type into your custom scalar.
-///     // NOTE: The error type should implement `IntoFieldError<S>`.
 ///     pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
 ///       v.as_string_value()
 ///           .ok_or_else(|| format!("Expected `String`, found: {}", v))
@@ -243,6 +536,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
 /// # fn main() { }
 /// ```
 ///
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
+/// [1]: https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html
 /// [orphan rules]: https://bit.ly/3glAGC2
 /// [`GraphQLScalar`]: juniper::GraphQLScalar
 /// [`ScalarValue`]: juniper::ScalarValue
diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs
index 93db3e4a..811daf57 100644
--- a/juniper_codegen/src/result.rs
+++ b/juniper_codegen/src/result.rs
@@ -8,13 +8,11 @@ use std::fmt;
 /// URL of the GraphQL specification (June 2018 Edition).
 pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/";
 
-#[allow(unused_variables)]
 pub enum GraphQLScope {
     InterfaceAttr,
     ObjectAttr,
     ObjectDerive,
     ScalarAttr,
-    #[allow(dead_code)]
     ScalarDerive,
     UnionAttr,
     UnionDerive,