diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs
index 68eb1be43..790df06cc 100644
--- a/juniper/src/ast.rs
+++ b/juniper/src/ast.rs
@@ -1,4 +1,4 @@
-use std::{borrow::Cow, fmt, hash::Hash, slice, vec};
+use std::{any::TypeId, borrow::Cow, convert::Into, fmt, hash::Hash, mem, slice, vec};
 
 use indexmap::IndexMap;
 
@@ -256,13 +256,17 @@ impl<S> InputValue<S> {
         Self::Variable(v.as_ref().into())
     }
 
-    /// Construct a [`Spanning::unlocated`] list.
+    /// Constructs a [`Spanning::unlocated`] [`InputValue::List`].
     ///
-    /// Convenience function to make each [`InputValue`] in the input vector
-    /// not contain any location information. Can be used from [`ToInputValue`]
-    /// implementations, where no source code position information is available.
-    pub fn list(l: Vec<Self>) -> Self {
-        Self::List(l.into_iter().map(Spanning::unlocated).collect())
+    /// Convenience function to make each [`InputValue`] in the input `list` to
+    /// not contain any location information.
+    ///
+    /// Intended for [`resolve::ToInputValue`] implementations, where no source
+    /// code position information is available.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    pub fn list(list: impl IntoIterator<Item = Self>) -> Self {
+        Self::List(list.into_iter().map(Spanning::unlocated).collect())
     }
 
     /// Construct a located list.
@@ -270,16 +274,25 @@ impl<S> InputValue<S> {
         Self::List(l)
     }
 
-    /// Construct aa [`Spanning::unlocated`] object.
+    /// Construct a [`Spanning::unlocated`] [`InputValue::Onject`].
+    ///
+    /// Similarly to [`InputValue::list()`] it makes each key and value in the
+    /// given `obj`ect to not contain any location information.
     ///
-    /// Similarly to [`InputValue::list`] it makes each key and value in the
-    /// given hash map not contain any location information.
-    pub fn object<K>(o: IndexMap<K, Self>) -> Self
+    /// Intended for [`resolve::ToInputValue`] implementations, where no source
+    /// code position information is available.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    // TODO: Use `impl IntoIterator<Item = (K, Self)>` argument once feature
+    //       `explicit_generic_args_with_impl_trait` hits stable:
+    //       https://github.com/rust-lang/rust/issues/83701
+    pub fn object<K, O>(obj: O) -> Self
     where
         K: AsRef<str> + Eq + Hash,
+        O: IntoIterator<Item = (K, Self)>,
     {
         Self::Object(
-            o.into_iter()
+            obj.into_iter()
                 .map(|(k, v)| {
                     (
                         Spanning::unlocated(k.as_ref().into()),
@@ -459,6 +472,42 @@ impl<S> InputValue<S> {
             _ => false,
         }
     }
+
+    /// Maps the [`ScalarValue`] type of this [`InputValue`] into the specified
+    /// one.
+    pub fn map_scalar_value<Into>(self) -> InputValue<Into>
+    where
+        S: ScalarValue,
+        Into: ScalarValue,
+    {
+        if TypeId::of::<Into>() == TypeId::of::<S>() {
+            // SAFETY: This is safe, because we're transmuting the value into
+            //         itself, so no invariants may change and we're just
+            //         satisfying the type checker.
+            //         As `mem::transmute_copy` creates a copy of data, we need
+            //         `mem::ManuallyDrop` here to omit double-free when
+            //         `S: Drop`.
+            let val = mem::ManuallyDrop::new(self);
+            unsafe { mem::transmute_copy(&*val) }
+        } else {
+            match self {
+                Self::Null => InputValue::Null,
+                Self::Scalar(s) => InputValue::Scalar(s.into_another()),
+                Self::Enum(v) => InputValue::Enum(v),
+                Self::Variable(n) => InputValue::Variable(n),
+                Self::List(l) => InputValue::List(
+                    l.into_iter()
+                        .map(|i| i.map(InputValue::map_scalar_value))
+                        .collect(),
+                ),
+                Self::Object(o) => InputValue::Object(
+                    o.into_iter()
+                        .map(|(k, v)| (k, v.map(InputValue::map_scalar_value)))
+                        .collect(),
+                ),
+            }
+        }
+    }
 }
 
 impl<S: ScalarValue> fmt::Display for InputValue<S> {
diff --git a/juniper/src/behavior.rs b/juniper/src/behavior.rs
new file mode 100644
index 000000000..cb1976cd4
--- /dev/null
+++ b/juniper/src/behavior.rs
@@ -0,0 +1,130 @@
+//! GraphQL types behavior machinery.
+
+use std::{marker::PhantomData, sync::atomic::AtomicPtr};
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Registry,
+};
+
+/// Default standard behavior of GraphQL types implementation.
+#[derive(Debug)]
+pub enum Standard {}
+
+/// Transparent wrapper allowing coercion of behavior types and type parameters.
+#[repr(transparent)]
+pub struct Coerce<T: ?Sized, To: ?Sized = Standard>(PhantomData<AtomicPtr<Box<To>>>, T);
+
+impl<T, To: ?Sized> Coerce<T, To> {
+    /// Wraps the provided `value` into a [`Coerce`] wrapper.
+    #[must_use]
+    pub const fn wrap(value: T) -> Self {
+        Self(PhantomData, value)
+    }
+
+    /// Unwraps into the inner value.
+    #[must_use]
+    pub fn into_inner(self) -> T {
+        self.1
+    }
+}
+
+/// Wraps the provided `value` into a [`Coerce`] wrapper.
+#[must_use]
+pub const fn coerce<T, To: ?Sized>(value: T) -> Coerce<T, To> {
+    Coerce::wrap(value)
+}
+
+impl<T, TI, SV, B1, B2> resolve::Type<TI, SV, B1> for Coerce<T, B2>
+where
+    T: resolve::Type<TI, SV, B2> + ?Sized,
+    TI: ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<T, TI, B1, B2> resolve::TypeName<TI, B1> for Coerce<T, B2>
+where
+    T: resolve::TypeName<TI, B2> + ?Sized,
+    TI: ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<'i, T, SV, B1, B2> resolve::InputValue<'i, SV, B1> for Coerce<T, B2>
+where
+    T: resolve::InputValue<'i, SV, B2>,
+    SV: 'i,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v).map(Self::wrap)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null().map(Self::wrap)
+    }
+}
+
+impl<T, SV, B1, B2> resolve::ScalarToken<SV, B1> for Coerce<T, B2>
+where
+    T: resolve::ScalarToken<SV, B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<T, B1, B2> reflect::BaseType<B1> for Coerce<T, B2>
+where
+    T: reflect::BaseType<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, B1, B2> reflect::BaseSubTypes<B1> for Coerce<T, B2>
+where
+    T: reflect::BaseSubTypes<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, B1, B2> reflect::WrappedType<B1> for Coerce<T, B2>
+where
+    T: reflect::WrappedType<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
+
+impl<T, B1, B2> reflect::Implements<B1> for Coerce<T, B2>
+where
+    T: reflect::Implements<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs
index 9ab412d4f..cd550d7bb 100644
--- a/juniper/src/executor/mod.rs
+++ b/juniper/src/executor/mod.rs
@@ -3,7 +3,8 @@
 use std::{
     borrow::Cow,
     cmp::Ordering,
-    collections::HashMap,
+    collections::{hash_map, HashMap},
+    convert,
     fmt::{Debug, Display},
     sync::{Arc, RwLock},
 };
@@ -17,6 +18,7 @@ use crate::{
         Selection, ToInputValue, Type,
     },
     parser::{SourcePosition, Spanning},
+    resolve,
     schema::{
         meta::{
             Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta,
@@ -69,7 +71,7 @@ pub enum FieldPath<'a> {
 /// of the current field stack, context, variables, and errors.
 pub struct Executor<'r, 'a, CtxT, S = DefaultScalarValue>
 where
-    CtxT: 'a,
+    CtxT: ?Sized + 'a,
     S: 'a,
 {
     fragments: &'r HashMap<&'a str, Fragment<'a, S>>,
@@ -83,6 +85,41 @@ where
     field_path: Arc<FieldPath<'a>>,
 }
 
+impl<'r, 'a, CX: ?Sized, SV> Executor<'r, 'a, CX, SV> {
+    pub(crate) fn current_type_reworked(&self) -> &TypeType<'a, SV> {
+        &self.current_type
+    }
+
+    /// Resolves the specified single arbitrary `Type` `value` as
+    /// [`graphql::Value`].
+    ///
+    /// # Errors
+    ///
+    /// Whenever [`Type::resolve_value()`] errors.
+    ///
+    /// [`graphql::Value`]: crate::graphql::Value
+    /// [`Type::resolve_value()`]: resolve::Value::resolve_value
+    pub fn resolve_value<BH, Type, TI>(&self, value: &Type, type_info: &TI) -> ExecutionResult<SV>
+    where
+        Type: resolve::Value<TI, CX, SV, BH> + ?Sized,
+        TI: ?Sized,
+        BH: ?Sized,
+    {
+        value.resolve_value(self.current_selection_set, type_info, self)
+    }
+
+    /// Returns the current context of this [`Executor`].
+    ///
+    /// Context is usually provided when the top-level [`execute()`] function is
+    /// called.
+    ///
+    /// [`execute()`]: crate::execute
+    #[must_use]
+    pub fn context(&self) -> &'r CX {
+        self.context
+    }
+}
+
 /// Error type for errors that occur during query execution
 ///
 /// All execution errors contain the source position in the query of the field
@@ -627,14 +664,6 @@ where
         self.current_selection_set
     }
 
-    /// Access the current context
-    ///
-    /// You usually provide the context when calling the top-level `execute`
-    /// function, or using the context factory in the Iron integration.
-    pub fn context(&self) -> &'r CtxT {
-        self.context
-    }
-
     /// The currently executing schema
     pub fn schema(&self) -> &'a SchemaType<S> {
         self.schema
@@ -1183,6 +1212,21 @@ impl<'r, S: 'r> Registry<'r, S> {
         }
     }
 
+    /// Returns an entry with a [`Type`] meta information for the specified
+    /// named [`graphql::Type`], registered in this [`Registry`].
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    pub fn entry_type<T, TI>(
+        &mut self,
+        type_info: &TI,
+    ) -> hash_map::Entry<'_, Name, MetaType<'r, S>>
+    where
+        T: resolve::TypeName<TI> + ?Sized,
+        TI: ?Sized,
+    {
+        self.types.entry(T::type_name(type_info).parse().unwrap())
+    }
+
     /// Creates a [`Field`] with the provided `name`.
     pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
     where
@@ -1240,6 +1284,16 @@ impl<'r, S: 'r> Registry<'r, S> {
         Argument::new(name, self.get_type::<T>(info)).default_value(value.to_input_value())
     }
 
+    /// Creates an [`Argument`] with the provided `name`.
+    pub fn arg_reworked<'ti, T, TI>(&mut self, name: &str, type_info: &'ti TI) -> Argument<'r, S>
+    where
+        T: resolve::Type<TI, S> + resolve::InputValueOwned<S>,
+        TI: ?Sized,
+        'ti: 'r,
+    {
+        Argument::new(name, T::meta(self, type_info).as_type())
+    }
+
     fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) {
         self.types
             .entry(name)
@@ -1258,6 +1312,84 @@ impl<'r, S: 'r> Registry<'r, S> {
         ScalarMeta::new::<T>(Cow::Owned(name.into()))
     }
 
+    /// Builds a [`ScalarMeta`] information for the specified [`graphql::Type`],
+    /// allowing to `customize` the created [`ScalarMeta`], and stores it in
+    /// this [`Registry`].
+    ///
+    /// # Idempotent
+    ///
+    /// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
+    /// already, then just returns it without doing anything.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    /// [`TypeName`]: resolve::TypeName
+    pub fn register_scalar_with<'ti, T, TI>(
+        &mut self,
+        type_info: &'ti TI,
+        customize: impl FnOnce(ScalarMeta<'r, S>) -> ScalarMeta<'r, S>,
+    ) -> MetaType<'r, S>
+    where
+        T: resolve::TypeName<TI> + resolve::InputValueOwned<S> + resolve::ScalarToken<S>,
+        TI: ?Sized,
+        'ti: 'r,
+        S: Clone,
+    {
+        self.entry_type::<T, _>(type_info)
+            .or_insert_with(move || {
+                customize(ScalarMeta::new_reworked::<T>(T::type_name(type_info))).into_meta()
+            })
+            .clone()
+    }
+
+    /// Builds a [`ScalarMeta`] information for the specified non-[`Sized`]
+    /// [`graphql::Type`], and stores it in this [`Registry`].
+    ///
+    /// # Idempotent
+    ///
+    /// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
+    /// already, then just returns it without doing anything.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    /// [`TypeName`]: resolve::TypeName
+    pub fn register_scalar_unsized<'ti, T, TI>(&mut self, type_info: &'ti TI) -> MetaType<'r, S>
+    where
+        T: resolve::TypeName<TI> + resolve::InputValueAsRef<S> + resolve::ScalarToken<S> + ?Sized,
+        TI: ?Sized,
+        'ti: 'r,
+        S: Clone,
+    {
+        self.register_scalar_unsized_with::<T, TI>(type_info, convert::identity)
+    }
+
+    /// Builds a [`ScalarMeta`] information for the specified non-[`Sized`]
+    /// [`graphql::Type`], allowing to `customize` the created [`ScalarMeta`],
+    /// and stores it in this [`Registry`].
+    ///
+    /// # Idempotent
+    ///
+    /// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
+    /// already, then just returns it without doing anything.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    /// [`TypeName`]: resolve::TypeName
+    pub fn register_scalar_unsized_with<'ti, T, TI>(
+        &mut self,
+        type_info: &'ti TI,
+        customize: impl FnOnce(ScalarMeta<'r, S>) -> ScalarMeta<'r, S>,
+    ) -> MetaType<'r, S>
+    where
+        T: resolve::TypeName<TI> + resolve::InputValueAsRef<S> + resolve::ScalarToken<S> + ?Sized,
+        TI: ?Sized,
+        'ti: 'r,
+        S: Clone,
+    {
+        self.entry_type::<T, _>(type_info)
+            .or_insert_with(move || {
+                customize(ScalarMeta::new_unsized::<T>(T::type_name(type_info))).into_meta()
+            })
+            .clone()
+    }
+
     /// Creates a [`ListMeta`] type.
     ///
     /// Specifying `expected_size` will be used to ensure that values of this
@@ -1275,6 +1407,25 @@ impl<'r, S: 'r> Registry<'r, S> {
         ListMeta::new(of_type, expected_size)
     }
 
+    /// Builds a [`ListMeta`] information for the specified [`graphql::Type`].
+    ///
+    /// Specifying `expected_size` will be used in validation to ensure that
+    /// values of this type matches it.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    pub fn wrap_list<'ti, T, TI>(
+        &mut self,
+        type_info: &'ti TI,
+        expected_size: Option<usize>,
+    ) -> MetaType<'r, S>
+    where
+        T: resolve::Type<TI, S> + ?Sized,
+        TI: ?Sized,
+        'ti: 'r,
+    {
+        ListMeta::new(T::meta(self, type_info).into(), expected_size).into_meta()
+    }
+
     /// Creates a [`NullableMeta`] type.
     pub fn build_nullable_type<T>(&mut self, info: &T::TypeInfo) -> NullableMeta<'r>
     where
@@ -1285,6 +1436,19 @@ impl<'r, S: 'r> Registry<'r, S> {
         NullableMeta::new(of_type)
     }
 
+    /// Builds a [`NullableMeta`] information for the specified
+    /// [`graphql::Type`].
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    pub fn wrap_nullable<'ti, T, TI>(&mut self, type_info: &'ti TI) -> MetaType<'r, S>
+    where
+        T: resolve::Type<TI, S> + ?Sized,
+        TI: ?Sized,
+        'ti: 'r,
+    {
+        NullableMeta::new(T::meta(self, type_info).into()).into_meta()
+    }
+
     /// Creates an [`ObjectMeta`] type with the given `fields`.
     pub fn build_object_type<T>(
         &mut self,
@@ -1318,6 +1482,36 @@ impl<'r, S: 'r> Registry<'r, S> {
         EnumMeta::new::<T>(Cow::Owned(name.into()), values)
     }
 
+    /// Builds an [`EnumMeta`] information for the specified [`graphql::Type`],
+    /// allowing to `customize` the created [`ScalarMeta`], and stores it in
+    /// this [`Registry`].
+    ///
+    /// # Idempotent
+    ///
+    /// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
+    /// already, then just returns it without doing anything.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    /// [`TypeName`]: resolve::TypeName
+    pub fn register_enum_with<'ti, T, TI>(
+        &mut self,
+        values: &[EnumValue],
+        type_info: &'ti TI,
+        customize: impl FnOnce(EnumMeta<'r, S>) -> EnumMeta<'r, S>,
+    ) -> MetaType<'r, S>
+    where
+        T: resolve::TypeName<TI> + resolve::InputValueOwned<S>,
+        TI: ?Sized,
+        'ti: 'r,
+        S: Clone,
+    {
+        self.entry_type::<T, _>(type_info)
+            .or_insert_with(move || {
+                customize(EnumMeta::new_reworked::<T>(T::type_name(type_info), values)).into_meta()
+            })
+            .clone()
+    }
+
     /// Creates an [`InterfaceMeta`] type with the given `fields`.
     pub fn build_interface_type<T>(
         &mut self,
@@ -1361,4 +1555,38 @@ impl<'r, S: 'r> Registry<'r, S> {
 
         InputObjectMeta::new::<T>(Cow::Owned(name.into()), args)
     }
+
+    /// Builds an [`InputObjectMeta`] information for the specified
+    /// [`graphql::Type`], allowing to `customize` the created [`ScalarMeta`],
+    /// and stores it in this [`Registry`].
+    ///
+    /// # Idempotent
+    ///
+    /// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
+    /// already, then just returns it without doing anything.
+    ///
+    /// [`graphql::Type`]: resolve::Type
+    /// [`TypeName`]: resolve::TypeName
+    pub fn register_input_object_with<'ti, T, TI>(
+        &mut self,
+        fields: &[Argument<'r, S>],
+        type_info: &'ti TI,
+        customize: impl FnOnce(InputObjectMeta<'r, S>) -> InputObjectMeta<'r, S>,
+    ) -> MetaType<'r, S>
+    where
+        T: resolve::TypeName<TI> + resolve::InputValueOwned<S>,
+        TI: ?Sized,
+        'ti: 'r,
+        S: Clone,
+    {
+        self.entry_type::<T, _>(type_info)
+            .or_insert_with(move || {
+                customize(InputObjectMeta::new_reworked::<T>(
+                    T::type_name(type_info),
+                    fields,
+                ))
+                .into_meta()
+            })
+            .clone()
+    }
 }
diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs
index 06d93fc0e..088974187 100644
--- a/juniper/src/executor_tests/executor.rs
+++ b/juniper/src/executor_tests/executor.rs
@@ -369,6 +369,8 @@ mod threads_context_correctly {
     }
 }
 
+// TODO: Remove as should be unnecessary with generic context.
+/*
 mod dynamic_context_switching {
     use indexmap::IndexMap;
 
@@ -672,7 +674,7 @@ mod dynamic_context_switching {
         assert_eq!(result, graphql_value!({"first": {"value": "First value"}}));
     }
 }
-
+*/
 mod propagates_errors_to_nullable_fields {
     use crate::{
         executor::{ExecutionError, FieldError, FieldResult, IntoFieldError},
diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs
index 10d5e0889..c1cd3b43f 100644
--- a/juniper/src/executor_tests/interfaces_unions.rs
+++ b/juniper/src/executor_tests/interfaces_unions.rs
@@ -1,6 +1,6 @@
 mod interface {
     use crate::{
-        graphql_interface, graphql_object,
+        graphql_interface, graphql_object, graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
         GraphQLObject,
@@ -96,19 +96,16 @@ mod interface {
 
 mod union {
     use crate::{
-        graphql_object, graphql_union,
+        graphql_object, graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
+        GraphQLUnion,
     };
 
-    #[graphql_union]
-    trait Pet {
-        fn as_dog(&self) -> Option<&Dog> {
-            None
-        }
-        fn as_cat(&self) -> Option<&Cat> {
-            None
-        }
+    #[derive(GraphQLUnion)]
+    enum Pet {
+        Dog(Dog),
+        Cat(Cat),
     }
 
     struct Dog {
@@ -116,12 +113,6 @@ mod union {
         woofs: bool,
     }
 
-    impl Pet for Dog {
-        fn as_dog(&self) -> Option<&Dog> {
-            Some(self)
-        }
-    }
-
     #[graphql_object]
     impl Dog {
         fn name(&self) -> &str {
@@ -137,12 +128,6 @@ mod union {
         meows: bool,
     }
 
-    impl Pet for Cat {
-        fn as_cat(&self) -> Option<&Cat> {
-            Some(self)
-        }
-    }
-
     #[graphql_object]
     impl Cat {
         fn name(&self) -> &str {
@@ -154,13 +139,13 @@ mod union {
     }
 
     struct Schema {
-        pets: Vec<Box<dyn Pet + Send + Sync>>,
+        pets: Vec<Pet>,
     }
 
     #[graphql_object]
     impl Schema {
-        fn pets(&self) -> Vec<&(dyn Pet + Send + Sync)> {
-            self.pets.iter().map(|p| p.as_ref()).collect()
+        fn pets(&self) -> &[Pet] {
+            &self.pets
         }
     }
 
@@ -169,11 +154,11 @@ mod union {
         let schema = RootNode::new(
             Schema {
                 pets: vec![
-                    Box::new(Dog {
+                    Pet::Dog(Dog {
                         name: "Odie".into(),
                         woofs: true,
                     }),
-                    Box::new(Cat {
+                    Pet::Cat(Cat {
                         name: "Garfield".into(),
                         meows: false,
                     }),
diff --git a/juniper/src/extract.rs b/juniper/src/extract.rs
new file mode 100644
index 000000000..3b02845b5
--- /dev/null
+++ b/juniper/src/extract.rs
@@ -0,0 +1,9 @@
+pub trait Extract<T: ?Sized> {
+    fn extract(&self) -> &T;
+}
+
+impl<T: ?Sized> Extract<T> for T {
+    fn extract(&self) -> &Self {
+        self
+    }
+}
diff --git a/juniper/src/graphql/mod.rs b/juniper/src/graphql/mod.rs
new file mode 100644
index 000000000..387690d16
--- /dev/null
+++ b/juniper/src/graphql/mod.rs
@@ -0,0 +1,140 @@
+use crate::{behavior, resolve};
+
+pub use crate::{
+    ast::InputValue,
+    executor::Variables,
+    macros::{input_value, value, vars},
+    resolve::Type,
+    value::Value,
+    GraphQLEnum as Enum, GraphQLScalar as Scalar,
+};
+
+pub trait Enum<
+    'inp,
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    InputType<'inp, TypeInfo, ScalarValue, Behavior>
+    + OutputType<TypeInfo, Context, ScalarValue, Behavior>
+{
+    fn assert_enum();
+}
+
+/*
+pub trait Interface<S>: OutputType<S>
+   + resolve::TypeName
+   + resolve::ConcreteTypeName
+   + resolve::Value<S>
+   + resolve::ValueAsync<S>
+   + resolve::ConcreteValue<S>
+   + resolve::ConcreteValueAsync<S>
+   + resolve::Field<S>
+   + resolve::FieldAsync<S>
+{
+    fn assert_interface();
+}
+
+pub trait Object<S>: OutputType<S>
+   + resolve::TypeName
+   + resolve::ConcreteTypeName
+   + resolve::Value<S>
+   + resolve::ValueAsync<S>
+   + resolve::Field<S>
+   + resolve::FieldAsync<S>
+{
+    fn assert_object();
+}*/
+
+pub trait InputObject<
+    'inp,
+    TypeInfo: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>: InputType<'inp, TypeInfo, ScalarValue, Behavior>
+{
+    fn assert_input_object();
+}
+
+pub trait Scalar<
+    'inp,
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    InputType<'inp, TypeInfo, ScalarValue, Behavior>
+    + OutputType<TypeInfo, Context, ScalarValue, Behavior>
+    + resolve::ScalarToken<ScalarValue, Behavior>
+{
+    fn assert_scalar();
+}
+
+pub trait ScalarAs<
+    'inp,
+    Wrapper,
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    InputTypeAs<'inp, Wrapper, TypeInfo, ScalarValue, Behavior>
+    + OutputType<TypeInfo, Context, ScalarValue, Behavior>
+    + resolve::ScalarToken<ScalarValue, Behavior>
+{
+    fn assert_scalar();
+}
+
+/*
+pub trait Union<S>
+ OutputType<S>
++ resolve::TypeName
++ resolve::ConcreteTypeName
++ resolve::Value<S>
++ resolve::ValueAsync<S>
++ resolve::ConcreteValue<S>
++ resolve::ConcreteValueAsync<S>
+{
+    fn assert_union();
+}*/
+
+pub trait InputType<
+    'inp,
+    TypeInfo: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    Type<TypeInfo, ScalarValue, Behavior>
+    + resolve::ToInputValue<ScalarValue, Behavior>
+    + resolve::InputValue<'inp, ScalarValue, Behavior>
+{
+    fn assert_input_type();
+}
+
+pub trait InputTypeAs<
+    'inp,
+    Wrapper,
+    TypeInfo: ?Sized,
+    ScalarValue: 'inp,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    Type<TypeInfo, ScalarValue, Behavior>
+    + resolve::ToInputValue<ScalarValue, Behavior>
+    + resolve::InputValueAs<'inp, Wrapper, ScalarValue, Behavior>
+{
+    fn assert_input_type();
+}
+
+pub trait OutputType<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>:
+    Type<TypeInfo, ScalarValue, Behavior>
+    + resolve::Value<TypeInfo, Context, ScalarValue, Behavior>
+    + resolve::ValueAsync<TypeInfo, Context, ScalarValue, Behavior>
+{
+    fn assert_output_type();
+}
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index ac1e0329e..9e54f70f0 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -29,19 +29,23 @@ pub use juniper_codegen::{
 #[doc(hidden)]
 #[macro_use]
 pub mod macros;
+
 mod ast;
+pub mod behavior;
 pub mod executor;
+pub mod extract;
+pub mod graphql;
+pub mod http;
+pub mod integrations;
 mod introspection;
 pub mod parser;
+pub mod reflect;
+pub mod resolve;
 pub(crate) mod schema;
 mod types;
 mod util;
 pub mod validation;
-mod value;
-// This needs to be public until docs have support for private modules:
-// https://github.com/rust-lang/cargo/issues/1520
-pub mod http;
-pub mod integrations;
+pub(crate) mod value;
 
 #[cfg(all(test, not(feature = "expose-test-schema")))]
 mod tests;
@@ -71,6 +75,7 @@ pub use crate::{
         FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
         LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables,
     },
+    extract::Extract,
     introspection::IntrospectionFormat,
     macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult},
     parser::{ParseError, ScalarToken, Spanning},
@@ -82,12 +87,12 @@ pub use crate::{
         async_await::{GraphQLTypeAsync, GraphQLValueAsync},
         base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
         marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion},
-        nullable::Nullable,
         scalars::{EmptyMutation, EmptySubscription, ID},
         subscriptions::{
             ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
             SubscriptionConnection, SubscriptionCoordinator,
         },
+        Nullable,
     },
     validation::RuleError,
     value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},
diff --git a/juniper/src/macros/graphql_input_value.rs b/juniper/src/macros/graphql_input_value.rs
index 69641e376..73233a799 100644
--- a/juniper/src/macros/graphql_input_value.rs
+++ b/juniper/src/macros/graphql_input_value.rs
@@ -1,45 +1,43 @@
-//! [`graphql_input_value!`] macro implementation.
-//!
-//! [`graphql_input_value!`]: graphql_input_value
+//! [`input_value!`] macro implementation.
 
-/// Constructs [`InputValue`]s via JSON-like syntax.
+/// Constructs [`graphql::InputValue`]s via JSON-like syntax.
 ///
 /// # Differences from [`graphql_value!`]
 ///
 /// - [`InputValue::Enum`] is constructed with `ident`, so to capture outer
 ///   variable as [`InputValue::Scalar`] surround it with parens: `(var)`.
 /// ```rust
-/// # use juniper::{graphql_input_value, graphql_value};
+/// # use juniper::graphql;
 /// #
-/// # type InputValue = juniper::InputValue;
-/// # type Value = juniper::Value;
+/// # type InputValue = graphql::InputValue;
+/// # type Value = graphql::Value;
 /// #
 /// const OUTER_VAR: i32 = 42;
-/// assert_eq!(graphql_value!(OUTER_VAR), Value::scalar(42));
-/// assert_eq!(graphql_input_value!(OUTER_VAR), InputValue::enum_value("OUTER_VAR"));
-/// assert_eq!(graphql_input_value!((OUTER_VAR)), InputValue::scalar(42));
+/// assert_eq!(graphql::value!(OUTER_VAR), Value::scalar(42));
+/// assert_eq!(graphql::input_value!(OUTER_VAR), InputValue::enum_value("OUTER_VAR"));
+/// assert_eq!(graphql::input_value!((OUTER_VAR)), InputValue::scalar(42));
 /// ```
 ///
 /// - [`InputValue::Variable`] is constructed by prefixing `ident` with `@`.
 /// ```rust
-/// # use juniper::graphql_input_value;
+/// # use juniper::graphql;
 /// #
-/// # type InputValue = juniper::InputValue;
+/// # type InputValue = graphql::InputValue;
 /// #
-/// assert_eq!(graphql_input_value!(@var), InputValue::variable("var"));
+/// assert_eq!(graphql::input_value!(@var), InputValue::variable("var"));
 /// ```
 ///
 /// - [`InputValue::Object`] key should implement [`Into`]`<`[`String`]`>`.
 /// ```rust
 /// # use std::borrow::Cow;
 /// #
-/// # use juniper::{graphql_input_value, InputValue};
+/// # use juniper::graphql;
 /// #
 /// let code = 200;
 /// let features = vec!["key", "value"];
 /// let key: Cow<'static, str> = "key".into();
 ///
-/// let value: InputValue = graphql_input_value!({
+/// let value: graphql::InputValue = graphql::input_value!({
 ///     "code": code,
 ///     "success": code == 200,
 ///     "payload": {
@@ -55,35 +53,35 @@
 /// # Example
 ///
 /// ```rust
-/// # use juniper::{graphql_input_value, InputValue};
+/// # use juniper::graphql;
 /// #
-/// # type V = InputValue;
+/// # type V = graphql::InputValue;
 /// #
 /// # let _: V =
-/// graphql_input_value!(null);
+/// graphql::input_value!(null);
 /// # let _: V =
-/// graphql_input_value!(1234);
+/// graphql::input_value!(1234);
 /// # let _: V =
-/// graphql_input_value!("test");
+/// graphql::input_value!("test");
 /// # let _: V =
-/// graphql_input_value!([1234, "test", true]);
+/// graphql::input_value!([1234, "test", true]);
 /// # let _: V =
-/// graphql_input_value!({"key": "value", "foo": 1234});
+/// graphql::input_value!({"key": "value", "foo": 1234});
 /// # let _: V =
-/// graphql_input_value!({"key": ENUM});
+/// graphql::input_value!({"key": ENUM});
 /// let captured_var = 42;
 /// # let _: V =
-/// graphql_input_value!({"key": (captured_var)});
+/// graphql::input_value!({"key": (captured_var)});
 /// # let _: V =
-/// graphql_input_value!({"key": @variable});
+/// graphql::input_value!({"key": @variable});
 /// ```
 ///
-/// [`InputValue`]: crate::InputValue
-/// [`InputValue::Enum`]: crate::InputValue::Enum
-/// [`InputValue::List`]: crate::InputValue::List
-/// [`InputValue::Object`]: crate::InputValue::Object
-/// [`InputValue::Scalar`]: crate::InputValue::Scalar
-/// [`InputValue::Variable`]: crate::InputValue::Variable
+/// [`graphql::InputValue`]: crate::graphql::InputValue
+/// [`InputValue::Enum`]: crate::graphql::InputValue::Enum
+/// [`InputValue::List`]: crate::graphql::InputValue::List
+/// [`InputValue::Object`]: crate::graphql::InputValue::Object
+/// [`InputValue::Scalar`]: crate::graphql::InputValue::Scalar
+/// [`InputValue::Variable`]: crate::graphql::InputValue::Variable
 /// [`Spanning::unlocated`]: crate::Spanning::unlocated
 #[macro_export]
 macro_rules! graphql_input_value {
@@ -93,90 +91,90 @@ macro_rules! graphql_input_value {
 
     // Done with trailing comma.
     (@@array [$($elems:expr,)*]) => {
-        $crate::InputValue::list(vec![
+        $crate::graphql::InputValue::list(::std::vec![
             $( $elems, )*
         ])
     };
 
     // Done without trailing comma.
     (@@array [$($elems:expr),*]) => {
-        $crate::InputValue::list(vec![
+        $crate::graphql::InputValue::list(::std::vec![
             $( $elems, )*
         ])
     };
 
     // Next element is `null`.
     (@@array [$($elems:expr,)*] null $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!(null)] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!(null)] $($rest)*
         )
     };
 
     // Next element is `None`.
     (@@array [$($elems:expr,)*] None $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!(None)] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!(None)] $($rest)*
         )
     };
 
     // Next element is a variable.
     (@@array [$($elems:expr,)*] @$var:ident $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!(@$var)] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!(@$var)] $($rest)*
         )
     };
 
 
     // Next element is an array.
     (@@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!([$($array)*])] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!([$($array)*])] $($rest)*
         )
     };
 
     // Next element is a map.
     (@@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!({$($map)*})] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!({$($map)*})] $($rest)*
         )
     };
 
     // Next element is `true`, `false` or enum ident followed by comma.
     (@@array [$($elems:expr,)*] $ident:ident, $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!($ident),] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!($ident),] $($rest)*
         )
     };
 
     // Next element is `true`, `false` or enum ident without trailing comma.
     (@@array [$($elems:expr,)*] $last:ident ) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!($last)]
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!($last)]
         )
     };
 
     // Next element is an expression followed by comma.
     (@@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!($next),] $($rest)*
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!($next),] $($rest)*
         )
     };
 
     // Last element is an expression with no trailing comma.
     (@@array [$($elems:expr,)*] $last:expr) => {
-        $crate::graphql_input_value!(
-            @@array [$($elems,)* $crate::graphql_input_value!($last)]
+        $crate::graphql::input_value!(
+            @@array [$($elems,)* $crate::graphql::input_value!($last)]
         )
     };
 
     // Comma after the most recent element.
     (@@array [$($elems:expr),*] , $($rest:tt)*) => {
-        $crate::graphql_input_value!(@@array [$($elems,)*] $($rest)*)
+        $crate::graphql::input_value!(@@array [$($elems,)*] $($rest)*)
     };
 
     // Unexpected token after most recent element.
     (@@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
-        $crate::graphql_input_value!(@unexpected $unexpected)
+        $crate::graphql::input_value!(@unexpected $unexpected)
     };
 
     ////////////
@@ -192,12 +190,12 @@ macro_rules! graphql_input_value {
             $crate::Spanning::unlocated(($($key)+).into()),
             $crate::Spanning::unlocated($value),
         ));
-        $crate::graphql_input_value!(@@object $object () ($($rest)*) ($($rest)*));
+        $crate::graphql::input_value!(@@object $object () ($($rest)*) ($($rest)*));
     };
 
     // Current entry followed by unexpected token.
     (@@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
-        $crate::graphql_input_value!(@unexpected $unexpected);
+        $crate::graphql::input_value!(@unexpected $unexpected);
     };
 
     // Insert the last entry without trailing comma.
@@ -210,114 +208,114 @@ macro_rules! graphql_input_value {
 
     // Next value is `null`.
     (@@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!(null)) $($rest)*
+            ($crate::graphql::input_value!(null)) $($rest)*
         );
     };
 
     // Next value is `None`.
     (@@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!(None)) $($rest)*
+            ($crate::graphql::input_value!(None)) $($rest)*
         );
     };
 
     // Next value is a variable.
     (@@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!(@$var)) $($rest)*
+            ($crate::graphql::input_value!(@$var)) $($rest)*
         );
     };
 
     // Next value is an array.
     (@@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!([$($array)*])) $($rest)*
+            ($crate::graphql::input_value!([$($array)*])) $($rest)*
         );
     };
 
     // Next value is a map.
     (@@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!({$($map)*})) $($rest)*
+            ($crate::graphql::input_value!({$($map)*})) $($rest)*
         );
     };
 
     // Next value is `true`, `false` or enum ident followed by comma.
     (@@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!($ident)) , $($rest)*
+            ($crate::graphql::input_value!($ident)) , $($rest)*
         );
     };
 
     // Next value is `true`, `false` or enum ident without trailing comma.
     (@@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!($last))
+            ($crate::graphql::input_value!($last))
         );
     };
 
     // Next value is an expression followed by comma.
     (@@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!($value)) , $($rest)*
+            ($crate::graphql::input_value!($value)) , $($rest)*
         );
     };
 
     // Last value is an expression with no trailing comma.
     (@@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             [$($key)+]
-            ($crate::graphql_input_value!($value))
+            ($crate::graphql::input_value!($value))
         );
     };
 
     // Missing value for last entry. Trigger a reasonable error message.
     (@@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_input_value!();
+        $crate::graphql::input_value!();
     };
 
     // Missing colon and value for last entry. Trigger a reasonable error
     // message.
     (@@object $object:ident ($($key:tt)+) () $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_input_value!();
+        $crate::graphql::input_value!();
     };
 
     // Misplaced colon. Trigger a reasonable error message.
     (@@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `:`".
-        $crate::graphql_input_value!(@unexpected $colon);
+        $crate::graphql::input_value!(@unexpected $colon);
     };
 
     // Found a comma inside a key. Trigger a reasonable error message.
     (@@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `,`".
-        $crate::graphql_input_value!(@unexpected $comma);
+        $crate::graphql::input_value!(@unexpected $comma);
     };
 
     // Key is fully parenthesized. This avoids `clippy::double_parens` false
     // positives because the parenthesization may be necessary here.
     (@@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             ($key)
             (: $($rest)*) (: $($rest)*)
@@ -326,12 +324,12 @@ macro_rules! graphql_input_value {
 
     // Refuse to absorb colon token into key expression.
     (@@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
-        $crate::graphql_input_value!(@@unexpected $($unexpected)+);
+        $crate::graphql::input_value!(@@unexpected $($unexpected)+);
     };
 
     // Munch a token into the current key.
     (@@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_input_value!(
+        $crate::graphql::input_value!(
             @@object $object
             ($($key)* $tt)
             ($($rest)*) ($($rest)*)
@@ -349,109 +347,107 @@ macro_rules! graphql_input_value {
     //////////////
 
     ([ $($arr:tt)* ]$(,)?) => {
-        $crate::graphql_input_value!(@@array [] $($arr)*)
+        $crate::graphql::input_value!(@@array [] $($arr)*)
     };
 
     ({}$(,)?) => {
-        $crate::InputValue::parsed_object(vec![])
+        $crate::graphql::InputValue::parsed_object(vec![])
     };
 
     ({ $($map:tt)+ }$(,)?) => {
-        $crate::InputValue::parsed_object({
+        $crate::graphql::InputValue::parsed_object({
             let mut object = vec![];
-            $crate::graphql_input_value!(@@object object () ($($map)*) ($($map)*));
+            $crate::graphql::input_value!(@@object object () ($($map)*) ($($map)*));
             object
         })
     };
 
-    (null$(,)?) => ($crate::InputValue::null());
+    (null$(,)?) => ($crate::graphql::InputValue::null());
 
-    (None$(,)?) => ($crate::InputValue::null());
+    (None$(,)?) => ($crate::graphql::InputValue::null());
 
-    (true$(,)?) => ($crate::InputValue::from(true));
+    (true$(,)?) => ($crate::graphql::InputValue::from(true));
 
-    (false$(,)?) => ($crate::InputValue::from(false));
+    (false$(,)?) => ($crate::graphql::InputValue::from(false));
 
-    (@$var:ident$(,)?) => ($crate::InputValue::variable(stringify!($var)));
+    (@$var:ident$(,)?) => ($crate::graphql::InputValue::variable(stringify!($var)));
 
-    ($enum:ident$(,)?) => ($crate::InputValue::enum_value(stringify!($enum)));
+    ($enum:ident$(,)?) => ($crate::graphql::InputValue::enum_value(stringify!($enum)));
 
-    (($e:expr)$(,)?) => ($crate::InputValue::from($e));
+    (($e:expr)$(,)?) => ($crate::graphql::InputValue::from($e));
 
-    ($e:expr$(,)?) => ($crate::InputValue::from($e));
+    ($e:expr$(,)?) => ($crate::graphql::InputValue::from($e));
 }
 
+#[doc(inline)]
+pub use graphql_input_value as input_value;
+
 #[cfg(test)]
 mod tests {
     use indexmap::{indexmap, IndexMap};
 
-    type V = crate::InputValue;
+    use crate::graphql;
+
+    use super::input_value;
+
+    type V = graphql::InputValue;
 
     #[test]
     fn null() {
-        assert_eq!(graphql_input_value!(null), V::Null);
+        assert_eq!(input_value!(null), V::Null);
     }
 
     #[test]
     fn scalar() {
         let val = 42;
-        assert_eq!(graphql_input_value!(1), V::scalar(1));
-        assert_eq!(graphql_input_value!("val"), V::scalar("val"));
-        assert_eq!(graphql_input_value!(1.34), V::scalar(1.34));
-        assert_eq!(graphql_input_value!(false), V::scalar(false));
-        assert_eq!(graphql_input_value!(1 + 2), V::scalar(3));
-        assert_eq!(graphql_input_value!((val)), V::scalar(42));
+        assert_eq!(input_value!(1), V::scalar(1));
+        assert_eq!(input_value!("val"), V::scalar("val"));
+        assert_eq!(input_value!(1.34), V::scalar(1.34));
+        assert_eq!(input_value!(false), V::scalar(false));
+        assert_eq!(input_value!(1 + 2), V::scalar(3));
+        assert_eq!(input_value!((val)), V::scalar(42));
     }
 
     #[test]
     fn r#enum() {
-        assert_eq!(graphql_input_value!(ENUM), V::enum_value("ENUM"));
-        assert_eq!(graphql_input_value!(lowercase), V::enum_value("lowercase"));
+        assert_eq!(input_value!(ENUM), V::enum_value("ENUM"));
+        assert_eq!(input_value!(lowercase), V::enum_value("lowercase"));
     }
 
     #[test]
     fn variable() {
-        assert_eq!(graphql_input_value!(@var), V::variable("var"));
-        assert_eq!(graphql_input_value!(@array), V::variable("array"));
-        assert_eq!(graphql_input_value!(@object), V::variable("object"));
+        assert_eq!(input_value!(@var), V::variable("var"));
+        assert_eq!(input_value!(@array), V::variable("array"));
+        assert_eq!(input_value!(@object), V::variable("object"));
     }
 
     #[test]
     fn list() {
         let val = 42;
 
-        assert_eq!(graphql_input_value!([]), V::list(vec![]));
+        assert_eq!(input_value!([]), V::list(vec![]));
 
-        assert_eq!(graphql_input_value!([null]), V::list(vec![V::Null]));
+        assert_eq!(input_value!([null]), V::list(vec![V::Null]));
 
-        assert_eq!(graphql_input_value!([1]), V::list(vec![V::scalar(1)]));
-        assert_eq!(graphql_input_value!([1 + 2]), V::list(vec![V::scalar(3)]));
-        assert_eq!(graphql_input_value!([(val)]), V::list(vec![V::scalar(42)]));
+        assert_eq!(input_value!([1]), V::list(vec![V::scalar(1)]));
+        assert_eq!(input_value!([1 + 2]), V::list(vec![V::scalar(3)]));
+        assert_eq!(input_value!([(val)]), V::list(vec![V::scalar(42)]));
 
+        assert_eq!(input_value!([ENUM]), V::list(vec![V::enum_value("ENUM")]));
         assert_eq!(
-            graphql_input_value!([ENUM]),
-            V::list(vec![V::enum_value("ENUM")]),
-        );
-        assert_eq!(
-            graphql_input_value!([lowercase]),
+            input_value!([lowercase]),
             V::list(vec![V::enum_value("lowercase")]),
         );
 
+        assert_eq!(input_value!([@var]), V::list(vec![V::variable("var")]),);
+        assert_eq!(input_value!([@array]), V::list(vec![V::variable("array")]));
         assert_eq!(
-            graphql_input_value!([@var]),
-            V::list(vec![V::variable("var")]),
-        );
-        assert_eq!(
-            graphql_input_value!([@array]),
-            V::list(vec![V::variable("array")]),
-        );
-        assert_eq!(
-            graphql_input_value!([@object]),
+            input_value!([@object]),
             V::list(vec![V::variable("object")]),
         );
 
         assert_eq!(
-            graphql_input_value!([1, [2], 3]),
+            input_value!([1, [2], 3]),
             V::list(vec![
                 V::scalar(1),
                 V::list(vec![V::scalar(2)]),
@@ -459,7 +455,7 @@ mod tests {
             ]),
         );
         assert_eq!(
-            graphql_input_value!([1, [2 + 3], 3]),
+            input_value!([1, [2 + 3], 3]),
             V::list(vec![
                 V::scalar(1),
                 V::list(vec![V::scalar(5)]),
@@ -467,7 +463,7 @@ mod tests {
             ]),
         );
         assert_eq!(
-            graphql_input_value!([1, [ENUM], (val)]),
+            input_value!([1, [ENUM], (val)]),
             V::list(vec![
                 V::scalar(1),
                 V::list(vec![V::enum_value("ENUM")]),
@@ -475,7 +471,7 @@ mod tests {
             ]),
         );
         assert_eq!(
-            graphql_input_value!([1 + 2, [(val)], @val]),
+            input_value!([1 + 2, [(val)], @val]),
             V::list(vec![
                 V::scalar(3),
                 V::list(vec![V::scalar(42)]),
@@ -483,7 +479,7 @@ mod tests {
             ]),
         );
         assert_eq!(
-            graphql_input_value!([1, [@val], ENUM]),
+            input_value!([1, [@val], ENUM]),
             V::list(vec![
                 V::scalar(1),
                 V::list(vec![V::variable("val")]),
@@ -495,68 +491,65 @@ mod tests {
     #[test]
     fn object() {
         let val = 42;
-        assert_eq!(
-            graphql_input_value!({}),
-            V::object(IndexMap::<String, _>::new()),
-        );
+        assert_eq!(input_value!({}), V::object(IndexMap::<String, _>::new()));
 
         assert_eq!(
-            graphql_input_value!({ "key": null }),
+            input_value!({ "key": null }),
             V::object(indexmap! {"key" => V::Null}),
         );
 
         assert_eq!(
-            graphql_input_value!({"key": 123}),
+            input_value!({"key": 123}),
             V::object(indexmap! {"key" => V::scalar(123)}),
         );
         assert_eq!(
-            graphql_input_value!({"key": 1 + 2}),
+            input_value!({"key": 1 + 2}),
             V::object(indexmap! {"key" => V::scalar(3)}),
         );
         assert_eq!(
-            graphql_input_value!({ "key": (val) }),
+            input_value!({ "key": (val) }),
             V::object(indexmap! {"key" => V::scalar(42)}),
         );
 
         assert_eq!(
-            graphql_input_value!({"key": []}),
+            input_value!({"key": []}),
             V::object(indexmap! {"key" => V::list(vec![])}),
         );
         assert_eq!(
-            graphql_input_value!({ "key": [null] }),
+            input_value!({ "key": [null] }),
             V::object(indexmap! {"key" => V::list(vec![V::Null])}),
         );
         assert_eq!(
-            graphql_input_value!({"key": [1] }),
+            input_value!({"key": [1] }),
             V::object(indexmap! {"key" => V::list(vec![V::scalar(1)])}),
         );
         assert_eq!(
-            graphql_input_value!({"key": [1 + 2] }),
+            input_value!({"key": [1 + 2] }),
             V::object(indexmap! {"key" => V::list(vec![V::scalar(3)])}),
         );
         assert_eq!(
-            graphql_input_value!({ "key": [(val)] }),
+            input_value!({ "key": [(val)] }),
             V::object(indexmap! {"key" => V::list(vec![V::scalar(42)])}),
         );
         assert_eq!(
-            graphql_input_value!({ "key": ENUM }),
+            input_value!({ "key": ENUM }),
             V::object(indexmap! {"key" => V::enum_value("ENUM")}),
         );
         assert_eq!(
-            graphql_input_value!({ "key": lowercase }),
+            input_value!({ "key": lowercase }),
             V::object(indexmap! {"key" => V::enum_value("lowercase")}),
         );
         assert_eq!(
-            graphql_input_value!({"key": @val}),
+            input_value!({"key": @val}),
             V::object(indexmap! {"key" => V::variable("val")}),
         );
         assert_eq!(
-            graphql_input_value!({"key": @array }),
+            input_value!({"key": @array }),
             V::object(indexmap! {"key" => V::variable("array")}),
         );
 
         assert_eq!(
-            graphql_input_value!({
+            input_value!({
                 "inner": {
                     "key1": (val),
                     "key2": "val",
@@ -606,8 +599,8 @@ mod tests {
     fn option() {
         let val = Some(42);
 
-        assert_eq!(graphql_input_value!(None), V::Null);
-        assert_eq!(graphql_input_value!(Some(42)), V::scalar(42));
-        assert_eq!(graphql_input_value!((val)), V::scalar(42));
+        assert_eq!(input_value!(None), V::Null);
+        assert_eq!(input_value!(Some(42)), V::scalar(42));
+        assert_eq!(input_value!((val)), V::scalar(42));
     }
 }
diff --git a/juniper/src/macros/graphql_value.rs b/juniper/src/macros/graphql_value.rs
index cc4e34d38..aa72a7fbd 100644
--- a/juniper/src/macros/graphql_value.rs
+++ b/juniper/src/macros/graphql_value.rs
@@ -1,19 +1,18 @@
-//! [`graphql_value!`] macro implementation.
-//!
-//! [`graphql_value!`]: graphql_value
+//! [`value!`] macro implementation.
 
-/// Constructs [`Value`]s via JSON-like syntax.
+/// Constructs [`graphql::Value`]s via JSON-like syntax.
 ///
-/// [`Value`] objects are used mostly when creating custom errors from fields.
+/// [`graphql::Value`] objects are used mostly when creating custom errors from
+/// fields.
 ///
 /// [`Value::Object`] key should implement [`AsRef`]`<`[`str`]`>`.
 /// ```rust
-/// # use juniper::{graphql_value, Value};
+/// # use juniper::graphql;
 /// #
 /// let code = 200;
 /// let features = ["key", "value"];
 ///
-/// let value: Value = graphql_value!({
+/// let value: graphql::Value = graphql::value!({
 ///     "code": code,
 ///     "success": code == 200,
 ///     "payload": {
@@ -26,24 +25,24 @@
 ///
 /// Resulting JSON will look just like what you passed in.
 /// ```rust
-/// # use juniper::{graphql_value, DefaultScalarValue, Value};
+/// # use juniper::graphql;
 /// #
-/// # type V = Value<DefaultScalarValue>;
+/// # type V = graphql::Value;
 /// #
 /// # let _: V =
-/// graphql_value!(null);
+/// graphql::value!(null);
 /// # let _: V =
-/// graphql_value!(1234);
+/// graphql::value!(1234);
 /// # let _: V =
-/// graphql_value!("test");
+/// graphql::value!("test");
 /// # let _: V =
-/// graphql_value!([1234, "test", true]);
+/// graphql::value!([1234, "test", true]);
 /// # let _: V =
-/// graphql_value!({"key": "value", "foo": 1234});
+/// graphql::value!({"key": "value", "foo": 1234});
 /// ```
 ///
-/// [`Value`]: crate::Value
-/// [`Value::Object`]: crate::Value::Object
+/// [`graphql::Value`]: crate::graphql::Value
+/// [`Value::Object`]: crate::graphql::Value::Object
 #[macro_export]
 macro_rules! graphql_value {
     ///////////
@@ -52,68 +51,68 @@ macro_rules! graphql_value {
 
     // Done with trailing comma.
     (@array [$($elems:expr,)*]) => {
-        $crate::Value::list(vec![
+        $crate::graphql::Value::list(::std::vec![
             $( $elems, )*
         ])
     };
 
     // Done without trailing comma.
     (@array [$($elems:expr),*]) => {
-        $crate::Value::list(vec![
-            $( $crate::graphql_value!($elems), )*
+        $crate::graphql::Value::list(::std::vec![
+            $( $crate::graphql::value!($elems), )*
         ])
     };
 
     // Next element is `null`.
     (@array [$($elems:expr,)*] null $($rest:tt)*) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!(null)] $($rest)*
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!(null)] $($rest)*
         )
     };
 
     // Next element is `None`.
     (@array [$($elems:expr,)*] None $($rest:tt)*) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!(None)] $($rest)*
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!(None)] $($rest)*
         )
     };
 
     // Next element is an array.
     (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!([$($array)*])] $($rest)*
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!([$($array)*])] $($rest)*
         )
     };
 
     // Next element is a map.
     (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!({$($map)*})] $($rest)*
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!({$($map)*})] $($rest)*
         )
     };
 
     // Next element is an expression followed by comma.
     (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!($next),] $($rest)*
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!($next),] $($rest)*
         )
     };
 
     // Last element is an expression with no trailing comma.
     (@array [$($elems:expr,)*] $last:expr) => {
-        $crate::graphql_value!(
-            @array [$($elems,)* $crate::graphql_value!($last)]
+        $crate::graphql::value!(
+            @array [$($elems,)* $crate::graphql::value!($last)]
         )
     };
 
     // Comma after the most recent element.
     (@array [$($elems:expr),*] , $($rest:tt)*) => {
-        $crate::graphql_value!(@array [$($elems,)*] $($rest)*)
+        $crate::graphql::value!(@array [$($elems,)*] $($rest)*)
     };
 
     // Unexpected token after most recent element.
     (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
-        $crate::graphql_value!(@unexpected $unexpected)
+        $crate::graphql::value!(@unexpected $unexpected)
     };
 
     ////////////
@@ -126,12 +125,12 @@ macro_rules! graphql_value {
     // Insert the current entry followed by trailing comma.
     (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
         let _ = $object.add_field(($($key)+), $value);
-        $crate::graphql_value!(@object $object () ($($rest)*) ($($rest)*));
+        $crate::graphql::value!(@object $object () ($($rest)*) ($($rest)*));
     };
 
     // Current entry followed by unexpected token.
     (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
-        $crate::graphql_value!(@unexpected $unexpected);
+        $crate::graphql::value!(@unexpected $unexpected);
     };
 
     // Insert the last entry without trailing comma.
@@ -141,97 +140,97 @@ macro_rules! graphql_value {
 
     // Next value is `null`.
     (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!(null)) $($rest)*
+            ($crate::graphql::value!(null)) $($rest)*
         );
     };
 
     // Next value is `None`.
     (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!(None)) $($rest)*
+            ($crate::graphql::value!(None)) $($rest)*
         );
     };
 
     // Next value is an array.
     (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!([$($array)*])) $($rest)*
+            ($crate::graphql::value!([$($array)*])) $($rest)*
         );
     };
 
     // Next value is a map.
     (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!({$($map)*})) $($rest)*
+            ($crate::graphql::value!({$($map)*})) $($rest)*
         );
     };
 
     // Next value is an expression followed by comma.
     (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!($value)) , $($rest)*
+            ($crate::graphql::value!($value)) , $($rest)*
         );
     };
 
     // Last value is an expression with no trailing comma.
     (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             [$($key)+]
-            ($crate::graphql_value!($value))
+            ($crate::graphql::value!($value))
         );
     };
 
     // Missing value for last entry. Trigger a reasonable error message.
     (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_value!();
+        $crate::graphql::value!();
     };
 
     // Missing colon and value for last entry. Trigger a reasonable error
     // message.
     (@object $object:ident ($($key:tt)+) () $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_value!();
+        $crate::graphql::value!();
     };
 
     // Misplaced colon. Trigger a reasonable error message.
     (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `:`".
-        $crate::graphql_value!(@unexpected $colon);
+        $crate::graphql::value!(@unexpected $colon);
     };
 
     // Found a comma inside a key. Trigger a reasonable error message.
     (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `,`".
-        $crate::graphql_value!(@unexpected $comma);
+        $crate::graphql::value!(@unexpected $comma);
     };
 
     // Key is fully parenthesized. This avoids `clippy::double_parens` false
     // positives because the parenthesization may be necessary here.
     (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(@object $object ($key) (: $($rest)*) (: $($rest)*));
+        $crate::graphql::value!(@object $object ($key) (: $($rest)*) (: $($rest)*));
     };
 
     // Refuse to absorb colon token into key expression.
     (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
-        $crate::graphql_value!(@unexpected $($unexpected)+);
+        $crate::graphql::value!(@unexpected $($unexpected)+);
     };
 
     // Munch a token into the current key.
     (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_value!(
+        $crate::graphql::value!(
             @object $object
             ($($key)* $tt)
             ($($rest)*) ($($rest)*)
@@ -249,63 +248,70 @@ macro_rules! graphql_value {
     //////////////
 
     ([ $($arr:tt)* ]$(,)?) => {
-        $crate::graphql_value!(@array [] $($arr)*)
+        $crate::graphql::value!(@array [] $($arr)*)
     };
 
     ({}$(,)?) => {
-        $crate::Value::object($crate::Object::with_capacity(0))
+        $crate::graphql::Value::object($crate::Object::with_capacity(0))
     };
 
     ({ $($map:tt)+ }$(,)?) => {
-        $crate::Value::object({
+        $crate::graphql::Value::object({
             let mut object = $crate::Object::with_capacity(0);
-            $crate::graphql_value!(@object object () ($($map)*) ($($map)*));
+            $crate::graphql::value!(@object object () ($($map)*) ($($map)*));
             object
         })
     };
 
-    (null$(,)?) => ($crate::Value::null());
+    (null$(,)?) => ($crate::graphql::Value::null());
 
-    (None$(,)?) => ($crate::Value::null());
+    (None$(,)?) => ($crate::graphql::Value::null());
 
-    ($e:expr$(,)?) => ($crate::Value::from($e));
+    ($e:expr$(,)?) => ($crate::graphql::Value::from($e));
 }
 
+#[doc(inline)]
+pub use graphql_value as value;
+
 #[cfg(test)]
 mod tests {
-    type V = crate::Value;
+    use crate::graphql;
+
+    use super::value;
+
+    type V = graphql::Value;
 
     #[test]
     fn null() {
-        assert_eq!(graphql_value!(null), V::Null);
+        assert_eq!(value!(null), V::Null);
     }
 
     #[test]
     fn scalar() {
         let val = 42;
 
-        assert_eq!(graphql_value!(1), V::scalar(1));
-        assert_eq!(graphql_value!("val"), V::scalar("val"));
-        assert_eq!(graphql_value!(1.34), V::scalar(1.34));
-        assert_eq!(graphql_value!(false), V::scalar(false));
-        assert_eq!(graphql_value!(1 + 2), V::scalar(3));
-        assert_eq!(graphql_value!(val), V::scalar(42));
+        assert_eq!(value!(1), V::scalar(1));
+        assert_eq!(value!("val"), V::scalar("val"));
+        assert_eq!(value!(1.34), V::scalar(1.34));
+        assert_eq!(value!(false), V::scalar(false));
+        assert_eq!(value!(1 + 2), V::scalar(3));
+        assert_eq!(value!(val), V::scalar(42));
     }
 
     #[test]
     fn list() {
         let val = 42;
 
-        assert_eq!(graphql_value!([]), V::list(vec![]));
+        assert_eq!(value!([]), V::list(vec![]));
 
-        assert_eq!(graphql_value!([null]), V::list(vec![V::Null]));
+        assert_eq!(value!([null]), V::list(vec![V::Null]));
 
-        assert_eq!(graphql_value!([1]), V::list(vec![V::scalar(1)]));
-        assert_eq!(graphql_value!([1 + 2]), V::list(vec![V::scalar(3)]));
-        assert_eq!(graphql_value!([val]), V::list(vec![V::scalar(42)]));
+        assert_eq!(value!([1]), V::list(vec![V::scalar(1)]));
+        assert_eq!(value!([1 + 2]), V::list(vec![V::scalar(3)]));
+        assert_eq!(value!([val]), V::list(vec![V::scalar(42)]));
 
         assert_eq!(
-            graphql_value!([1, [2], 3]),
+            value!([1, [2], 3]),
             V::list(vec![
                 V::scalar(1),
                 V::list(vec![V::scalar(2)]),
@@ -313,7 +319,7 @@ mod tests {
             ]),
         );
         assert_eq!(
-            graphql_value!(["string", [2 + 3], true]),
+            value!(["string", [2 + 3], true]),
             V::list(vec![
                 V::scalar("string"),
                 V::list(vec![V::scalar(5)]),
@@ -327,31 +333,31 @@ mod tests {
         let val = 42;
 
         assert_eq!(
-            graphql_value!({}),
+            value!({}),
             V::object(Vec::<(String, _)>::new().into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": null }),
+            value!({ "key": null }),
             V::object(vec![("key", V::Null)].into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": 123 }),
+            value!({ "key": 123 }),
             V::object(vec![("key", V::scalar(123))].into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": 1 + 2 }),
+            value!({ "key": 1 + 2 }),
             V::object(vec![("key", V::scalar(3))].into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": [] }),
+            value!({ "key": [] }),
             V::object(vec![("key", V::list(vec![]))].into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": [null] }),
+            value!({ "key": [null] }),
             V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()),
         );
         assert_eq!(
-            graphql_value!({ "key": [1] }),
+            value!({ "key": [1] }),
             V::object(
                 vec![("key", V::list(vec![V::scalar(1)]))]
                     .into_iter()
@@ -359,7 +365,7 @@ mod tests {
             ),
         );
         assert_eq!(
-            graphql_value!({ "key": [1 + 2] }),
+            value!({ "key": [1 + 2] }),
             V::object(
                 vec![("key", V::list(vec![V::scalar(3)]))]
                     .into_iter()
@@ -367,7 +373,7 @@ mod tests {
             ),
         );
         assert_eq!(
-            graphql_value!({ "key": [val] }),
+            value!({ "key": [val] }),
             V::object(
                 vec![("key", V::list(vec![V::scalar(42)]))]
                     .into_iter()
@@ -380,8 +386,8 @@ mod tests {
     fn option() {
         let val = Some(42);
 
-        assert_eq!(graphql_value!(None), V::Null);
-        assert_eq!(graphql_value!(Some(42)), V::scalar(42));
-        assert_eq!(graphql_value!(val), V::scalar(42));
+        assert_eq!(value!(None), V::Null);
+        assert_eq!(value!(Some(42)), V::scalar(42));
+        assert_eq!(value!(val), V::scalar(42));
     }
 }
diff --git a/juniper/src/macros/graphql_vars.rs b/juniper/src/macros/graphql_vars.rs
index 5d0f55627..debd4201b 100644
--- a/juniper/src/macros/graphql_vars.rs
+++ b/juniper/src/macros/graphql_vars.rs
@@ -1,20 +1,18 @@
-//! [`graphql_vars!`] macro implementation.
-//!
-//! [`graphql_vars!`]: graphql_vars
+//! [`vars!`] macro implementation.
 
-/// Constructs [`Variables`] via JSON-like syntax.
+/// Constructs [`graphql::Variables`] via JSON-like syntax.
 ///
-/// [`Variables`] key should implement [`Into`]`<`[`String`]`>`.
+/// [`graphql::Variables`] key should implement [`Into`]`<`[`String`]`>`.
 /// ```rust
 /// # use std::borrow::Cow;
 /// #
-/// # use juniper::{graphql_vars, Variables};
+/// # use juniper::graphql;
 /// #
 /// let code = 200;
 /// let features = vec!["key", "value"];
 /// let key: Cow<'static, str> = "key".into();
 ///
-/// let value: Variables = graphql_vars! {
+/// let value: graphql::Variables = graphql::vars! {
 ///     "code": code,
 ///     "success": code == 200,
 ///     features[0]: features[1],
@@ -22,10 +20,10 @@
 /// };
 /// ```
 ///
-/// See [`graphql_input_value!`] for more info on syntax of value after `:`.
+/// See [`graphql::input_value!`] for more info on syntax of value after `:`.
 ///
-/// [`graphql_input_value!`]: crate::graphql_input_value
-/// [`Variables`]: crate::Variables
+/// [`graphql::input_value!`]: crate::graphql::input_value
+/// [`graphql::Variables`]: crate::graphql::Variables
 #[macro_export]
 macro_rules! graphql_vars {
     ////////////
@@ -38,12 +36,12 @@ macro_rules! graphql_vars {
     // Insert the current entry followed by trailing comma.
     (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
         let _ = $object.insert(($($key)+).into(), $value);
-        $crate::graphql_vars! {@object $object () ($($rest)*) ($($rest)*)};
+        $crate::graphql::vars! {@object $object () ($($rest)*) ($($rest)*)};
     };
 
     // Current entry followed by unexpected token.
     (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
-        $crate::graphql_vars! {@unexpected $unexpected};
+        $crate::graphql::vars! {@unexpected $unexpected};
     };
 
     // Insert the last entry without trailing comma.
@@ -53,7 +51,7 @@ macro_rules! graphql_vars {
 
     // Next value is `null`.
     (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!(null)) $($rest)*
@@ -62,7 +60,7 @@ macro_rules! graphql_vars {
 
     // Next value is `None`.
     (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!(None)) $($rest)*
@@ -71,7 +69,7 @@ macro_rules! graphql_vars {
 
     // Next value is a variable.
     (@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!(@$var)) $($rest)*
@@ -80,7 +78,7 @@ macro_rules! graphql_vars {
 
     // Next value is an array.
     (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!([$($array)*])) $($rest)*
@@ -89,7 +87,7 @@ macro_rules! graphql_vars {
 
     // Next value is a map.
     (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!({$($map)*})) $($rest)*
@@ -98,7 +96,7 @@ macro_rules! graphql_vars {
 
     // Next value is `true`, `false` or enum ident followed by a comma.
     (@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!($ident)) , $($rest)*
@@ -107,7 +105,7 @@ macro_rules! graphql_vars {
 
     // Next value is `true`, `false` or enum ident without trailing comma.
     (@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!($last))
@@ -116,7 +114,7 @@ macro_rules! graphql_vars {
 
     // Next value is an expression followed by comma.
     (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!($value)) , $($rest)*
@@ -125,7 +123,7 @@ macro_rules! graphql_vars {
 
     // Last value is an expression with no trailing comma.
     (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             [$($key)+]
             ($crate::graphql_input_value!($value))
@@ -135,44 +133,44 @@ macro_rules! graphql_vars {
     // Missing value for last entry. Trigger a reasonable error message.
     (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_vars! {};
+        $crate::graphql::vars! {};
     };
 
     // Missing colon and value for last entry. Trigger a reasonable error
     // message.
     (@object $object:ident ($($key:tt)+) () $copy:tt) => {
         // "unexpected end of macro invocation"
-        $crate::graphql_vars! {};
+        $crate::graphql::vars! {};
     };
 
     // Misplaced colon. Trigger a reasonable error message.
     (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `:`".
-        $crate::graphql_vars! {@unexpected $colon};
+        $crate::graphql::vars! {@unexpected $colon};
     };
 
     // Found a comma inside a key. Trigger a reasonable error message.
     (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
         // Takes no arguments so "no rules expected the token `,`".
-        $crate::graphql_vars! {@unexpected $comma};
+        $crate::graphql::vars! {@unexpected $comma};
     };
 
     // Key is fully parenthesized. This avoids clippy double_parens false
     // positives because the parenthesization may be necessary here.
     (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object ($key) (: $($rest)*) (: $($rest)*)
         };
     };
 
     // Refuse to absorb colon token into key expression.
     (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
-        $crate::graphql_vars! {@unexpected $($unexpected)+};
+        $crate::graphql::vars! {@unexpected $($unexpected)+};
     };
 
     // Munch a token into the current key.
     (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
-        $crate::graphql_vars! {
+        $crate::graphql::vars! {
             @object $object
             ($($key)* $tt)
             ($($rest)*) ($($rest)*)
@@ -189,26 +187,33 @@ macro_rules! graphql_vars {
     // Defaults //
     //////////////
 
-    () => {{ $crate::Variables::<_>::new() }};
+    () => {{ $crate::graphql::Variables::<_>::new() }};
 
     ( $($map:tt)+ ) => {{
-        let mut object = $crate::Variables::<_>::new();
-        $crate::graphql_vars! {@object object () ($($map)*) ($($map)*)};
+        let mut object = $crate::graphql::Variables::<_>::new();
+        $crate::graphql::vars! {@object object () ($($map)*) ($($map)*)};
         object
     }};
 }
 
+#[doc(inline)]
+pub use graphql_vars as vars;
+
 #[cfg(test)]
 mod tests {
     use indexmap::{indexmap, IndexMap};
 
-    type V = crate::Variables;
+    use crate::graphql;
+
+    use super::vars;
+
+    type V = graphql::Variables;
 
-    type IV = crate::InputValue;
+    type IV = graphql::InputValue;
 
     #[test]
     fn empty() {
-        assert_eq!(graphql_vars! {}, V::new());
+        assert_eq!(vars! {}, V::new());
     }
 
     #[test]
@@ -216,35 +221,35 @@ mod tests {
         let val = 42;
 
         assert_eq!(
-            graphql_vars! {"key": 123},
+            vars! {"key": 123},
             vec![("key".into(), IV::scalar(123))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": "val"},
+            vars! {"key": "val"},
             vec![("key".into(), IV::scalar("val"))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": 1.23},
+            vars! {"key": 1.23},
             vec![("key".into(), IV::scalar(1.23))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": 1 + 2},
+            vars! {"key": 1 + 2},
             vec![("key".into(), IV::scalar(3))].into_iter().collect(),
         );
         assert_eq!(
-            graphql_vars! {"key": false},
+            vars! {"key": false},
             vec![("key".into(), IV::scalar(false))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": (val)},
+            vars! {"key": (val)},
             vec![("key".into(), IV::scalar(42))]
                 .into_iter()
                 .collect::<V>(),
@@ -254,13 +259,13 @@ mod tests {
     #[test]
     fn r#enum() {
         assert_eq!(
-            graphql_vars! {"key": ENUM},
+            vars! {"key": ENUM},
             vec![("key".into(), IV::enum_value("ENUM"))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": lowercase},
+            vars! {"key": lowercase},
             vec![("key".into(), IV::enum_value("lowercase"))]
                 .into_iter()
                 .collect::<V>(),
@@ -270,19 +275,19 @@ mod tests {
     #[test]
     fn variable() {
         assert_eq!(
-            graphql_vars! {"key": @var},
+            vars! {"key": @var},
             vec![("key".into(), IV::variable("var"))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": @array},
+            vars! {"key": @array},
             vec![("key".into(), IV::variable("array"))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": @object},
+            vars! {"key": @object},
             vec![("key".into(), IV::variable("object"))]
                 .into_iter()
                 .collect::<V>(),
@@ -294,72 +299,72 @@ mod tests {
         let val = 42;
 
         assert_eq!(
-            graphql_vars! {"key": []},
+            vars! {"key": []},
             vec![("key".into(), IV::list(vec![]))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": [null]},
+            vars! {"key": [null]},
             vec![("key".into(), IV::list(vec![IV::Null]))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": [1]},
+            vars! {"key": [1]},
             vec![("key".into(), IV::list(vec![IV::scalar(1)]))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [1 + 2]},
+            vars! {"key": [1 + 2]},
             vec![("key".into(), IV::list(vec![IV::scalar(3)]))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [(val)]},
+            vars! {"key": [(val)]},
             vec![("key".into(), IV::list(vec![IV::scalar(42)]))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": [ENUM]},
+            vars! {"key": [ENUM]},
             vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [lowercase]},
+            vars! {"key": [lowercase]},
             vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": [@var]},
+            vars! {"key": [@var]},
             vec![("key".into(), IV::list(vec![IV::variable("var")]))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [@array]},
+            vars! {"key": [@array]},
             vec![("key".into(), IV::list(vec![IV::variable("array")]))]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [@object]},
+            vars! {"key": [@object]},
             vec![("key".into(), IV::list(vec![IV::variable("object")]))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": [1, [2], 3]},
+            vars! {"key": [1, [2], 3]},
             vec![(
                 "key".into(),
                 IV::list(vec![
@@ -372,7 +377,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [1, [2 + 3], 3]},
+            vars! {"key": [1, [2 + 3], 3]},
             vec![(
                 "key".into(),
                 IV::list(vec![
@@ -385,7 +390,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [1, [ENUM], (val)]},
+            vars! {"key": [1, [ENUM], (val)]},
             vec![(
                 "key".into(),
                 IV::list(vec![
@@ -398,7 +403,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [1 + 2, [(val)], @val]},
+            vars! {"key": [1 + 2, [(val)], @val]},
             vec![(
                 "key".into(),
                 IV::list(vec![
@@ -411,7 +416,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": [1, [@val], ENUM]},
+            vars! {"key": [1, [@val], ENUM]},
             vec![(
                 "key".into(),
                 IV::list(vec![
@@ -430,21 +435,21 @@ mod tests {
         let val = 42;
 
         assert_eq!(
-            graphql_vars! {"key": {}},
+            vars! {"key": {}},
             vec![("key".into(), IV::object(IndexMap::<String, _>::new()))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": {"key": null}},
+            vars! {"key": {"key": null}},
             vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))]
                 .into_iter()
                 .collect::<V>(),
         );
 
         assert_eq!(
-            graphql_vars! {"key": {"key": 123}},
+            vars! {"key": {"key": 123}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::scalar(123)}),
@@ -453,13 +458,13 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": 1 + 2}},
+            vars! {"key": {"key": 1 + 2}},
             vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)]
                 .into_iter()
                 .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": (val)}},
+            vars! {"key": {"key": (val)}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::scalar(42)}),
@@ -469,7 +474,7 @@ mod tests {
         );
 
         assert_eq!(
-            graphql_vars! {"key": {"key": []}},
+            vars! {"key": {"key": []}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::list(vec![])}),
@@ -478,7 +483,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": [null]}},
+            vars! {"key": {"key": [null]}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}),
@@ -487,7 +492,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": [1]}},
+            vars! {"key": {"key": [1]}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}),
@@ -496,7 +501,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": [1 + 2]}},
+            vars! {"key": {"key": [1 + 2]}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}),
@@ -505,7 +510,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": [(val)]}},
+            vars! {"key": {"key": [(val)]}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}),
@@ -514,7 +519,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": ENUM}},
+            vars! {"key": {"key": ENUM}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::enum_value("ENUM")}),
@@ -523,7 +528,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": lowercase}},
+            vars! {"key": {"key": lowercase}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::enum_value("lowercase")}),
@@ -532,7 +537,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": @val}},
+            vars! {"key": {"key": @val}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::variable("val")}),
@@ -541,7 +546,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {"key": {"key": @array}},
+            vars! {"key": {"key": @array}},
             vec![(
                 "key".into(),
                 IV::object(indexmap! {"key" => IV::variable("array")}),
@@ -550,7 +555,7 @@ mod tests {
             .collect::<V>(),
         );
         assert_eq!(
-            graphql_vars! {
+            vars! {
                 "inner": {
                     "key1": (val),
                     "key2": "val",
diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs
index 4bbcfb5f6..57f1a4c74 100644
--- a/juniper/src/macros/mod.rs
+++ b/juniper/src/macros/mod.rs
@@ -6,9 +6,9 @@ pub mod helper;
 #[macro_use]
 pub mod reflect;
 
-#[macro_use]
 mod graphql_input_value;
-#[macro_use]
 mod graphql_value;
-#[macro_use]
 mod graphql_vars;
+
+#[doc(inline)]
+pub use self::{graphql_input_value::input_value, graphql_value::value, graphql_vars::vars};
diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs
index 563b28dfe..33ea79ab9 100644
--- a/juniper/src/macros/reflect.rs
+++ b/juniper/src/macros/reflect.rs
@@ -8,20 +8,10 @@ use crate::{
     Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue,
 };
 
-/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name
-/// in a GraphQL schema.
-///
-/// See [`BaseType`] for more info.
-///
-/// [1]: https://spec.graphql.org/October2021#sec-Objects
-/// [2]: https://spec.graphql.org/October2021#sec-Scalars
-/// [3]: https://spec.graphql.org/October2021#sec-Interfaces
-pub type Type = &'static str;
-
-/// Alias for a slice of [`Type`]s.
-///
-/// See [`BaseSubTypes`] for more info.
-pub type Types = &'static [Type];
+pub use crate::reflect::{
+    can_be_subtype, fnv1a128, str_eq, str_exists_in_arr, type_len_with_wrapped_val, Argument,
+    Arguments, FieldName, Name, Names, Type, Types, WrappedValue,
+};
 
 /// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`].
 ///
@@ -153,9 +143,6 @@ impl<S, T: BaseSubTypes<S> + ?Sized> BaseSubTypes<S> for Rc<T> {
     const NAMES: Types = T::NAMES;
 }
 
-/// Alias for a value of a [`WrappedType`] (composed GraphQL type).
-pub type WrappedValue = u128;
-
 // TODO: Just use `&str`s once they're allowed in `const` generics.
 /// Encoding of a composed GraphQL type in numbers.
 ///
@@ -163,7 +150,7 @@ pub type WrappedValue = u128;
 /// because of the [wrapping types][2]. To work around this we use a
 /// [`WrappedValue`] which is represented via [`u128`] number in the following
 /// encoding:
-/// - In base case of non-nullable [object][1] [`VALUE`] is `1`.
+/// - In base case of non-nullable singular [object][1] [`VALUE`] is `1`.
 /// - To represent nullability we "append" `2` to the [`VALUE`], so
 ///   [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`.
 /// - To represent list we "append" `3` to the [`VALUE`], so
@@ -177,7 +164,7 @@ pub type WrappedValue = u128;
 ///
 /// ```rust
 /// # use juniper::{
-/// #     format_type,
+/// #     reflect::format_type,
 /// #     macros::reflect::{WrappedType, BaseType, WrappedValue, Type},
 /// #     DefaultScalarValue,
 /// # };
@@ -253,34 +240,6 @@ impl<S, T: WrappedType<S> + ?Sized> WrappedType<S> for Rc<T> {
     const VALUE: u128 = T::VALUE;
 }
 
-/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name.
-///
-/// See [`Fields`] for more info.
-///
-/// [1]: https://spec.graphql.org/October2021#sec-Objects
-/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
-/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments
-pub type Name = &'static str;
-
-/// Alias for a slice of [`Name`]s.
-///
-/// See [`Fields`] for more info.
-pub type Names = &'static [Name];
-
-/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`].
-///
-/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
-pub type Argument = (Name, Type, WrappedValue);
-
-/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and
-/// [`WrappedValue`].
-///
-/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
-pub type Arguments = &'static [(Name, Type, WrappedValue)];
-
-/// Alias for a `const`-hashed [`Name`] used in a `const` context.
-pub type FieldName = u128;
-
 /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`].
 ///
 /// [1]: https://spec.graphql.org/October2021#sec-Objects
@@ -394,107 +353,6 @@ pub trait AsyncField<S, const N: FieldName>: FieldMeta<S, N> {
     ) -> BoxFuture<'b, ExecutionResult<S>>;
 }
 
-/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in
-/// `const` generics. See [spec] for more info.
-///
-/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
-#[must_use]
-pub const fn fnv1a128(str: Name) -> u128 {
-    const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d;
-    const FNV_PRIME: u128 = 0x0000000001000000000000000000013b;
-
-    let bytes = str.as_bytes();
-    let mut hash = FNV_OFFSET_BASIS;
-    let mut i = 0;
-    while i < bytes.len() {
-        hash ^= bytes[i] as u128;
-        hash = hash.wrapping_mul(FNV_PRIME);
-        i += 1;
-    }
-    hash
-}
-
-/// Length __in bytes__ of the [`format_type!`] macro result.
-#[must_use]
-pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize {
-    let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type!
-
-    let mut curr = val;
-    while curr % 10 != 0 {
-        match curr % 10 {
-            2 => len -= "!".as_bytes().len(),   // remove !
-            3 => len += "[]!".as_bytes().len(), // [Type]!
-            _ => {}
-        }
-        curr /= 10;
-    }
-
-    len
-}
-
-/// Checks whether the given GraphQL [object][1] represents a `subtype` of the
-/// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding.
-///
-/// To fully determine the sub-typing relation the [`Type`] should be one of the
-/// [`BaseSubTypes::NAMES`].
-///
-/// [1]: https://spec.graphql.org/October2021#sec-Objects
-#[must_use]
-pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool {
-    let ty_curr = ty % 10;
-    let sub_curr = subtype % 10;
-
-    if ty_curr == sub_curr {
-        if ty_curr == 1 {
-            true
-        } else {
-            can_be_subtype(ty / 10, subtype / 10)
-        }
-    } else if ty_curr == 2 {
-        can_be_subtype(ty / 10, subtype)
-    } else {
-        false
-    }
-}
-
-/// Checks whether the given `val` exists in the given `arr`.
-#[must_use]
-pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool {
-    let mut i = 0;
-    while i < arr.len() {
-        if str_eq(val, arr[i]) {
-            return true;
-        }
-        i += 1;
-    }
-    false
-}
-
-/// Compares strings in a `const` context.
-///
-/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to
-/// write custom comparison function.
-///
-/// [`Eq`]: std::cmp::Eq
-// TODO: Remove once `Eq` trait is allowed in `const` context.
-pub const fn str_eq(l: &str, r: &str) -> bool {
-    let (l, r) = (l.as_bytes(), r.as_bytes());
-
-    if l.len() != r.len() {
-        return false;
-    }
-
-    let mut i = 0;
-    while i < l.len() {
-        if l[i] != r[i] {
-            return false;
-        }
-        i += 1;
-    }
-
-    true
-}
-
 /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing
 /// this interface in the `impl = ...` attribute argument.
 ///
@@ -509,7 +367,7 @@ macro_rules! assert_implemented_for {
                     <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
                 );
                 if !is_present {
-                    const MSG: &str = $crate::const_concat!(
+                    const MSG: &str = $crate::reflect::const_concat!(
                         "Failed to implement interface `",
                         <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME,
                         "` on `",
@@ -537,7 +395,7 @@ macro_rules! assert_interfaces_impls {
                     <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES,
                 );
                 if !is_present {
-                    const MSG: &str = $crate::const_concat!(
+                    const MSG: &str = $crate::reflect::const_concat!(
                         "Failed to implement interface `",
                         <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
                         "` on `",
@@ -622,7 +480,7 @@ macro_rules! assert_subtype {
                 <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
             const IMPL_TY: $crate::macros::reflect::Type =
                 <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
-            const ERR_PREFIX: &str = $crate::const_concat!(
+            const ERR_PREFIX: &str = $crate::reflect::const_concat!(
                 "Failed to implement interface `",
                 BASE_TY,
                 "` on `",
@@ -664,14 +522,14 @@ macro_rules! assert_subtype {
             let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES)
                 && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL);
             if !is_subtype {
-                const MSG: &str = $crate::const_concat!(
+                const MSG: &str = $crate::reflect::const_concat!(
                     ERR_PREFIX,
                     "Field `",
                     FIELD_NAME,
                     "`: implementor is expected to return a subtype of interface's return object: `",
-                    $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL),
+                    $crate::reflect::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL),
                     "` is not a subtype of `",
-                    $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL),
+                    $crate::reflect::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL),
                     "`.",
                 );
                 ::std::panic!("{}", MSG);
@@ -695,7 +553,7 @@ macro_rules! assert_field_args {
         const _: () = {
             const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
             const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
-            const ERR_PREFIX: &str = $crate::const_concat!(
+            const ERR_PREFIX: &str = $crate::reflect::const_concat!(
                 "Failed to implement interface `",
                 BASE_NAME,
                 "` on `",
@@ -818,13 +676,14 @@ macro_rules! assert_field_args {
                 const BASE_ARG_NAME: &str = ERROR.base.0;
                 const IMPL_ARG_NAME: &str = ERROR.implementation.0;
 
-                const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2);
+                const BASE_TYPE_FORMATTED: &str =
+                    $crate::reflect::format_type!(ERROR.base.1, ERROR.base.2);
                 const IMPL_TYPE_FORMATTED: &str =
-                    $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2);
+                    $crate::reflect::format_type!(ERROR.implementation.1, ERROR.implementation.2);
 
                 const MSG: &str = match ERROR.cause {
                     Cause::TypeMismatch => {
-                        $crate::const_concat!(
+                        $crate::reflect::const_concat!(
                             "Argument `",
                             BASE_ARG_NAME,
                             "`: expected type `",
@@ -835,7 +694,7 @@ macro_rules! assert_field_args {
                         )
                     }
                     Cause::RequiredField => {
-                        $crate::const_concat!(
+                        $crate::reflect::const_concat!(
                             "Argument `",
                             BASE_ARG_NAME,
                             "` of type `",
@@ -844,7 +703,7 @@ macro_rules! assert_field_args {
                         )
                     }
                     Cause::AdditionalNonNullableField => {
-                        $crate::const_concat!(
+                        $crate::reflect::const_concat!(
                             "Argument `",
                             IMPL_ARG_NAME,
                             "` of type `",
@@ -854,43 +713,13 @@ macro_rules! assert_field_args {
                     }
                 };
                 const ERROR_MSG: &str =
-                    $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG);
+                    $crate::reflect::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG);
                 ::std::panic!("{}", ERROR_MSG);
             }
         };
     };
 }
 
-/// Concatenates `const` [`str`](prim@str)s in a `const` context.
-#[macro_export]
-macro_rules! const_concat {
-    ($($s:expr),* $(,)?) => {{
-        const LEN: usize = 0 $(+ $s.as_bytes().len())*;
-        const CNT: usize = [$($s),*].len();
-        const fn concat(input: [&str; CNT]) -> [u8; LEN] {
-            let mut bytes = [0; LEN];
-            let (mut i, mut byte) = (0, 0);
-            while i < CNT {
-                let mut b = 0;
-                while b < input[i].len() {
-                    bytes[byte] = input[i].as_bytes()[b];
-                    byte += 1;
-                    b += 1;
-                }
-                i += 1;
-            }
-            bytes
-        }
-        const CON: [u8; LEN] = concat([$($s),*]);
-
-        // TODO: Use `.unwrap()` once it becomes `const`.
-        match ::std::str::from_utf8(&CON) {
-            ::std::result::Result::Ok(s) => s,
-            _ => unreachable!(),
-        }
-    }};
-}
-
 /// Ensures that the given `$impl_ty` implements [`Field`] and returns a
 /// [`fnv1a128`] hash for it, otherwise panics with understandable message.
 #[macro_export]
@@ -903,7 +732,7 @@ macro_rules! checked_hash {
         if exists {
             $crate::macros::reflect::fnv1a128(FIELD_NAME)
         } else {
-            const MSG: &str = $crate::const_concat!(
+            const MSG: &str = $crate::reflect::const_concat!(
                 $($prefix,)?
                 "Field `",
                 $field_name,
@@ -915,102 +744,3 @@ macro_rules! checked_hash {
         }
     }};
 }
-
-/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type
-/// name.
-///
-/// # Examples
-///
-/// ```rust
-/// # use juniper::format_type;
-/// #
-/// assert_eq!(format_type!("String", 123), "[String]!");
-/// assert_eq!(format_type!("🦀", 123), "[🦀]!");
-/// ```
-#[macro_export]
-macro_rules! format_type {
-    ($ty: expr, $wrapped_value: expr $(,)?) => {{
-        const TYPE: (
-            $crate::macros::reflect::Type,
-            $crate::macros::reflect::WrappedValue,
-        ) = ($ty, $wrapped_value);
-        const RES_LEN: usize = $crate::macros::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1);
-
-        const OPENING_BRACKET: &str = "[";
-        const CLOSING_BRACKET: &str = "]";
-        const BANG: &str = "!";
-
-        const fn format_type_arr() -> [u8; RES_LEN] {
-            let (ty, wrap_val) = TYPE;
-            let mut type_arr: [u8; RES_LEN] = [0; RES_LEN];
-
-            let mut current_start = 0;
-            let mut current_end = RES_LEN - 1;
-            let mut current_wrap_val = wrap_val;
-            let mut is_null = false;
-            while current_wrap_val % 10 != 0 {
-                match current_wrap_val % 10 {
-                    2 => is_null = true, // Skips writing `BANG` later.
-                    3 => {
-                        // Write `OPENING_BRACKET` at `current_start`.
-                        let mut i = 0;
-                        while i < OPENING_BRACKET.as_bytes().len() {
-                            type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i];
-                            i += 1;
-                        }
-                        current_start += i;
-                        if !is_null {
-                            // Write `BANG` at `current_end`.
-                            i = 0;
-                            while i < BANG.as_bytes().len() {
-                                type_arr[current_end - BANG.as_bytes().len() + i + 1] =
-                                    BANG.as_bytes()[i];
-                                i += 1;
-                            }
-                            current_end -= i;
-                        }
-                        // Write `CLOSING_BRACKET` at `current_end`.
-                        i = 0;
-                        while i < CLOSING_BRACKET.as_bytes().len() {
-                            type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] =
-                                CLOSING_BRACKET.as_bytes()[i];
-                            i += 1;
-                        }
-                        current_end -= i;
-                        is_null = false;
-                    }
-                    _ => {}
-                }
-
-                current_wrap_val /= 10;
-            }
-
-            // Writes `Type` at `current_start`.
-            let mut i = 0;
-            while i < ty.as_bytes().len() {
-                type_arr[current_start + i] = ty.as_bytes()[i];
-                i += 1;
-            }
-            i = 0;
-            if !is_null {
-                // Writes `BANG` at `current_end`.
-                while i < BANG.as_bytes().len() {
-                    type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i];
-                    i += 1;
-                }
-            }
-
-            type_arr
-        }
-
-        const TYPE_ARR: [u8; RES_LEN] = format_type_arr();
-
-        // TODO: Use `.unwrap()` once it becomes `const`.
-        const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) {
-            ::std::result::Result::Ok(s) => s,
-            _ => unreachable!(),
-        };
-
-        TYPE_FORMATTED
-    }};
-}
diff --git a/juniper/src/reflect/mod.rs b/juniper/src/reflect/mod.rs
new file mode 100644
index 000000000..49fff1689
--- /dev/null
+++ b/juniper/src/reflect/mod.rs
@@ -0,0 +1,874 @@
+//! Compile-time reflection of Rust types into GraphQL types.
+
+use crate::behavior;
+
+#[doc(inline)]
+pub use self::macros::{
+    assert_field, assert_field_args, assert_field_type, assert_has_field, assert_implemented_for,
+    assert_interfaces_impls, assert_transitive_impls, const_concat, format_type,
+};
+
+/// Name of a [GraphQL type][0] in a GraphQL schema.
+///
+/// See [`BaseType`] for details.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+pub type Type = &'static str;
+
+/// List of [`Type`]s.
+///
+/// See [`BaseSubTypes`] for details.
+pub type Types = &'static [Type];
+
+/// Basic reflection of a [GraphQL type][0].
+///
+/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to
+/// fully represent a [GraphQL object][1] we additionally use [`WrappedType`].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+pub trait BaseType<Behavior: ?Sized = behavior::Standard> {
+    /// [`Type`] of this [GraphQL type][0].
+    ///
+    /// Different Rust types may have the same [`NAME`]. For example, [`String`]
+    /// and [`&str`](prim@str) share the `String!` GraphQL [`Type`].
+    ///
+    /// [`NAME`]: Self::NAME
+    /// [0]: https://spec.graphql.org/October2021#sec-Types
+    const NAME: Type;
+}
+
+/// Reflection of [sub-types][2] of a [GraphQL type][0].
+///
+/// This trait is transparent to [`Option`], [`Vec`] and other containers.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+/// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC
+pub trait BaseSubTypes<Behavior: ?Sized = behavior::Standard> {
+    /// Sub-[`Types`] of this [GraphQL type][0].
+    ///
+    /// Contains [at least][2] the [`BaseType::NAME`] of this [GraphQL type][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Types
+    /// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC
+    const NAMES: Types;
+}
+
+/// Reflection of [GraphQL interfaces][1] implementations for a
+/// [GraphQL type][0].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
+pub trait Implements<Behavior: ?Sized = behavior::Standard> {
+    /// [`Types`] of the [GraphQL interfaces][1] implemented by this
+    /// [GraphQL type][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Types
+    /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
+    const NAMES: Types;
+}
+
+/// Encoded value of a [`WrappedType`] (composed [GraphQL wrapping type][0]).
+///
+/// See [`WrappedType`] for details.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Wrapping-Types
+// TODO: Just use `&str`s once they're allowed in `const` generics.
+pub type WrappedValue = u128;
+
+/// [`WrappedValue`] encoding helpers.
+pub mod wrap {
+    use super::WrappedValue;
+
+    /// [`WrappedValue`] of a singular non-nullable [GraphQL type][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Types
+    pub const SINGULAR: WrappedValue = 1;
+
+    /// Performs wrapping into a nullable [`WrappedValue`].
+    pub const fn nullable(val: WrappedValue) -> WrappedValue {
+        val * 10 + 2
+    }
+
+    /// Performs wrapping into a list [`WrappedValue`].
+    pub const fn list(val: WrappedValue) -> WrappedValue {
+        val * 10 + 3
+    }
+}
+
+/// Reflection of a composed [GraphQL wrapping type][1], encoded in numbers.
+///
+/// To fully represent a [GraphQL type][0] it's not enough to use [`Type`],
+/// because of the [wrapping types][1]. To work around this, a [`WrappedValue`]
+/// is used, which is represented via [`u128`] number in the following encoding:
+/// - In base case of non-nullable singular [type][0] [`VALUE`] is `1`.
+/// - To represent nullability we "append" `2` to the [`VALUE`], so
+///   [`Option`]`<`[type][0]`>` has [`VALUE`] of `12`.
+/// - To represent a list we "append" `3` to the [`VALUE`], so
+///   [`Vec`]`<`[type][0]`>` has [`VALUE`] of `13`.
+///
+/// Note, that due to Rust type system, the encoding here differs from the one
+/// of [GraphQL wrapping types][1], as it takes nullability as wrapping, while
+/// GraphQL [does the opposite][1] (takes non-nullability as wrapping).
+///
+/// This approach allows to uniquely represent any [GraphQL type][0] with a
+/// combination of a [`Type`] and a [`WrappedValue`], and even format it via
+/// [`format_type!`] macro in a `const` context.
+///
+/// # Example
+///
+/// ```rust
+/// # use juniper::reflect::{
+/// #     format_type, BaseType, Type, WrappedType, WrappedValue,
+/// # };
+/// #
+/// assert_eq!(<Option<i32> as WrappedType>::VALUE, 12);
+/// assert_eq!(<Vec<i32> as WrappedType>::VALUE, 13);
+/// assert_eq!(<Vec<Option<i32>> as WrappedType>::VALUE, 123);
+/// assert_eq!(<Option<Vec<i32>> as WrappedType>::VALUE, 132);
+/// assert_eq!(<Option<Vec<Option<i32>>> as WrappedType>::VALUE, 1232);
+///
+/// const TYPE_STRING: Type = <Option<Vec<Option<String>>> as BaseType>::NAME;
+/// const WRAP_VAL_STRING: WrappedValue = <Option<Vec<Option<String>>> as WrappedType>::VALUE;
+/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]");
+///
+/// const TYPE_STR: Type = <Option<Vec<Option<&str>>> as BaseType>::NAME;
+/// const WRAP_VAL_STR: WrappedValue = <Option<Vec<Option<&str>>> as WrappedType>::VALUE;
+/// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]");
+/// ```
+///
+/// [`VALUE`]: Self::VALUE
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+/// [1]: https://spec.graphql.org/October2021#sec-Wrapping-Types
+pub trait WrappedType<Behavior: ?Sized = behavior::Standard> {
+    /// [`WrappedValue`] of this this [GraphQL type][0], encoded in a number.
+    ///
+    /// Use [`format_type!`] macro on this number to represent it as a
+    /// human-readable [GraphQL type][0] string.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Types
+    const VALUE: WrappedValue;
+}
+
+/// Name of a [GraphQL field][0] or a [field argument][1].
+///
+/// See [`Fields`] for details.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
+pub type Name = &'static str;
+
+/// List of [`Name`]s.
+///
+/// See [`Fields`] for details.
+pub type Names = &'static [Name];
+
+/// Reflection of [fields][0] for a [GraphQL object][1] or an [interface][2].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+/// [1]: https://spec.graphql.org/October2021#sec-Objects
+/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
+pub trait Fields<Behavior: ?Sized = behavior::Standard> {
+    /// [`Names`] of this [GraphQL object][1]/[interface][2] [fields][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+    /// [1]: https://spec.graphql.org/October2021#sec-Objects
+    /// [2]: https://spec.graphql.org/October2021#sec-Interfaces
+    const NAMES: Names;
+}
+
+/// [GraphQL field argument][0], represented as its [`Name`], [`Type`] and
+/// [`WrappedValue`].
+///
+/// See [`Field`] for details.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Language.Arguments
+pub type Argument = (Name, Type, WrappedValue);
+
+/// List of [`Argument`]s.
+///
+/// See [`Field`] for details.
+pub type Arguments = &'static [(Name, Type, WrappedValue)];
+
+/// Alias for a `const`-hashed [`Name`] used in a `const` context.
+// TODO: Just use `&str`s once they're allowed in `const` generics.
+pub type FieldName = u128;
+
+/// Reflection of a single [GraphQL field][0].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+pub trait Field<const N: FieldName, Behavior: ?Sized = behavior::Standard> {
+    /// [`Type`] of this [GraphQL field][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+    const TYPE: Type;
+
+    /// [Sub-types][1] this [GraphQL field][0] is coercible into.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+    /// [1]: BaseSubTypes
+    const SUB_TYPES: Types;
+
+    /// [`WrappedValue`] of this [GraphQL field][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+    const WRAPPED_VALUE: WrappedValue;
+
+    /// [`Arguments`] of this [GraphQL field][0] .
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
+    const ARGUMENTS: Arguments;
+}
+
+/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in
+/// `const` generics. See [spec] for more info.
+///
+/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
+#[must_use]
+pub const fn fnv1a128(str: Name) -> FieldName {
+    const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d;
+    const FNV_PRIME: u128 = 0x0000000001000000000000000000013b;
+
+    let bytes = str.as_bytes();
+    let mut hash = FNV_OFFSET_BASIS;
+    let mut i = 0;
+    while i < bytes.len() {
+        hash ^= bytes[i] as u128;
+        hash = hash.wrapping_mul(FNV_PRIME);
+        i += 1;
+    }
+    hash
+}
+
+/// Length __in bytes__ of the [`format_type!`] macro result.
+#[must_use]
+pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize {
+    let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type!
+
+    let mut curr = val;
+    while curr % 10 != 0 {
+        match curr % 10 {
+            2 => len -= "!".as_bytes().len(),   // remove !
+            3 => len += "[]!".as_bytes().len(), // [Type]!
+            _ => {}
+        }
+        curr /= 10;
+    }
+
+    len
+}
+
+/// Checks whether the specified `subtype` [GraphQL type][0] represents a
+/// [sub-type][1] of the specified `supertype`, basing on the [`WrappedType`]
+/// encoding.
+///
+/// To fully determine the [sub-typing][1] relation the [`Type`] should be one
+/// of the [`BaseSubTypes::NAMES`].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Types
+/// [1]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC
+#[must_use]
+pub const fn can_be_subtype(supertype: WrappedValue, subtype: WrappedValue) -> bool {
+    let super_curr = supertype % 10;
+    let sub_curr = subtype % 10;
+
+    if super_curr == sub_curr {
+        if super_curr == 1 {
+            true
+        } else {
+            can_be_subtype(supertype / 10, subtype / 10)
+        }
+    } else if super_curr == 2 {
+        can_be_subtype(supertype / 10, subtype)
+    } else {
+        false
+    }
+}
+
+/// Checks whether the given `val` exists in the given `arr`.
+// TODO: Remove once `slice::contains()` method is allowed in `const` context.
+#[must_use]
+pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool {
+    let mut i = 0;
+    while i < arr.len() {
+        if str_eq(val, arr[i]) {
+            return true;
+        }
+        i += 1;
+    }
+    false
+}
+
+/// Compares strings in a `const` context.
+///
+/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to
+/// provide a custom comparison function.
+///
+/// [`Eq`]: std::cmp::Eq
+// TODO: Remove once `Eq` trait is allowed in `const` context.
+#[must_use]
+pub const fn str_eq(l: &str, r: &str) -> bool {
+    let (l, r) = (l.as_bytes(), r.as_bytes());
+
+    if l.len() != r.len() {
+        return false;
+    }
+
+    let mut i = 0;
+    while i < l.len() {
+        if l[i] != r[i] {
+            return false;
+        }
+        i += 1;
+    }
+
+    true
+}
+
+mod macros {
+    /// Asserts that `#[graphql::interface(for = ...)]` attribute has all the
+    /// types referencing this interface in the `impl = ...` attribute argument.
+    ///
+    /// Symmetrical to [`assert_interfaces_impls!`].
+    #[macro_export]
+    macro_rules! reflect_assert_implemented_for {
+        ($behavior: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => {
+            const _: () = { $({
+                let is_present = $crate::reflect::str_exists_in_arr(
+                    <$implementor as $crate::reflect::BaseType<$behavior>>::NAME,
+                    <$interfaces as $crate::reflect::BaseSubTypes<$behavior>>::NAMES,
+                );
+                if !is_present {
+                    const MSG: &str = $crate::reflect::const_concat!(
+                        "Failed to implement interface `",
+                        <$interfaces as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "` on `",
+                        <$implementor as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "`: missing implementer reference in interface's `for` attribute.",
+                    );
+                    ::std::panic!("{}", MSG);
+                }
+            })* };
+        };
+    }
+
+    /// Asserts that `impl = ...` attribute argument has all the interfaces
+    /// referencing this type in `#[graphql::interface(for = ...)]` attribute.
+    ///
+    /// Symmetrical to [`assert_implemented_for!`].
+    #[macro_export]
+    macro_rules! reflect_assert_interfaces_impls {
+        ($behavior: ty, $interface: ty $(, $implementers: ty)* $(,)?) => {
+            const _: () = { $({
+                let is_present = $crate::reflect::str_exists_in_arr(
+                    <$interface as $crate::reflect::BaseType<$behavior>>::NAME,
+                    <$implementers as $crate::reflect::Implements<$behavior>>::NAMES,
+                );
+                if !is_present {
+                    const MSG: &str = $crate::reflect::const_concat!(
+                        "Failed to implement interface `",
+                        <$interface as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "` on `",
+                        <$implementers as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "`: missing interface reference in implementer's `impl` attribute.",
+                    );
+                    ::std::panic!("{}", MSG);
+                }
+            })* };
+        };
+    }
+
+    /// Asserts that all [transitive interfaces][0] (the ones implemented by the
+    /// `$interface`) are also implemented by the `$implementor`.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P
+    #[macro_export]
+    macro_rules! reflect_assert_transitive_impls {
+        ($behavior: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => {
+            const _: () = { $({
+                let is_present = $crate::reflect::str_exists_in_arr(
+                    <$implementor as $crate::reflect::BaseType<$behavior>>::NAME,
+                    <$transitive as $crate::reflect::BaseSubTypes<$behavior>>::NAMES,
+                );
+                if !is_present {
+                    const MSG: &str = $crate::reflect::const_concat!(
+                        "Failed to implement interface `",
+                        <$interface as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "` on `",
+                        <$implementor as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "`: missing `impl = ` for transitive interface `",
+                        <$transitive as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "` on `",
+                        <$implementor as $crate::reflect::BaseType<$behavior>>::NAME,
+                        "`.",
+                    );
+                    ::std::panic!("{}", MSG);
+                }
+            })* };
+        };
+    }
+
+    /// Asserts validness of [`Field`] [`Arguments`] and its returned [`Type`].
+    ///
+    /// This assertion is a combination of [`assert_field_type!`] and
+    /// [`assert_field_args!`].
+    ///
+    /// See [spec][0] for assertion algorithm details.
+    ///
+    /// [`Arguments`]: super::Arguments
+    /// [`Field`]: super::Field
+    /// [`Type`]: super::Type
+    /// [0]: https://spec.graphql.org/October2021#IsValidImplementation()
+    #[macro_export]
+    macro_rules! reflect_assert_field {
+        (
+            $base_ty: ty,
+            $impl_ty: ty,
+            $behavior: ty,
+            $field_name: expr $(,)?
+        ) => {
+            $crate::reflect::assert_field_type!($base_ty, $impl_ty, $behavior, $field_name);
+            $crate::reflect::assert_field_args!($base_ty, $impl_ty, $behavior, $field_name);
+        };
+    }
+
+    /// Asserts validness of a [`Field`] type.
+    ///
+    /// See [spec][0] for assertion algorithm details.
+    ///
+    /// [`Field`]: super::Field
+    /// [0]: https://spec.graphql.org/October2021#IsValidImplementationFieldType()
+    #[macro_export]
+    macro_rules! reflect_assert_field_type {
+        (
+            $base_ty: ty,
+            $impl_ty: ty,
+            $behavior: ty,
+            $field_name: expr $(,)?
+        ) => {
+            const _: () = {
+                const BASE_TY: $crate::reflect::Type =
+                    <$base_ty as $crate::reflect::BaseType<$behavior>>::NAME;
+                const IMPL_TY: $crate::reflect::Type =
+                    <$impl_ty as $crate::reflect::BaseType<$behavior>>::NAME;
+                const ERR_PREFIX: &str = $crate::reflect::const_concat!(
+                    "Failed to implement interface `",
+                    BASE_TY,
+                    "` on `",
+                    IMPL_TY,
+                    "`: ",
+                );
+
+                const FIELD_NAME: $crate::reflect::Name = $field_name;
+                const FIELD_NAME_HASH: $crate::reflect::FieldName =
+                    $crate::reflect::fnv1a128(FIELD_NAME);
+
+                $crate::reflect::assert_has_field!(
+                    FIELD_NAME, $base_ty, $behavior, ERR_PREFIX,
+                );
+                $crate::reflect::assert_has_field!(
+                    FIELD_NAME, $impl_ty, $behavior, ERR_PREFIX,
+                );
+
+                const BASE_RETURN_WRAPPED_VAL: $crate::reflect::WrappedValue =
+                    <$base_ty as $crate::reflect::Field<
+                        FIELD_NAME_HASH, $behavior,
+                    >>::WRAPPED_VALUE;
+                const IMPL_RETURN_WRAPPED_VAL: $crate::reflect::WrappedValue =
+                    <$impl_ty as $crate::reflect::Field<
+                        FIELD_NAME_HASH, $behavior,
+                    >>::WRAPPED_VALUE;
+
+                const BASE_RETURN_TY: $crate::reflect::Type =
+                    <$base_ty as $crate::reflect::Field<
+                        FIELD_NAME_HASH, $behavior,
+                    >>::TYPE;
+                const IMPL_RETURN_TY: $crate::reflect::Type =
+                    <$impl_ty as $crate::reflect::Field<
+                        FIELD_NAME_HASH, $behavior,
+                    >>::TYPE;
+
+                const BASE_RETURN_SUB_TYPES: $crate::reflect::Types =
+                    <$base_ty as $crate::reflect::Field<
+                        FIELD_NAME_HASH, $behavior,
+                    >>::SUB_TYPES;
+
+                let is_subtype = $crate::reflect::str_exists_in_arr(
+                    IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES,
+                ) && $crate::reflect::can_be_subtype(
+                    BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL,
+                );
+                if !is_subtype {
+                    const MSG: &str = $crate::reflect::const_concat!(
+                        ERR_PREFIX,
+                        "Field `",
+                        FIELD_NAME,
+                        "`: implementor is expected to return a subtype of interface's return object: `",
+                        $crate::reflect::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL),
+                        "` is not a subtype of `",
+                        $crate::reflect::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL),
+                        "`.",
+                    );
+                    ::std::panic!("{}", MSG);
+                }
+            };
+        };
+    }
+
+    /// Asserts validness of a [`Field`] arguments.
+    ///
+    /// See [spec][0] for assertion algorithm details.
+    ///
+    /// [`Field`]: super::Field
+    /// [0]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob
+    #[macro_export]
+    macro_rules! reflect_assert_field_args {
+        (
+            $base_ty: ty,
+            $impl_ty: ty,
+            $behavior: ty,
+            $field_name: expr $(,)?
+        ) => {
+            const _: () = {
+                const BASE_TY: $crate::reflect::Type =
+                    <$base_ty as $crate::reflect::BaseType<$behavior>>::NAME;
+                const IMPL_TY: $crate::reflect::Type =
+                    <$impl_ty as $crate::reflect::BaseType<$behavior>>::NAME;
+                const ERR_PREFIX: &str = $crate::reflect::const_concat!(
+                    "Failed to implement interface `",
+                    BASE_TY,
+                    "` on `",
+                    IMPL_TY,
+                    "`: ",
+                );
+
+                const FIELD_NAME: $crate::reflect::Name = $field_name;
+                const FIELD_NAME_HASH: $crate::reflect::FieldName =
+                    $crate::reflect::fnv1a128(FIELD_NAME);
+
+                $crate::reflect::assert_has_field!(FIELD_NAME, $base_ty, $behavior, ERR_PREFIX);
+                $crate::reflect::assert_has_field!(FIELD_NAME, $impl_ty, $behavior, ERR_PREFIX);
+
+                const BASE_ARGS: ::juniper::reflect::Arguments =
+                    <$base_ty as $crate::reflect::Field<FIELD_NAME_HASH, $behavior>>::ARGUMENTS;
+                const IMPL_ARGS: ::juniper::reflect::Arguments =
+                    <$impl_ty as $crate::reflect::Field<FIELD_NAME_HASH, $behavior>>::ARGUMENTS;
+
+                struct Error {
+                    cause: Cause,
+                    base: ::juniper::reflect::Argument,
+                    implementation: ::juniper::reflect::Argument,
+                }
+
+                enum Cause {
+                    RequiredField,
+                    AdditionalNonNullableField,
+                    TypeMismatch,
+                }
+
+                const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error {
+                    match v {
+                        // Unfortunately, we cannot use `unreachable!()` here,
+                        // as this branch will be executed either way.
+                        Ok(()) => Error {
+                            cause: Cause::RequiredField,
+                            base: ("unreachable", "unreachable", 1),
+                            implementation: ("unreachable", "unreachable", 1),
+                        },
+                        Err(e) => e,
+                    }
+                }
+
+                const fn check() -> Result<(), Error> {
+                    let mut base_i = 0;
+                    while base_i < BASE_ARGS.len() {
+                        let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i];
+
+                        let mut impl_i = 0;
+                        let mut was_found = false;
+                        while impl_i < IMPL_ARGS.len() {
+                            let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i];
+
+                            if $crate::reflect::str_eq(base_name, impl_name) {
+                                if $crate::reflect::str_eq(base_type, impl_type)
+                                    && base_wrap_val == impl_wrap_val
+                                {
+                                    was_found = true;
+                                    break;
+                                } else {
+                                    return Err(Error {
+                                        cause: Cause::TypeMismatch,
+                                        base: (base_name, base_type, base_wrap_val),
+                                        implementation: (impl_name, impl_type, impl_wrap_val),
+                                    });
+                                }
+                            }
+
+                            impl_i += 1;
+                        }
+
+                        if !was_found {
+                            return Err(Error {
+                                cause: Cause::RequiredField,
+                                base: (base_name, base_type, base_wrap_val),
+                                implementation: (base_name, base_type, base_wrap_val),
+                            });
+                        }
+
+                        base_i += 1;
+                    }
+
+                    let mut impl_i = 0;
+                    while impl_i < IMPL_ARGS.len() {
+                        let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i];
+                        impl_i += 1;
+
+                        if impl_wrapped_val % 10 == 2 {
+                            continue;
+                        }
+
+                        let mut base_i = 0;
+                        let mut was_found = false;
+                        while base_i < BASE_ARGS.len() {
+                            let (base_name, _, _) = BASE_ARGS[base_i];
+                            if $crate::reflect::str_eq(base_name, impl_name) {
+                                was_found = true;
+                                break;
+                            }
+                            base_i += 1;
+                        }
+                        if !was_found {
+                            return Err(Error {
+                                cause: Cause::AdditionalNonNullableField,
+                                base: (impl_name, impl_type, impl_wrapped_val),
+                                implementation: (impl_name, impl_type, impl_wrapped_val),
+                            });
+                        }
+                    }
+
+                    Ok(())
+                }
+
+                const RES: ::std::result::Result<(), Error> = check();
+                if RES.is_err() {
+                    const ERROR: Error = unwrap_error(RES);
+
+                    const BASE_ARG_NAME: $crate::reflect::Name = ERROR.base.0;
+                    const IMPL_ARG_NAME: $crate::reflect::Name = ERROR.implementation.0;
+
+                    const BASE_TYPE_FORMATTED: &str =
+                        $crate::reflect::format_type!(ERROR.base.1, ERROR.base.2,);
+                    const IMPL_TYPE_FORMATTED: &str = $crate::reflect::format_type!(
+                        ERROR.implementation.1,
+                        ERROR.implementation.2,
+                    );
+
+                    const MSG: &str = match ERROR.cause {
+                        Cause::TypeMismatch => {
+                            $crate::reflect::const_concat!(
+                                "Argument `",
+                                BASE_ARG_NAME,
+                                "`: expected type `",
+                                BASE_TYPE_FORMATTED,
+                                "`, found: `",
+                                IMPL_TYPE_FORMATTED,
+                                "`.",
+                            )
+                        }
+                        Cause::RequiredField => {
+                            $crate::reflect::const_concat!(
+                                "Argument `",
+                                BASE_ARG_NAME,
+                                "` of type `",
+                                BASE_TYPE_FORMATTED,
+                                "` was expected, but not found.",
+                            )
+                        }
+                        Cause::AdditionalNonNullableField => {
+                            $crate::reflect::const_concat!(
+                                "Argument `",
+                                IMPL_ARG_NAME,
+                                "` of type `",
+                                IMPL_TYPE_FORMATTED,
+                                "` isn't present on the interface and so has to be nullable.",
+                            )
+                        }
+                    };
+                    const ERROR_MSG: &str = $crate::reflect::const_concat!(
+                        ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG,
+                    );
+                    ::std::panic!("{}", ERROR_MSG);
+                }
+            };
+        };
+    }
+
+    /// Ensures that the given `$impl_ty` has the specified [`Field`].
+    ///
+    /// [`Field`]: super::Field
+    /// [`fnv1a128`]: super::fnv1a128
+    #[macro_export]
+    macro_rules! reflect_assert_has_field {
+        (
+            $field_name: expr,
+            $impl_ty: ty,
+            $behavior: ty
+            $(, $prefix: expr)? $(,)?
+        ) => {{
+            let exists = $crate::reflect::str_exists_in_arr(
+                $field_name,
+                <$impl_ty as $crate::reflect::Fields<$behavior>>::NAMES,
+            );
+            if !exists {
+                const MSG: &str = $crate::reflect::const_concat!(
+                    $($prefix,)?
+                    "Field `",
+                    $field_name,
+                    "` isn't implemented on `",
+                    <$impl_ty as $crate::reflect::BaseType<$behavior>>::NAME,
+                    "`."
+                );
+                ::std::panic!("{}", MSG);
+            }
+        }};
+    }
+
+    /// Concatenates `const` [`str`](prim@str)s in a `const` context.
+    #[macro_export]
+    macro_rules! reflect_const_concat {
+        ($($s:expr),* $(,)?) => {{
+            const LEN: usize = 0 $(+ $s.as_bytes().len())*;
+            const CNT: usize = [$($s),*].len();
+            const fn concat(input: [&str; CNT]) -> [u8; LEN] {
+                let mut bytes = [0; LEN];
+                let (mut i, mut byte) = (0, 0);
+                while i < CNT {
+                    let mut b = 0;
+                    while b < input[i].len() {
+                        bytes[byte] = input[i].as_bytes()[b];
+                        byte += 1;
+                        b += 1;
+                    }
+                    i += 1;
+                }
+                bytes
+            }
+            const CON: [u8; LEN] = concat([$($s),*]);
+
+            // TODO: Use `.unwrap()` once it becomes `const`.
+            match ::std::str::from_utf8(&CON) {
+                ::std::result::Result::Ok(s) => s,
+                _ => unreachable!(),
+            }
+        }};
+    }
+
+    /// Formats the given [`Type`] and [`WrappedValue`] into a human-readable
+    /// GraphQL type name string.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// # use juniper::reflect::format_type;
+    /// #
+    /// assert_eq!(format_type!("String", 123), "[String]!");
+    /// assert_eq!(format_type!("🦀", 123), "[🦀]!");
+    /// ```
+    ///
+    /// [`Type`]: super::Type
+    /// [`WrappedValue`]: super::WrappedValue
+    #[macro_export]
+    macro_rules! reflect_format_type {
+        ($ty: expr, $wrapped_value: expr $(,)?) => {{
+            const TYPE: ($crate::reflect::Type, $crate::reflect::WrappedValue) =
+                ($ty, $wrapped_value);
+            const RES_LEN: usize = $crate::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1);
+
+            const OPENING_BRACKET: &str = "[";
+            const CLOSING_BRACKET: &str = "]";
+            const BANG: &str = "!";
+
+            const fn format_type_arr() -> [u8; RES_LEN] {
+                let (ty, wrap_val) = TYPE;
+                let mut type_arr: [u8; RES_LEN] = [0; RES_LEN];
+
+                let mut current_start = 0;
+                let mut current_end = RES_LEN - 1;
+                let mut current_wrap_val = wrap_val;
+                let mut is_null = false;
+                while current_wrap_val % 10 != 0 {
+                    match current_wrap_val % 10 {
+                        2 => is_null = true, // Skips writing `BANG` later.
+                        3 => {
+                            // Write `OPENING_BRACKET` at `current_start`.
+                            let mut i = 0;
+                            while i < OPENING_BRACKET.as_bytes().len() {
+                                type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i];
+                                i += 1;
+                            }
+                            current_start += i;
+                            if !is_null {
+                                // Write `BANG` at `current_end`.
+                                i = 0;
+                                while i < BANG.as_bytes().len() {
+                                    type_arr[current_end - BANG.as_bytes().len() + i + 1] =
+                                        BANG.as_bytes()[i];
+                                    i += 1;
+                                }
+                                current_end -= i;
+                            }
+                            // Write `CLOSING_BRACKET` at `current_end`.
+                            i = 0;
+                            while i < CLOSING_BRACKET.as_bytes().len() {
+                                type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] =
+                                    CLOSING_BRACKET.as_bytes()[i];
+                                i += 1;
+                            }
+                            current_end -= i;
+                            is_null = false;
+                        }
+                        _ => {}
+                    }
+
+                    current_wrap_val /= 10;
+                }
+
+                // Writes `Type` at `current_start`.
+                let mut i = 0;
+                while i < ty.as_bytes().len() {
+                    type_arr[current_start + i] = ty.as_bytes()[i];
+                    i += 1;
+                }
+                i = 0;
+                if !is_null {
+                    // Writes `BANG` at `current_end`.
+                    while i < BANG.as_bytes().len() {
+                        type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i];
+                        i += 1;
+                    }
+                }
+
+                type_arr
+            }
+
+            const TYPE_ARR: [u8; RES_LEN] = format_type_arr();
+
+            // TODO: Use `.unwrap()` once it becomes `const`.
+            const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) {
+                ::std::result::Result::Ok(s) => s,
+                _ => unreachable!(),
+            };
+            TYPE_FORMATTED
+        }};
+    }
+
+    #[doc(inline)]
+    pub use {
+        reflect_assert_field as assert_field, reflect_assert_field_args as assert_field_args,
+        reflect_assert_field_type as assert_field_type,
+        reflect_assert_has_field as assert_has_field,
+        reflect_assert_implemented_for as assert_implemented_for,
+        reflect_assert_interfaces_impls as assert_interfaces_impls,
+        reflect_assert_transitive_impls as assert_transitive_impls,
+        reflect_const_concat as const_concat, reflect_format_type as format_type,
+    };
+}
diff --git a/juniper/src/resolve/mod.rs b/juniper/src/resolve/mod.rs
new file mode 100644
index 000000000..3c782e92a
--- /dev/null
+++ b/juniper/src/resolve/mod.rs
@@ -0,0 +1,211 @@
+use crate::{
+    behavior, graphql,
+    meta::MetaType,
+    parser::{self, ParseError},
+    reflect, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, IntoFieldError,
+    Registry, Selection,
+};
+
+pub trait Type<TypeInfo: ?Sized, ScalarValue, Behavior: ?Sized = behavior::Standard> {
+    fn meta<'r, 'ti: 'r>(
+        registry: &mut Registry<'r, ScalarValue>,
+        type_info: &'ti TypeInfo,
+    ) -> MetaType<'r, ScalarValue>
+    where
+        ScalarValue: 'r; // TODO: remove?
+}
+
+pub trait TypeName<TypeInfo: ?Sized, Behavior: ?Sized = behavior::Standard> {
+    fn type_name(type_info: &TypeInfo) -> &str;
+}
+
+pub trait ConcreteTypeName<TypeInfo: ?Sized, Behavior: ?Sized = behavior::Standard> {
+    fn concrete_type_name<'i>(&self, type_info: &'i TypeInfo) -> &'i str;
+}
+
+pub trait Value<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, ScalarValue>]>,
+        type_info: &TypeInfo,
+        executor: &Executor<Context, ScalarValue>,
+    ) -> ExecutionResult<ScalarValue>;
+}
+
+pub trait ValueAsync<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, ScalarValue>]>,
+        type_info: &'r TypeInfo,
+        executor: &'r Executor<Context, ScalarValue>,
+    ) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
+}
+
+pub trait Resolvable<ScalarValue, Behavior: ?Sized = behavior::Standard> {
+    type Value;
+
+    fn into_value(self) -> FieldResult<Self::Value, ScalarValue>;
+}
+
+pub trait ConcreteValue<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, ScalarValue>]>,
+        type_info: &TypeInfo,
+        executor: &Executor<Context, ScalarValue>,
+    ) -> ExecutionResult<ScalarValue>;
+}
+
+pub trait ConcreteValueAsync<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, ScalarValue>]>,
+        type_info: &'r TypeInfo,
+        executor: &'r Executor<Context, ScalarValue>,
+    ) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
+}
+
+pub trait Field<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<ScalarValue>,
+        type_info: &TypeInfo,
+        executor: &Executor<Context, ScalarValue>,
+    ) -> ExecutionResult<ScalarValue>;
+}
+
+pub trait StaticField<
+    const N: reflect::FieldName,
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_static_field(
+        &self,
+        arguments: &Arguments<'_, ScalarValue>,
+        type_info: &TypeInfo,
+        executor: &Executor<'_, '_, Context, ScalarValue>,
+    ) -> ExecutionResult<ScalarValue>;
+}
+
+pub trait FieldAsync<
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<ScalarValue>,
+        type_info: &'r TypeInfo,
+        executor: &'r Executor<Context, ScalarValue>,
+    ) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
+}
+
+pub trait StaticFieldAsync<
+    const N: reflect::FieldName,
+    TypeInfo: ?Sized,
+    Context: ?Sized,
+    ScalarValue,
+    Behavior: ?Sized = behavior::Standard,
+>
+{
+    fn resolve_static_field_async<'r>(
+        &'r self,
+        arguments: &'r Arguments<ScalarValue>,
+        type_info: &'r TypeInfo,
+        executor: &'r Executor<Context, ScalarValue>,
+    ) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
+}
+
+pub trait ToInputValue<ScalarValue, Behavior: ?Sized = behavior::Standard> {
+    fn to_input_value(&self) -> graphql::InputValue<ScalarValue>;
+}
+
+pub trait InputValue<'input, ScalarValue: 'input, Behavior: ?Sized = behavior::Standard>:
+    Sized
+{
+    type Error: IntoFieldError<ScalarValue>;
+
+    fn try_from_input_value(
+        v: &'input graphql::InputValue<ScalarValue>,
+    ) -> Result<Self, Self::Error>;
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        Self::try_from_input_value(&graphql::InputValue::Null)
+    }
+}
+
+pub trait InputValueOwned<ScalarValue, Behavior: ?Sized = behavior::Standard>:
+    for<'i> InputValue<'i, ScalarValue, Behavior>
+{
+}
+
+impl<T, SV, BH: ?Sized> InputValueOwned<SV, BH> for T where T: for<'i> InputValue<'i, SV, BH> {}
+
+pub trait InputValueAs<'input, Wrapper, ScalarValue: 'input, Behavior: ?Sized = behavior::Standard>
+{
+    type Error: IntoFieldError<ScalarValue>;
+
+    fn try_from_input_value(
+        v: &'input graphql::InputValue<ScalarValue>,
+    ) -> Result<Wrapper, Self::Error>;
+
+    fn try_from_implicit_null() -> Result<Wrapper, Self::Error> {
+        Self::try_from_input_value(&graphql::InputValue::Null)
+    }
+}
+
+pub trait InputValueAsRef<ScalarValue, Behavior: ?Sized = behavior::Standard> {
+    type Error: IntoFieldError<ScalarValue>;
+
+    fn try_from_input_value(v: &graphql::InputValue<ScalarValue>) -> Result<&Self, Self::Error>;
+
+    fn try_from_implicit_null<'a>() -> Result<&'a Self, Self::Error>
+    where
+        ScalarValue: 'a,
+    {
+        Self::try_from_input_value(&graphql::InputValue::Null)
+    }
+}
+
+pub trait ScalarToken<ScalarValue, Behavior: ?Sized = behavior::Standard> {
+    fn parse_scalar_token(token: parser::ScalarToken<'_>) -> Result<ScalarValue, ParseError>;
+}
diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs
index 04f5eda96..870e23321 100644
--- a/juniper/src/schema/meta.rs
+++ b/juniper/src/schema/meta.rs
@@ -1,6 +1,5 @@
 //! Types used to describe a `GraphQL` schema
 
-use juniper::IntoFieldError;
 use std::{
     borrow::{Cow, ToOwned},
     fmt,
@@ -9,10 +8,11 @@ use std::{
 use crate::{
     ast::{FromInputValue, InputValue, Type},
     parser::{ParseError, ScalarToken},
+    resolve,
     schema::model::SchemaType,
     types::base::TypeKind,
     value::{DefaultScalarValue, ParseScalarValue},
-    FieldError,
+    FieldError, IntoFieldError,
 };
 
 /// Whether an item is deprecated, with context.
@@ -28,16 +28,16 @@ impl DeprecationStatus {
     /// If this deprecation status indicates the item is deprecated.
     pub fn is_deprecated(&self) -> bool {
         match self {
-            DeprecationStatus::Current => false,
-            DeprecationStatus::Deprecated(_) => true,
+            Self::Current => false,
+            Self::Deprecated(_) => true,
         }
     }
 
     /// An optional reason for the deprecation, or none if `Current`.
     pub fn reason(&self) -> Option<&str> {
         match self {
-            DeprecationStatus::Current => None,
-            DeprecationStatus::Deprecated(rsn) => rsn.as_deref(),
+            Self::Current => None,
+            Self::Deprecated(rsn) => rsn.as_deref(),
         }
     }
 }
@@ -54,6 +54,20 @@ pub struct ScalarMeta<'a, S> {
     pub(crate) parse_fn: ScalarTokenParseFn<S>,
 }
 
+// Manual implementation is required here to omit redundant `S: Clone` trait
+// bound, imposed by `#[derive(Clone)]`.
+impl<'a, S> Clone for ScalarMeta<'a, S> {
+    fn clone(&self) -> Self {
+        Self {
+            name: self.name.clone(),
+            description: self.description.clone(),
+            specified_by_url: self.specified_by_url.clone(),
+            try_parse_fn: self.try_parse_fn,
+            parse_fn: self.parse_fn,
+        }
+    }
+}
+
 /// Shortcut for an [`InputValue`] parsing function.
 pub type InputValueParseFn<S> = for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>;
 
@@ -61,7 +75,7 @@ pub type InputValueParseFn<S> = for<'b> fn(&'b InputValue<S>) -> Result<(), Fiel
 pub type ScalarTokenParseFn<S> = for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError>;
 
 /// List type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct ListMeta<'a> {
     #[doc(hidden)]
     pub of_type: Type<'a>,
@@ -71,14 +85,14 @@ pub struct ListMeta<'a> {
 }
 
 /// Nullable type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct NullableMeta<'a> {
     #[doc(hidden)]
     pub of_type: Type<'a>,
 }
 
 /// Object type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct ObjectMeta<'a, S> {
     #[doc(hidden)]
     pub name: Cow<'a, str>,
@@ -101,8 +115,21 @@ pub struct EnumMeta<'a, S> {
     pub(crate) try_parse_fn: InputValueParseFn<S>,
 }
 
+// Manual implementation is required here to omit redundant `S: Clone` trait
+// bound, imposed by `#[derive(Clone)]`.
+impl<'a, S> Clone for EnumMeta<'a, S> {
+    fn clone(&self) -> Self {
+        Self {
+            name: self.name.clone(),
+            description: self.description.clone(),
+            values: self.values.clone(),
+            try_parse_fn: self.try_parse_fn,
+        }
+    }
+}
+
 /// Interface type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct InterfaceMeta<'a, S> {
     #[doc(hidden)]
     pub name: Cow<'a, str>,
@@ -115,7 +142,7 @@ pub struct InterfaceMeta<'a, S> {
 }
 
 /// Union type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct UnionMeta<'a> {
     #[doc(hidden)]
     pub name: Cow<'a, str>,
@@ -126,6 +153,7 @@ pub struct UnionMeta<'a> {
 }
 
 /// Input object metadata
+#[derive(Clone)]
 pub struct InputObjectMeta<'a, S> {
     #[doc(hidden)]
     pub name: Cow<'a, str>,
@@ -140,14 +168,14 @@ pub struct InputObjectMeta<'a, S> {
 ///
 /// After a type's `meta` method has been called but before it has returned, a placeholder type
 /// is inserted into a registry to indicate existence.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct PlaceholderMeta<'a> {
     #[doc(hidden)]
     pub of_type: Type<'a>,
 }
 
 /// Generic type metadata
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub enum MetaType<'a, S = DefaultScalarValue> {
     #[doc(hidden)]
     Scalar(ScalarMeta<'a, S>),
@@ -170,7 +198,7 @@ pub enum MetaType<'a, S = DefaultScalarValue> {
 }
 
 /// Metadata for a field
-#[derive(Debug, Clone)]
+#[derive(Clone, Debug)]
 pub struct Field<'a, S> {
     #[doc(hidden)]
     pub name: smartstring::alias::String,
@@ -193,7 +221,7 @@ impl<'a, S> Field<'a, S> {
 }
 
 /// Metadata for an argument to a field
-#[derive(Debug, Clone)]
+#[derive(Clone, Debug)]
 pub struct Argument<'a, S> {
     #[doc(hidden)]
     pub name: String,
@@ -434,6 +462,29 @@ impl<'a, S> MetaType<'a, S> {
     }
 }
 
+impl<'a, S> From<MetaType<'a, S>> for Type<'a> {
+    fn from(meta: MetaType<'a, S>) -> Self {
+        match meta {
+            MetaType::Scalar(ScalarMeta { name, .. })
+            | MetaType::Object(ObjectMeta { name, .. })
+            | MetaType::Enum(EnumMeta { name, .. })
+            | MetaType::Interface(InterfaceMeta { name, .. })
+            | MetaType::Union(UnionMeta { name, .. })
+            | MetaType::InputObject(InputObjectMeta { name, .. }) => Type::NonNullNamed(name),
+            MetaType::List(ListMeta {
+                of_type,
+                expected_size,
+            }) => Type::NonNullList(Box::new(of_type), expected_size),
+            MetaType::Nullable(NullableMeta { of_type }) => match of_type {
+                Type::NonNullNamed(inner) => Type::Named(inner),
+                Type::NonNullList(inner, expected_size) => Type::List(inner, expected_size),
+                t => t,
+            },
+            MetaType::Placeholder(PlaceholderMeta { of_type }) => of_type,
+        }
+    }
+}
+
 impl<'a, S> ScalarMeta<'a, S> {
     /// Builds a new [`ScalarMeta`] type with the specified `name`.
     pub fn new<T>(name: Cow<'a, str>) -> Self
@@ -450,6 +501,35 @@ impl<'a, S> ScalarMeta<'a, S> {
         }
     }
 
+    /// Builds a new [`ScalarMeta`] information with the specified `name`.
+    pub fn new_reworked<T>(name: impl Into<Cow<'a, str>>) -> Self
+    where
+        T: resolve::InputValueOwned<S> + resolve::ScalarToken<S>,
+    {
+        Self {
+            name: name.into(),
+            description: None,
+            specified_by_url: None,
+            try_parse_fn: try_parse_fn_reworked::<T, S>,
+            parse_fn: <T as resolve::ScalarToken<S>>::parse_scalar_token,
+        }
+    }
+
+    /// Builds a new [`ScalarMeta`] information with the specified `name` for
+    /// the non-[`Sized`] `T`ype that may only be parsed as a reference.
+    pub fn new_unsized<T>(name: impl Into<Cow<'a, str>>) -> Self
+    where
+        T: resolve::InputValueAsRef<S> + resolve::ScalarToken<S> + ?Sized,
+    {
+        Self {
+            name: name.into(),
+            description: None,
+            specified_by_url: None,
+            try_parse_fn: try_parse_unsized_fn::<T, S>,
+            parse_fn: <T as resolve::ScalarToken<S>>::parse_scalar_token,
+        }
+    }
+
     /// Sets the `description` of this [`ScalarMeta`] type.
     ///
     /// Overwrites any previously set description.
@@ -477,7 +557,7 @@ impl<'a, S> ScalarMeta<'a, S> {
 }
 
 impl<'a> ListMeta<'a> {
-    /// Build a new [`ListMeta`] type by wrapping the specified [`Type`].
+    /// Builds a new [`ListMeta`] type by wrapping the specified [`Type`].
     ///
     /// Specifying `expected_size` will be used to ensure that values of this
     /// type will always match it.
@@ -495,7 +575,7 @@ impl<'a> ListMeta<'a> {
 }
 
 impl<'a> NullableMeta<'a> {
-    /// Build a new [`NullableMeta`] type by wrapping the specified [`Type`].
+    /// Builds a new [`NullableMeta`] type by wrapping the specified [`Type`].
     pub fn new(of_type: Type<'a>) -> Self {
         Self { of_type }
     }
@@ -563,6 +643,20 @@ impl<'a, S> EnumMeta<'a, S> {
         }
     }
 
+    /// Builds a new [`EnumMeta`] information with the specified `name` and
+    /// possible `values`.
+    pub fn new_reworked<T>(name: impl Into<Cow<'a, str>>, values: &[EnumValue]) -> Self
+    where
+        T: resolve::InputValueOwned<S>,
+    {
+        Self {
+            name: name.into(),
+            description: None,
+            values: values.to_owned(),
+            try_parse_fn: try_parse_fn_reworked::<T, S>,
+        }
+    }
+
     /// Sets the `description` of this [`EnumMeta`] type.
     ///
     /// Overwrites any previously set description.
@@ -663,6 +757,21 @@ impl<'a, S> InputObjectMeta<'a, S> {
         }
     }
 
+    /// Builds a new [`InputObjectMeta`] information with the specified `name`
+    /// and its `fields`.
+    pub fn new_reworked<T>(name: impl Into<Cow<'a, str>>, fields: &[Argument<'a, S>]) -> Self
+    where
+        T: resolve::InputValueOwned<S>,
+        S: Clone,
+    {
+        Self {
+            name: name.into(),
+            description: None,
+            input_fields: fields.to_vec(),
+            try_parse_fn: try_parse_fn_reworked::<T, S>,
+        }
+    }
+
     /// Set the `description` of this [`InputObjectMeta`] type.
     ///
     /// Overwrites any previously set description.
@@ -811,3 +920,21 @@ where
         .map(drop)
         .map_err(T::Error::into_field_error)
 }
+
+fn try_parse_fn_reworked<T, SV>(v: &InputValue<SV>) -> Result<(), FieldError<SV>>
+where
+    T: resolve::InputValueOwned<SV>,
+{
+    T::try_from_input_value(v)
+        .map(drop)
+        .map_err(T::Error::into_field_error)
+}
+
+fn try_parse_unsized_fn<T, SV>(v: &InputValue<SV>) -> Result<(), FieldError<SV>>
+where
+    T: resolve::InputValueAsRef<SV> + ?Sized,
+{
+    T::try_from_input_value(v)
+        .map(drop)
+        .map_err(T::Error::into_field_error)
+}
diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs
index 618227462..311bcab26 100644
--- a/juniper/src/schema/schema.rs
+++ b/juniper/src/schema/schema.rs
@@ -141,7 +141,7 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
         self.description.as_deref()
     }
 
-    fn types(&self) -> Vec<TypeType<S>> {
+    fn types(&self) -> Vec<TypeType<'_, S>> {
         self.type_list()
             .into_iter()
             .filter(|t| {
@@ -156,21 +156,21 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
     }
 
     #[graphql(name = "queryType")]
-    fn query_type_(&self) -> TypeType<S> {
+    fn query_type_(&self) -> TypeType<'_, S> {
         self.query_type()
     }
 
     #[graphql(name = "mutationType")]
-    fn mutation_type_(&self) -> Option<TypeType<S>> {
+    fn mutation_type_(&self) -> Option<TypeType<'_, S>> {
         self.mutation_type()
     }
 
     #[graphql(name = "subscriptionType")]
-    fn subscription_type_(&self) -> Option<TypeType<S>> {
+    fn subscription_type_(&self) -> Option<TypeType<'_, S>> {
         self.subscription_type()
     }
 
-    fn directives(&self) -> Vec<&DirectiveType<S>> {
+    fn directives(&self) -> Vec<&DirectiveType<'_, S>> {
         self.directive_list()
     }
 }
@@ -214,7 +214,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
     fn fields(
         &self,
         #[graphql(default = false)] include_deprecated: Option<bool>,
-    ) -> Option<Vec<&Field<S>>> {
+    ) -> Option<Vec<&Field<'_, S>>> {
         match self {
             TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
             | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some(
@@ -231,14 +231,14 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
         }
     }
 
-    fn of_type(&self) -> Option<&TypeType<S>> {
+    fn of_type(&self) -> Option<&TypeType<'_, S>> {
         match self {
             TypeType::Concrete(_) => None,
             TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l),
         }
     }
 
-    fn input_fields(&self) -> Option<&[Argument<S>]> {
+    fn input_fields(&self) -> Option<&[Argument<'_, S>]> {
         match self {
             TypeType::Concrete(&MetaType::InputObject(InputObjectMeta {
                 ref input_fields,
@@ -343,7 +343,7 @@ impl<'a, S: ScalarValue + 'a> Field<'a, S> {
         self.description.as_deref()
     }
 
-    fn args(&self) -> Vec<&Argument<S>> {
+    fn args(&self) -> Vec<&Argument<'_, S>> {
         self.arguments
             .as_ref()
             .map_or_else(Vec::new, |v| v.iter().collect())
@@ -434,7 +434,7 @@ impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> {
         self.is_repeatable
     }
 
-    fn args(&self) -> &[Argument<S>] {
+    fn args(&self) -> &[Argument<'_, S>] {
         &self.arguments
     }
 
diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs
index f4f064914..a887907ce 100644
--- a/juniper/src/tests/introspection_tests.rs
+++ b/juniper/src/tests/introspection_tests.rs
@@ -1,7 +1,7 @@
 use std::collections::HashSet;
 
 use crate::{
-    graphql_vars,
+    graphql_value, graphql_vars,
     introspection::IntrospectionFormat,
     schema::model::RootNode,
     tests::fixtures::starwars::schema::{Database, Query},
diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs
index d2e4643e5..06a79325b 100644
--- a/juniper/src/tests/schema_introspection.rs
+++ b/juniper/src/tests/schema_introspection.rs
@@ -1,4 +1,4 @@
-use crate::value::Value;
+use crate::graphql::{self, Value};
 
 // Sort a nested schema Value.
 // In particular, lists are sorted by the "name" key of children, if present.
@@ -34,7 +34,7 @@ pub(super) fn sort_schema_value(value: &mut Value) {
 }
 
 pub(crate) fn schema_introspection_result() -> Value {
-    let mut v = graphql_value!({
+    let mut v = graphql::value!({
         "__schema": {
           "description": null,
           "queryType": {
@@ -1451,7 +1451,7 @@ pub(crate) fn schema_introspection_result() -> Value {
 }
 
 pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
-    let mut v = graphql_value!({
+    let mut v = graphql::value!({
         "__schema": {
           "queryType": {
             "name": "Query"
diff --git a/juniper/src/types/arc.rs b/juniper/src/types/arc.rs
new file mode 100644
index 000000000..2d3a75000
--- /dev/null
+++ b/juniper/src/types/arc.rs
@@ -0,0 +1,305 @@
+//! GraphQL implementation for [`Arc`].
+
+use std::sync::Arc;
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Arc<T>
+where
+    T: resolve::Type<TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::TypeName<TI, BH> for Arc<T>
+where
+    T: resolve::TypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::ConcreteTypeName<TI, BH> for Arc<T>
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Arc<T>
+where
+    T: ?Sized,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::Field<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for Arc<T>
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Arc<T>
+where
+    T: resolve::ToInputValue<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Arc<T>
+where
+    T: resolve::InputValueAs<'i, Self, SV, BH> + ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Arc<Self>, SV, BH> for T
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Arc<Self>, Self::Error> {
+        T::try_from_input_value(v).map(Arc::new)
+    }
+
+    fn try_from_implicit_null() -> Result<Arc<Self>, Self::Error> {
+        T::try_from_implicit_null().map(Arc::new)
+    }
+}
+
+impl<T, SV, BH> resolve::ScalarToken<SV, BH> for Arc<T>
+where
+    T: resolve::ScalarToken<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Arc<T>
+where
+    T: graphql::InputTypeAs<'i, Self, TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Arc<T>, TI, SV, BH> for T
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Arc<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::Scalar<'i, TI, CX, SV, BH> for Arc<T>
+where
+    T: graphql::ScalarAs<'i, Self, TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::ScalarAs<'i, Arc<T>, TI, CX, SV, BH> for T
+where
+    T: graphql::Scalar<'i, TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Arc<T>
+where
+    T: reflect::BaseType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Arc<T>
+where
+    T: reflect::BaseSubTypes<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Arc<T>
+where
+    T: reflect::WrappedType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/array.rs b/juniper/src/types/array.rs
new file mode 100644
index 000000000..ad1812513
--- /dev/null
+++ b/juniper/src/types/array.rs
@@ -0,0 +1,448 @@
+//! GraphQL implementation for [array].
+//!
+//! [array]: prim@array
+
+use std::{
+    mem::{self, MaybeUninit},
+    ptr,
+};
+
+use crate::{
+    behavior,
+    executor::{ExecutionResult, Executor, Registry},
+    graphql, reflect, resolve,
+    schema::meta::MetaType,
+    BoxFuture, FieldError, FieldResult, IntoFieldError, Selection,
+};
+
+use super::iter;
+
+impl<T, TI, SV, BH, const N: usize> resolve::Type<TI, SV, BH> for [T; N]
+where
+    T: resolve::Type<TI, SV, BH>,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.wrap_list::<behavior::Coerce<T, BH>, _>(type_info, None)
+    }
+}
+
+impl<T, TI, CX, SV, BH, const N: usize> resolve::Value<TI, CX, SV, BH> for [T; N]
+where
+    T: resolve::Value<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        iter::resolve_list(self.iter(), selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH, const N: usize> resolve::ValueAsync<TI, CX, SV, BH> for [T; N]
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + Sync,
+    TI: Sync + ?Sized,
+    CX: Sync + ?Sized,
+    SV: Send + Sync,
+    BH: ?Sized + 'static, // TODO: Lift `'static` bound if possible.
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        Box::pin(iter::resolve_list_async(
+            self.iter(),
+            selection_set,
+            type_info,
+            executor,
+        ))
+    }
+}
+
+impl<T, SV, BH, const N: usize> resolve::Resolvable<SV, BH> for [T; N]
+where
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, SV, BH, const N: usize> resolve::ToInputValue<SV, BH> for [T; N]
+where
+    T: resolve::ToInputValue<SV, BH>,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
+impl<'i, T, SV, BH, const N: usize> resolve::InputValue<'i, SV, BH> for [T; N]
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = TryFromInputValueError<T::Error>;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        struct PartiallyInitializedArray<T, const N: usize> {
+            arr: [MaybeUninit<T>; N],
+            init_len: usize,
+            no_drop: bool,
+        }
+
+        impl<T, const N: usize> Drop for PartiallyInitializedArray<T, N> {
+            fn drop(&mut self) {
+                if self.no_drop {
+                    return;
+                }
+                // Dropping a `MaybeUninit` does nothing, thus we need to drop
+                // the initialized elements manually, otherwise we may introduce
+                // a memory/resource leak if `T: Drop`.
+                for elem in &mut self.arr[0..self.init_len] {
+                    // SAFETY: This is safe, because `self.init_len` represents
+                    //         the number of the initialized elements exactly.
+                    unsafe {
+                        ptr::drop_in_place(elem.as_mut_ptr());
+                    }
+                }
+            }
+        }
+
+        match v {
+            graphql::InputValue::List(ls) => {
+                if ls.len() != N {
+                    return Err(TryFromInputValueError::WrongCount {
+                        actual: ls.len(),
+                        expected: N,
+                    });
+                }
+                if N == 0 {
+                    // TODO: Use `mem::transmute` instead of
+                    //       `mem::transmute_copy` below, once it's allowed
+                    //       for const generics:
+                    //       https://github.com/rust-lang/rust/issues/61956
+                    // SAFETY: `mem::transmute_copy` is safe here, because we
+                    //         check `N` to be `0`. It's no-op, actually.
+                    return Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) });
+                }
+
+                // SAFETY: The reason we're using a wrapper struct implementing
+                //         `Drop` here is to be panic safe:
+                //         `T: resolve::InputValue` implementation is not
+                //         controlled by us, so calling
+                //         `T::try_from_input_value(&i.item)` below may cause a
+                //         panic when our array is initialized only partially.
+                //         In such situation we need to drop already initialized
+                //         values to avoid possible memory/resource leaks if
+                //         `T: Drop`.
+                let mut out = PartiallyInitializedArray::<T, N> {
+                    // SAFETY: The `.assume_init()` here is safe, because the
+                    //         type we are claiming to have initialized here is
+                    //         a bunch of `MaybeUninit`s, which do not require
+                    //         any initialization.
+                    arr: unsafe { MaybeUninit::uninit().assume_init() },
+                    init_len: 0,
+                    no_drop: false,
+                };
+
+                let mut items = ls.iter().map(|i| T::try_from_input_value(&i.item));
+                for elem in &mut out.arr[..] {
+                    if let Some(i) = items
+                        .next()
+                        .transpose()
+                        .map_err(TryFromInputValueError::Item)?
+                    {
+                        *elem = MaybeUninit::new(i);
+                        out.init_len += 1;
+                    }
+                }
+
+                // Do not drop collected `items`, because we're going to return
+                // them.
+                out.no_drop = true;
+
+                // TODO: Use `mem::transmute` instead of `mem::transmute_copy`
+                //       below, once it's allowed for const generics:
+                //       https://github.com/rust-lang/rust/issues/61956
+                // SAFETY: `mem::transmute_copy` is safe here, because we have
+                //         exactly `N` initialized `items`.
+                //         Also, despite `mem::transmute_copy` copies the value,
+                //         we won't have a double-free when `T: Drop` here,
+                //         because original array elements are `MaybeUninit`, so
+                //         do nothing on `Drop`.
+                Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
+            }
+            // See "Input Coercion" on List types:
+            // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+            graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
+            other => T::try_from_input_value(other)
+                .map_err(TryFromInputValueError::Item)
+                .and_then(|e: T| {
+                    // TODO: Use `mem::transmute` instead of
+                    //       `mem::transmute_copy` below, once it's allowed
+                    //       for const generics:
+                    //       https://github.com/rust-lang/rust/issues/61956
+                    if N == 1 {
+                        // SAFETY: `mem::transmute_copy` is safe here, because
+                        //         we check `N` to be `1`. Also, despite
+                        //         `mem::transmute_copy` copies the value, we
+                        //         won't have a double-free when `T: Drop` here,
+                        //         because original `e: T` value is wrapped into
+                        //         `mem::ManuallyDrop`, so does nothing on
+                        //         `Drop`.
+                        Ok(unsafe { mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)]) })
+                    } else {
+                        Err(TryFromInputValueError::WrongCount {
+                            actual: 1,
+                            expected: N,
+                        })
+                    }
+                }),
+        }
+    }
+}
+
+impl<'i, T, TI, SV, BH, const N: usize> graphql::InputType<'i, TI, SV, BH> for [T; N]
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH, const N: usize> graphql::OutputType<TI, CX, SV, BH> for [T; N]
+where
+    T: graphql::OutputType<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: resolve::ValueAsync<TI, CX, SV, BH>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<T, BH, const N: usize> reflect::BaseType<BH> for [T; N]
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH, const N: usize> reflect::BaseSubTypes<BH> for [T; N]
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH, const N: usize> reflect::WrappedType<BH> for [T; N]
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
+}
+
+/// Possible errors of converting a [`graphql::InputValue`] into an exact-size
+/// [array](prim@array).
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum TryFromInputValueError<E> {
+    /// [`graphql::InputValue`] cannot be [`Null`].
+    ///
+    /// See ["Combining List and Non-Null" section of spec][0].
+    ///
+    /// [`Null`]: [`InputValue::Null`]
+    /// [0]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+    IsNull,
+
+    /// Wrong count of items.
+    WrongCount {
+        /// Actual count of items.
+        actual: usize,
+
+        /// Expected count of items ([array](prim@array) size).
+        expected: usize,
+    },
+
+    /// Error of converting a [`graphql::InputValue::List`]'s item.
+    Item(E),
+}
+
+impl<E, SV> IntoFieldError<SV> for TryFromInputValueError<E>
+where
+    E: IntoFieldError<SV>,
+{
+    fn into_field_error(self) -> FieldError<SV> {
+        const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
+        match self {
+            Self::IsNull => format!("{ERROR_PREFIX}: Value cannot be `null`").into(),
+            Self::WrongCount { actual, expected } => {
+                format!("{ERROR_PREFIX}: wrong elements count: {actual} instead of {expected}",)
+                    .into()
+            }
+            Self::Item(s) => s.into_field_error(),
+        }
+    }
+}
+
+// See "Input Coercion" examples on List types:
+// https://spec.graphql.org/October2021#sec-List.Input-Coercion
+#[cfg(test)]
+mod coercion {
+    use crate::{graphql, resolve::InputValue as _, IntoFieldError as _};
+
+    use super::TryFromInputValueError;
+
+    type V = graphql::InputValue;
+
+    #[test]
+    fn from_null() {
+        let v: V = graphql::input_value!(null);
+        assert_eq!(
+            <[i32; 0]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <[i32; 1]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <[Option<i32>; 0]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <[Option<i32>; 1]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(<Option<[i32; 0]>>::try_from_input_value(&v), Ok(None));
+        assert_eq!(<Option<[i32; 1]>>::try_from_input_value(&v), Ok(None));
+        assert_eq!(
+            <Option<[Option<i32>; 0]>>::try_from_input_value(&v),
+            Ok(None),
+        );
+        assert_eq!(
+            <Option<[Option<i32>; 1]>>::try_from_input_value(&v),
+            Ok(None),
+        );
+        assert_eq!(
+            <[[i32; 1]; 1]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <Option<[Option<[Option<i32>; 1]>; 1]>>::try_from_input_value(&v),
+            Ok(None),
+        );
+    }
+
+    #[test]
+    fn from_value() {
+        let v: V = graphql::input_value!(1);
+        assert_eq!(<[i32; 1]>::try_from_input_value(&v), Ok([1]));
+        assert_eq!(
+            <[i32; 0]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::WrongCount {
+                expected: 0,
+                actual: 1,
+            }),
+        );
+        assert_eq!(<[Option<i32>; 1]>::try_from_input_value(&v), Ok([Some(1)]));
+        assert_eq!(<Option<[i32; 1]>>::try_from_input_value(&v), Ok(Some([1])));
+        assert_eq!(
+            <Option<[Option<i32>; 1]>>::try_from_input_value(&v),
+            Ok(Some([Some(1)])),
+        );
+        assert_eq!(<[[i32; 1]; 1]>::try_from_input_value(&v), Ok([[1]]));
+        assert_eq!(
+            <Option<[Option<[Option<i32>; 1]>; 1]>>::try_from_input_value(&v),
+            Ok(Some([Some([Some(1)])])),
+        );
+    }
+
+    #[test]
+    fn from_list() {
+        let v: V = graphql::input_value!([1, 2, 3]);
+        assert_eq!(<[i32; 3]>::try_from_input_value(&v), Ok([1, 2, 3]));
+        assert_eq!(
+            <Option<[i32; 3]>>::try_from_input_value(&v),
+            Ok(Some([1, 2, 3])),
+        );
+        assert_eq!(
+            <[Option<i32>; 3]>::try_from_input_value(&v),
+            Ok([Some(1), Some(2), Some(3)]),
+        );
+        assert_eq!(
+            <Option<[Option<i32>; 3]>>::try_from_input_value(&v),
+            Ok(Some([Some(1), Some(2), Some(3)])),
+        );
+        assert_eq!(
+            <[[i32; 1]; 3]>::try_from_input_value(&v),
+            Ok([[1], [2], [3]]),
+        );
+        // Looks like the spec ambiguity.
+        // See: https://github.com/graphql/graphql-spec/pull/515
+        assert_eq!(
+            <Option<[Option<[Option<i32>; 1]>; 3]>>::try_from_input_value(&v),
+            Ok(Some([Some([Some(1)]), Some([Some(2)]), Some([Some(3)])])),
+        );
+    }
+
+    #[test]
+    fn from_list_with_null() {
+        let v: V = graphql::input_value!([1, 2, null]);
+        assert_eq!(
+            <[i32; 3]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(
+                "Expected `Int`, found: null".into_field_error(),
+            )),
+        );
+        assert_eq!(
+            <Option<[i32; 3]>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(
+                "Expected `Int`, found: null".into_field_error(),
+            )),
+        );
+        assert_eq!(
+            <[Option<i32>; 3]>::try_from_input_value(&v),
+            Ok([Some(1), Some(2), None]),
+        );
+        assert_eq!(
+            <Option<[Option<i32>; 3]>>::try_from_input_value(&v),
+            Ok(Some([Some(1), Some(2), None])),
+        );
+        assert_eq!(
+            <[[i32; 1]; 3]>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(TryFromInputValueError::IsNull)),
+        );
+        // Looks like the spec ambiguity.
+        // See: https://github.com/graphql/graphql-spec/pull/515
+        assert_eq!(
+            <Option<[Option<[Option<i32>; 1]>; 3]>>::try_from_input_value(&v),
+            Ok(Some([Some([Some(1)]), Some([Some(2)]), None])),
+        );
+    }
+}
diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs
index 9d54fee08..aab0c6df4 100644
--- a/juniper/src/types/base.rs
+++ b/juniper/src/types/base.rs
@@ -4,6 +4,7 @@ use crate::{
     ast::{Directive, FromInputValue, InputValue, Selection},
     executor::{ExecutionResult, Executor, Registry, Variables},
     parser::Spanning,
+    resolve,
     schema::meta::{Argument, MetaType},
     value::{DefaultScalarValue, Object, ScalarValue, Value},
     FieldResult, GraphQLEnum, IntoFieldError,
@@ -98,6 +99,8 @@ impl<'a, S> Arguments<'a, S> {
         Self { args }
     }
 
+    /// TODO: DEPRECATED!
+    ///
     /// Gets an argument by the given `name` and converts it into the desired
     /// type.
     ///
@@ -121,6 +124,38 @@ impl<'a, S> Arguments<'a, S> {
             .transpose()
             .map_err(IntoFieldError::into_field_error)
     }
+
+    /// Resolves an argument with the provided `name` as the specified type `T`.
+    ///
+    /// If [`None`] argument is found, then `T` is
+    /// [tried to be resolved from implicit `null`][0].
+    ///
+    /// # Errors
+    ///
+    /// If the [`resolve::InputValue`] conversion fails.
+    ///
+    /// [0]: resolve::InputValue::try_from_implicit_null
+    pub fn resolve<'s, T, BH>(&'s self, name: &str) -> FieldResult<T, S>
+    where
+        T: resolve::InputValue<'s, S, BH>,
+        BH: ?Sized,
+    {
+        self.args
+            .as_ref()
+            .and_then(|args| args.get(name))
+            .map(<T as resolve::InputValue<'s, S, BH>>::try_from_input_value)
+            .transpose()
+            .map_err(IntoFieldError::into_field_error)?
+            .map_or_else(
+                || {
+                    <T as resolve::InputValue<'s, S, BH>>::try_from_implicit_null().map_err(|e| {
+                        IntoFieldError::into_field_error(e)
+                            .map_message(|m| format!("Missing argument `{name}`: {m}"))
+                    })
+                },
+                Ok,
+            )
+    }
 }
 
 /// Primary trait used to resolve GraphQL values.
diff --git a/juniper/src/types/box.rs b/juniper/src/types/box.rs
new file mode 100644
index 000000000..c6f2fcb46
--- /dev/null
+++ b/juniper/src/types/box.rs
@@ -0,0 +1,303 @@
+//! GraphQL implementation for [`Box`].
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Box<T>
+where
+    T: resolve::Type<TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::TypeName<TI, BH> for Box<T>
+where
+    T: resolve::TypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::ConcreteTypeName<TI, BH> for Box<T>
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Box<T>
+where
+    T: ?Sized,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::Field<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for Box<T>
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Box<T>
+where
+    T: resolve::ToInputValue<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Box<T>
+where
+    T: resolve::InputValueAs<'i, Self, SV, BH> + ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Box<Self>, SV, BH> for T
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Box<Self>, Self::Error> {
+        T::try_from_input_value(v).map(Box::new)
+    }
+
+    fn try_from_implicit_null() -> Result<Box<Self>, Self::Error> {
+        T::try_from_implicit_null().map(Box::new)
+    }
+}
+
+impl<T, SV, BH> resolve::ScalarToken<SV, BH> for Box<T>
+where
+    T: resolve::ScalarToken<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Box<T>
+where
+    T: graphql::InputTypeAs<'i, Self, TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Box<T>, TI, SV, BH> for T
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Box<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::Scalar<'i, TI, CX, SV, BH> for Box<T>
+where
+    T: graphql::ScalarAs<'i, Self, TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::ScalarAs<'i, Box<T>, TI, CX, SV, BH> for T
+where
+    T: graphql::Scalar<'i, TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Box<T>
+where
+    T: reflect::BaseType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Box<T>
+where
+    T: reflect::BaseSubTypes<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Box<T>
+where
+    T: reflect::WrappedType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs
index 108f0e021..379cbae27 100644
--- a/juniper/src/types/containers.rs
+++ b/juniper/src/types/containers.rs
@@ -183,7 +183,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
@@ -283,7 +283,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
@@ -481,7 +481,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
diff --git a/juniper/src/types/cow.rs b/juniper/src/types/cow.rs
new file mode 100644
index 000000000..0701eb314
--- /dev/null
+++ b/juniper/src/types/cow.rs
@@ -0,0 +1,304 @@
+//! GraphQL implementation for [`Cow`].
+
+use std::{borrow::Cow, ops::Deref};
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<'me, T, TI, SV, BH> resolve::Type<TI, SV, BH> for Cow<'me, T>
+where
+    T: resolve::Type<TI, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::TypeName<TI, BH> for Cow<'me, T>
+where
+    T: resolve::TypeName<TI, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::ConcreteTypeName<TI, BH> for Cow<'me, T>
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::Value<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::Resolvable<SV, BH> for Cow<'me, T>
+where
+    T: ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::Field<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ToInputValue<SV, BH> for Cow<'me, T>
+where
+    T: resolve::ToInputValue<SV, BH> + ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'me, 'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Cow<'me, T>
+where
+    'i: 'me,
+    T: resolve::InputValueAs<'i, Self, SV, BH> + ToOwned + ?Sized + 'me,
+    SV: 'i,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'me, 'i, T, SV, BH> resolve::InputValueAs<'i, Cow<'me, Self>, SV, BH> for T
+where
+    'i: 'me,
+    T: resolve::InputValueAsRef<SV, BH> + ToOwned + 'me,
+    SV: 'i,
+    BH: ?Sized,
+    Cow<'me, Self>: Deref<Target = Self>,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Cow<'me, Self>, Self::Error> {
+        T::try_from_input_value(v).map(Cow::Borrowed)
+    }
+
+    fn try_from_implicit_null() -> Result<Cow<'me, Self>, Self::Error> {
+        T::try_from_implicit_null().map(Cow::Borrowed)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ScalarToken<SV, BH> for Cow<'me, T>
+where
+    T: resolve::ScalarToken<SV, BH> + ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'me, 'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Cow<'me, T>
+where
+    'i: 'me,
+    T: graphql::InputTypeAs<'i, Self, TI, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Cow<'me, T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'me, 'i, T, TI, CX, SV, BH> graphql::Scalar<'i, TI, CX, SV, BH> for Cow<'me, T>
+where
+    'i: 'me,
+    T: graphql::ScalarAs<'i, Self, TI, CX, SV, BH> + ToOwned + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+
+    Self: Deref<Target = T>,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<'me, T, BH> reflect::BaseType<BH> for Cow<'me, T>
+where
+    T: reflect::BaseType<BH> + ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<'me, T, BH> reflect::BaseSubTypes<BH> for Cow<'me, T>
+where
+    T: reflect::BaseSubTypes<BH> + ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<'me, T, BH> reflect::WrappedType<BH> for Cow<'me, T>
+where
+    T: reflect::WrappedType<BH> + ToOwned + ?Sized + 'me,
+    BH: ?Sized,
+    Self: Deref<Target = T>,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/iter.rs b/juniper/src/types/iter.rs
new file mode 100644
index 000000000..f0f599a04
--- /dev/null
+++ b/juniper/src/types/iter.rs
@@ -0,0 +1,72 @@
+//! GraphQL implementation for [`Iterator`].
+
+use crate::{graphql, resolve, ExecutionResult, Executor, Selection};
+
+pub fn resolve_list<'t, T, TI, CX, SV, BH, I>(
+    iter: I,
+    selection_set: Option<&[Selection<'_, SV>]>,
+    type_info: &TI,
+    executor: &Executor<CX, SV>,
+) -> ExecutionResult<SV>
+where
+    I: Iterator<Item = &'t T> + ExactSizeIterator,
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized + 't,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    let is_non_null = executor
+        .current_type_reworked()
+        .list_contents()
+        .ok_or("Iterating over non-list type")?
+        .is_non_null();
+
+    let mut values = Vec::with_capacity(iter.len());
+    for v in iter {
+        let val = v.resolve_value(selection_set, type_info, executor)?;
+        if is_non_null && val.is_null() {
+            return Err("Resolved `null` on non-null type".into());
+        }
+        values.push(val);
+    }
+    Ok(graphql::Value::list(values))
+}
+
+pub async fn resolve_list_async<'t, 'r, T, TI, CX, SV, BH, I>(
+    iter: I,
+    selection_set: Option<&[Selection<'_, SV>]>,
+    type_info: &'r TI,
+    executor: &'r Executor<'r, '_, CX, SV>,
+) -> ExecutionResult<SV>
+where
+    I: Iterator<Item = &'t T> + ExactSizeIterator,
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized + 't,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    use futures::stream::{FuturesOrdered, StreamExt as _};
+
+    let is_non_null = executor
+        .current_type_reworked()
+        .list_contents()
+        .ok_or("Iterating over non-list type")?
+        .is_non_null();
+
+    let mut futs = iter
+        .map(|v| async move {
+            v.resolve_value_async(selection_set, type_info, executor)
+                .await
+        })
+        .collect::<FuturesOrdered<_>>();
+
+    let mut values = Vec::with_capacity(futs.len());
+    while let Some(res) = futs.next().await {
+        let val = res?;
+        if is_non_null && val.is_null() {
+            return Err("Resolved `null` on non-null type".into());
+        }
+        values.push(val);
+    }
+    Ok(graphql::Value::list(values))
+}
diff --git a/juniper/src/types/mod.rs b/juniper/src/types/mod.rs
index 8ac005ba0..fd5e0abbf 100644
--- a/juniper/src/types/mod.rs
+++ b/juniper/src/types/mod.rs
@@ -1,10 +1,27 @@
+mod arc;
+pub mod array;
+mod r#box;
+mod cow;
+pub mod iter;
+mod nullable;
+mod option;
+mod rc;
+mod r#ref;
+mod ref_mut;
+mod result;
+mod slice;
+mod r#str;
+pub mod vec;
+
 pub mod async_await;
 pub mod base;
 pub mod containers;
 pub mod marker;
 pub mod name;
-pub mod nullable;
 pub mod pointers;
 pub mod scalars;
 pub mod subscriptions;
 pub mod utilities;
+
+#[doc(inline)]
+pub use self::nullable::Nullable;
diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs
index 20f16708b..e2e6524bb 100644
--- a/juniper/src/types/nullable.rs
+++ b/juniper/src/types/nullable.rs
@@ -1,42 +1,53 @@
+//! GraphQL implementation for [`Nullable`].
+
+use std::mem;
+
+use futures::future;
+
 use crate::{
-    ast::{FromInputValue, InputValue, Selection, ToInputValue},
+    ast::{FromInputValue, InputValue, ToInputValue},
+    behavior,
     executor::{ExecutionResult, Executor, Registry},
+    graphql, reflect, resolve,
     schema::meta::MetaType,
     types::{
         async_await::GraphQLValueAsync,
         base::{GraphQLType, GraphQLValue},
         marker::IsInputType,
     },
-    value::{ScalarValue, Value},
+    BoxFuture, FieldResult, ScalarValue, Selection,
 };
 
-/// `Nullable` can be used in situations where you need to distinguish between an implicitly and
-/// explicitly null input value.
+/// [`Nullable`] wrapper allowing to distinguish between an implicit and
+/// explicit `null` input value.
 ///
-/// The GraphQL spec states that these two field calls are similar, but are not identical:
+/// [GraphQL spec states][0] that these two field calls are similar, but are not
+/// identical:
 ///
-/// ```graphql
-/// {
-///   field(arg: null)
-///   field
-/// }
-/// ```
+/// > ```graphql
+/// > {
+/// >   field(arg: null)
+/// >   field
+/// > }
+/// > ```
+/// > The first has explicitly provided `null` to the argument "arg", while the
+/// > second has implicitly not provided a value to the argument "arg". These
+/// > two forms may be interpreted differently. For example, a mutation
+/// > representing deleting a field vs not altering a field, respectively.
 ///
-/// The first has explicitly provided null to the argument “arg”, while the second has implicitly
-/// not provided a value to the argument “arg”. These two forms may be interpreted differently. For
-/// example, a mutation representing deleting a field vs not altering a field, respectively.
+/// In cases where there is no need to distinguish between the two types of
+/// `null`, it's better to simply use [`Option`].
 ///
-/// In cases where you do not need to be able to distinguish between the two types of null, you
-/// should simply use `Option<T>`.
+/// [0]: https://spec.graphql.org/October2021#example-1c7eb
 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub enum Nullable<T> {
-    /// No value
+    /// No value specified.
     ImplicitNull,
 
-    /// No value, explicitly specified to be null
+    /// Value explicitly specified to be `null`.
     ExplicitNull,
 
-    /// Some value `T`
+    /// Explicitly specified non-`null` value of `T`.
     Some(T),
 }
 
@@ -49,101 +60,134 @@ impl<T> Default for Nullable<T> {
 }
 
 impl<T> Nullable<T> {
-    /// Returns `true` if the nullable is a `ExplicitNull` value.
+    /// Indicates whether this [`Nullable`] represents an [`ExplicitNull`].
+    ///
+    /// [`ExplicitNull`]: Nullable::ExplicitNull
     #[inline]
     pub fn is_explicit_null(&self) -> bool {
         matches!(self, Self::ExplicitNull)
     }
 
-    /// Returns `true` if the nullable is a `ImplicitNull` value.
+    /// Indicates whether this [`Nullable`] represents an [`ImplicitNull`].
+    ///
+    /// [`ImplicitNull`]: Nullable::ImplicitNull
     #[inline]
     pub fn is_implicit_null(&self) -> bool {
         matches!(self, Self::ImplicitNull)
     }
 
-    /// Returns `true` if the nullable is a `Some` value.
+    /// Indicates whether this [`Nullable`] contains a non-`null` value.
     #[inline]
     pub fn is_some(&self) -> bool {
         matches!(self, Self::Some(_))
     }
 
-    /// Returns `true` if the nullable is not a `Some` value.
+    /// Indicates whether this [`Nullable`] represents a `null`.
     #[inline]
     pub fn is_null(&self) -> bool {
         !matches!(self, Self::Some(_))
     }
 
+    /// Converts from `&Nullable<T>` to `Nullable<&T>`.
+    #[inline]
+    pub fn as_ref(&self) -> Nullable<&T> {
+        match self {
+            Self::Some(x) => Nullable::Some(x),
+            Self::ImplicitNull => Nullable::ImplicitNull,
+            Self::ExplicitNull => Nullable::ExplicitNull,
+        }
+    }
+
     /// Converts from `&mut Nullable<T>` to `Nullable<&mut T>`.
     #[inline]
     pub fn as_mut(&mut self) -> Nullable<&mut T> {
-        match *self {
-            Self::Some(ref mut x) => Nullable::Some(x),
+        match self {
+            Self::Some(x) => Nullable::Some(x),
             Self::ImplicitNull => Nullable::ImplicitNull,
             Self::ExplicitNull => Nullable::ExplicitNull,
         }
     }
 
-    /// Returns the contained `Some` value, consuming the `self` value.
+    /// Returns the contained non-`null` value, consuming the `self` value.
     ///
     /// # Panics
     ///
-    /// Panics if the value is not a `Some` with a custom panic message provided by `msg`.
+    /// With a custom `msg` if this [`Nullable`] represents a `null`.
     #[inline]
     #[track_caller]
     pub fn expect(self, msg: &str) -> T {
         self.some().expect(msg)
     }
 
-    /// Returns the contained `Some` value or a provided default.
+    /// Returns the contained non-`null` value or the provided `default` one.
     #[inline]
     pub fn unwrap_or(self, default: T) -> T {
         self.some().unwrap_or(default)
     }
 
-    /// Returns the contained `Some` value or computes it from a closure.
+    /// Returns thecontained non-`null` value  or computes it from the provided
+    /// `func`tion.
+    #[inline]
+    pub fn unwrap_or_else<F: FnOnce() -> T>(self, func: F) -> T {
+        self.some().unwrap_or_else(func)
+    }
+
+    /// Returns the contained non-`null` value or the [`Default`] one.
     #[inline]
-    pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
-        self.some().unwrap_or_else(f)
+    pub fn unwrap_or_default(self) -> T
+    where
+        T: Default,
+    {
+        self.some().unwrap_or_default()
     }
 
-    /// Maps a `Nullable<T>` to `Nullable<U>` by applying a function to a contained value.
+    /// Maps this `Nullable<T>` to `Nullable<U>` by applying the provided
+    /// `func`tion to the contained non-`null` value.
     #[inline]
-    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Nullable<U> {
+    pub fn map<U, F: FnOnce(T) -> U>(self, func: F) -> Nullable<U> {
         match self {
-            Self::Some(x) => Nullable::Some(f(x)),
+            Self::Some(x) => Nullable::Some(func(x)),
             Self::ImplicitNull => Nullable::ImplicitNull,
             Self::ExplicitNull => Nullable::ExplicitNull,
         }
     }
 
-    /// Applies a function to the contained value (if any), or returns the provided default (if
-    /// not).
+    /// Applies the provided `func`tion to the contained non-`null` value (if
+    /// any), or returns the provided `default` value (if not).
     #[inline]
-    pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
-        self.some().map_or(default, f)
+    pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, func: F) -> U {
+        self.some().map_or(default, func)
     }
 
-    /// Applies a function to the contained value (if any), or computes a default (if not).
+    /// Applies the provided `func`tion to the contained non-`null` value (if
+    /// any), or computes the provided `default` one (if not).
     #[inline]
-    pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
-        self.some().map_or_else(default, f)
+    pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, func: F) -> U {
+        self.some().map_or_else(default, func)
     }
 
-    /// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and
-    /// `ImplicitNull` or `ExplicitNull` to `Err(err)`.
+    /// Transforms this `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)`
+    /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err)`.
+    ///
+    /// [`ExplicitNull`]: Nullable::ExplicitNull
+    /// [`ImplicitNull`]: Nullable::ImplicitNull
     #[inline]
     pub fn ok_or<E>(self, err: E) -> Result<T, E> {
         self.some().ok_or(err)
     }
 
-    /// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and
-    /// `ImplicitNull` or `ExplicitNull` to `Err(err())`.
+    /// Transforms this `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)`
+    /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err())`.
+    ///
+    /// [`ExplicitNull`]: Nullable::ExplicitNull
+    /// [`ImplicitNull`]: Nullable::ImplicitNull
     #[inline]
     pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
         self.some().ok_or_else(err)
     }
 
-    /// Returns the nullable if it contains a value, otherwise returns `b`.
+    /// Returns this [`Nullable`] if it contains a non-`null` value, otherwise
+    /// returns the specified `b` [`Nullable`] value.
     #[inline]
     #[must_use]
     pub fn or(self, b: Self) -> Self {
@@ -153,35 +197,43 @@ impl<T> Nullable<T> {
         }
     }
 
-    /// Returns the nullable if it contains a value, otherwise calls `f` and
-    /// returns the result.
+    /// Returns this [`Nullable`] if it contains a non-`null` value, otherwise
+    /// computes a [`Nullable`] value from the specified `func`tion.
     #[inline]
     #[must_use]
-    pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
+    pub fn or_else<F: FnOnce() -> Nullable<T>>(self, func: F) -> Nullable<T> {
         match self {
             Self::Some(_) => self,
-            _ => f(),
+            _ => func(),
         }
     }
 
-    /// Replaces the actual value in the nullable by the value given in parameter, returning the
-    /// old value if present, leaving a `Some` in its place without deinitializing either one.
+    /// Replaces the contained non-`null` value in this [`Nullable`] by the
+    /// provided `value`, returning the old one if present, leaving a [`Some`]
+    /// in its place without deinitializing either one.
+    ///
+    /// [`Some`]: Nullable::Some
     #[inline]
     #[must_use]
     pub fn replace(&mut self, value: T) -> Self {
-        std::mem::replace(self, Self::Some(value))
+        mem::replace(self, Self::Some(value))
     }
 
-    /// Converts from `Nullable<T>` to `Option<T>`.
+    /// Converts this [`Nullable`] to [Option].
+    #[inline]
     pub fn some(self) -> Option<T> {
         match self {
             Self::Some(v) => Some(v),
-            _ => None,
+            Self::ExplicitNull | Self::ImplicitNull => None,
         }
     }
 
-    /// Converts from `Nullable<T>` to `Option<Option<T>>`, mapping `Some(v)` to `Some(Some(v))`,
-    /// `ExplicitNull` to `Some(None)`, and `ImplicitNull` to `None`.
+    /// Converts this [`Nullable`] to `Option<Option<T>>`, mapping `Some(v)` to
+    /// `Some(Some(v))`, [`ExplicitNull`] to `Some(None)`, and [`ImplicitNull`]
+    /// to [`None`].
+    ///
+    /// [`ExplicitNull`]: Nullable::ExplicitNull
+    /// [`ImplicitNull`]: Nullable::ImplicitNull
     pub fn explicit(self) -> Option<Option<T>> {
         match self {
             Self::Some(v) => Some(Some(v)),
@@ -192,33 +244,188 @@ impl<T> Nullable<T> {
 }
 
 impl<T: Copy> Nullable<&T> {
-    /// Maps a `Nullable<&T>` to a `Nullable<T>` by copying the contents of the nullable.
+    /// Maps this `Nullable<&T>` to a `Nullable<T>` by [`Copy`]ing the contents
+    /// of this [`Nullable`].
     pub fn copied(self) -> Nullable<T> {
-        self.map(|&t| t)
+        self.map(|t| *t)
     }
 }
 
 impl<T: Copy> Nullable<&mut T> {
-    /// Maps a `Nullable<&mut T>` to a `Nullable<T>` by copying the contents of the nullable.
+    /// Maps this `Nullable<&mut T>` to a `Nullable<T>` by [`Copy`]ing the
+    /// contents of this [`Nullable`].
     pub fn copied(self) -> Nullable<T> {
-        self.map(|&mut t| t)
+        self.map(|t| *t)
     }
 }
 
 impl<T: Clone> Nullable<&T> {
-    /// Maps a `Nullable<&T>` to a `Nullable<T>` by cloning the contents of the nullable.
+    /// Maps this `Nullable<&T>` to a `Nullable<T>` by [`Clone`]ing the contents
+    /// of this [`Nullable`].
     pub fn cloned(self) -> Nullable<T> {
-        self.map(|t| t.clone())
+        self.map(T::clone)
     }
 }
 
 impl<T: Clone> Nullable<&mut T> {
-    /// Maps a `Nullable<&mut T>` to a `Nullable<T>` by cloning the contents of the nullable.
+    /// Maps this `Nullable<&mut T>` to a `Nullable<T>` by [`Clone`]ing the
+    /// contents of this [`Nullable`].
     pub fn cloned(self) -> Nullable<T> {
         self.map(|t| t.clone())
     }
 }
 
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Nullable<T>
+where
+    T: resolve::Type<TI, SV, BH>,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.wrap_nullable::<behavior::Coerce<T, BH>, _>(type_info)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Nullable<T>
+where
+    T: resolve::Value<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        match self {
+            Self::Some(v) => v.resolve_value(selection_set, type_info, executor),
+            Self::ImplicitNull | Self::ExplicitNull => Ok(graphql::Value::Null),
+        }
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Nullable<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: Send,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        match self {
+            Self::Some(v) => v.resolve_value_async(selection_set, type_info, executor),
+            Self::ImplicitNull | Self::ExplicitNull => Box::pin(future::ok(graphql::Value::Null)),
+        }
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Nullable<T>
+where
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Nullable<T>
+where
+    T: resolve::ToInputValue<SV, BH>,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        match self {
+            Self::Some(v) => v.to_input_value(),
+            Self::ImplicitNull | Self::ExplicitNull => graphql::InputValue::Null,
+        }
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Nullable<T>
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        if v.is_null() {
+            Ok(Self::ExplicitNull)
+        } else {
+            T::try_from_input_value(v).map(Self::Some)
+        }
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        Ok(Self::ImplicitNull)
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Nullable<T>
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Nullable<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: resolve::ValueAsync<TI, CX, SV, BH>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Nullable<T>
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Nullable<T>
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Nullable<T>
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = reflect::wrap::nullable(T::VALUE);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 impl<S, T> GraphQLType<S> for Nullable<T>
 where
     T: GraphQLType<S>,
@@ -256,7 +463,7 @@ where
     ) -> ExecutionResult<S> {
         match *self {
             Self::Some(ref obj) => executor.resolve(info, obj),
-            _ => Ok(Value::null()),
+            _ => Ok(graphql::Value::null()),
         }
     }
 }
@@ -273,11 +480,11 @@ where
         info: &'a Self::TypeInfo,
         _: Option<&'a [Selection<S>]>,
         executor: &'a Executor<Self::Context, S>,
-    ) -> crate::BoxFuture<'a, ExecutionResult<S>> {
+    ) -> BoxFuture<'a, ExecutionResult<S>> {
         let f = async move {
             let value = match self {
                 Self::Some(obj) => executor.resolve_into_value_async(info, obj).await,
-                _ => Value::null(),
+                _ => graphql::Value::null(),
             };
             Ok(value)
         };
diff --git a/juniper/src/types/option.rs b/juniper/src/types/option.rs
new file mode 100644
index 000000000..a999802d9
--- /dev/null
+++ b/juniper/src/types/option.rs
@@ -0,0 +1,175 @@
+//! GraphQL implementation for [`Option`].
+
+use futures::future;
+
+use crate::{
+    behavior,
+    executor::{ExecutionResult, Executor, Registry},
+    graphql, reflect, resolve,
+    schema::meta::MetaType,
+    BoxFuture, FieldResult, Selection,
+};
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Option<T>
+where
+    T: resolve::Type<TI, SV, BH>,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.wrap_nullable::<behavior::Coerce<T, BH>, _>(type_info)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Option<T>
+where
+    T: resolve::Value<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        match self {
+            Some(v) => v.resolve_value(selection_set, type_info, executor),
+            None => Ok(graphql::Value::Null),
+        }
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Option<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: Send,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        match self {
+            Some(v) => v.resolve_value_async(selection_set, type_info, executor),
+            None => Box::pin(future::ok(graphql::Value::Null)),
+        }
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Option<T>
+where
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Option<T>
+where
+    T: resolve::ToInputValue<SV, BH>,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        match self {
+            Some(v) => v.to_input_value(),
+            None => graphql::InputValue::Null,
+        }
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Option<T>
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        if v.is_null() {
+            Ok(None)
+        } else {
+            T::try_from_input_value(v).map(Some)
+        }
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Option<T>
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Option<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: resolve::ValueAsync<TI, CX, SV, BH>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Option<T>
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Option<T>
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Option<T>
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = reflect::wrap::nullable(T::VALUE);
+}
+
+#[cfg(test)]
+mod coercion {
+    use crate::{graphql, resolve::InputValue as _};
+
+    type V = graphql::InputValue;
+
+    #[test]
+    fn from_null() {
+        let v: V = graphql::input_value!(null);
+        assert_eq!(<Option<i32>>::try_from_input_value(&v), Ok(None));
+    }
+
+    #[test]
+    fn from_value() {
+        let v: V = graphql::input_value!(1);
+        assert_eq!(<Option<i32>>::try_from_input_value(&v), Ok(Some(1)));
+    }
+}
diff --git a/juniper/src/types/rc.rs b/juniper/src/types/rc.rs
new file mode 100644
index 000000000..eddffd74f
--- /dev/null
+++ b/juniper/src/types/rc.rs
@@ -0,0 +1,305 @@
+//! GraphQL implementation for [`Rc`].
+
+use std::rc::Rc;
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Rc<T>
+where
+    T: resolve::Type<TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::TypeName<TI, BH> for Rc<T>
+where
+    T: resolve::TypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<T, TI, BH> resolve::ConcreteTypeName<TI, BH> for Rc<T>
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Rc<T>
+where
+    T: ?Sized,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::Field<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for Rc<T>
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Rc<T>
+where
+    T: resolve::ToInputValue<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Rc<T>
+where
+    T: resolve::InputValueAs<'i, Self, SV, BH> + ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Rc<Self>, SV, BH> for T
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Rc<Self>, Self::Error> {
+        T::try_from_input_value(v).map(Rc::new)
+    }
+
+    fn try_from_implicit_null() -> Result<Rc<Self>, Self::Error> {
+        T::try_from_implicit_null().map(Rc::new)
+    }
+}
+
+impl<T, SV, BH> resolve::ScalarToken<SV, BH> for Rc<T>
+where
+    T: resolve::ScalarToken<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Rc<T>
+where
+    T: graphql::InputTypeAs<'i, Self, TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Rc<T>, TI, SV, BH> for T
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Rc<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::Scalar<'i, TI, CX, SV, BH> for Rc<T>
+where
+    T: graphql::ScalarAs<'i, Self, TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<'i, T, TI, CX, SV, BH> graphql::ScalarAs<'i, Rc<T>, TI, CX, SV, BH> for T
+where
+    T: graphql::Scalar<'i, TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Rc<T>
+where
+    T: reflect::BaseType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Rc<T>
+where
+    T: reflect::BaseSubTypes<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Rc<T>
+where
+    T: reflect::WrappedType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/ref.rs b/juniper/src/types/ref.rs
new file mode 100644
index 000000000..def629f0b
--- /dev/null
+++ b/juniper/src/types/ref.rs
@@ -0,0 +1,284 @@
+//! GraphQL implementation for [reference].
+//!
+//! [reference]: primitive@std::reference
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<'me, T, TI, SV, BH> resolve::Type<TI, SV, BH> for &'me T
+where
+    T: resolve::Type<TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::TypeName<TI, BH> for &'me T
+where
+    T: resolve::TypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::ConcreteTypeName<TI, BH> for &'me T
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::Resolvable<SV, BH> for &'me T
+where
+    T: ?Sized,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::Field<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for &'me T
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ToInputValue<SV, BH> for &'me T
+where
+    T: resolve::ToInputValue<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'me, 'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for &'me T
+where
+    'i: 'me,
+    T: resolve::InputValueAs<'i, Self, SV, BH> + ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'me, 'i, T, SV, BH> resolve::InputValueAs<'i, &'me Self, SV, BH> for T
+where
+    'i: 'me,
+    T: resolve::InputValueAsRef<SV, BH> + ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = T::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<&'me Self, Self::Error> {
+        T::try_from_input_value(v)
+    }
+
+    fn try_from_implicit_null() -> Result<&'me Self, Self::Error> {
+        T::try_from_implicit_null()
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ScalarToken<SV, BH> for &'me T
+where
+    T: resolve::ScalarToken<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'me, 'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for &'me T
+where
+    'i: 'me,
+    T: graphql::InputTypeAs<'i, Self, TI, SV, BH> + ?Sized + 'me,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for &'me T
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'me, 'i, T, TI, CX, SV, BH> graphql::Scalar<'i, TI, CX, SV, BH> for &'me T
+where
+    'i: 'me,
+    T: graphql::ScalarAs<'i, Self, TI, CX, SV, BH> + ?Sized + 'me,
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_scalar() {
+        T::assert_scalar()
+    }
+}
+
+impl<'me, T, BH> reflect::BaseType<BH> for &'me T
+where
+    T: reflect::BaseType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<'me, T, BH> reflect::BaseSubTypes<BH> for &'me T
+where
+    T: reflect::BaseSubTypes<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<'me, T, BH> reflect::WrappedType<BH> for &'me T
+where
+    T: reflect::WrappedType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/ref_mut.rs b/juniper/src/types/ref_mut.rs
new file mode 100644
index 000000000..d289e8d82
--- /dev/null
+++ b/juniper/src/types/ref_mut.rs
@@ -0,0 +1,221 @@
+//! GraphQL implementation for mutable [reference].
+//!
+//! [reference]: primitive@std::reference
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry,
+    Selection,
+};
+
+impl<'me, T, TI, SV, BH> resolve::Type<TI, SV, BH> for &'me mut T
+where
+    T: resolve::Type<TI, SV, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        T::meta(registry, type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::TypeName<TI, BH> for &'me mut T
+where
+    T: resolve::TypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn type_name(type_info: &TI) -> &str {
+        T::type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, BH> resolve::ConcreteTypeName<TI, BH> for &'me mut T
+where
+    T: resolve::ConcreteTypeName<TI, BH> + ?Sized,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str {
+        (**self).concrete_type_name(type_info)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::Value<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_value(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_value_async(selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::Resolvable<SV, BH> for &'me mut T
+where
+    T: ?Sized,
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::ConcreteValue<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value(
+        &self,
+        type_name: &str,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_concrete_value(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::ConcreteValueAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_concrete_value_async<'r>(
+        &'r self,
+        type_name: &str,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::Field<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::Field<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field(
+        &self,
+        field_name: &str,
+        arguments: &Arguments<SV>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        (**self).resolve_field(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync<TI, CX, SV, BH> for &'me mut T
+where
+    T: resolve::FieldAsync<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_field_async<'r>(
+        &'r self,
+        field_name: &'r str,
+        arguments: &'r Arguments<SV>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        (**self).resolve_field_async(field_name, arguments, type_info, executor)
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ToInputValue<SV, BH> for &'me mut T
+where
+    T: resolve::ToInputValue<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        (**self).to_input_value()
+    }
+}
+
+impl<'me, T, SV, BH> resolve::ScalarToken<SV, BH> for &'me mut T
+where
+    T: resolve::ScalarToken<SV, BH> + ?Sized,
+    BH: ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        T::parse_scalar_token(token)
+    }
+}
+
+impl<'me, T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for &'me mut T
+where
+    T: graphql::OutputType<TI, CX, SV, BH> + ?Sized,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<'me, T, BH> reflect::BaseType<BH> for &'me mut T
+where
+    T: reflect::BaseType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<'me, T, BH> reflect::BaseSubTypes<BH> for &'me mut T
+where
+    T: reflect::BaseSubTypes<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<'me, T, BH> reflect::WrappedType<BH> for &'me mut T
+where
+    T: reflect::WrappedType<BH> + ?Sized,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/result.rs b/juniper/src/types/result.rs
new file mode 100644
index 000000000..ae47d440b
--- /dev/null
+++ b/juniper/src/types/result.rs
@@ -0,0 +1,39 @@
+//! GraphQL implementation for [`Result`].
+
+use crate::{reflect, resolve, FieldResult, IntoFieldError};
+
+impl<T, E, SV, BH> resolve::Resolvable<SV, BH> for Result<T, E>
+where
+    E: IntoFieldError<SV>,
+    BH: ?Sized,
+{
+    type Value = T;
+
+    fn into_value(self) -> FieldResult<Self::Value, SV> {
+        self.map_err(IntoFieldError::into_field_error)
+    }
+}
+
+impl<T, E, BH> reflect::BaseType<BH> for Result<T, E>
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, E, BH> reflect::BaseSubTypes<BH> for Result<T, E>
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, E, BH> reflect::WrappedType<BH> for Result<T, E>
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
diff --git a/juniper/src/types/slice.rs b/juniper/src/types/slice.rs
new file mode 100644
index 000000000..6a3f0124b
--- /dev/null
+++ b/juniper/src/types/slice.rs
@@ -0,0 +1,235 @@
+//! GraphQL implementation for [slice].
+//!
+//! [slice]: prim@slice
+
+use std::{borrow::Cow, rc::Rc, sync::Arc};
+
+use crate::{
+    behavior,
+    executor::{ExecutionResult, Executor, Registry},
+    graphql, reflect, resolve,
+    schema::meta::MetaType,
+    BoxFuture, Selection,
+};
+
+use super::{iter, vec::TryFromInputValueError};
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for [T]
+where
+    T: resolve::Type<TI, SV, BH>,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.wrap_list::<behavior::Coerce<T, BH>, _>(type_info, None)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for [T]
+where
+    T: resolve::Value<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        iter::resolve_list(self.iter(), selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for [T]
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + Sync,
+    TI: Sync + ?Sized,
+    CX: Sync + ?Sized,
+    SV: Send + Sync,
+    BH: ?Sized + 'static, // TODO: Lift `'static` bound if possible.
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        Box::pin(iter::resolve_list_async(
+            self.iter(),
+            selection_set,
+            type_info,
+            executor,
+        ))
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for [T]
+where
+    T: resolve::ToInputValue<SV, BH>,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
+impl<'me, 'i, T, SV, BH> resolve::InputValueAs<'i, Cow<'me, Self>, SV, BH> for [T]
+where
+    Vec<T>: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+    Self: ToOwned,
+{
+    type Error = <Vec<T> as resolve::InputValue<'i, SV, BH>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Cow<'me, Self>, Self::Error> {
+        <Vec<T> as resolve::InputValue<'i, SV, BH>>::try_from_input_value(v)
+            .map(|v| Cow::Owned(v.to_owned()))
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Box<Self>, SV, BH> for [T]
+where
+    Vec<T>: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = <Vec<T> as resolve::InputValue<'i, SV, BH>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Box<Self>, Self::Error> {
+        <Vec<T> as resolve::InputValue<'i, SV, BH>>::try_from_input_value(v)
+            .map(Vec::into_boxed_slice)
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Rc<Self>, SV, BH> for [T]
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = TryFromInputValueError<T::Error>;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Rc<Self>, Self::Error> {
+        // We don't want to reuse `Vec<T>` implementation in the same way we do
+        // for `Box<[T]>`, because `impl From<Vec<T>> for Rc<[T]>` reallocates.
+        match v {
+            graphql::InputValue::List(l) => l
+                .iter()
+                .map(|i| T::try_from_input_value(&i.item).map_err(TryFromInputValueError::Item))
+                .collect(),
+            // See "Input Coercion" on List types:
+            // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+            graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
+            // TODO: Use `.into_iter()` after upgrade to 2021 Rust edition.
+            other => T::try_from_input_value(other)
+                .map(|e| std::iter::once(e).collect())
+                .map_err(TryFromInputValueError::Item),
+        }
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValueAs<'i, Arc<Self>, SV, BH> for [T]
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = TryFromInputValueError<T::Error>;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Arc<Self>, Self::Error> {
+        // We don't want to reuse `Vec<T>` implementation in the same way we do
+        // for `Box<[T]>`, because `impl From<Vec<T>> for Arc<[T]>` reallocates.
+        match v {
+            graphql::InputValue::List(l) => l
+                .iter()
+                .map(|i| T::try_from_input_value(&i.item).map_err(TryFromInputValueError::Item))
+                .collect(),
+            // See "Input Coercion" on List types:
+            // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+            graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
+            // TODO: Use `.into_iter()` after upgrade to 2021 Rust edition.
+            other => T::try_from_input_value(other)
+                .map(|e| std::iter::once(e).collect())
+                .map_err(TryFromInputValueError::Item),
+        }
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Box<Self>, TI, SV, BH> for [T]
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Rc<Self>, TI, SV, BH> for [T]
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputTypeAs<'i, Arc<Self>, TI, SV, BH> for [T]
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for [T]
+where
+    T: graphql::OutputType<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: resolve::ValueAsync<TI, CX, SV, BH>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for [T]
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for [T]
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for [T]
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
+}
diff --git a/juniper/src/types/str.rs b/juniper/src/types/str.rs
new file mode 100644
index 000000000..ebcc61495
--- /dev/null
+++ b/juniper/src/types/str.rs
@@ -0,0 +1,273 @@
+//! GraphQL implementation for [`str`].
+//!
+//! [`str`]: primitive@std::str
+
+use std::{borrow::Cow, rc::Rc, sync::Arc};
+
+use futures::future;
+
+use crate::{
+    graphql,
+    meta::MetaType,
+    parser::{ParseError, ScalarToken},
+    reflect, resolve, BoxFuture, ExecutionResult, Executor, Registry, ScalarValue, Selection,
+};
+
+impl<TI: ?Sized, SV: ScalarValue> resolve::Type<TI, SV> for str {
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.register_scalar_unsized::<Self, _>(type_info)
+    }
+}
+
+impl<TI: ?Sized> resolve::TypeName<TI> for str {
+    fn type_name(_: &TI) -> &'static str {
+        <Self as reflect::BaseType>::NAME
+    }
+}
+
+impl<TI, CX, SV> resolve::Value<TI, CX, SV> for str
+where
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: From<String>,
+{
+    fn resolve_value(
+        &self,
+        _: Option<&[Selection<'_, SV>]>,
+        _: &TI,
+        _: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        // TODO: Remove redundant `.to_owned()` allocation by allowing
+        //       `ScalarValue` creation from reference?
+        Ok(graphql::Value::scalar(self.to_owned()))
+    }
+}
+
+impl<TI, CX, SV> resolve::ValueAsync<TI, CX, SV> for str
+where
+    TI: ?Sized,
+    CX: ?Sized,
+    SV: From<String> + Send,
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        _: Option<&'r [Selection<'_, SV>]>,
+        _: &'r TI,
+        _: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        // TODO: Remove redundant `.to_owned()` allocation by allowing
+        //       `ScalarValue` creation from reference?
+        Box::pin(future::ok(graphql::Value::scalar(self.to_owned())))
+    }
+}
+
+impl<SV> resolve::ToInputValue<SV> for str
+where
+    SV: From<String>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        // TODO: Remove redundant `.to_owned()` allocation by allowing
+        //       `ScalarValue` creation from reference?
+        graphql::InputValue::scalar(self.to_owned())
+    }
+}
+
+impl<SV: ScalarValue> resolve::InputValueAsRef<SV> for str {
+    type Error = String;
+
+    fn try_from_input_value(v: &graphql::InputValue<SV>) -> Result<&Self, Self::Error> {
+        v.as_string_value()
+            .ok_or_else(|| format!("Expected `String`, found: {v}"))
+    }
+}
+
+impl<'me, 'i, SV> resolve::InputValueAs<'i, Cow<'me, Self>, SV> for str
+where
+    'i: 'me,
+    SV: 'i,
+    Self: resolve::InputValueAsRef<SV>,
+{
+    type Error = <Self as resolve::InputValueAsRef<SV>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Cow<'me, Self>, Self::Error> {
+        <Self as resolve::InputValueAsRef<SV>>::try_from_input_value(v).map(Cow::Borrowed)
+    }
+}
+
+impl<'i, SV> resolve::InputValueAs<'i, Box<Self>, SV> for str
+where
+    SV: 'i,
+    Self: resolve::InputValueAsRef<SV>,
+{
+    type Error = <Self as resolve::InputValueAsRef<SV>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Box<Self>, Self::Error> {
+        <Self as resolve::InputValueAsRef<SV>>::try_from_input_value(v).map(Into::into)
+    }
+}
+
+impl<'i, SV> resolve::InputValueAs<'i, Rc<Self>, SV> for str
+where
+    SV: 'i,
+    Self: resolve::InputValueAsRef<SV>,
+{
+    type Error = <Self as resolve::InputValueAsRef<SV>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Rc<Self>, Self::Error> {
+        <Self as resolve::InputValueAsRef<SV>>::try_from_input_value(v).map(Into::into)
+    }
+}
+
+impl<'i, SV> resolve::InputValueAs<'i, Arc<Self>, SV> for str
+where
+    SV: 'i,
+    Self: resolve::InputValueAsRef<SV>,
+{
+    type Error = <Self as resolve::InputValueAsRef<SV>>::Error;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Arc<Self>, Self::Error> {
+        <Self as resolve::InputValueAsRef<SV>>::try_from_input_value(v).map(Into::into)
+    }
+}
+
+impl<SV> resolve::ScalarToken<SV> for str
+where
+    String: resolve::ScalarToken<SV>,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<SV, ParseError> {
+        <String as resolve::ScalarToken<SV>>::parse_scalar_token(token)
+    }
+}
+
+impl<'me, 'i, TI, SV> graphql::InputTypeAs<'i, &'me Self, TI, SV> for str
+where
+    Self: graphql::Type<TI, SV>
+        + resolve::ToInputValue<SV>
+        + resolve::InputValueAs<'i, &'me Self, SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_input_type() {}
+}
+
+impl<'me, 'i, TI, SV> graphql::InputTypeAs<'i, Cow<'me, Self>, TI, SV> for str
+where
+    Self: graphql::Type<TI, SV>
+        + resolve::ToInputValue<SV>
+        + resolve::InputValueAs<'i, Cow<'me, Self>, SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_input_type() {}
+}
+
+impl<'i, TI, SV> graphql::InputTypeAs<'i, Box<Self>, TI, SV> for str
+where
+    Self: graphql::Type<TI, SV>
+        + resolve::ToInputValue<SV>
+        + resolve::InputValueAs<'i, Box<Self>, SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_input_type() {}
+}
+
+impl<'i, TI, SV> graphql::InputTypeAs<'i, Rc<Self>, TI, SV> for str
+where
+    Self:
+        graphql::Type<TI, SV> + resolve::ToInputValue<SV> + resolve::InputValueAs<'i, Rc<Self>, SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_input_type() {}
+}
+
+impl<'i, TI, SV> graphql::InputTypeAs<'i, Arc<Self>, TI, SV> for str
+where
+    Self: graphql::Type<TI, SV>
+        + resolve::ToInputValue<SV>
+        + resolve::InputValueAs<'i, Arc<Self>, SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_input_type() {}
+}
+
+impl<TI, CX, SV> graphql::OutputType<TI, CX, SV> for str
+where
+    Self: graphql::Type<TI, SV> + resolve::Value<TI, CX, SV> + resolve::ValueAsync<TI, CX, SV>,
+    TI: ?Sized,
+    CX: ?Sized,
+{
+    fn assert_output_type() {}
+}
+
+impl<'me, 'i, TI, CX, SV> graphql::ScalarAs<'i, &'me Self, TI, CX, SV> for str
+where
+    Self: graphql::InputTypeAs<'i, &'me Self, TI, SV>
+        + graphql::OutputType<TI, CX, SV>
+        + resolve::ScalarToken<SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_scalar() {}
+}
+
+impl<'me, 'i, TI, CX, SV> graphql::ScalarAs<'i, Cow<'me, Self>, TI, CX, SV> for str
+where
+    Self: graphql::InputTypeAs<'i, Cow<'me, Self>, TI, SV>
+        + graphql::OutputType<TI, CX, SV>
+        + resolve::ScalarToken<SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_scalar() {}
+}
+
+impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Box<Self>, TI, CX, SV> for str
+where
+    Self: graphql::InputTypeAs<'i, Box<Self>, TI, SV>
+        + graphql::OutputType<TI, CX, SV>
+        + resolve::ScalarToken<SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_scalar() {}
+}
+
+impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Rc<Self>, TI, CX, SV> for str
+where
+    Self: graphql::InputTypeAs<'i, Rc<Self>, TI, SV>
+        + graphql::OutputType<TI, CX, SV>
+        + resolve::ScalarToken<SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_scalar() {}
+}
+
+impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Arc<Self>, TI, CX, SV> for str
+where
+    Self: graphql::InputTypeAs<'i, Arc<Self>, TI, SV>
+        + graphql::OutputType<TI, CX, SV>
+        + resolve::ScalarToken<SV>,
+    TI: ?Sized,
+    SV: 'i,
+{
+    fn assert_scalar() {}
+}
+
+impl reflect::BaseType for str {
+    const NAME: reflect::Type = <String as reflect::BaseType>::NAME;
+}
+
+impl reflect::BaseSubTypes for str {
+    const NAMES: reflect::Types = &[<Self as reflect::BaseType>::NAME];
+}
+
+impl reflect::WrappedType for str {
+    const VALUE: reflect::WrappedValue = reflect::wrap::SINGULAR;
+}
diff --git a/juniper/src/types/vec.rs b/juniper/src/types/vec.rs
new file mode 100644
index 000000000..cf01b0301
--- /dev/null
+++ b/juniper/src/types/vec.rs
@@ -0,0 +1,313 @@
+//! GraphQL implementation for [`Vec`].
+
+use crate::{
+    behavior,
+    executor::{ExecutionResult, Executor, Registry},
+    graphql, reflect, resolve,
+    schema::meta::MetaType,
+    BoxFuture, FieldError, FieldResult, IntoFieldError, Selection,
+};
+
+use super::iter;
+
+impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Vec<T>
+where
+    T: resolve::Type<TI, SV, BH>,
+    TI: ?Sized,
+    BH: ?Sized,
+{
+    fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
+    where
+        SV: 'r,
+    {
+        registry.wrap_list::<behavior::Coerce<T, BH>, _>(type_info, None)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::Value<TI, CX, SV, BH> for Vec<T>
+where
+    T: resolve::Value<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+{
+    fn resolve_value(
+        &self,
+        selection_set: Option<&[Selection<'_, SV>]>,
+        type_info: &TI,
+        executor: &Executor<CX, SV>,
+    ) -> ExecutionResult<SV> {
+        iter::resolve_list(self.iter(), selection_set, type_info, executor)
+    }
+}
+
+impl<T, TI, CX, SV, BH> resolve::ValueAsync<TI, CX, SV, BH> for Vec<T>
+where
+    T: resolve::ValueAsync<TI, CX, SV, BH> + Sync,
+    TI: Sync + ?Sized,
+    CX: Sync + ?Sized,
+    SV: Send + Sync,
+    BH: ?Sized + 'static, // TODO: Lift `'static` bound if possible.
+{
+    fn resolve_value_async<'r>(
+        &'r self,
+        selection_set: Option<&'r [Selection<'_, SV>]>,
+        type_info: &'r TI,
+        executor: &'r Executor<CX, SV>,
+    ) -> BoxFuture<'r, ExecutionResult<SV>> {
+        Box::pin(iter::resolve_list_async(
+            self.iter(),
+            selection_set,
+            type_info,
+            executor,
+        ))
+    }
+}
+
+impl<T, SV, BH> resolve::Resolvable<SV, BH> for Vec<T>
+where
+    BH: ?Sized,
+{
+    type Value = Self;
+
+    fn into_value(self) -> FieldResult<Self, SV> {
+        Ok(self)
+    }
+}
+
+impl<T, SV, BH> resolve::ToInputValue<SV, BH> for Vec<T>
+where
+    T: resolve::ToInputValue<SV, BH>,
+    BH: ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<SV> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
+impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Vec<T>
+where
+    T: resolve::InputValue<'i, SV, BH>,
+    SV: 'i,
+    BH: ?Sized,
+{
+    type Error = TryFromInputValueError<T::Error>;
+
+    fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
+        match v {
+            graphql::InputValue::List(l) => l
+                .iter()
+                .map(|i| T::try_from_input_value(&i.item).map_err(TryFromInputValueError::Item))
+                .collect(),
+            // See "Input Coercion" on List types:
+            // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+            graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
+            other => T::try_from_input_value(other)
+                .map(|e| vec![e])
+                .map_err(TryFromInputValueError::Item),
+        }
+    }
+}
+
+impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Vec<T>
+where
+    T: graphql::InputType<'i, TI, SV, BH>,
+    TI: ?Sized,
+    SV: 'i,
+    BH: ?Sized,
+{
+    fn assert_input_type() {
+        T::assert_input_type()
+    }
+}
+
+impl<T, TI, CX, SV, BH> graphql::OutputType<TI, CX, SV, BH> for Vec<T>
+where
+    T: graphql::OutputType<TI, CX, SV, BH>,
+    TI: ?Sized,
+    CX: ?Sized,
+    BH: ?Sized,
+    Self: resolve::ValueAsync<TI, CX, SV, BH>,
+{
+    fn assert_output_type() {
+        T::assert_output_type()
+    }
+}
+
+impl<T, BH> reflect::BaseType<BH> for Vec<T>
+where
+    T: reflect::BaseType<BH>,
+    BH: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, BH> reflect::BaseSubTypes<BH> for Vec<T>
+where
+    T: reflect::BaseSubTypes<BH>,
+    BH: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, BH> reflect::WrappedType<BH> for Vec<T>
+where
+    T: reflect::WrappedType<BH>,
+    BH: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
+}
+
+/// Possible errors of converting a [`graphql::InputValue`] into a [`Vec`].
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum TryFromInputValueError<E> {
+    /// [`graphql::InputValue`] cannot be [`Null`].
+    ///
+    /// See ["Combining List and Non-Null" section of spec][0].
+    ///
+    /// [`Null`]: [`InputValue::Null`]
+    /// [0]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
+    IsNull,
+
+    /// Error of converting a [`graphql::InputValue::List`]'s item.
+    Item(E),
+}
+
+impl<E, SV> IntoFieldError<SV> for TryFromInputValueError<E>
+where
+    E: IntoFieldError<SV>,
+{
+    fn into_field_error(self) -> FieldError<SV> {
+        match self {
+            Self::IsNull => "Failed to convert into `Vec`: Value cannot be `null`".into(),
+            Self::Item(e) => e.into_field_error(),
+        }
+    }
+}
+
+// See "Input Coercion" examples on List types:
+// https://spec.graphql.org/October2021#sec-List.Input-Coercion
+#[cfg(test)]
+mod coercion {
+    use crate::{graphql, resolve::InputValue as _, IntoFieldError as _};
+
+    use super::TryFromInputValueError;
+
+    type V = graphql::InputValue;
+
+    #[test]
+    fn from_null() {
+        let v: V = graphql::input_value!(null);
+        assert_eq!(
+            <Vec<i32>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <Vec<Option<i32>>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(<Option<Vec<i32>>>::try_from_input_value(&v), Ok(None));
+        assert_eq!(
+            <Option<Vec<Option<i32>>>>::try_from_input_value(&v),
+            Ok(None),
+        );
+        assert_eq!(
+            <Vec<Vec<i32>>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::IsNull),
+        );
+        assert_eq!(
+            <Option<Vec<Option<Vec<Option<i32>>>>>>::try_from_input_value(&v),
+            Ok(None),
+        );
+    }
+
+    #[test]
+    fn from_value() {
+        let v: V = graphql::input_value!(1);
+        assert_eq!(<Vec<i32>>::try_from_input_value(&v), Ok(vec![1]));
+        assert_eq!(
+            <Vec<Option<i32>>>::try_from_input_value(&v),
+            Ok(vec![Some(1)]),
+        );
+        assert_eq!(
+            <Option<Vec<i32>>>::try_from_input_value(&v),
+            Ok(Some(vec![1])),
+        );
+        assert_eq!(
+            <Option<Vec<Option<i32>>>>::try_from_input_value(&v),
+            Ok(Some(vec![Some(1)])),
+        );
+        assert_eq!(<Vec<Vec<i32>>>::try_from_input_value(&v), Ok(vec![vec![1]]));
+        assert_eq!(
+            <Option<Vec<Option<Vec<Option<i32>>>>>>::try_from_input_value(&v),
+            Ok(Some(vec![Some(vec![Some(1)])])),
+        );
+    }
+
+    #[test]
+    fn from_list() {
+        let v: V = graphql::input_value!([1, 2, 3]);
+        assert_eq!(<Vec<i32>>::try_from_input_value(&v), Ok(vec![1, 2, 3]));
+        assert_eq!(
+            <Option<Vec<i32>>>::try_from_input_value(&v),
+            Ok(Some(vec![1, 2, 3])),
+        );
+        assert_eq!(
+            <Vec<Option<i32>>>::try_from_input_value(&v),
+            Ok(vec![Some(1), Some(2), Some(3)]),
+        );
+        assert_eq!(
+            <Option<Vec<Option<i32>>>>::try_from_input_value(&v),
+            Ok(Some(vec![Some(1), Some(2), Some(3)])),
+        );
+        assert_eq!(
+            <Vec<Vec<i32>>>::try_from_input_value(&v),
+            Ok(vec![vec![1], vec![2], vec![3]]),
+        );
+        // Looks like the spec ambiguity.
+        // See: https://github.com/graphql/graphql-spec/pull/515
+        assert_eq!(
+            <Option<Vec<Option<Vec<Option<i32>>>>>>::try_from_input_value(&v),
+            Ok(Some(vec![
+                Some(vec![Some(1)]),
+                Some(vec![Some(2)]),
+                Some(vec![Some(3)]),
+            ])),
+        );
+    }
+
+    #[test]
+    fn from_list_with_null() {
+        let v: V = graphql::input_value!([1, 2, null]);
+        assert_eq!(
+            <Vec<i32>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(
+                "Expected `Int`, found: null".into_field_error(),
+            )),
+        );
+        assert_eq!(
+            <Option<Vec<i32>>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(
+                "Expected `Int`, found: null".into_field_error(),
+            )),
+        );
+        assert_eq!(
+            <Vec<Option<i32>>>::try_from_input_value(&v),
+            Ok(vec![Some(1), Some(2), None]),
+        );
+        assert_eq!(
+            <Option<Vec<Option<i32>>>>::try_from_input_value(&v),
+            Ok(Some(vec![Some(1), Some(2), None])),
+        );
+        assert_eq!(
+            <Vec<Vec<i32>>>::try_from_input_value(&v),
+            Err(TryFromInputValueError::Item(TryFromInputValueError::IsNull)),
+        );
+        // Looks like the spec ambiguity.
+        // See: https://github.com/graphql/graphql-spec/pull/515
+        assert_eq!(
+            <Option<Vec<Option<Vec<Option<i32>>>>>>::try_from_input_value(&v),
+            Ok(Some(vec![Some(vec![Some(1)]), Some(vec![Some(2)]), None])),
+        );
+    }
+}
diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs
index 6a160807d..b4798e90a 100644
--- a/juniper/src/value/mod.rs
+++ b/juniper/src/value/mod.rs
@@ -6,6 +6,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem};
 use crate::{
     ast::{InputValue, ToInputValue},
     parser::Spanning,
+    resolve,
 };
 
 pub use self::{
@@ -190,6 +191,27 @@ impl<S: Clone> ToInputValue<S> for Value<S> {
     }
 }
 
+impl<S: Clone> resolve::ToInputValue<S> for Value<S> {
+    fn to_input_value(&self) -> InputValue<S> {
+        // TODO: Simplify recursive calls syntax, once old `ToInputValue` trait
+        //       is removed.
+        match self {
+            Self::Null => InputValue::Null,
+            Self::Scalar(s) => InputValue::Scalar(s.clone()),
+            Self::List(l) => InputValue::list(
+                l.iter()
+                    .map(<Self as resolve::ToInputValue<S>>::to_input_value),
+            ),
+            Self::Object(o) => InputValue::object(o.iter().map(|(k, v)| {
+                (
+                    k.clone(),
+                    <Self as resolve::ToInputValue<S>>::to_input_value(v),
+                )
+            })),
+        }
+    }
+}
+
 impl<S: ScalarValue> fmt::Display for Value<S> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml
index b22916581..9d696a7a1 100644
--- a/juniper_codegen/Cargo.toml
+++ b/juniper_codegen/Cargo.toml
@@ -31,5 +31,5 @@ url = "2.0"
 [dev-dependencies]
 derive_more = "0.99.7"
 futures = "0.3.22"
-juniper = { path = "../juniper" }
+#juniper = { path = "../juniper" }
 serde = "1.0"
diff --git a/juniper_codegen/src/common/behavior.rs b/juniper_codegen/src/common/behavior.rs
new file mode 100644
index 000000000..618ae5bc0
--- /dev/null
+++ b/juniper_codegen/src/common/behavior.rs
@@ -0,0 +1,60 @@
+//! Common functions, definitions and extensions for parsing and code generation
+//! related to [`Behaviour`] type parameter.
+//!
+//! [`Behaviour`]: juniper::behavior
+
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+};
+
+use crate::common::SpanContainer;
+
+/// [`Behaviour`] parametrization of the code generation.
+///
+/// [`Behaviour`]: juniper::behavior
+#[derive(Clone, Debug, Default)]
+pub(crate) enum Type {
+    /// [`behavior::Standard`] should be used in the generated code.
+    ///
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    #[default]
+    Standard,
+
+    /// Concrete custom Rust type should be used as [`Behaviour`] in the
+    /// generated code.
+    ///
+    /// [`Behaviour`]: juniper::behavior
+    Custom(syn::Type),
+}
+
+impl Parse for Type {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        input.parse::<syn::Type>().map(Self::Custom)
+    }
+}
+
+impl ToTokens for Type {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.ty().to_tokens(into)
+    }
+}
+
+impl Type {
+    /// Returns a Rust type representing this [`Type`].
+    #[must_use]
+    pub(crate) fn ty(&self) -> syn::Type {
+        match self {
+            Self::Standard => parse_quote! { ::juniper::behavior::Standard },
+            Self::Custom(ty) => ty.clone(),
+        }
+    }
+}
+
+impl From<Option<SpanContainer<Self>>> for Type {
+    fn from(attr: Option<SpanContainer<Self>>) -> Self {
+        attr.map(SpanContainer::into_inner).unwrap_or_default()
+    }
+}
diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs
index afbe53ed8..06bedfa02 100644
--- a/juniper_codegen/src/common/field/arg.rs
+++ b/juniper_codegen/src/common/field/arg.rs
@@ -15,7 +15,7 @@ use syn::{
 };
 
 use crate::common::{
-    default, diagnostic, filter_attrs,
+    behavior, default, diagnostic, filter_attrs,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _, TypeExt as _,
@@ -52,6 +52,19 @@ pub(crate) struct Attr {
     /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
     pub(crate) default: Option<SpanContainer<default::Value>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] this
+    /// [GraphQL argument][1] implementation is parametrized with, to [coerce]
+    /// in the generated code from.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
+    /// [coerce]: juniper::behavior::Coerce
+    pub(crate) behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified marker indicating that this method argument doesn't
     /// represent a [GraphQL argument][1], but is a [`Context`] being injected
     /// into a [GraphQL field][2] resolving function.
@@ -103,6 +116,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(val.span()), val))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "ctx" | "context" | "Context" => {
                     let span = ident.span();
                     out.context
@@ -133,6 +153,7 @@ impl Attr {
             name: try_merge_opt!(name: self, another),
             description: try_merge_opt!(description: self, another),
             default: try_merge_opt!(default: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             context: try_merge_opt!(context: self, another),
             executor: try_merge_opt!(executor: self, another),
         })
@@ -229,6 +250,14 @@ pub(crate) struct OnField {
     /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
     /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
     pub(crate) default: Option<default::Value>,
+
+    /// [`Behavior`] parametrization of this [GraphQL field argument][1]
+    /// implementation to [coerce] from in the generated code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
+    /// [coerce]: juniper::behavior::Coerce
+    pub(crate) behavior: behavior::Type,
 }
 
 /// Possible kinds of Rust method arguments for code generation.
@@ -433,6 +462,7 @@ impl OnMethod {
             ty: argument.ty.as_ref().clone(),
             description: attr.description.map(SpanContainer::into_inner),
             default: attr.default.map(SpanContainer::into_inner),
+            behavior: attr.behavior.into(),
         })))
     }
 }
diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs
index 8e8cd2973..24751fab6 100644
--- a/juniper_codegen/src/common/field/mod.rs
+++ b/juniper_codegen/src/common/field/mod.rs
@@ -15,7 +15,7 @@ use syn::{
 };
 
 use crate::common::{
-    deprecation, filter_attrs,
+    behavior, deprecation, filter_attrs,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _,
@@ -56,6 +56,19 @@ pub(crate) struct Attr {
     /// [2]: https://spec.graphql.org/October2021#sec-Deprecation
     pub(crate) deprecated: Option<SpanContainer<deprecation::Directive>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] this
+    /// [GraphQL field][1] implementation is parametrized with, to [coerce] in
+    /// the generated code from.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
+    /// [coerce]: juniper::behavior::Coerce
+    pub(crate) behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified marker indicating that this method (or struct
     /// field) should be omitted by code generation and not considered as the
     /// [GraphQL field][1] definition.
@@ -94,6 +107,13 @@ impl Parse for Attr {
                         ))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "ignore" | "skip" => out
                     .ignore
                     .replace(SpanContainer::new(ident.span(), None, ident.clone()))
@@ -116,6 +136,7 @@ impl Attr {
             name: try_merge_opt!(name: self, another),
             description: try_merge_opt!(description: self, another),
             deprecated: try_merge_opt!(deprecated: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             ignore: try_merge_opt!(ignore: self, another),
         })
     }
@@ -184,6 +205,14 @@ pub(crate) struct Definition {
     /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
     pub(crate) ident: syn::Ident,
 
+    /// [`Behavior`] parametrization of this [GraphQL field][1] implementation
+    /// to [coerce] from in the generated code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
+    /// [coerce]: juniper::behavior::Coerce
+    pub(crate) behavior: behavior::Type,
+
     /// Rust [`MethodArgument`]s required to call the method representing this
     /// [GraphQL field][1].
     ///
diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs
index a0b5e7315..846ed25e1 100644
--- a/juniper_codegen/src/common/gen.rs
+++ b/juniper_codegen/src/common/gen.rs
@@ -1,7 +1,81 @@
 //! Common code generated parts, used by this crate.
 
 use proc_macro2::TokenStream;
-use quote::quote;
+use quote::{quote, ToTokens};
+use syn::parse_quote;
+
+use crate::common::behavior;
+
+/// Returns generated code implementing [`resolve::Resolvable`] trait for the
+/// provided [`syn::Type`] with its [`syn::Generics`].
+///
+/// [`resolve::Resolvable`]: juniper::resolve::Resolvable
+/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+pub(crate) fn impl_resolvable(
+    bh: &behavior::Type,
+    (ty, generics): (syn::Type, syn::Generics),
+) -> TokenStream {
+    let (sv, generics) = mix_scalar_value(generics);
+    let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+    quote! {
+        #[automatically_derived]
+        impl #impl_gens ::juniper::resolve::Resolvable<#sv, #bh>
+         for #ty #where_clause
+        {
+            type Value = Self;
+
+            fn into_value(self) -> ::juniper::FieldResult<Self, #sv> {
+                ::juniper::FieldResult::Ok(self)
+            }
+        }
+    }
+}
+
+/// Mixes a type info [`syn::GenericParam`] into the provided [`syn::Generics`]
+/// and returns its [`syn::Ident`].
+#[must_use]
+pub(crate) fn mix_type_info(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) {
+    let ty = parse_quote! { __TypeInfo };
+    generics.params.push(parse_quote! { #ty: ?Sized });
+    (ty, generics)
+}
+
+/// Mixes a context [`syn::GenericParam`] into the provided [`syn::Generics`]
+/// and returns its [`syn::Ident`].
+pub(crate) fn mix_context(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) {
+    let ty = parse_quote! { __Context };
+    generics.params.push(parse_quote! { #ty: ?Sized });
+    (ty, generics)
+}
+
+/// Mixes a [`ScalarValue`] [`syn::GenericParam`] into the provided
+/// [`syn::Generics`] and returns it.
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+pub(crate) fn mix_scalar_value(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) {
+    let sv = parse_quote! { __ScalarValue };
+    generics.params.push(parse_quote! { #sv });
+    (sv, generics)
+}
+
+/// Mixes an [`InputValue`]'s lifetime [`syn::GenericParam`] into the provided
+/// [`syn::Generics`] and returns it.
+///
+/// [`InputValue`]: juniper::resolve::InputValue
+#[must_use]
+pub(crate) fn mix_input_lifetime(
+    mut generics: syn::Generics,
+    sv: impl ToTokens,
+) -> (syn::GenericParam, syn::Generics) {
+    let lt: syn::GenericParam = parse_quote! { '__inp };
+    generics.params.push(lt.clone());
+    generics
+        .make_where_clause()
+        .predicates
+        .push(parse_quote! { #sv: #lt });
+    (lt, generics)
+}
 
 /// Generate the code resolving some [GraphQL type][1] in a synchronous manner.
 ///
diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs
index e6e3dd8fa..9c5f4a117 100644
--- a/juniper_codegen/src/common/mod.rs
+++ b/juniper_codegen/src/common/mod.rs
@@ -1,5 +1,6 @@
 //! Common functions, definitions and extensions for code generation, used by this crate.
 
+pub(crate) mod behavior;
 pub(crate) mod default;
 pub(crate) mod deprecation;
 mod description;
diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs
index 53781675e..ba4e87181 100644
--- a/juniper_codegen/src/common/parse/mod.rs
+++ b/juniper_codegen/src/common/parse/mod.rs
@@ -105,15 +105,37 @@ pub(crate) trait TypeExt {
     #[must_use]
     fn unreferenced(&self) -> &Self;
 
-    /// Iterates mutably over all the lifetime parameters of this [`syn::Type`]
-    /// with the given `func`tion.
-    fn lifetimes_iter_mut<F: FnMut(&mut syn::Lifetime)>(&mut self, func: &mut F);
+    /// Iterates mutably over all the lifetime parameters (even elided) of this
+    /// [`syn::Type`] with the given `func`tion.
+    fn lifetimes_iter_mut<F: FnMut(MaybeElidedLifetimeMut<'_>)>(&mut self, func: &mut F);
+
+    /// Iterates mutably over all the named lifetime parameters (no elided) of
+    /// this [`syn::Type`] with the given `func`tion.
+    fn named_lifetimes_iter_mut<F: FnMut(&mut syn::Lifetime)>(&mut self, func: &mut F) {
+        self.lifetimes_iter_mut(&mut |lt| {
+            if let MaybeElidedLifetimeMut::Named(lt) = lt {
+                func(lt);
+            }
+        })
+    }
 
     /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except
     /// the `'static` ones), making it suitable for using in contexts with
     /// inferring.
     fn lifetimes_anonymized(&mut self);
 
+    /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except
+    /// the `'static` ones), making it suitable for using in contexts with
+    /// inferring, and returns it as a new type, leaving the original one
+    /// unchanged.
+    fn to_anonymized_lifetimes(&self) -> syn::Type;
+
+    /// Lifts all the lifetimes of this [`syn::Type`] (even elided, but except
+    /// `'static`) to a `for<>` quantifier, preserving the type speciality as
+    /// much as possible, and returns it as a new type, leaving the original one
+    /// unchanged.
+    fn to_hrtb_lifetimes(&self) -> (Option<syn::BoundLifetimes>, syn::Type);
+
     /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any.
     #[must_use]
     fn topmost_ident(&self) -> Option<&syn::Ident>;
@@ -135,16 +157,16 @@ impl TypeExt for syn::Type {
         }
     }
 
-    fn lifetimes_iter_mut<F: FnMut(&mut syn::Lifetime)>(&mut self, func: &mut F) {
+    fn lifetimes_iter_mut<F: FnMut(MaybeElidedLifetimeMut<'_>)>(&mut self, func: &mut F) {
         use syn::{GenericArgument as GA, Type as T};
 
-        fn iter_path<F: FnMut(&mut syn::Lifetime)>(path: &mut syn::Path, func: &mut F) {
+        fn iter_path<F: FnMut(MaybeElidedLifetimeMut<'_>)>(path: &mut syn::Path, func: &mut F) {
             for seg in path.segments.iter_mut() {
                 match &mut seg.arguments {
                     syn::PathArguments::AngleBracketed(angle) => {
                         for arg in angle.args.iter_mut() {
                             match arg {
-                                GA::Lifetime(lt) => func(lt),
+                                GA::Lifetime(lt) => func(lt.into()),
                                 GA::Type(ty) => ty.lifetimes_iter_mut(func),
                                 GA::Binding(b) => b.ty.lifetimes_iter_mut(func),
                                 GA::Constraint(_) | GA::Const(_) => {}
@@ -181,7 +203,7 @@ impl TypeExt for syn::Type {
             | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
                 for bound in bounds.iter_mut() {
                     match bound {
-                        syn::TypeParamBound::Lifetime(lt) => func(lt),
+                        syn::TypeParamBound::Lifetime(lt) => func(lt.into()),
                         syn::TypeParamBound::Trait(bound) => {
                             if bound.lifetimes.is_some() {
                                 todo!("Iterating over HRTB lifetimes in trait is not yet supported")
@@ -193,9 +215,7 @@ impl TypeExt for syn::Type {
             }
 
             T::Reference(ref_ty) => {
-                if let Some(lt) = ref_ty.lifetime.as_mut() {
-                    func(lt)
-                }
+                func((&mut ref_ty.lifetime).into());
                 (*ref_ty.elem).lifetimes_iter_mut(func)
             }
 
@@ -213,13 +233,48 @@ impl TypeExt for syn::Type {
     }
 
     fn lifetimes_anonymized(&mut self) {
-        self.lifetimes_iter_mut(&mut |lt| {
+        self.named_lifetimes_iter_mut(&mut |lt| {
             if lt.ident != "_" && lt.ident != "static" {
                 lt.ident = syn::Ident::new("_", Span::call_site());
             }
         });
     }
 
+    fn to_anonymized_lifetimes(&self) -> syn::Type {
+        let mut ty = self.clone();
+        ty.lifetimes_anonymized();
+        ty
+    }
+
+    fn to_hrtb_lifetimes(&self) -> (Option<syn::BoundLifetimes>, syn::Type) {
+        let mut ty = self.clone();
+        let mut lts = vec![];
+
+        let anon_ident = &syn::Ident::new("_", Span::call_site());
+
+        ty.lifetimes_iter_mut(&mut |mut lt_mut| {
+            let ident = match &lt_mut {
+                MaybeElidedLifetimeMut::Elided(v) => {
+                    v.as_ref().map(|l| &l.ident).unwrap_or(anon_ident)
+                }
+                MaybeElidedLifetimeMut::Named(l) => &l.ident,
+            };
+            if ident != "static" {
+                let new_lt = syn::Lifetime::new(&format!("'__fa_f_{ident}"), Span::call_site());
+                if !lts.contains(&new_lt) {
+                    lts.push(new_lt.clone());
+                }
+                lt_mut.set(new_lt);
+            }
+        });
+
+        let for_ = (!lts.is_empty()).then(|| {
+            parse_quote! {
+            for< #( #lts ),* >}
+        });
+        (for_, ty)
+    }
+
     fn topmost_ident(&self) -> Option<&syn::Ident> {
         match self.unparenthesized() {
             syn::Type::Path(p) => Some(&p.path),
@@ -239,6 +294,38 @@ impl TypeExt for syn::Type {
     }
 }
 
+/// Mutable reference to a place that may containing a [`syn::Lifetime`].
+pub(crate) enum MaybeElidedLifetimeMut<'a> {
+    /// [`syn::Lifetime`] may be elided.
+    Elided(&'a mut Option<syn::Lifetime>),
+
+    /// [`syn::Lifetime`] is always present.
+    Named(&'a mut syn::Lifetime),
+}
+
+impl<'a> MaybeElidedLifetimeMut<'a> {
+    /// Assigns the provided [`syn::Lifetime`] to the place pointed by this
+    /// [`MaybeElidedLifetimeMut`] reference.
+    fn set(&mut self, lt: syn::Lifetime) {
+        match self {
+            Self::Elided(v) => **v = Some(lt),
+            Self::Named(l) => **l = lt,
+        }
+    }
+}
+
+impl<'a> From<&'a mut Option<syn::Lifetime>> for MaybeElidedLifetimeMut<'a> {
+    fn from(lt: &'a mut Option<syn::Lifetime>) -> Self {
+        Self::Elided(lt)
+    }
+}
+
+impl<'a> From<&'a mut syn::Lifetime> for MaybeElidedLifetimeMut<'a> {
+    fn from(lt: &'a mut syn::Lifetime) -> Self {
+        Self::Named(lt)
+    }
+}
+
 /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing.
 pub(crate) trait GenericsExt {
     /// Removes all default types out of type parameters and const parameters in these
@@ -354,3 +441,43 @@ impl<'a> VisitMut for ReplaceWithDefaults<'a> {
         }
     }
 }
+
+#[cfg(test)]
+mod test_type_ext_to_hrtb_lifetimes {
+    use quote::quote;
+    use syn::parse_quote;
+
+    use super::TypeExt as _;
+
+    #[test]
+    fn test() {
+        for (input, expected) in [
+            (parse_quote! { &'static T }, quote! { &'static T }),
+            (parse_quote! { &T }, quote! { for<'__fa_f__>: &'__fa_f__ T }),
+            (
+                parse_quote! { &'a mut T },
+                quote! { for<'__fa_f_a>: &'__fa_f_a mut T },
+            ),
+            (
+                parse_quote! { &str },
+                quote! { for<'__fa_f__>: &'__fa_f__ str },
+            ),
+            (
+                parse_quote! { &Cow<'static, str> },
+                quote! { for<'__fa_f__>: &'__fa_f__ Cow<'static, str> },
+            ),
+            (
+                parse_quote! { &Cow<'a, str> },
+                quote! { for<'__fa_f__, '__fa_f_a>: &'__fa_f__ Cow<'__fa_f_a, str> },
+            ),
+        ] {
+            let (actual_for, actual_ty) = syn::Type::to_hrtb_lifetimes(&input);
+            let actual_for = actual_for.map(|for_| quote! { #for_: });
+
+            assert_eq!(
+                quote! { #actual_for #actual_ty }.to_string(),
+                expected.to_string(),
+            );
+        }
+    }
+}
diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs
index 8f11833de..103c3ac07 100644
--- a/juniper_codegen/src/common/scalar.rs
+++ b/juniper_codegen/src/common/scalar.rs
@@ -63,15 +63,17 @@ pub(crate) enum Type {
     /// [`ScalarValue`]: juniper::ScalarValue
     Concrete(syn::Type),
 
-    /// One of type parameters of the original type is specified as [`ScalarValue`].
+    /// One of type parameters of the original type is specified as
+    /// [`ScalarValue`].
     ///
     /// The original type is the type that the code is generated for.
     ///
     /// [`ScalarValue`]: juniper::ScalarValue
     ExplicitGeneric(syn::Ident),
 
-    /// [`ScalarValue`] parametrization is assumed to be generic and is not specified
-    /// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`).
+    /// [`ScalarValue`] parametrization is assumed to be generic and is not
+    /// specified explicitly, or specified as bound predicate (like
+    /// `S: ScalarValue + Send + Sync`).
     ///
     /// [`ScalarValue`]: juniper::ScalarValue
     ImplicitGeneric(Option<syn::PredicateType>),
diff --git a/juniper_codegen/src/graphql_enum/derive.rs b/juniper_codegen/src/graphql_enum/derive.rs
index f0564a264..05b727caf 100644
--- a/juniper_codegen/src/graphql_enum/derive.rs
+++ b/juniper_codegen/src/graphql_enum/derive.rs
@@ -87,6 +87,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         values,
         has_ignored_variants,
     };
diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs
index c14356f65..4718cd87d 100644
--- a/juniper_codegen/src/graphql_enum/mod.rs
+++ b/juniper_codegen/src/graphql_enum/mod.rs
@@ -15,7 +15,7 @@ use syn::{
 };
 
 use crate::common::{
-    deprecation, filter_attrs,
+    behavior, deprecation, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _,
@@ -65,6 +65,17 @@ struct ContainerAttr {
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL enum][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified [`rename::Policy`] for all [values][1] of this
     /// [GraphQL enum][0].
     ///
@@ -118,6 +129,13 @@ impl Parse for ContainerAttr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "rename_all" => {
                     input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
@@ -151,6 +169,7 @@ impl ContainerAttr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             rename_values: try_merge_opt!(rename_values: self, another),
             is_internal: self.is_internal || another.is_internal,
         })
@@ -364,6 +383,13 @@ struct Definition {
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
     scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    behavior: behavior::Type,
+
     /// [Values][1] of this [GraphQL enum][0].
     ///
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
@@ -386,6 +412,18 @@ impl ToTokens for Definition {
         self.impl_from_input_value_tokens().to_tokens(into);
         self.impl_to_input_value_tokens().to_tokens(into);
         self.impl_reflection_traits_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_type().to_tokens(into);
+        self.impl_resolve_type_name().to_tokens(into);
+        self.impl_resolve_value().to_tokens(into);
+        self.impl_resolve_value_async().to_tokens(into);
+        gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into);
+        self.impl_resolve_to_input_value().to_tokens(into);
+        self.impl_resolve_input_value().to_tokens(into);
+        self.impl_graphql_input_type().to_tokens(into);
+        self.impl_graphql_output_type().to_tokens(into);
+        self.impl_graphql_enum().to_tokens(into);
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -416,6 +454,98 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`graphql::InputType`] trait for
+    /// this [GraphQL enum][0].
+    ///
+    /// [`graphql::InputType`]: juniper::graphql::InputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    #[must_use]
+    fn impl_graphql_input_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+                  + ::juniper::resolve::ToInputValue<#sv, #bh>
+                  + ::juniper::resolve::InputValue<#lt, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_input_type() {}
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`graphql::OutputType`] trait for
+    /// this [GraphQL enum][0].
+    ///
+    /// [`graphql::OutputType`]: juniper::graphql::OutputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    #[must_use]
+    fn impl_graphql_output_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+                  + ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+                  + ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_output_type() {}
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`graphql::Enum`] trait for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`graphql::Enum`]: juniper::graphql::Enum
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    #[must_use]
+    fn impl_graphql_enum(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+                  + ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::Enum<#lt, #inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_enum() {
+                    <Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
+                        ::assert_input_type();
+                    <Self as ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>>
+                        ::assert_output_type();
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLType`] trait for this
     /// [GraphQL enum][0].
     ///
@@ -470,6 +600,86 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Type`] trait for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`resolve::Type`]: juniper::resolve::Type
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        let preds = &mut generics.make_where_clause().predicates;
+        preds.push(parse_quote! { #sv: Clone });
+        preds.push(parse_quote! {
+            ::juniper::behavior::Coerce<Self>:
+                ::juniper::resolve::TypeName<#inf>
+                + ::juniper::resolve::InputValueOwned<#sv>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let description = &self.description;
+
+        let values_meta = self.values.iter().map(|v| {
+            let v_name = &v.name;
+            let v_description = &v.description;
+            let v_deprecation = &v.deprecated;
+
+            quote! {
+                ::juniper::meta::EnumValue::new(#v_name)
+                    #v_description
+                    #v_deprecation
+            }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Type<#inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn meta<'__r, '__ti: '__r>(
+                    registry: &mut ::juniper::Registry<'__r, #sv>,
+                    type_info: &'__ti #inf,
+                ) -> ::juniper::meta::MetaType<'__r, #sv>
+                where
+                    #sv: '__r,
+                {
+                    let values = [#( #values_meta ),*];
+
+                    registry.register_enum_with::<
+                        ::juniper::behavior::Coerce<Self>, _,
+                    >(&values, type_info, |meta| {
+                        meta #description
+                    })
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`resolve::TypeName`] trait for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`resolve::TypeName`]: juniper::resolve::TypeName
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_type_name(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::TypeName<#inf, #bh>
+             for #ty #where_clause
+            {
+                fn type_name(_: &#inf) -> &'static str {
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValue`] trait for this
     /// [GraphQL enum][0].
     ///
@@ -528,6 +738,64 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Value`] trait for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            #sv: From<String>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let variant_arms = self.values.iter().map(|v| {
+            let v_ident = &v.ident;
+            let v_name = &v.name;
+
+            quote! {
+                Self::#v_ident => ::std::result::Result::Ok(
+                    ::juniper::Value::<#sv>::scalar(
+                        ::std::string::String::from(#v_name),
+                    ),
+                ),
+            }
+        });
+        let ignored_arm = self.has_ignored_variants.then(|| {
+            let err_msg = format!("Cannot resolve ignored variant of `{}` enum", self.ident);
+
+            quote! {
+                _ => ::std::result::Result::Err(
+                    ::juniper::FieldError::<#sv>::from(#err_msg),
+                ),
+            }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value(
+                    &self,
+                    _: Option<&[::juniper::Selection<'_, #sv>]>,
+                    _: &#inf,
+                    _: &::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::ExecutionResult<#sv> {
+                    match self {
+                        #( #variant_arms )*
+                        #ignored_arm
+                    }
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
     /// [GraphQL enum][0].
     ///
@@ -559,6 +827,48 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ValueAsync`] trait for
+    /// this [GraphQL enum][0].
+    ///
+    /// [`resolve::ValueAsync`]: juniper::resolve::ValueAsync
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_value_async(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        let preds = &mut generics.make_where_clause().predicates;
+        preds.push(parse_quote! {
+            Self: ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+        });
+        preds.push(parse_quote! {
+            #sv: Send
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value_async<'__r>(
+                    &'__r self,
+                    sel_set: Option<&'__r [::juniper::Selection<'_, #sv>]>,
+                    type_info: &'__r #inf,
+                    executor: &'__r ::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::BoxFuture<
+                    '__r, ::juniper::ExecutionResult<#sv>,
+                > {
+                    let v =
+                        <Self as ::juniper::resolve::Value<#inf, #cx, #sv, #bh>>
+                            ::resolve_value(self, sel_set, type_info, executor);
+                    ::std::boxed::Box::pin(::juniper::futures::future::ready(v))
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`FromInputValue`] trait for this
     /// [GraphQL enum][0].
     ///
@@ -598,6 +908,55 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::InputValue`] trait for
+    /// this [GraphQL enum][0].
+    ///
+    /// [`resolve::InputValue`]: juniper::resolve::InputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            #sv: ::juniper::ScalarValue
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let variant_arms = self.values.iter().map(|v| {
+            let v_ident = &v.ident;
+            let v_name = &v.name;
+
+            quote! {
+                ::std::option::Option::Some(#v_name) =>
+                    ::std::result::Result::Ok(Self::#v_ident),
+            }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh>
+             for #ty #where_clause
+            {
+                type Error = ::std::string::String;
+
+                fn try_from_input_value(
+                    input: &#lt ::juniper::graphql::InputValue<#sv>,
+                ) -> ::std::result::Result<Self, Self::Error> {
+                    match input
+                        .as_enum_value()
+                        .or_else(|| input.as_string_value())
+                    {
+                        #( #variant_arms )*
+                        _ => ::std::result::Result::Err(
+                            ::std::format!("Unknown enum value: {}", input),
+                        ),
+                    }
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`ToInputValue`] trait for this
     /// [GraphQL enum][0].
     ///
@@ -643,6 +1002,53 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ToInputValue`] trait for
+    /// this [GraphQL enum][0].
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_resolve_to_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            #sv: From<String>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let variant_arms = self.values.iter().map(|v| {
+            let v_ident = &v.ident;
+            let v_name = &v.name;
+
+            quote! {
+                Self::#v_ident => ::juniper::graphql::InputValue::<#sv>::scalar(
+                    ::std::string::String::from(#v_name),
+                ),
+            }
+        });
+        let ignored_arm = self.has_ignored_variants.then(|| {
+            let err_msg = format!("Cannot resolve ignored variant of `{}` enum", self.ident);
+
+            quote! {
+                _ => ::std::panic!(#err_msg),
+            }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh>
+             for #ty #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> {
+                    match self {
+                        #( #variant_arms )*
+                        #ignored_arm
+                    }
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
     /// [`WrappedType`] traits for this [GraphQL enum][0].
     ///
@@ -684,6 +1090,47 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this
+    /// [GraphQL enum][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Enums
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types =
+                    &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+        }
+    }
+
     /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
     /// similar) implementation of this enum.
     ///
@@ -742,4 +1189,16 @@ impl Definition {
 
         generics
     }
+
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let generics = self.generics.clone();
+        let ty = {
+            let ident = &self.ident;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident #ty_gen }
+        };
+        (ty, generics)
+    }
 }
diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs
index 370fc2002..562343ea2 100644
--- a/juniper_codegen/src/graphql_input_object/derive.rs
+++ b/juniper_codegen/src/graphql_input_object/derive.rs
@@ -80,6 +80,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         fields,
     };
 
@@ -94,13 +95,13 @@ fn parse_field(
     renaming: rename::Policy,
     is_internal: bool,
 ) -> Option<FieldDefinition> {
-    let field_attr = FieldAttr::from_attrs("graphql", &f.attrs)
+    let attr = FieldAttr::from_attrs("graphql", &f.attrs)
         .map_err(|e| proc_macro_error::emit_error!(e))
         .ok()?;
 
     let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?;
 
-    let name = field_attr
+    let name = attr
         .name
         .map_or_else(
             || renaming.apply(&ident.unraw().to_string()),
@@ -114,10 +115,11 @@ fn parse_field(
     Some(FieldDefinition {
         ident: ident.clone(),
         ty: f.ty.clone(),
-        default: field_attr.default.map(SpanContainer::into_inner),
+        default: attr.default.map(SpanContainer::into_inner),
+        behavior: attr.behavior.into(),
         name,
-        description: field_attr.description.map(SpanContainer::into_inner),
-        ignored: field_attr.ignore.is_some(),
+        description: attr.description.map(SpanContainer::into_inner),
+        ignored: attr.ignore.is_some(),
     })
 }
 
diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs
index 93c21d6f8..c339b4346 100644
--- a/juniper_codegen/src/graphql_input_object/mod.rs
+++ b/juniper_codegen/src/graphql_input_object/mod.rs
@@ -15,7 +15,7 @@ use syn::{
 };
 
 use crate::common::{
-    default, filter_attrs,
+    behavior, default, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _,
@@ -66,6 +66,17 @@ struct ContainerAttr {
     /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL input object][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified [`rename::Policy`] for all fields of this
     /// [GraphQL input object][0].
     ///
@@ -118,6 +129,13 @@ impl Parse for ContainerAttr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "rename_all" => {
                     input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
@@ -151,6 +169,7 @@ impl ContainerAttr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             rename_fields: try_merge_opt!(rename_fields: self, another),
             is_internal: self.is_internal || another.is_internal,
         })
@@ -185,6 +204,16 @@ struct FieldAttr {
     /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
     name: Option<SpanContainer<String>>,
 
+    /// Explicitly specified [description][2] of this
+    /// [GraphQL input object field][1].
+    ///
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
+    description: Option<SpanContainer<Description>>,
+
     /// Explicitly specified [default value][2] of this
     /// [GraphQL input object field][1] to be used used in case a field value is
     /// not provided.
@@ -195,15 +224,18 @@ struct FieldAttr {
     /// [2]: https://spec.graphql.org/October2021#DefaultValue
     default: Option<SpanContainer<default::Value>>,
 
-    /// Explicitly specified [description][2] of this
-    /// [GraphQL input object field][1].
+    /// Explicitly specified type of the custom [`Behavior`] this
+    /// [GraphQL input object field][1] implementation is parametrized with, to
+    /// [coerce] in the generated code from.
     ///
-    /// If [`None`], then Rust doc comment will be used as the [description][2],
-    /// if any.
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
     ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
     /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
-    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
-    description: Option<SpanContainer<Description>>,
+    /// [coerce]: juniper::behavior::Coerce
+    behavior: Option<SpanContainer<behavior::Type>>,
 
     /// Explicitly specified marker for the Rust struct field to be ignored and
     /// not included into the code generated for a [GraphQL input object][0]
@@ -234,17 +266,24 @@ impl Parse for FieldAttr {
                         ))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "desc" | "description" => {
+                    input.parse::<token::Eq>()?;
+                    let desc = input.parse::<Description>()?;
+                    out.description
+                        .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "default" => {
                     let val = input.parse::<default::Value>()?;
                     out.default
                         .replace(SpanContainer::new(ident.span(), Some(val.span()), val))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
-                "desc" | "description" => {
+                "behave" | "behavior" => {
                     input.parse::<token::Eq>()?;
-                    let desc = input.parse::<Description>()?;
-                    out.description
-                        .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "ignore" | "skip" => out
@@ -267,8 +306,9 @@ impl FieldAttr {
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
             name: try_merge_opt!(name: self, another),
-            default: try_merge_opt!(default: self, another),
             description: try_merge_opt!(description: self, another),
+            default: try_merge_opt!(default: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             ignore: try_merge_opt!(ignore: self, another),
         })
     }
@@ -314,6 +354,14 @@ struct FieldDefinition {
     /// [2]: https://spec.graphql.org/October2021#DefaultValue
     default: Option<default::Value>,
 
+    /// [`Behavior`] parametrization of this [GraphQL input object field][1]
+    /// implementation to [coerce] from in the generated code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [coerce]: juniper::behavior::Coerce
+    behavior: behavior::Type,
+
     /// Name of this [GraphQL input object field][1] in GraphQL schema.
     ///
     /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
@@ -338,6 +386,14 @@ struct FieldDefinition {
     ignored: bool,
 }
 
+impl FieldDefinition {
+    /// Indicates whether this [`FieldDefinition`] uses [`Default::default()`]
+    /// ans its [`FieldDefinition::default`] value.
+    fn needs_default_trait_bound(&self) -> bool {
+        matches!(self.default, Some(default::Value::Default))
+    }
+}
+
 /// Representation of [GraphQL input object][0] for code generation.
 ///
 /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
@@ -383,6 +439,13 @@ struct Definition {
     /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
     scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    behavior: behavior::Type,
+
     /// [Fields][1] of this [GraphQL input object][0].
     ///
     /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
@@ -399,6 +462,14 @@ impl ToTokens for Definition {
         self.impl_from_input_value_tokens().to_tokens(into);
         self.impl_to_input_value_tokens().to_tokens(into);
         self.impl_reflection_traits_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_type().to_tokens(into);
+        self.impl_resolve_type_name().to_tokens(into);
+        self.impl_resolve_to_input_value().to_tokens(into);
+        self.impl_resolve_input_value().to_tokens(into);
+        self.impl_graphql_input_type().to_tokens(into);
+        self.impl_graphql_input_object().to_tokens(into);
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -440,6 +511,88 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`graphql::InputType`] trait for
+    /// [GraphQL input object][0].
+    ///
+    /// [`graphql::InputType`]: juniper::graphql::InputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_graphql_input_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+                  + ::juniper::resolve::ToInputValue<#sv, #bh>
+                  + ::juniper::resolve::InputValue<#lt, #sv, #bh>
+        });
+        for f in self.fields.iter().filter(|f| !f.ignored) {
+            let field_ty = &f.ty;
+            let field_bh = &f.behavior;
+            generics.make_where_clause().predicates.push(parse_quote! {
+                #field_ty:
+                    ::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh>
+            });
+        }
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let fields_assertions = self.fields.iter().filter_map(|f| {
+            (!f.ignored).then(|| {
+                let field_ty = &f.ty;
+                let field_bh = &f.behavior;
+
+                quote! {
+                    <#field_ty as
+                     ::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh>>
+                        ::assert_input_type();
+                }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_input_type() {
+                    #( #fields_assertions )*
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`graphql::InputObject`] trait for
+    /// this [GraphQL input object][0].
+    ///
+    /// [`graphql::InputObject`]: juniper::graphql::InputObject
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_graphql_input_object(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::InputObject<#lt, #inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_input_object() {
+                    <Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
+                        ::assert_input_type();
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLType`] trait for this
     /// [GraphQL input object][0].
     ///
@@ -500,6 +653,119 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Type`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`resolve::Type`]: juniper::resolve::Type
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    fn impl_resolve_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        let preds = &mut generics.make_where_clause().predicates;
+        preds.push(parse_quote! { #sv: Clone });
+        preds.push(parse_quote! {
+            ::juniper::behavior::Coerce<Self>:
+                ::juniper::resolve::TypeName<#inf>
+                + ::juniper::resolve::InputValueOwned<#sv>
+        });
+        for f in self.fields.iter().filter(|f| !f.ignored) {
+            let field_ty = &f.ty;
+            let field_bh = &f.behavior;
+            preds.push(parse_quote! {
+                ::juniper::behavior::Coerce<#field_ty>:
+                    ::juniper::resolve::Type<#inf, #sv>
+                    + ::juniper::resolve::InputValueOwned<#sv>
+            });
+            if f.default.is_some() {
+                preds.push(parse_quote! {
+                    #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh>
+                });
+            }
+            if f.needs_default_trait_bound() {
+                preds.push(parse_quote! {
+                    #field_ty: ::std::default::Default
+                });
+            }
+        }
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let description = &self.description;
+
+        let fields_meta = self.fields.iter().filter_map(|f| {
+            (!f.ignored).then(|| {
+                let f_ty = &f.ty;
+                let f_bh = &f.behavior;
+                let f_name = &f.name;
+                let f_description = &f.description;
+                let f_default = f.default.as_ref().map(|expr| {
+                    quote! {
+                        .default_value(
+                            <#f_ty as
+                             ::juniper::resolve::ToInputValue<#sv, #f_bh>>
+                                ::to_input_value(&{ #expr }),
+                        )
+                    }
+                });
+
+                quote! {
+                    registry.arg_reworked::<
+                        ::juniper::behavior::Coerce<#f_ty>, _,
+                    >(#f_name, type_info)
+                        #f_description
+                        #f_default
+                }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Type<#inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn meta<'__r, '__ti: '__r>(
+                    registry: &mut ::juniper::Registry<'__r, #sv>,
+                    type_info: &'__ti #inf,
+                ) -> ::juniper::meta::MetaType<'__r, #sv>
+                where
+                    #sv: '__r,
+                {
+                    let fields = [#( #fields_meta ),*];
+
+                    registry.register_input_object_with::<
+                        ::juniper::behavior::Coerce<Self>, _,
+                    >(&fields, type_info, |meta| {
+                        meta #description
+                    })
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`resolve::TypeName`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`resolve::TypeName`]: juniper::resolve::TypeName
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    fn impl_resolve_type_name(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::TypeName<#inf, #bh>
+             for #ty #where_clause
+            {
+                fn type_name(_: &#inf) -> &'static str {
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValue`] trait for this
     /// [GraphQL input object][0].
     ///
@@ -631,6 +897,96 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::InputValue`] trait for
+    /// this [GraphQL input object][0].
+    ///
+    /// [`resolve::InputValue`]: juniper::resolve::InputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    fn impl_resolve_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            #sv: ::juniper::ScalarValue
+        });
+        for f in self.fields.iter().filter(|f| !f.ignored) {
+            let field_ty = &f.ty;
+            let field_bh = &f.behavior;
+            generics.make_where_clause().predicates.push(parse_quote! {
+                #field_ty: ::juniper::resolve::InputValue<#lt, #sv, #field_bh>
+            });
+        }
+        for f in self.fields.iter().filter(|f| f.needs_default_trait_bound()) {
+            let field_ty = &f.ty;
+            generics.make_where_clause().predicates.push(parse_quote! {
+                #field_ty: ::std::default::Default,
+            });
+        }
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let fields = self.fields.iter().map(|f| {
+            let field = &f.ident;
+            let field_ty = &f.ty;
+            let field_bh = &f.behavior;
+
+            let constructor = if f.ignored {
+                let expr = f.default.clone().unwrap_or_default();
+
+                quote! { #expr }
+            } else {
+                let name = &f.name;
+
+                let fallback = f.default.as_ref().map_or_else(
+                    || {
+                        quote! {
+                            <#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>
+                                ::try_from_implicit_null()
+                                .map_err(::juniper::IntoFieldError::<#sv>::into_field_error)?
+                        }
+                    },
+                    |expr| quote! { #expr },
+                );
+
+                quote! {
+                    match obj.get(#name) {
+                        ::std::option::Option::Some(v) => {
+                            <#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>
+                                ::try_from_input_value(v)
+                                .map_err(::juniper::IntoFieldError::<#sv>::into_field_error)?
+                        }
+                        ::std::option::Option::None => { #fallback }
+                    }
+                }
+            };
+
+            quote! { #field: { #constructor }, }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh>
+             for #ty #where_clause
+            {
+                type Error = ::juniper::FieldError<#sv>;
+
+                fn try_from_input_value(
+                    input: &#lt ::juniper::graphql::InputValue<#sv>,
+                ) -> ::std::result::Result<Self, Self::Error> {
+                    let obj = input
+                        .to_object_value()
+                        .ok_or_else(|| ::std::format!(
+                            "Expected input object, found: {}", input,
+                        ))?;
+
+                    ::std::result::Result::Ok(Self {
+                        #( #fields )*
+                    })
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`ToInputValue`] trait for this
     /// [GraphQL input object][0].
     ///
@@ -663,11 +1019,52 @@ impl Definition {
                 #where_clause
             {
                 fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
-                    ::juniper::InputValue::object(
-                        #[allow(deprecated)]
-                        ::std::array::IntoIter::new([#( #fields ),*])
-                            .collect()
-                    )
+                    ::juniper::InputValue::object([#( #fields ),*])
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`resolve::ToInputValue`] trait for
+    /// this [GraphQL input object][0].
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    fn impl_resolve_to_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, mut generics) = gen::mix_scalar_value(generics);
+        for f in self.fields.iter().filter(|f| !f.ignored) {
+            let field_ty = &f.ty;
+            let field_bh = &f.behavior;
+            generics.make_where_clause().predicates.push(parse_quote! {
+                #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh>
+            });
+        }
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let fields = self.fields.iter().filter_map(|f| {
+            (!f.ignored).then(|| {
+                let field = &f.ident;
+                let field_ty = &f.ty;
+                let field_bh = &f.behavior;
+                let name = &f.name;
+
+                quote! {
+                    (#name, <#field_ty as
+                             ::juniper::resolve::ToInputValue<#sv, #field_bh>>
+                                ::to_input_value(&self.#field))
+                }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh>
+             for #ty #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> {
+                    ::juniper::InputValue::object([#( #fields ),*])
                 }
             }
         }
@@ -716,6 +1113,47 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types =
+                    &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+        }
+    }
+
     /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
     /// similar) implementation of this struct.
     ///
@@ -775,4 +1213,16 @@ impl Definition {
 
         generics
     }
+
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let generics = self.generics.clone();
+        let ty = {
+            let ident = &self.ident;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident #ty_gen }
+        };
+        (ty, generics)
+    }
 }
diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs
index 634a1419a..16bf2fbc1 100644
--- a/juniper_codegen/src/graphql_interface/attr.rs
+++ b/juniper_codegen/src/graphql_interface/attr.rs
@@ -120,6 +120,7 @@ fn expand_on_trait(
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         fields,
         implemented_for: attr
             .implemented_for
@@ -207,6 +208,7 @@ fn parse_trait_method(
         description: attr.description.map(SpanContainer::into_inner),
         deprecated: attr.deprecated.map(SpanContainer::into_inner),
         ident: method_ident.clone(),
+        behavior: attr.behavior.into(),
         arguments: Some(arguments),
         has_receiver: method.sig.receiver().is_some(),
         is_async: method.sig.asyncness.is_some(),
@@ -301,6 +303,7 @@ fn expand_on_derive_input(
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         fields,
         implemented_for: attr
             .implemented_for
@@ -372,6 +375,7 @@ fn parse_struct_field(
         description: attr.description.map(SpanContainer::into_inner),
         deprecated: attr.deprecated.map(SpanContainer::into_inner),
         ident: field_ident.clone(),
+        behavior: attr.behavior.into(),
         arguments: None,
         has_receiver: false,
         is_async: false,
diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs
index 25bc096bd..52f88792a 100644
--- a/juniper_codegen/src/graphql_interface/derive.rs
+++ b/juniper_codegen/src/graphql_interface/derive.rs
@@ -93,6 +93,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         fields,
         implemented_for: attr
             .implemented_for
@@ -149,6 +150,7 @@ fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option<field::D
         description: attr.description.map(SpanContainer::into_inner),
         deprecated: attr.deprecated.map(SpanContainer::into_inner),
         ident: field_ident.clone(),
+        behavior: attr.behavior.into(),
         arguments: None,
         has_receiver: false,
         is_async: false,
diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs
index afb856e14..0c5fcf0ee 100644
--- a/juniper_codegen/src/graphql_interface/mod.rs
+++ b/juniper_codegen/src/graphql_interface/mod.rs
@@ -20,7 +20,7 @@ use syn::{
 };
 
 use crate::common::{
-    field, filter_attrs, gen,
+    behavior, field, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
         GenericsExt as _, ParseBufferExt as _,
@@ -116,6 +116,17 @@ struct Attr {
     /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL interface][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified marker indicating that the Rust trait should be
     /// transformed into [`async_trait`].
     ///
@@ -175,6 +186,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "for" | "implementers" => {
                     input.parse::<token::Eq>()?;
                     for impler in input.parse_maybe_wrapped_and_punctuated::<
@@ -245,6 +263,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined),
             implements: try_merge_hashset!(implements: self, another => span_joined),
             r#enum: try_merge_opt!(r#enum: self, another),
@@ -324,6 +343,13 @@ struct Definition {
     /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
     scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL interface][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    behavior: behavior::Type,
+
     /// Defined [GraphQL fields][2] of this [GraphQL interface][1].
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
@@ -368,6 +394,10 @@ impl ToTokens for Definition {
         self.impl_field_meta_tokens().to_tokens(into);
         self.impl_field_tokens().to_tokens(into);
         self.impl_async_field_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_value().to_tokens(into);
+        gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into);
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -822,6 +852,36 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Value`] trait for this
+    /// [GraphQL interface][0].
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    fn impl_resolve_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value(
+                    &self,
+                    _: Option<&[::juniper::Selection<'_, #sv>]>,
+                    _: &#inf,
+                    _: &::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::ExecutionResult<#sv> {
+                    todo!()
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
     /// [GraphQL interface][1].
     ///
@@ -953,6 +1013,69 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`], [`reflect::WrappedType`] and
+    /// [`reflect::Fields`] traits for this [GraphQL interface][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::Fields`]: juniper::reflect::Fields
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+        let implers = &self.implemented_for;
+        let interfaces = &self.implements;
+        let fields = self.fields.iter().map(|f| &f.name);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME,
+                    #( <#implers as ::juniper::reflect::BaseType<#bh>>::NAME ),*
+                ];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::Implements<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[#(
+                    <#interfaces as ::juniper::reflect::BaseType<#bh>>::NAME
+                ),*];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::Fields<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Names = &[#( #fields ),*];
+            }
+        }
+    }
+
     /// Returns generated code implementing [`FieldMeta`] for each field of this
     /// [GraphQL interface][1].
     ///
@@ -1373,6 +1496,20 @@ impl Definition {
         generics
     }
 
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let generics = self.generics.clone();
+
+        let ty = {
+            let ident = &self.enum_alias_ident;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident #ty_gen }
+        };
+
+        (ty, generics)
+    }
+
     /// Indicates whether this enum has non-exhaustive phantom variant to hold
     /// type parameters.
     #[must_use]
diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs
index 37b47d166..83c084848 100644
--- a/juniper_codegen/src/graphql_object/attr.rs
+++ b/juniper_codegen/src/graphql_object/attr.rs
@@ -117,6 +117,7 @@ where
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.into(),
         fields,
         interfaces: attr
             .interfaces
@@ -218,6 +219,7 @@ fn parse_field(
         description: attr.description.map(SpanContainer::into_inner),
         deprecated: attr.deprecated.map(SpanContainer::into_inner),
         ident: method_ident.clone(),
+        behavior: attr.behavior.into(),
         arguments: Some(arguments),
         has_receiver: method.sig.receiver().is_some(),
         is_async: method.sig.asyncness.is_some(),
diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs
index 189ed261d..be1c939b1 100644
--- a/juniper_codegen/src/graphql_object/derive.rs
+++ b/juniper_codegen/src/graphql_object/derive.rs
@@ -94,6 +94,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
             .map(SpanContainer::into_inner)
             .unwrap_or_else(|| parse_quote! { () }),
         scalar,
+        behavior: attr.behavior.into(),
         fields,
         interfaces: attr
             .interfaces
@@ -143,6 +144,7 @@ fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option<field::D
         description: attr.description.map(SpanContainer::into_inner),
         deprecated: attr.deprecated.map(SpanContainer::into_inner),
         ident: field_ident.clone(),
+        behavior: attr.behavior.into(),
         arguments: None,
         has_receiver: false,
         is_async: false,
diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs
index facc73002..a0cef4cb2 100644
--- a/juniper_codegen/src/graphql_object/mod.rs
+++ b/juniper_codegen/src/graphql_object/mod.rs
@@ -18,10 +18,10 @@ use syn::{
 };
 
 use crate::common::{
-    field, filter_attrs, gen,
+    behavior, field, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
-        GenericsExt as _, ParseBufferExt as _, TypeExt,
+        GenericsExt as _, ParseBufferExt as _, TypeExt as _,
     },
     rename, scalar, Description, SpanContainer,
 };
@@ -71,6 +71,17 @@ pub(crate) struct Attr {
     /// [1]: https://spec.graphql.org/October2021#sec-Objects
     pub(crate) scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL object][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    pub(crate) behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1]
     /// type implements.
     ///
@@ -130,6 +141,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "impl" | "implements" | "interfaces" => {
                     input.parse::<token::Eq>()?;
                     for iface in input.parse_maybe_wrapped_and_punctuated::<
@@ -175,6 +193,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             interfaces: try_merge_hashset!(interfaces: self, another => span_joined),
             rename_fields: try_merge_opt!(rename_fields: self, another),
             is_internal: self.is_internal || another.is_internal,
@@ -240,6 +259,13 @@ pub(crate) struct Definition<Operation: ?Sized> {
     /// [1]: https://spec.graphql.org/October2021#sec-Objects
     pub(crate) scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL object][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    pub(crate) behavior: behavior::Type,
+
     /// Defined [GraphQL fields][2] of this [GraphQL object][1].
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Objects
@@ -297,7 +323,7 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
                 // Modify lifetime names to omit "lifetime name `'a` shadows a
                 // lifetime name that is already in scope" error.
                 let mut ty = self.ty.clone();
-                ty.lifetimes_iter_mut(&mut |lt| {
+                ty.named_lifetimes_iter_mut(&mut |lt| {
                     let ident = lt.ident.unraw();
                     lt.ident = format_ident!("__fa__{ident}");
                     lifetimes.push(lt.clone());
@@ -324,6 +350,12 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
         (quote! { #impl_generics }, where_clause.cloned())
     }
 
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        (self.ty.clone(), self.generics.clone())
+    }
+
     /// Returns generated code implementing [`marker::IsOutputType`] trait for
     /// this [GraphQL object][1].
     ///
@@ -418,6 +450,274 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`], [`reflect::Implements`],
+    /// [`reflect::WrappedType`] and [`reflect::Fields`] traits for this
+    /// [GraphQL object][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::Fields`]: juniper::reflect::Fields
+    /// [`reflect::Implements`]: juniper::reflect::Implements
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    #[must_use]
+    pub(crate) fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+        let interfaces = self.interfaces.iter();
+        let fields = self.fields.iter().map(|f| &f.name);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types =
+                    &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::Implements<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[#(
+                    <#interfaces as ::juniper::reflect::BaseType<#bh>>::NAME
+                ),*];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::Fields<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Names = &[#( #fields ),*];
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`reflect::Field`] trait for each
+    /// [field][1] of this [GraphQL object][0].
+    ///
+    /// [`reflect::Field`]: juniper::reflect::Field
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn impl_reflect_field(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        self.fields
+            .iter()
+            .map(|field| {
+                let (f_name, f_ty, f_bh) = (&field.name, &field.ty, &field.behavior);
+
+                let arguments = field.arguments.as_ref();
+                let arguments = arguments
+                    .iter()
+                    .flat_map(|vec| vec.iter().filter_map(field::MethodArgument::as_regular))
+                    .map(|arg| {
+                        let (a_name, a_ty, a_bh) = (&arg.name, &arg.ty, &arg.behavior);
+
+                        quote! {(
+                            #a_name,
+                            <#a_ty as ::juniper::reflect::BaseType<#a_bh>>
+                                ::NAME,
+                            <#a_ty as ::juniper::reflect::WrappedType<#a_bh>>
+                                ::VALUE,
+                        )}
+                    });
+
+                quote! {
+                    #[automatically_derived]
+                    impl #impl_gens ::juniper::reflect::Field<
+                        { ::juniper::reflect::fnv1a128(#f_name) }, #bh,
+                    > for #ty #where_clause {
+                        const TYPE: ::juniper::reflect::Type =
+                            <#f_ty as ::juniper::reflect::BaseType<#f_bh>>
+                                ::NAME;
+
+                        const SUB_TYPES: ::juniper::reflect::Types =
+                            <#f_ty as ::juniper::reflect::BaseSubTypes<#f_bh>>
+                                ::NAMES;
+
+                        const WRAPPED_VALUE: juniper::reflect::WrappedValue =
+                            <#f_ty as ::juniper::reflect::WrappedType<#f_bh>>
+                                ::VALUE;
+
+                        const ARGUMENTS: &'static [(
+                            ::juniper::reflect::Name,
+                            ::juniper::reflect::Type,
+                            ::juniper::reflect::WrappedValue,
+                        )] = &[#( #arguments ),*];
+                    }
+                }
+            })
+            .collect()
+    }
+
+    /// Returns generated code implementing [`resolve::Value`] trait for this
+    /// [GraphQL object][0].
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    pub(crate) fn impl_resolve_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value(
+                    &self,
+                    _: Option<&[::juniper::Selection<'_, #sv>]>,
+                    _: &#inf,
+                    _: &::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::ExecutionResult<#sv> {
+                    todo!()
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`resolve::StaticField`] trait for
+    /// each [field][1] of this [GraphQL object][0].
+    ///
+    /// [`resolve::StaticField`]: juniper::resolve::StaticField
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn impl_resolve_static_field(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+
+        self.fields
+            .iter()
+            .map(|field| {
+                let mut generics = generics.clone();
+                let (f_name, f_bh) = (&field.name, &field.behavior);
+                let (f_ident, f_ty) = (&field.ident, &field.ty);
+
+                let body = if !field.is_async {
+                    let (f_for_ty, f_hrtb_ty) = f_ty.to_hrtb_lifetimes();
+                    generics.make_where_clause().predicates.push(parse_quote! {
+                        #f_for_ty #f_hrtb_ty:
+                            ::juniper::resolve::Resolvable<#sv, #f_bh>
+                    });
+                    generics.make_where_clause().predicates.push(parse_quote! {
+                        #f_for_ty <#f_hrtb_ty as ::juniper::resolve::Resolvable<#sv, #f_bh>>::Value:
+                            ::juniper::resolve::Value<#inf, #cx, #sv, #f_bh>
+                    });
+
+                    let val = if field.is_method() {
+                        let f_anon_ty = f_ty.to_anonymized_lifetimes();
+                        let args = field
+                            .arguments
+                            .as_ref()
+                            .unwrap()
+                            .iter()
+                            .map(|arg| match arg {
+                                field::MethodArgument::Regular(arg) => {
+                                    let a_name = &arg.name;
+                                    let (a_ty, a_bh) = (&arg.ty, &arg.behavior);
+                                    generics.make_where_clause().predicates.push(parse_quote! {
+                                        #a_ty: ::juniper::resolve::InputValueOwned<#sv, #a_bh>
+                                    });
+                                    quote! {
+                                        args.resolve::<#a_ty, #a_bh>(#a_name)?
+                                    }
+                                }
+                                field::MethodArgument::Context(cx_ty) => {
+                                    generics.make_where_clause().predicates.push(parse_quote! {
+                                        #cx: ::juniper::Extract<#cx_ty>
+                                    });
+                                    quote! {
+                                        <#cx as ::juniper::Extract<#cx_ty>>
+                                            ::extract(executor.context())
+                                    }
+                                }
+                                field::MethodArgument::Executor => {
+                                    quote! {
+                                        executor
+                                    }
+                                }
+                            });
+                        let rcv = field.has_receiver.then(|| {
+                            quote! { self, }
+                        });
+
+                        quote! {
+                            <#f_anon_ty as ::juniper::resolve::Resolvable<#sv, #f_bh>>
+                                ::into_value(Self::#f_ident(#rcv #( #args ),*))?
+                        }
+                    } else {
+                        quote! {
+                            self.#f_ident
+                        }
+                    };
+
+                    quote! {
+                        executor.resolve_value::<#f_bh, _, _>(&#val, type_info)
+                    }
+                } else {
+                    quote! {
+                        ::std::panic!(
+                             "Tried to resolve async field `{}` on type `{}` with a sync resolver",
+                             #f_name,
+                             <Self as ::juniper::reflect::BaseType<#bh>>::NAME,
+                         );
+                    }
+                };
+
+                let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+                quote! {
+                    #[automatically_derived]
+                    impl #impl_gens ::juniper::resolve::StaticField<
+                        { ::juniper::reflect::fnv1a128(#f_name) },
+                        #inf, #cx, #sv, #bh,
+                    > for #ty #where_clause {
+                        fn resolve_static_field(
+                            &self,
+                            args: &::juniper::Arguments<'_, #sv>,
+                            type_info: &#inf,
+                            executor: &::juniper::Executor<'_, '_, #cx, #sv>,
+                        ) -> ::juniper::ExecutionResult<#sv> {
+                            #body
+                        }
+                    }
+                }
+            })
+            .collect()
+    }
+
     /// Returns generated code implementing [`GraphQLType`] trait for this
     /// [GraphQL object][1].
     ///
@@ -496,6 +796,12 @@ impl ToTokens for Definition<Query> {
         self.impl_field_meta_tokens().to_tokens(into);
         self.impl_field_tokens().to_tokens(into);
         self.impl_async_field_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_value().to_tokens(into);
+        gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into);
+        self.impl_resolve_static_field().to_tokens(into);
+        self.impl_reflect().to_tokens(into);
+        self.impl_reflect_field().to_tokens(into);
     }
 }
 
diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs
index 90024820f..a336712c1 100644
--- a/juniper_codegen/src/graphql_scalar/attr.rs
+++ b/juniper_codegen/src/graphql_scalar/attr.rs
@@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned};
 
 use crate::common::{diagnostic, parse, scalar, SpanContainer};
 
-use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken, TypeOrIdent};
+use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken};
 
 /// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro.
 const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr;
@@ -47,7 +47,7 @@ fn expand_on_type_alias(
     let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
     let def = Definition {
-        ty: TypeOrIdent::Type(ast.ty.clone()),
+        ident: ast.ident.clone(),
         where_clause: attr
             .where_clause
             .map_or_else(Vec::new, |cl| cl.into_inner()),
@@ -60,6 +60,8 @@ fn expand_on_type_alias(
         description: attr.description.map(SpanContainer::into_inner),
         specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
         scalar,
+        scalar_value: attr.scalar.as_deref().into(),
+        behavior: attr.behavior.into(),
     };
 
     Ok(quote! {
@@ -74,11 +76,12 @@ fn expand_on_derive_input(
     ast: syn::DeriveInput,
 ) -> syn::Result<TokenStream> {
     let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
+
     let methods = parse_derived_methods(&ast, &attr)?;
     let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
     let def = Definition {
-        ty: TypeOrIdent::Ident(ast.ident.clone()),
+        ident: ast.ident.clone(),
         where_clause: attr
             .where_clause
             .map_or_else(Vec::new, |cl| cl.into_inner()),
@@ -91,6 +94,8 @@ fn expand_on_derive_input(
         description: attr.description.map(SpanContainer::into_inner),
         specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
         scalar,
+        scalar_value: attr.scalar.as_deref().into(),
+        behavior: attr.behavior.into(),
     };
 
     Ok(quote! {
diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs
index ff4f93b12..53a27ddbf 100644
--- a/juniper_codegen/src/graphql_scalar/derive.rs
+++ b/juniper_codegen/src/graphql_scalar/derive.rs
@@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned};
 
 use crate::common::{diagnostic, scalar, SpanContainer};
 
-use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent};
+use super::{Attr, Definition, Field, Methods, ParseToken};
 
 /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro.
 const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive;
@@ -15,23 +15,28 @@ const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive;
 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);
 
+    let name = attr
+        .name
+        .map(SpanContainer::into_inner)
+        .unwrap_or_else(|| ast.ident.to_string());
+
     Ok(Definition {
-        ty: TypeOrIdent::Ident(ast.ident.clone()),
+        ident: ast.ident,
         where_clause: attr
             .where_clause
             .map_or_else(Vec::new, |cl| cl.into_inner()),
-        generics: ast.generics.clone(),
+        generics: ast.generics,
         methods,
-        name: attr
-            .name
-            .map(SpanContainer::into_inner)
-            .unwrap_or_else(|| ast.ident.to_string()),
+        name,
         description: attr.description.map(SpanContainer::into_inner),
         specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
         scalar,
+        scalar_value: attr.scalar.as_deref().into(),
+        behavior: attr.behavior.into(),
     }
     .to_token_stream())
 }
@@ -81,7 +86,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
                     .first()
                     .filter(|_| fields.unnamed.len() == 1)
                     .cloned()
-                    .map(Field::Unnamed)
+                    .map(Field::try_from)
+                    .transpose()?
                     .ok_or_else(|| {
                         ERR.custom_error(
                             ast.span(),
@@ -94,7 +100,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
                     .first()
                     .filter(|_| fields.named.len() == 1)
                     .cloned()
-                    .map(Field::Named)
+                    .map(Field::try_from)
+                    .transpose()?
                     .ok_or_else(|| {
                         ERR.custom_error(
                             ast.span(),
diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs
index 2280d9c24..eba30018c 100644
--- a/juniper_codegen/src/graphql_scalar/mod.rs
+++ b/juniper_codegen/src/graphql_scalar/mod.rs
@@ -2,6 +2,9 @@
 //!
 //! [1]: https://spec.graphql.org/October2021#sec-Scalars
 
+pub mod attr;
+pub mod derive;
+
 use proc_macro2::{Literal, TokenStream};
 use quote::{format_ident, quote, ToTokens, TokenStreamExt};
 use syn::{
@@ -15,7 +18,7 @@ use syn::{
 use url::Url;
 
 use crate::common::{
-    filter_attrs,
+    behavior, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _,
@@ -23,9 +26,6 @@ use crate::common::{
     scalar, Description, SpanContainer,
 };
 
-pub mod attr;
-pub mod derive;
-
 /// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when
 /// generating code for [GraphQL scalar][1].
 ///
@@ -61,6 +61,17 @@ struct Attr {
     /// [1]: https://spec.graphql.org/October2021#sec-Scalars
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL scalar][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified function to be used as
     /// [`ToInputValue::to_input_value`] implementation.
     ///
@@ -86,7 +97,7 @@ 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
+    /// Indicator for single-field structs allowing to delegate implementations
     /// of non-provided resolvers to that field.
     transparent: bool,
 }
@@ -132,6 +143,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "to_output_with" => {
                     input.parse::<token::Eq>()?;
                     let scl = input.parse::<syn::ExprPath>()?;
@@ -232,6 +250,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             specified_by_url: try_merge_opt!(specified_by_url: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             to_output: try_merge_opt!(to_output: self, another),
             from_input: try_merge_opt!(from_input: self, another),
             parse_token: try_merge_opt!(parse_token: self, another),
@@ -256,17 +275,6 @@ impl Attr {
     }
 }
 
-/// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of
-/// `#[derive(GraphQLScalar)]`.
-#[derive(Clone)]
-enum TypeOrIdent {
-    /// [`syn::Type`].
-    Type(Box<syn::Type>),
-
-    /// [`syn::Ident`].
-    Ident(syn::Ident),
-}
-
 /// Definition of [GraphQL scalar][1] for code generation.
 ///
 /// [1]: https://spec.graphql.org/October2021#sec-Scalars
@@ -276,10 +284,10 @@ struct Definition {
     /// [1]: https://spec.graphql.org/October2021#sec-Scalars
     name: String,
 
-    /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema.
+    /// [`syn::Ident`] of the Rust type implementing this [GraphQL scalar][0].
     ///
-    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
-    ty: TypeOrIdent,
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    ident: syn::Ident,
 
     /// Additional [`Self::generics`] [`syn::WhereClause`] predicates.
     where_clause: Vec<syn::WherePredicate>,
@@ -312,6 +320,20 @@ struct Definition {
     /// [`ScalarValue`]: juniper::ScalarValue
     /// [1]: https://spec.graphql.org/October2021#sec-Scalars
     scalar: scalar::Type,
+
+    /// [`ScalarValue`] parametrization to generate code with for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    scalar_value: ScalarValue,
+
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    behavior: behavior::Type,
 }
 
 impl ToTokens for Definition {
@@ -324,6 +346,19 @@ impl ToTokens for Definition {
         self.impl_from_input_value_tokens().to_tokens(into);
         self.impl_parse_scalar_value_tokens().to_tokens(into);
         self.impl_reflection_traits_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_type().to_tokens(into);
+        self.impl_resolve_type_name().to_tokens(into);
+        self.impl_resolve_value().to_tokens(into);
+        self.impl_resolve_value_async().to_tokens(into);
+        gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into);
+        self.impl_resolve_to_input_value().to_tokens(into);
+        self.impl_resolve_input_value().to_tokens(into);
+        self.impl_resolve_scalar_token().to_tokens(into);
+        self.impl_graphql_input_type().to_tokens(into);
+        self.impl_graphql_output_type().to_tokens(into);
+        self.impl_graphql_scalar().to_tokens(into);
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -352,6 +387,99 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`graphql::InputType`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`graphql::InputType`]: juniper::graphql::InputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    #[must_use]
+    fn impl_graphql_input_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, generics) = self.mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+                  + ::juniper::resolve::ToInputValue<#sv, #bh>
+                  + ::juniper::resolve::InputValue<#lt, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_input_type() {}
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`graphql::OutputType`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`graphql::OutputType`]: juniper::graphql::OutputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    #[must_use]
+    fn impl_graphql_output_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+                  + ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+                  + ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_output_type() {}
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`graphql::Scalar`] trait for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`graphql::Scalar`]: juniper::graphql::Scalar
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    #[must_use]
+    fn impl_graphql_scalar(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = self.mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, sv);
+        generics.make_where_clause().predicates.push(parse_quote! {
+            Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
+                  + ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>
+                  + ::juniper::resolve::ScalarToken<#sv, #bh>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::graphql::Scalar<#lt, #inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn assert_scalar() {
+                    <Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
+                        ::assert_input_type();
+                    <Self as ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>>
+                        ::assert_output_type();
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLType`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -395,6 +523,77 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::TypeName`] trait for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`resolve::TypeName`]: juniper::resolve::TypeName
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_type_name(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::TypeName<#inf, #bh>
+             for #ty #where_clause
+            {
+                fn type_name(_: &#inf) -> &'static str {
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`resolve::Type`] trait for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`resolve::Type`]: juniper::resolve::Type
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_type(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        let preds = &mut generics.make_where_clause().predicates;
+        preds.push(parse_quote! { #sv: Clone });
+        preds.push(parse_quote! {
+            ::juniper::behavior::Coerce<Self>:
+                ::juniper::resolve::TypeName<#inf>
+                + ::juniper::resolve::ScalarToken<#sv>
+                + ::juniper::resolve::InputValueOwned<#sv>
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let description = &self.description;
+        let specified_by_url = self.specified_by_url.as_ref().map(|url| {
+            let url_lit = url.as_str();
+            quote! { .specified_by_url(#url_lit) }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Type<#inf, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn meta<'__r, '__ti: '__r>(
+                    registry: &mut ::juniper::Registry<'__r, #sv>,
+                    type_info: &'__ti #inf,
+                ) -> ::juniper::meta::MetaType<'__r, #sv>
+                where
+                    #sv: '__r,
+                {
+                    registry.register_scalar_with::<
+                        ::juniper::behavior::Coerce<Self>, _,
+                    >(type_info, |meta| {
+                        meta #description #specified_by_url
+                    })
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValue`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -432,6 +631,42 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Value`] trait for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        generics
+            .make_where_clause()
+            .predicates
+            .extend(self.methods.bound_resolve_value(&inf, &cx, sv));
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let body = self.methods.expand_resolve_value(&inf, &cx, sv);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value(
+                    &self,
+                    selection_set: Option<&[::juniper::Selection<'_, #sv>]>,
+                    type_info: &#inf,
+                    executor: &::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::ExecutionResult<#sv> {
+                    #body
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -462,6 +697,48 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ValueAsync`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`resolve::ValueAsync`]: juniper::resolve::ValueAsync
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_value_async(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        let preds = &mut generics.make_where_clause().predicates;
+        preds.push(parse_quote! {
+            Self: ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+        });
+        preds.push(parse_quote! {
+            #sv: Send
+        });
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value_async<'__r>(
+                    &'__r self,
+                    sel_set: Option<&'__r [::juniper::Selection<'_, #sv>]>,
+                    type_info: &'__r #inf,
+                    executor: &'__r ::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::BoxFuture<
+                    '__r, ::juniper::ExecutionResult<#sv>,
+                > {
+                    let v =
+                        <Self as ::juniper::resolve::Value<#inf, #cx, #sv, #bh>>
+                            ::resolve_value(self, sel_set, type_info, executor);
+                    ::std::boxed::Box::pin(::juniper::futures::future::ready(v))
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`InputValue`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -470,7 +747,7 @@ impl Definition {
     fn impl_to_input_value_tokens(&self) -> TokenStream {
         let scalar = &self.scalar;
 
-        let to_input_value = self.methods.expand_to_input_value(scalar);
+        let to_input_value = self.methods.expand_old_to_input_value(scalar);
 
         let (ty, generics) = self.impl_self_and_generics(false);
         let (impl_gens, _, where_clause) = generics.split_for_impl();
@@ -487,6 +764,35 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ToInputValue`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_to_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        generics
+            .make_where_clause()
+            .predicates
+            .extend(self.methods.bound_to_input_value(sv));
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let body = self.methods.expand_to_input_value(sv);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh>
+             for #ty #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> {
+                    #body
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`FromInputValue`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -515,6 +821,41 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::InputValue`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`resolve::InputValue`]: juniper::resolve::InputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_input_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, generics) = self.mix_scalar_value(generics);
+        let (lt, mut generics) = gen::mix_input_lifetime(generics, sv);
+        generics
+            .make_where_clause()
+            .predicates
+            .extend(self.methods.bound_try_from_input_value(&lt, sv));
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let error_ty = self.methods.expand_try_from_input_value_error(&lt, sv);
+        let body = self.methods.expand_try_from_input_value(sv);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh>
+             for #ty #where_clause
+            {
+                type Error = #error_ty;
+
+                fn try_from_input_value(
+                    input: &#lt ::juniper::graphql::InputValue<#sv>,
+                ) -> ::std::result::Result<Self, Self::Error> {
+                    #body
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`ParseScalarValue`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -542,6 +883,37 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ScalarToken`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_scalar_token(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        generics
+            .make_where_clause()
+            .predicates
+            .extend(self.methods.bound_parse_scalar_token(sv));
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let body = self.methods.expand_parse_scalar_token(sv);
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::ScalarToken<#sv, #bh>
+             for #ty #where_clause
+            {
+                fn parse_scalar_token(
+                    token: ::juniper::parser::ScalarToken<'_>,
+                ) -> ::std::result::Result<#sv, ::juniper::parser::ParseError> {
+                    #body
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
     /// [`WrappedType`] traits for this [GraphQL scalar][1].
     ///
@@ -581,6 +953,47 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this
+    /// [GraphQL scalar][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types =
+                    &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+        }
+    }
+
     /// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`]
     /// trait (and similar) implementation.
     ///
@@ -593,12 +1006,10 @@ impl Definition {
     fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) {
         let mut generics = self.generics.clone();
 
-        let ty = match &self.ty {
-            TypeOrIdent::Type(ty) => ty.into_token_stream(),
-            TypeOrIdent::Ident(ident) => {
-                let (_, ty_gen, _) = self.generics.split_for_impl();
-                quote! { #ident #ty_gen }
-            }
+        let ty = {
+            let ident = &self.ident;
+            let (_, ty_gen, _) = self.generics.split_for_impl();
+            quote! { #ident #ty_gen }
         };
 
         if !self.where_clause.is_empty() {
@@ -628,15 +1039,10 @@ impl Definition {
                 ModifyLifetimes.visit_generics_mut(&mut generics);
 
                 let lifetimes = generics.lifetimes().map(|lt| &lt.lifetime);
-                let ty = match self.ty.clone() {
-                    TypeOrIdent::Type(mut ty) => {
-                        ModifyLifetimes.visit_type_mut(&mut ty);
-                        ty.into_token_stream()
-                    }
-                    TypeOrIdent::Ident(ident) => {
-                        let (_, ty_gens, _) = generics.split_for_impl();
-                        quote! { #ident #ty_gens }
-                    }
+                let ty = {
+                    let ident = &self.ident;
+                    let (_, ty_gen, _) = generics.split_for_impl();
+                    quote! { #ident #ty_gen }
                 };
 
                 quote! { for<#( #lifetimes ),*> #ty }
@@ -658,6 +1064,42 @@ impl Definition {
 
         (ty, generics)
     }
+
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    #[must_use]
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let mut generics = self.generics.clone();
+        let ty = {
+            let ident = &self.ident;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident #ty_gen }
+        };
+
+        if !self.where_clause.is_empty() {
+            generics
+                .make_where_clause()
+                .predicates
+                .extend(self.where_clause.clone())
+        }
+
+        (ty, generics)
+    }
+
+    /// Mixes a [`ScalarValue`] [`syn::GenericParam`] into the provided
+    /// [`syn::Generics`] and returns it.
+    ///
+    /// [`ScalarValue`] trait bound is not made here, because some trait
+    /// implementations may not require it, depending on the generated code or
+    /// even at all.
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    #[must_use]
+    fn mix_scalar_value(&self, mut generics: syn::Generics) -> (&ScalarValue, syn::Generics) {
+        let sv = &self.scalar_value;
+        generics.params.push(parse_quote! { #sv });
+        (sv, generics)
+    }
 }
 
 /// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a
@@ -670,29 +1112,30 @@ impl VisitMut for ModifyLifetimes {
     }
 }
 
-/// Methods representing [GraphQL scalar][1].
+/// User-provided methods for implementing a [GraphQL scalar][0].
 ///
-/// [1]: https://spec.graphql.org/October2021#sec-Scalars
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
 enum Methods {
-    /// [GraphQL scalar][1] represented with only custom resolvers.
+    /// [GraphQL scalar][0] represented with custom resolving methods only.
     ///
-    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
     Custom {
-        /// Function provided with `#[graphql(to_output_with = ...)]`.
+        /// Function provided with `#[graphql(to_output_with = ...)]` attribute.
         to_output: syn::ExprPath,
 
-        /// Function provided with `#[graphql(from_input_with = ...)]`.
+        /// Function provided with `#[graphql(from_input_with = ...)]`
+        /// attribute.
         from_input: syn::ExprPath,
 
         /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]`
-        /// or `#[graphql(parse_token(...))]`.
+        /// or `#[graphql(parse_token(...))]` attribute.
         parse_token: ParseToken,
     },
 
-    /// [GraphQL scalar][1] maybe partially represented with custom resolver.
-    /// Other methods are used from [`Field`].
+    /// [GraphQL scalar][0] maybe partially represented with custom resolving
+    /// methods. Other methods are re-used from its inner [`Field`].
     ///
-    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
     Delegated {
         /// Function provided with `#[graphql(to_output_with = ...)]`.
         to_output: Option<syn::ExprPath>,
@@ -735,10 +1178,88 @@ impl Methods {
         }
     }
 
+    /// Expands body of [`resolve::Value::resolve_value()`][0] method.
+    ///
+    /// [0]: juniper::resolve::Value::resolve_value
+    fn expand_resolve_value(
+        &self,
+        inf: &syn::Ident,
+        cx: &syn::Ident,
+        sv: &ScalarValue,
+    ) -> TokenStream {
+        match self {
+            Self::Custom { to_output, .. }
+            | Self::Delegated {
+                to_output: Some(to_output),
+                ..
+            } => {
+                let into = sv.custom.is_some().then(|| {
+                    quote! { .map_scalar_value() }
+                });
+                quote! { ::std::result::Result::Ok(#to_output(self)#into) }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                quote! {
+                    <#field_ty as
+                     ::juniper::resolve::Value<#inf, #cx, #sv, #field_bh>>
+                        ::resolve_value(
+                            &self.#field,
+                            selection_set,
+                            type_info,
+                            executor,
+                        )
+                }
+            }
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::Value`] implementation
+    /// allowing to execute [`resolve::Value::resolve_value()`][0] method.
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: juniper::resolve::Value::resolve_value
+    fn bound_resolve_value(
+        &self,
+        inf: &syn::Ident,
+        cx: &syn::Ident,
+        sv: &ScalarValue,
+    ) -> Vec<syn::WherePredicate> {
+        match self {
+            Self::Custom { .. }
+            | Self::Delegated {
+                to_output: Some(_), ..
+            } => {
+                let mut bounds = vec![parse_quote! {
+                    #sv: ::juniper::ScalarValue
+                }];
+                if let Some(custom_sv) = &sv.custom {
+                    bounds.push(parse_quote! {
+                        #custom_sv: ::juniper::ScalarValue
+                    });
+                }
+                bounds
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                vec![parse_quote! {
+                    #field_ty:
+                        ::juniper::resolve::Value<#inf, #cx, #sv, #field_bh>
+                }]
+            }
+        }
+    }
+
     /// Expands [`ToInputValue::to_input_value`] method.
     ///
     /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
-    fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
+    fn expand_old_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
         match self {
             Self::Custom { to_output, .. }
             | Self::Delegated {
@@ -758,6 +1279,72 @@ impl Methods {
         }
     }
 
+    /// Expands body of [`resolve::ToInputValue::to_input_value()`][0] method.
+    ///
+    /// [0]: juniper::resolve::ToInputValue::to_input_value
+    fn expand_to_input_value(&self, sv: &ScalarValue) -> TokenStream {
+        match self {
+            Self::Custom { to_output, .. }
+            | Self::Delegated {
+                to_output: Some(to_output),
+                ..
+            } => {
+                let into = sv.custom.is_some().then(|| {
+                    quote! { .map_scalar_value() }
+                });
+                quote! {
+                    let v = #to_output(self)#into;
+                    ::juniper::resolve::ToInputValue::<#sv>::to_input_value(&v)
+                }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                quote! {
+                    <#field_ty as
+                     ::juniper::resolve::ToInputValue<#sv, #field_bh>>
+                        ::to_input_value(&self.#field)
+                }
+            }
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::ToInputValue`]
+    /// implementation allowing to execute
+    /// [`resolve::ToInputValue::to_input_value()`][0] method.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: juniper::resolve::ToInputValue::to_input_value
+    fn bound_to_input_value(&self, sv: &ScalarValue) -> Vec<syn::WherePredicate> {
+        match self {
+            Self::Custom { .. }
+            | Self::Delegated {
+                to_output: Some(_), ..
+            } => {
+                let mut bounds = vec![parse_quote! {
+                    #sv: ::juniper::ScalarValue
+                }];
+                if let Some(custom_sv) = &sv.custom {
+                    bounds.push(parse_quote! {
+                        #custom_sv: ::juniper::ScalarValue
+                    });
+                }
+                bounds
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                vec![parse_quote! {
+                    #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh>
+                }]
+            }
+        }
+    }
+
     /// Expands [`FromInputValue::from_input_value`][1] method.
     ///
     /// [1]: juniper::FromInputValue::from_input_value
@@ -781,6 +1368,114 @@ impl Methods {
         }
     }
 
+    /// Expands body of [`resolve::InputValue::try_from_input_value()`][0]
+    /// method.
+    ///
+    /// [0]: juniper::resolve::InputValue::try_from_input_value
+    fn expand_try_from_input_value(&self, sv: &ScalarValue) -> TokenStream {
+        match self {
+            Self::Custom { from_input, .. }
+            | Self::Delegated {
+                from_input: Some(from_input),
+                ..
+            } => {
+                let map_sv = sv.custom.is_some().then(|| {
+                    quote! { .map_scalar_value() }
+                });
+                quote! {
+                    #from_input(input #map_sv)
+                        .map_err(
+                            ::juniper::IntoFieldError::<#sv>::into_field_error,
+                        )
+                }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+                let self_constructor = field.closure_constructor();
+
+                quote! {
+                    <#field_ty as
+                     ::juniper::resolve::InputValue<'_, #sv, #field_bh>>
+                        ::try_from_input_value(input)
+                            .map(#self_constructor)
+                }
+            }
+        }
+    }
+
+    /// Expands error type of [`resolve::InputValue`] trait.
+    ///
+    /// [`resolve::InputValue`]: juniper::resolve::InputValue
+    fn expand_try_from_input_value_error(
+        &self,
+        lt: &syn::GenericParam,
+        sv: &ScalarValue,
+    ) -> syn::Type {
+        match self {
+            Self::Custom { .. }
+            | Self::Delegated {
+                from_input: Some(_),
+                ..
+            } => {
+                parse_quote! {
+                    ::juniper::FieldError<#sv>
+                }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                parse_quote! {
+                    <#field_ty as
+                     ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>::Error
+                }
+            }
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::InputValue`]
+    /// implementation allowing to execute
+    /// [`resolve::InputValue::try_from_input_value()`][0] method.
+    ///
+    /// [`resolve::InputValue`]: juniper::resolve::InputValue
+    /// [0]: juniper::resolve::InputValue::try_from_input_value
+    fn bound_try_from_input_value(
+        &self,
+        lt: &syn::GenericParam,
+        sv: &ScalarValue,
+    ) -> Vec<syn::WherePredicate> {
+        match self {
+            Self::Custom { .. }
+            | Self::Delegated {
+                from_input: Some(_),
+                ..
+            } => {
+                let mut bounds = vec![parse_quote! {
+                    #sv: ::juniper::ScalarValue
+                }];
+                if let Some(custom_sv) = &sv.custom {
+                    bounds.push(parse_quote! {
+                        #custom_sv: ::juniper::ScalarValue
+                    });
+                }
+                bounds
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                vec![parse_quote! {
+                    #field_ty:
+                        ::juniper::resolve::InputValue<#lt, #sv, #field_bh>
+                }]
+            }
+        }
+    }
+
     /// Expands [`ParseScalarValue::from_str`] method.
     ///
     /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
@@ -802,6 +1497,56 @@ impl Methods {
             }
         }
     }
+
+    /// Expands body of [`resolve::ScalarToken::parse_scalar_token()`][0]
+    /// method.
+    ///
+    /// [0]: juniper::resolve::ScalarToken::parse_scalar_token
+    fn expand_parse_scalar_token(&self, sv: &ScalarValue) -> TokenStream {
+        match self {
+            Self::Custom { parse_token, .. }
+            | Self::Delegated {
+                parse_token: Some(parse_token),
+                ..
+            } => parse_token.expand_parse_scalar_token(sv),
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                quote! {
+                    <#field_ty as
+                     ::juniper::resolve::ScalarToken<#sv, #field_bh>>
+                        ::parse_scalar_token(token)
+                }
+            }
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::ScalarToken`]
+    /// implementation allowing to execute
+    /// [`resolve::ScalarToken::parse_scalar_token()`][0] method.
+    ///
+    /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken
+    /// [0]: juniper::resolve::ScalarToken::parse_scalar_token
+    fn bound_parse_scalar_token(&self, sv: &ScalarValue) -> Vec<syn::WherePredicate> {
+        match self {
+            Self::Custom { parse_token, .. }
+            | Self::Delegated {
+                parse_token: Some(parse_token),
+                ..
+            } => parse_token.bound_parse_scalar_token(sv),
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+                let field_bh = &field.behavior;
+
+                vec![parse_quote! {
+                    #field_ty: ::juniper::resolve::ScalarToken<#sv, #field_bh>
+                }]
+            }
+        }
+    }
 }
 
 /// Representation of [`ParseScalarValue::from_str`] method.
@@ -845,22 +1590,169 @@ impl ParseToken {
                 .unwrap_or_default(),
         }
     }
+
+    /// Expands body of [`resolve::ScalarToken::parse_scalar_token()`][0]
+    /// method.
+    ///
+    /// [0]: juniper::resolve::ScalarToken::parse_scalar_token
+    fn expand_parse_scalar_token(&self, sv: &ScalarValue) -> TokenStream {
+        match self {
+            Self::Custom(parse_token) => {
+                let into = sv.custom.is_some().then(|| {
+                    quote! { .map(::juniper::ScalarValue::into_another) }
+                });
+                quote! {
+                    #parse_token(token)#into
+                }
+            }
+
+            Self::Delegated(delegated) => delegated
+                .iter()
+                .fold(None, |acc, ty| {
+                    acc.map_or_else(
+                        || {
+                            Some(quote! {
+                                <#ty as ::juniper::resolve::ScalarToken<#sv>>
+                                    ::parse_scalar_token(token)
+                            })
+                        },
+                        |prev| {
+                            Some(quote! {
+                                #prev.or_else(|_| {
+                                    <#ty as ::juniper::resolve::ScalarToken<#sv>>
+                                        ::parse_scalar_token(token)
+                                })
+                            })
+                        },
+                    )
+                })
+                .unwrap_or_default(),
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::ScalarToken`]
+    /// implementation allowing to execute
+    /// [`resolve::ScalarToken::parse_scalar_token()`][0] method.
+    ///
+    /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken
+    /// [0]: juniper::resolve::ScalarToken::parse_scalar_token
+    fn bound_parse_scalar_token(&self, sv: &ScalarValue) -> Vec<syn::WherePredicate> {
+        match self {
+            Self::Custom(_) => {
+                let mut bounds = vec![parse_quote! {
+                    #sv: ::juniper::ScalarValue
+                }];
+                if let Some(custom_sv) = &sv.custom {
+                    bounds.push(parse_quote! {
+                        #custom_sv: ::juniper::ScalarValue
+                    });
+                }
+                bounds
+            }
+
+            Self::Delegated(delegated) => delegated
+                .iter()
+                .map(|ty| {
+                    parse_quote! {
+                        #ty: ::juniper::resolve::ScalarToken<#sv>
+                    }
+                })
+                .collect(),
+        }
+    }
+}
+
+/// Available arguments behind `#[graphql]` attribute on a [`Field`] when
+/// generating code for a [GraphQL scalar][0] implementation.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
+#[derive(Debug, Default)]
+struct FieldAttr {
+    /// Explicitly specified type of the custom [`Behavior`] used for
+    /// [GraphQL scalar][0] implementation by this [`Field`].
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    behavior: Option<SpanContainer<behavior::Type>>,
+}
+
+impl Parse for FieldAttr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = Self::default();
+        while !input.is_empty() {
+            let ident = input.parse_any_ident()?;
+            match ident.to_string().as_str() {
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+        Ok(out)
+    }
+}
+
+impl FieldAttr {
+    /// Tries to merge two [`FieldAttr`]s into a single one, reporting about
+    /// duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        Ok(Self {
+            behavior: try_merge_opt!(behavior: self, another),
+        })
+    }
+
+    /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a field definition.
+    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
+    }
 }
 
-/// Struct field to resolve not provided methods.
-enum Field {
-    /// Named [`Field`].
-    Named(syn::Field),
+/// Inner field of a type implementing [GraphQL scalar][0], that the
+/// implementation delegates calls to.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
+struct Field {
+    /// This [`Field`] itself.
+    itself: syn::Field,
 
-    /// Unnamed [`Field`].
-    Unnamed(syn::Field),
+    /// [`Behavior`] parametrization of this [`Field`].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    behavior: behavior::Type,
+}
+
+impl TryFrom<syn::Field> for Field {
+    type Error = syn::Error;
+
+    fn try_from(field: syn::Field) -> syn::Result<Self> {
+        let attr = FieldAttr::from_attrs("graphql", &field.attrs)?;
+        Ok(Self {
+            itself: field,
+            behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
+        })
+    }
 }
 
 impl ToTokens for Field {
     fn to_tokens(&self, tokens: &mut TokenStream) {
-        match self {
-            Self::Named(f) => f.ident.to_tokens(tokens),
-            Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)),
+        if let Some(name) = &self.itself.ident {
+            name.to_tokens(tokens)
+        } else {
+            tokens.append(Literal::u8_unsuffixed(0))
         }
     }
 }
@@ -868,20 +1760,47 @@ impl ToTokens for Field {
 impl Field {
     /// [`syn::Type`] of this [`Field`].
     fn ty(&self) -> &syn::Type {
-        match self {
-            Self::Named(f) | Self::Unnamed(f) => &f.ty,
-        }
+        &self.itself.ty
     }
 
-    /// Closure to construct [GraphQL scalar][1] struct from [`Field`].
+    /// Generates closure to construct a [GraphQL scalar][0] struct from an
+    /// inner [`Field`] value.
     ///
-    /// [1]: https://spec.graphql.org/October2021#sec-Scalars
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
     fn closure_constructor(&self) -> TokenStream {
-        match self {
-            Field::Named(syn::Field { ident, .. }) => {
-                quote! { |v| Self { #ident: v } }
-            }
-            Field::Unnamed(_) => quote! { Self },
+        if let Some(name) = &self.itself.ident {
+            quote! { |v| Self { #name: v } }
+        } else {
+            quote! { Self }
+        }
+    }
+}
+
+/// [`ScalarValue`] parametrization of a [GraphQL scalar][0] implementation.
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+/// [0]: https://spec.graphql.org/October2021#sec-Scalars
+struct ScalarValue {
+    /// Concrete custom Rust type used in user-provided [`Methods`] as
+    /// [`ScalarValue`].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    custom: Option<syn::Type>,
+}
+
+impl ToTokens for ScalarValue {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        (quote! { __ScalarValue }).to_tokens(tokens)
+    }
+}
+
+impl<'a> From<Option<&'a scalar::AttrValue>> for ScalarValue {
+    fn from(attr: Option<&'a scalar::AttrValue>) -> Self {
+        Self {
+            custom: match attr {
+                Some(scalar::AttrValue::Concrete(ty)) => Some(ty.clone()),
+                Some(scalar::AttrValue::Generic(_)) | None => None,
+            },
         }
     }
 }
diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs
index 006ad2263..a07ac78ee 100644
--- a/juniper_codegen/src/graphql_union/attr.rs
+++ b/juniper_codegen/src/graphql_union/attr.rs
@@ -93,6 +93,7 @@ fn expand_on_trait(
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
+        behavior: attr.behavior.into(),
         generics: ast.generics.clone(),
         variants,
     };
@@ -210,6 +211,7 @@ fn parse_variant_from_trait_method(
         ty,
         resolver_code,
         resolver_check,
+        behavior: attr.behavior.into(),
         context: method_context_ty,
     })
 }
diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs
index a10be60cc..0a2818c2b 100644
--- a/juniper_codegen/src/graphql_union/derive.rs
+++ b/juniper_codegen/src/graphql_union/derive.rs
@@ -84,6 +84,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<Definition> {
             .map(SpanContainer::into_inner)
             .unwrap_or_else(|| parse_quote! { () }),
         scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
+        behavior: attr.behavior.into(),
         generics: ast.generics,
         variants,
     })
@@ -163,6 +164,7 @@ fn parse_variant_from_enum_variant(
         ty,
         resolver_code,
         resolver_check,
+        behavior: attr.behavior.into(),
         context: None,
     })
 }
@@ -214,6 +216,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition> {
             .map(SpanContainer::into_inner)
             .unwrap_or_else(|| parse_quote! { () }),
         scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
+        behavior: attr.behavior.into(),
         generics: ast.generics,
         variants,
     })
diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs
index fa585b004..7931fccee 100644
--- a/juniper_codegen/src/graphql_union/mod.rs
+++ b/juniper_codegen/src/graphql_union/mod.rs
@@ -18,7 +18,7 @@ use syn::{
 };
 
 use crate::common::{
-    filter_attrs, gen,
+    behavior, filter_attrs, gen,
     parse::{
         attr::{err, OptionExt as _},
         ParseBufferExt as _,
@@ -74,6 +74,17 @@ struct Attr {
     /// [1]: https://spec.graphql.org/October2021#sec-Unions
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL union][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified external resolver functions for [GraphQL union][1]
     /// variants.
     ///
@@ -128,6 +139,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "on" => {
                     let ty = input.parse::<syn::Type>()?;
                     input.parse::<token::Eq>()?;
@@ -160,6 +178,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             external_resolvers: try_merge_hashmap!(
                 external_resolvers: self, another => span_joined
             ),
@@ -188,6 +207,19 @@ impl Attr {
 /// [1]: https://spec.graphql.org/October2021#sec-Unions
 #[derive(Debug, Default)]
 struct VariantAttr {
+    /// Explicitly specified type of the custom [`Behavior`] this
+    /// [GraphQL union][0] member implementation is parametrized with, to
+    /// [coerce] in the generated code from.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    /// [coerce]: juniper::behavior::Coerce
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified marker for the variant/field being ignored and not
     /// included into [GraphQL union][1].
     ///
@@ -210,6 +242,13 @@ impl Parse for VariantAttr {
         while !input.is_empty() {
             let ident = input.parse::<syn::Ident>()?;
             match ident.to_string().as_str() {
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "ignore" | "skip" => out
                     .ignore
                     .replace(SpanContainer::new(ident.span(), None, ident.clone()))
@@ -236,6 +275,7 @@ impl VariantAttr {
     /// duplicates, if any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
+            behavior: try_merge_opt!(behavior: self, another),
             ignore: try_merge_opt!(ignore: self, another),
             external_resolver: try_merge_opt!(external_resolver: self, another),
         })
@@ -301,6 +341,13 @@ struct Definition {
     /// [1]: https://spec.graphql.org/October2021#sec-Unions
     scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL union][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    behavior: behavior::Type,
+
     /// Variants definitions of this [GraphQL union][1].
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Unions
@@ -315,6 +362,10 @@ impl ToTokens for Definition {
         self.impl_graphql_value_tokens().to_tokens(into);
         self.impl_graphql_value_async_tokens().to_tokens(into);
         self.impl_reflection_traits_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_resolve_value().to_tokens(into);
+        gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into);
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -560,6 +611,36 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::Value`] trait for this
+    /// [GraphQL union][0].
+    ///
+    /// [`resolve::Value`]: juniper::resolve::Value
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    fn impl_resolve_value(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (inf, generics) = gen::mix_type_info(generics);
+        let (cx, generics) = gen::mix_context(generics);
+        let (sv, generics) = gen::mix_scalar_value(generics);
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh>
+             for #ty #where_clause
+            {
+                fn resolve_value(
+                    &self,
+                    _: Option<&[::juniper::Selection<'_, #sv>]>,
+                    _: &#inf,
+                    _: &::juniper::Executor<'_, '_, #cx, #sv>,
+                ) -> ::juniper::ExecutionResult<#sv> {
+                    todo!()
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
     /// [GraphQL union][1].
     ///
@@ -645,6 +726,70 @@ impl Definition {
             }
         }
     }
+
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this
+    /// [GraphQL union][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+
+        let member_names = self.variants.iter().map(|m| {
+            let m_ty = &m.ty;
+            let m_bh = &m.behavior;
+
+            quote! {
+                <#m_ty as ::juniper::reflect::BaseType<#m_bh>>::NAME
+            }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseType<#bh>
+             for #ty #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh>
+             for #ty #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME,
+                    #( #member_names ),*
+                ];
+            }
+
+            #[automatically_derived]
+            impl #impl_gens ::juniper::reflect::WrappedType<#bh>
+             for #ty #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+        }
+    }
+
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let generics = self.generics.clone();
+        let ty = {
+            let ident = &self.ty;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident #ty_gen }
+        };
+        (ty, generics)
+    }
 }
 
 /// Definition of [GraphQL union][1] variant for code generation.
@@ -667,6 +812,14 @@ struct VariantDefinition {
     /// [1]: https://spec.graphql.org/October2021#sec-Unions
     resolver_check: syn::Expr,
 
+    /// [`Behavior`] parametrization of this [GraphQL union][0] member
+    /// implementation to [coerce] from in the generated code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Unions
+    /// [coerce]: juniper::behavior::Coerce
+    behavior: behavior::Type,
+
     /// Rust type of [`Context`] that this [GraphQL union][1] variant requires
     /// for resolution.
     ///
@@ -783,6 +936,7 @@ fn emerge_union_variants_from_attr(
                 ty,
                 resolver_code,
                 resolver_check,
+                behavior: behavior::Type::default(), // TODO: remove at all
                 context: None,
             })
         }