Compare commits

...

68 commits

Author SHA1 Message Date
tyranron
2d01486ee9
Yay! Static fields, finally, minimally resolving [skip ci] 2022-09-20 18:06:07 +03:00
tyranron
0b6544db48
Merge branch 'master' into rework-core-traits 2022-09-20 15:45:32 +03:00
tyranron
1ddf0bd611
Impl hrtbifying 2022-09-20 15:45:26 +03:00
tyranron
bf219867b6
Impl fields resolving, vol.4 [skip ci] 2022-09-12 17:54:57 +03:00
tyranron
129ff559d1
Impl fields resolving, vol.3 [skip ci] 2022-09-09 14:40:09 +03:00
tyranron
d1f2173109
Impl fields resolving, vol.2 [skip ci] 2022-08-29 20:19:52 +03:00
tyranron
c75d33ae0a
Impl field resolving, vol.1 2022-08-29 16:46:09 +03:00
tyranron
e6861bc516
Merge branch 'master' into rework-core-traits [skip ci] 2022-08-12 18:03:38 +03:00
tyranron
b3659b88a4
Impl objects, vol.1 [skip ci] 2022-08-11 19:06:10 +03:00
tyranron
37257934b7
Some code style corrections [skip ci] 2022-08-11 18:23:46 +03:00
tyranron
fea722b196
Impl input objects, vol.2 [skip ci] 2022-08-11 18:06:43 +03:00
tyranron
464ff10c14
Impl input objects, vol.1 [skip ci] 2022-08-10 19:10:38 +03:00
tyranron
d8813aff13
Merge branch master into rework-core-traits [skip ci] 2022-08-10 18:08:22 +03:00
tyranron
fa03c7fa4d
Impl graphql::Enum trait for enums [skip ci] 2022-06-27 17:05:01 +02:00
tyranron
4257e72c75
Impl resolve::Type trait for enums 2022-06-27 16:57:54 +02:00
tyranron
98b282d06d
Impl resolve::InputValue trait for enums 2022-06-27 16:17:50 +02:00
tyranron
7364187437
Impl resolve::Value and resolve::ToInputValue traits for enums [skip ci] 2022-06-27 15:58:20 +02:00
tyranron
48631e9b25
Impl reflect traits for enums 2022-06-27 15:14:05 +02:00
tyranron
505e25cea2
Temporary disable new reflection for fields as it requires all leaf types implement 2022-06-27 14:53:49 +02:00
tyranron
2e2d365d64
Merge branch master into rework-core-traits [skip ci] 2022-06-27 14:42:43 +02:00
tyranron
1819ab6e12
Impl reflect traits for objects and interfaces fields, vol.1 [skip ci] 2022-06-23 18:37:14 +02:00
tyranron
23e01956b3
Impl reflect traits for objects and interfaces [skip ci] 2022-06-23 17:42:55 +02:00
tyranron
42e2780142
Fix tests 2022-06-23 16:41:16 +02:00
tyranron
07ed785a51
Move tests for containers [skip ci] 2022-06-21 18:10:33 +02:00
tyranron
3cfc4eb0e4
Impl new machinery for Cow [skip ci] 2022-06-21 17:49:35 +02:00
tyranron
4a6b3abfc6
Impl new machinery for slice [skip ci] 2022-06-21 16:15:21 +02:00
tyranron
17af7c5ac5
Impl new machinery for array 2022-06-21 15:25:06 +02:00
tyranron
d648181b26
Fix reflect impls for Result 2022-06-21 14:50:16 +02:00
tyranron
2d89de3403
Impl new machinery for Vec [skip ci] 2022-06-20 19:12:58 +02:00
tyranron
66c11ec782
Impl macro for scalars, vol.6 [skip ci] 2022-06-20 17:41:45 +02:00
tyranron
4d7770d226
Impl macro for scalars, vol.5 [skip ci] 2022-06-19 23:06:59 +02:00
tyranron
1aafd3da5d
Impl macro for scalars, vol.4 [skip ci] 2022-06-19 22:18:50 +02:00
tyranron
679c1c1162
Impl macro for scalars, vol.3 [skip ci] 2022-06-17 23:53:18 +02:00
tyranron
a05a0091f6
Impl macro for scalars, vol.2 [skip ci] 2022-06-17 21:30:30 +02:00
tyranron
f2718cc01a
Impl macro for scalars, vol.1 [skip ci] 2022-06-17 18:24:02 +02:00
tyranron
25404eb4a7
Rework meta creation [skip ci] 2022-06-17 16:04:48 +02:00
tyranron
067b1e532a
Re-implement str, vol.2 [skip ci] 2022-06-13 18:12:44 +02:00
tyranron
88046e3ce4
Re-implement str, vol.1 [skip ci] 2022-06-09 18:10:33 +02:00
tyranron
be010e8123
Re-impl renewed traits machinery for container types 2022-06-09 16:16:30 +02:00
tyranron
5ab398e22b
Re-impl renewed traits machinery for other pointer types 2022-06-09 15:39:14 +02:00
tyranron
017e87c791
Reimpl renewed traits machinery for Box [skip ci] 2022-06-08 19:17:46 +02:00
tyranron
5a62ddfbf2
Restore old reflection impls 2022-06-08 11:06:28 +02:00
tyranron
e347f25718
Refactor vars! macro position [skip ci] 2022-06-08 10:50:47 +02:00
tyranron
b28acbd96d
Refactor value! macro position [skip ci] 2022-06-07 19:03:39 +02:00
tyranron
b716a45215
Refactor input_value! macro position [skip ci] 2022-06-07 18:53:01 +02:00
tyranron
97c88d219c
Reworking base traits, vol.2 2022-06-07 18:19:44 +02:00
tyranron
8235ac22c0
Reworking base traits, vol.1 [skip ci] 2022-06-06 19:06:59 +02:00
tyranron
97d2da581a
Impl codegen for scalars (resolve::ToInputValue trait), vol.5 2022-06-01 17:31:02 +02:00
tyranron
740aa9061e
Impl codegen for scalars (resolve::ValueAsync trait), vol.4 2022-06-01 16:20:19 +02:00
tyranron
113b112daf
Impl codegen for scalars (resolve::Value trait), vol.3 2022-05-31 19:08:36 +02:00
tyranron
22f3f1887e
Bikeshed reflection, vol.1 2022-05-31 00:31:28 +02:00
tyranron
b1be8f1d29
Improve codegen for scalars, vol.2 2022-05-30 19:09:15 +02:00
tyranron
b381c696ff
Improve and polish codegen for scalars 2022-05-27 18:11:40 +02:00
tyranron
224b04973a
Merge branch 'master' into rework-core-traits 2022-05-27 16:36:16 +02:00
tyranron
8ea231f395
Building up codegen for scalars, vol.2 2022-05-19 17:32:43 +02:00
tyranron
21c7a3a653
Bootstrap codegen for scalars 2022-05-17 17:13:25 +02:00
tyranron
5a3bd6c8a9
Pave way to parse ?Sized types from InputValue 2022-05-17 16:59:42 +02:00
tyranron
e381602d5a
Merge branch 'master' into rework-core-traits 2022-05-17 16:00:36 +02:00
tyranron
912d31f66b
Poking with parsing input 2022-05-12 18:53:19 +03:00
tyranron
ffc42b16a1
Merge branch 'master' into rework-core-traits 2022-05-12 17:28:18 +03:00
tyranron
bde5b83eaf
Upd 2022-05-11 19:12:24 +03:00
tyranron
72cd4be715
Merge branch 'master' into rework-core-traits 2022-05-10 17:45:27 +03:00
tyranron
64cf7adb4f
Impl basic types, vol.3 2022-05-05 18:35:42 +03:00
tyranron
c8759cd16e
Impl basic types, vol.2 2022-04-25 15:50:28 +03:00
tyranron
2d1e5d38b7
Impl some basic types 2022-04-22 16:48:19 +03:00
tyranron
d00549968c
Merge branch 'master' into rework-core-traits 2022-04-22 11:03:03 +03:00
tyranron
4ef63e0b7c
Bootstrap, vol.2 2022-04-19 19:04:34 +03:00
tyranron
56a68a9e24
Bootstrap, vol.1 2022-04-19 18:47:42 +03:00
61 changed files with 8722 additions and 892 deletions

View file

@ -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; use indexmap::IndexMap;
@ -256,13 +256,17 @@ impl<S> InputValue<S> {
Self::Variable(v.as_ref().into()) 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 /// Convenience function to make each [`InputValue`] in the input `list` to
/// not contain any location information. Can be used from [`ToInputValue`] /// not contain any location information.
/// implementations, where no source code position information is available. ///
pub fn list(l: Vec<Self>) -> Self { /// Intended for [`resolve::ToInputValue`] implementations, where no source
Self::List(l.into_iter().map(Spanning::unlocated).collect()) /// 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. /// Construct a located list.
@ -270,16 +274,25 @@ impl<S> InputValue<S> {
Self::List(l) 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 /// Similarly to [`InputValue::list()`] it makes each key and value in the
/// given hash map not contain any location information. /// given `obj`ect to 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 where
K: AsRef<str> + Eq + Hash, K: AsRef<str> + Eq + Hash,
O: IntoIterator<Item = (K, Self)>,
{ {
Self::Object( Self::Object(
o.into_iter() obj.into_iter()
.map(|(k, v)| { .map(|(k, v)| {
( (
Spanning::unlocated(k.as_ref().into()), Spanning::unlocated(k.as_ref().into()),
@ -459,6 +472,42 @@ impl<S> InputValue<S> {
_ => false, _ => 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> { impl<S: ScalarValue> fmt::Display for InputValue<S> {

130
juniper/src/behavior.rs Normal file
View file

@ -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;
}

View file

@ -3,7 +3,8 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
cmp::Ordering, cmp::Ordering,
collections::HashMap, collections::{hash_map, HashMap},
convert,
fmt::{Debug, Display}, fmt::{Debug, Display},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -17,6 +18,7 @@ use crate::{
Selection, ToInputValue, Type, Selection, ToInputValue, Type,
}, },
parser::{SourcePosition, Spanning}, parser::{SourcePosition, Spanning},
resolve,
schema::{ schema::{
meta::{ meta::{
Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta, Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta,
@ -69,7 +71,7 @@ pub enum FieldPath<'a> {
/// of the current field stack, context, variables, and errors. /// of the current field stack, context, variables, and errors.
pub struct Executor<'r, 'a, CtxT, S = DefaultScalarValue> pub struct Executor<'r, 'a, CtxT, S = DefaultScalarValue>
where where
CtxT: 'a, CtxT: ?Sized + 'a,
S: 'a, S: 'a,
{ {
fragments: &'r HashMap<&'a str, Fragment<'a, S>>, fragments: &'r HashMap<&'a str, Fragment<'a, S>>,
@ -83,6 +85,41 @@ where
field_path: Arc<FieldPath<'a>>, 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 /// Error type for errors that occur during query execution
/// ///
/// All execution errors contain the source position in the query of the field /// All execution errors contain the source position in the query of the field
@ -627,14 +664,6 @@ where
self.current_selection_set 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 /// The currently executing schema
pub fn schema(&self) -> &'a SchemaType<S> { pub fn schema(&self) -> &'a SchemaType<S> {
self.schema 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`. /// Creates a [`Field`] with the provided `name`.
pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S> pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
where 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()) 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>) { fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) {
self.types self.types
.entry(name) .entry(name)
@ -1258,6 +1312,84 @@ impl<'r, S: 'r> Registry<'r, S> {
ScalarMeta::new::<T>(Cow::Owned(name.into())) 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. /// Creates a [`ListMeta`] type.
/// ///
/// Specifying `expected_size` will be used to ensure that values of this /// 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) 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. /// Creates a [`NullableMeta`] type.
pub fn build_nullable_type<T>(&mut self, info: &T::TypeInfo) -> NullableMeta<'r> pub fn build_nullable_type<T>(&mut self, info: &T::TypeInfo) -> NullableMeta<'r>
where where
@ -1285,6 +1436,19 @@ impl<'r, S: 'r> Registry<'r, S> {
NullableMeta::new(of_type) 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`. /// Creates an [`ObjectMeta`] type with the given `fields`.
pub fn build_object_type<T>( pub fn build_object_type<T>(
&mut self, &mut self,
@ -1318,6 +1482,36 @@ impl<'r, S: 'r> Registry<'r, S> {
EnumMeta::new::<T>(Cow::Owned(name.into()), values) 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`. /// Creates an [`InterfaceMeta`] type with the given `fields`.
pub fn build_interface_type<T>( pub fn build_interface_type<T>(
&mut self, &mut self,
@ -1361,4 +1555,38 @@ impl<'r, S: 'r> Registry<'r, S> {
InputObjectMeta::new::<T>(Cow::Owned(name.into()), args) 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()
}
} }

View file

@ -369,6 +369,8 @@ mod threads_context_correctly {
} }
} }
// TODO: Remove as should be unnecessary with generic context.
/*
mod dynamic_context_switching { mod dynamic_context_switching {
use indexmap::IndexMap; use indexmap::IndexMap;
@ -672,7 +674,7 @@ mod dynamic_context_switching {
assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); assert_eq!(result, graphql_value!({"first": {"value": "First value"}}));
} }
} }
*/
mod propagates_errors_to_nullable_fields { mod propagates_errors_to_nullable_fields {
use crate::{ use crate::{
executor::{ExecutionError, FieldError, FieldResult, IntoFieldError}, executor::{ExecutionError, FieldError, FieldResult, IntoFieldError},

View file

@ -1,6 +1,6 @@
mod interface { mod interface {
use crate::{ use crate::{
graphql_interface, graphql_object, graphql_interface, graphql_object, graphql_value,
schema::model::RootNode, schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription}, types::scalars::{EmptyMutation, EmptySubscription},
GraphQLObject, GraphQLObject,
@ -96,19 +96,16 @@ mod interface {
mod union { mod union {
use crate::{ use crate::{
graphql_object, graphql_union, graphql_object, graphql_value,
schema::model::RootNode, schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription}, types::scalars::{EmptyMutation, EmptySubscription},
GraphQLUnion,
}; };
#[graphql_union] #[derive(GraphQLUnion)]
trait Pet { enum Pet {
fn as_dog(&self) -> Option<&Dog> { Dog(Dog),
None Cat(Cat),
}
fn as_cat(&self) -> Option<&Cat> {
None
}
} }
struct Dog { struct Dog {
@ -116,12 +113,6 @@ mod union {
woofs: bool, woofs: bool,
} }
impl Pet for Dog {
fn as_dog(&self) -> Option<&Dog> {
Some(self)
}
}
#[graphql_object] #[graphql_object]
impl Dog { impl Dog {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -137,12 +128,6 @@ mod union {
meows: bool, meows: bool,
} }
impl Pet for Cat {
fn as_cat(&self) -> Option<&Cat> {
Some(self)
}
}
#[graphql_object] #[graphql_object]
impl Cat { impl Cat {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -154,13 +139,13 @@ mod union {
} }
struct Schema { struct Schema {
pets: Vec<Box<dyn Pet + Send + Sync>>, pets: Vec<Pet>,
} }
#[graphql_object] #[graphql_object]
impl Schema { impl Schema {
fn pets(&self) -> Vec<&(dyn Pet + Send + Sync)> { fn pets(&self) -> &[Pet] {
self.pets.iter().map(|p| p.as_ref()).collect() &self.pets
} }
} }
@ -169,11 +154,11 @@ mod union {
let schema = RootNode::new( let schema = RootNode::new(
Schema { Schema {
pets: vec![ pets: vec![
Box::new(Dog { Pet::Dog(Dog {
name: "Odie".into(), name: "Odie".into(),
woofs: true, woofs: true,
}), }),
Box::new(Cat { Pet::Cat(Cat {
name: "Garfield".into(), name: "Garfield".into(),
meows: false, meows: false,
}), }),

9
juniper/src/extract.rs Normal file
View file

@ -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
}
}

140
juniper/src/graphql/mod.rs Normal file
View file

@ -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();
}

View file

@ -29,19 +29,23 @@ pub use juniper_codegen::{
#[doc(hidden)] #[doc(hidden)]
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
mod ast; mod ast;
pub mod behavior;
pub mod executor; pub mod executor;
pub mod extract;
pub mod graphql;
pub mod http;
pub mod integrations;
mod introspection; mod introspection;
pub mod parser; pub mod parser;
pub mod reflect;
pub mod resolve;
pub(crate) mod schema; pub(crate) mod schema;
mod types; mod types;
mod util; mod util;
pub mod validation; pub mod validation;
mod value; pub(crate) 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;
#[cfg(all(test, not(feature = "expose-test-schema")))] #[cfg(all(test, not(feature = "expose-test-schema")))]
mod tests; mod tests;
@ -71,6 +75,7 @@ pub use crate::{
FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods, FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables,
}, },
extract::Extract,
introspection::IntrospectionFormat, introspection::IntrospectionFormat,
macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult}, macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult},
parser::{ParseError, ScalarToken, Spanning}, parser::{ParseError, ScalarToken, Spanning},
@ -82,12 +87,12 @@ pub use crate::{
async_await::{GraphQLTypeAsync, GraphQLValueAsync}, async_await::{GraphQLTypeAsync, GraphQLValueAsync},
base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion}, marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion},
nullable::Nullable,
scalars::{EmptyMutation, EmptySubscription, ID}, scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{ subscriptions::{
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue, ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
SubscriptionConnection, SubscriptionCoordinator, SubscriptionConnection, SubscriptionCoordinator,
}, },
Nullable,
}, },
validation::RuleError, validation::RuleError,
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},

View file

@ -1,45 +1,43 @@
//! [`graphql_input_value!`] macro implementation. //! [`input_value!`] macro implementation.
//!
//! [`graphql_input_value!`]: graphql_input_value
/// Constructs [`InputValue`]s via JSON-like syntax. /// Constructs [`graphql::InputValue`]s via JSON-like syntax.
/// ///
/// # Differences from [`graphql_value!`] /// # Differences from [`graphql_value!`]
/// ///
/// - [`InputValue::Enum`] is constructed with `ident`, so to capture outer /// - [`InputValue::Enum`] is constructed with `ident`, so to capture outer
/// variable as [`InputValue::Scalar`] surround it with parens: `(var)`. /// variable as [`InputValue::Scalar`] surround it with parens: `(var)`.
/// ```rust /// ```rust
/// # use juniper::{graphql_input_value, graphql_value}; /// # use juniper::graphql;
/// # /// #
/// # type InputValue = juniper::InputValue; /// # type InputValue = graphql::InputValue;
/// # type Value = juniper::Value; /// # type Value = graphql::Value;
/// # /// #
/// const OUTER_VAR: i32 = 42; /// const OUTER_VAR: i32 = 42;
/// assert_eq!(graphql_value!(OUTER_VAR), Value::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::enum_value("OUTER_VAR"));
/// assert_eq!(graphql_input_value!((OUTER_VAR)), InputValue::scalar(42)); /// assert_eq!(graphql::input_value!((OUTER_VAR)), InputValue::scalar(42));
/// ``` /// ```
/// ///
/// - [`InputValue::Variable`] is constructed by prefixing `ident` with `@`. /// - [`InputValue::Variable`] is constructed by prefixing `ident` with `@`.
/// ```rust /// ```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`]`>`. /// - [`InputValue::Object`] key should implement [`Into`]`<`[`String`]`>`.
/// ```rust /// ```rust
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # /// #
/// # use juniper::{graphql_input_value, InputValue}; /// # use juniper::graphql;
/// # /// #
/// let code = 200; /// let code = 200;
/// let features = vec!["key", "value"]; /// let features = vec!["key", "value"];
/// let key: Cow<'static, str> = "key".into(); /// let key: Cow<'static, str> = "key".into();
/// ///
/// let value: InputValue = graphql_input_value!({ /// let value: graphql::InputValue = graphql::input_value!({
/// "code": code, /// "code": code,
/// "success": code == 200, /// "success": code == 200,
/// "payload": { /// "payload": {
@ -55,35 +53,35 @@
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # use juniper::{graphql_input_value, InputValue}; /// # use juniper::graphql;
/// # /// #
/// # type V = InputValue; /// # type V = graphql::InputValue;
/// # /// #
/// # let _: V = /// # let _: V =
/// graphql_input_value!(null); /// graphql::input_value!(null);
/// # let _: V = /// # let _: V =
/// graphql_input_value!(1234); /// graphql::input_value!(1234);
/// # let _: V = /// # let _: V =
/// graphql_input_value!("test"); /// graphql::input_value!("test");
/// # let _: V = /// # let _: V =
/// graphql_input_value!([1234, "test", true]); /// graphql::input_value!([1234, "test", true]);
/// # let _: V = /// # let _: V =
/// graphql_input_value!({"key": "value", "foo": 1234}); /// graphql::input_value!({"key": "value", "foo": 1234});
/// # let _: V = /// # let _: V =
/// graphql_input_value!({"key": ENUM}); /// graphql::input_value!({"key": ENUM});
/// let captured_var = 42; /// let captured_var = 42;
/// # let _: V = /// # let _: V =
/// graphql_input_value!({"key": (captured_var)}); /// graphql::input_value!({"key": (captured_var)});
/// # let _: V = /// # let _: V =
/// graphql_input_value!({"key": @variable}); /// graphql::input_value!({"key": @variable});
/// ``` /// ```
/// ///
/// [`InputValue`]: crate::InputValue /// [`graphql::InputValue`]: crate::graphql::InputValue
/// [`InputValue::Enum`]: crate::InputValue::Enum /// [`InputValue::Enum`]: crate::graphql::InputValue::Enum
/// [`InputValue::List`]: crate::InputValue::List /// [`InputValue::List`]: crate::graphql::InputValue::List
/// [`InputValue::Object`]: crate::InputValue::Object /// [`InputValue::Object`]: crate::graphql::InputValue::Object
/// [`InputValue::Scalar`]: crate::InputValue::Scalar /// [`InputValue::Scalar`]: crate::graphql::InputValue::Scalar
/// [`InputValue::Variable`]: crate::InputValue::Variable /// [`InputValue::Variable`]: crate::graphql::InputValue::Variable
/// [`Spanning::unlocated`]: crate::Spanning::unlocated /// [`Spanning::unlocated`]: crate::Spanning::unlocated
#[macro_export] #[macro_export]
macro_rules! graphql_input_value { macro_rules! graphql_input_value {
@ -93,90 +91,90 @@ macro_rules! graphql_input_value {
// Done with trailing comma. // Done with trailing comma.
(@@array [$($elems:expr,)*]) => { (@@array [$($elems:expr,)*]) => {
$crate::InputValue::list(vec![ $crate::graphql::InputValue::list(::std::vec![
$( $elems, )* $( $elems, )*
]) ])
}; };
// Done without trailing comma. // Done without trailing comma.
(@@array [$($elems:expr),*]) => { (@@array [$($elems:expr),*]) => {
$crate::InputValue::list(vec![ $crate::graphql::InputValue::list(::std::vec![
$( $elems, )* $( $elems, )*
]) ])
}; };
// Next element is `null`. // Next element is `null`.
(@@array [$($elems:expr,)*] null $($rest:tt)*) => { (@@array [$($elems:expr,)*] null $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!(null)] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!(null)] $($rest)*
) )
}; };
// Next element is `None`. // Next element is `None`.
(@@array [$($elems:expr,)*] None $($rest:tt)*) => { (@@array [$($elems:expr,)*] None $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!(None)] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!(None)] $($rest)*
) )
}; };
// Next element is a variable. // Next element is a variable.
(@@array [$($elems:expr,)*] @$var:ident $($rest:tt)*) => { (@@array [$($elems:expr,)*] @$var:ident $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!(@$var)] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!(@$var)] $($rest)*
) )
}; };
// Next element is an array. // Next element is an array.
(@@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { (@@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!([$($array)*])] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!([$($array)*])] $($rest)*
) )
}; };
// Next element is a map. // Next element is a map.
(@@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { (@@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!({$($map)*})] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!({$($map)*})] $($rest)*
) )
}; };
// Next element is `true`, `false` or enum ident followed by comma. // Next element is `true`, `false` or enum ident followed by comma.
(@@array [$($elems:expr,)*] $ident:ident, $($rest:tt)*) => { (@@array [$($elems:expr,)*] $ident:ident, $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!($ident),] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!($ident),] $($rest)*
) )
}; };
// Next element is `true`, `false` or enum ident without trailing comma. // Next element is `true`, `false` or enum ident without trailing comma.
(@@array [$($elems:expr,)*] $last:ident ) => { (@@array [$($elems:expr,)*] $last:ident ) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!($last)] @@array [$($elems,)* $crate::graphql::input_value!($last)]
) )
}; };
// Next element is an expression followed by comma. // Next element is an expression followed by comma.
(@@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { (@@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!($next),] $($rest)* @@array [$($elems,)* $crate::graphql::input_value!($next),] $($rest)*
) )
}; };
// Last element is an expression with no trailing comma. // Last element is an expression with no trailing comma.
(@@array [$($elems:expr,)*] $last:expr) => { (@@array [$($elems:expr,)*] $last:expr) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@array [$($elems,)* $crate::graphql_input_value!($last)] @@array [$($elems,)* $crate::graphql::input_value!($last)]
) )
}; };
// Comma after the most recent element. // Comma after the most recent element.
(@@array [$($elems:expr),*] , $($rest:tt)*) => { (@@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. // Unexpected token after most recent element.
(@@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { (@@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(($($key)+).into()),
$crate::Spanning::unlocated($value), $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. // Current entry followed by unexpected token.
(@@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { (@@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. // Insert the last entry without trailing comma.
@ -210,114 +208,114 @@ macro_rules! graphql_input_value {
// Next value is `null`. // Next value is `null`.
(@@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(null)) $($rest)* ($crate::graphql::input_value!(null)) $($rest)*
); );
}; };
// Next value is `None`. // Next value is `None`.
(@@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(None)) $($rest)* ($crate::graphql::input_value!(None)) $($rest)*
); );
}; };
// Next value is a variable. // Next value is a variable.
(@@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(@$var)) $($rest)* ($crate::graphql::input_value!(@$var)) $($rest)*
); );
}; };
// Next value is an array. // Next value is an array.
(@@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!([$($array)*])) $($rest)* ($crate::graphql::input_value!([$($array)*])) $($rest)*
); );
}; };
// Next value is a map. // Next value is a map.
(@@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!({$($map)*})) $($rest)* ($crate::graphql::input_value!({$($map)*})) $($rest)*
); );
}; };
// Next value is `true`, `false` or enum ident followed by comma. // Next value is `true`, `false` or enum ident followed by comma.
(@@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($ident)) , $($rest)* ($crate::graphql::input_value!($ident)) , $($rest)*
); );
}; };
// Next value is `true`, `false` or enum ident without trailing comma. // Next value is `true`, `false` or enum ident without trailing comma.
(@@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($last)) ($crate::graphql::input_value!($last))
); );
}; };
// Next value is an expression followed by comma. // Next value is an expression followed by comma.
(@@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($value)) , $($rest)* ($crate::graphql::input_value!($value)) , $($rest)*
); );
}; };
// Last value is an expression with no trailing comma. // Last value is an expression with no trailing comma.
(@@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($value)) ($crate::graphql::input_value!($value))
); );
}; };
// Missing value for last entry. Trigger a reasonable error message. // Missing value for last entry. Trigger a reasonable error message.
(@@object $object:ident ($($key:tt)+) (:) $copy:tt) => { (@@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_input_value!(); $crate::graphql::input_value!();
}; };
// Missing colon and value for last entry. Trigger a reasonable error // Missing colon and value for last entry. Trigger a reasonable error
// message. // message.
(@@object $object:ident ($($key:tt)+) () $copy:tt) => { (@@object $object:ident ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_input_value!(); $crate::graphql::input_value!();
}; };
// Misplaced colon. Trigger a reasonable error message. // Misplaced colon. Trigger a reasonable error message.
(@@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { (@@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`". // 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. // Found a comma inside a key. Trigger a reasonable error message.
(@@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { (@@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`". // 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 // Key is fully parenthesized. This avoids `clippy::double_parens` false
// positives because the parenthesization may be necessary here. // positives because the parenthesization may be necessary here.
(@@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { (@@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
($key) ($key)
(: $($rest)*) (: $($rest)*) (: $($rest)*) (: $($rest)*)
@ -326,12 +324,12 @@ macro_rules! graphql_input_value {
// Refuse to absorb colon token into key expression. // Refuse to absorb colon token into key expression.
(@@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { (@@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. // Munch a token into the current key.
(@@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { (@@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
$crate::graphql_input_value!( $crate::graphql::input_value!(
@@object $object @@object $object
($($key)* $tt) ($($key)* $tt)
($($rest)*) ($($rest)*) ($($rest)*) ($($rest)*)
@ -349,109 +347,107 @@ macro_rules! graphql_input_value {
////////////// //////////////
([ $($arr:tt)* ]$(,)?) => { ([ $($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)+ }$(,)?) => { ({ $($map:tt)+ }$(,)?) => {
$crate::InputValue::parsed_object({ $crate::graphql::InputValue::parsed_object({
let mut object = vec![]; let mut object = vec![];
$crate::graphql_input_value!(@@object object () ($($map)*) ($($map)*)); $crate::graphql::input_value!(@@object object () ($($map)*) ($($map)*));
object 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)] #[cfg(test)]
mod tests { mod tests {
use indexmap::{indexmap, IndexMap}; use indexmap::{indexmap, IndexMap};
type V = crate::InputValue; use crate::graphql;
use super::input_value;
type V = graphql::InputValue;
#[test] #[test]
fn null() { fn null() {
assert_eq!(graphql_input_value!(null), V::Null); assert_eq!(input_value!(null), V::Null);
} }
#[test] #[test]
fn scalar() { fn scalar() {
let val = 42; let val = 42;
assert_eq!(graphql_input_value!(1), V::scalar(1)); assert_eq!(input_value!(1), V::scalar(1));
assert_eq!(graphql_input_value!("val"), V::scalar("val")); assert_eq!(input_value!("val"), V::scalar("val"));
assert_eq!(graphql_input_value!(1.34), V::scalar(1.34)); assert_eq!(input_value!(1.34), V::scalar(1.34));
assert_eq!(graphql_input_value!(false), V::scalar(false)); assert_eq!(input_value!(false), V::scalar(false));
assert_eq!(graphql_input_value!(1 + 2), V::scalar(3)); assert_eq!(input_value!(1 + 2), V::scalar(3));
assert_eq!(graphql_input_value!((val)), V::scalar(42)); assert_eq!(input_value!((val)), V::scalar(42));
} }
#[test] #[test]
fn r#enum() { fn r#enum() {
assert_eq!(graphql_input_value!(ENUM), V::enum_value("ENUM")); assert_eq!(input_value!(ENUM), V::enum_value("ENUM"));
assert_eq!(graphql_input_value!(lowercase), V::enum_value("lowercase")); assert_eq!(input_value!(lowercase), V::enum_value("lowercase"));
} }
#[test] #[test]
fn variable() { fn variable() {
assert_eq!(graphql_input_value!(@var), V::variable("var")); assert_eq!(input_value!(@var), V::variable("var"));
assert_eq!(graphql_input_value!(@array), V::variable("array")); assert_eq!(input_value!(@array), V::variable("array"));
assert_eq!(graphql_input_value!(@object), V::variable("object")); assert_eq!(input_value!(@object), V::variable("object"));
} }
#[test] #[test]
fn list() { fn list() {
let val = 42; 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!(input_value!([1]), V::list(vec![V::scalar(1)]));
assert_eq!(graphql_input_value!([1 + 2]), V::list(vec![V::scalar(3)])); assert_eq!(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!([(val)]), V::list(vec![V::scalar(42)]));
assert_eq!(input_value!([ENUM]), V::list(vec![V::enum_value("ENUM")]));
assert_eq!( assert_eq!(
graphql_input_value!([ENUM]), input_value!([lowercase]),
V::list(vec![V::enum_value("ENUM")]),
);
assert_eq!(
graphql_input_value!([lowercase]),
V::list(vec![V::enum_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!( assert_eq!(
graphql_input_value!([@var]), input_value!([@object]),
V::list(vec![V::variable("var")]),
);
assert_eq!(
graphql_input_value!([@array]),
V::list(vec![V::variable("array")]),
);
assert_eq!(
graphql_input_value!([@object]),
V::list(vec![V::variable("object")]), V::list(vec![V::variable("object")]),
); );
assert_eq!( assert_eq!(
graphql_input_value!([1, [2], 3]), input_value!([1, [2], 3]),
V::list(vec![ V::list(vec![
V::scalar(1), V::scalar(1),
V::list(vec![V::scalar(2)]), V::list(vec![V::scalar(2)]),
@ -459,7 +455,7 @@ mod tests {
]), ]),
); );
assert_eq!( assert_eq!(
graphql_input_value!([1, [2 + 3], 3]), input_value!([1, [2 + 3], 3]),
V::list(vec![ V::list(vec![
V::scalar(1), V::scalar(1),
V::list(vec![V::scalar(5)]), V::list(vec![V::scalar(5)]),
@ -467,7 +463,7 @@ mod tests {
]), ]),
); );
assert_eq!( assert_eq!(
graphql_input_value!([1, [ENUM], (val)]), input_value!([1, [ENUM], (val)]),
V::list(vec![ V::list(vec![
V::scalar(1), V::scalar(1),
V::list(vec![V::enum_value("ENUM")]), V::list(vec![V::enum_value("ENUM")]),
@ -475,7 +471,7 @@ mod tests {
]), ]),
); );
assert_eq!( assert_eq!(
graphql_input_value!([1 + 2, [(val)], @val]), input_value!([1 + 2, [(val)], @val]),
V::list(vec![ V::list(vec![
V::scalar(3), V::scalar(3),
V::list(vec![V::scalar(42)]), V::list(vec![V::scalar(42)]),
@ -483,7 +479,7 @@ mod tests {
]), ]),
); );
assert_eq!( assert_eq!(
graphql_input_value!([1, [@val], ENUM]), input_value!([1, [@val], ENUM]),
V::list(vec![ V::list(vec![
V::scalar(1), V::scalar(1),
V::list(vec![V::variable("val")]), V::list(vec![V::variable("val")]),
@ -495,68 +491,65 @@ mod tests {
#[test] #[test]
fn object() { fn object() {
let val = 42; let val = 42;
assert_eq!( assert_eq!(input_value!({}), V::object(IndexMap::<String, _>::new()));
graphql_input_value!({}),
V::object(IndexMap::<String, _>::new()),
);
assert_eq!( assert_eq!(
graphql_input_value!({ "key": null }), input_value!({ "key": null }),
V::object(indexmap! {"key" => V::Null}), V::object(indexmap! {"key" => V::Null}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": 123}), input_value!({"key": 123}),
V::object(indexmap! {"key" => V::scalar(123)}), V::object(indexmap! {"key" => V::scalar(123)}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": 1 + 2}), input_value!({"key": 1 + 2}),
V::object(indexmap! {"key" => V::scalar(3)}), V::object(indexmap! {"key" => V::scalar(3)}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ "key": (val) }), input_value!({ "key": (val) }),
V::object(indexmap! {"key" => V::scalar(42)}), V::object(indexmap! {"key" => V::scalar(42)}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": []}), input_value!({"key": []}),
V::object(indexmap! {"key" => V::list(vec![])}), V::object(indexmap! {"key" => V::list(vec![])}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ "key": [null] }), input_value!({ "key": [null] }),
V::object(indexmap! {"key" => V::list(vec![V::Null])}), V::object(indexmap! {"key" => V::list(vec![V::Null])}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": [1] }), input_value!({"key": [1] }),
V::object(indexmap! {"key" => V::list(vec![V::scalar(1)])}), V::object(indexmap! {"key" => V::list(vec![V::scalar(1)])}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": [1 + 2] }), input_value!({"key": [1 + 2] }),
V::object(indexmap! {"key" => V::list(vec![V::scalar(3)])}), V::object(indexmap! {"key" => V::list(vec![V::scalar(3)])}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ "key": [(val)] }), input_value!({ "key": [(val)] }),
V::object(indexmap! {"key" => V::list(vec![V::scalar(42)])}), V::object(indexmap! {"key" => V::list(vec![V::scalar(42)])}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ "key": ENUM }), input_value!({ "key": ENUM }),
V::object(indexmap! {"key" => V::enum_value("ENUM")}), V::object(indexmap! {"key" => V::enum_value("ENUM")}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ "key": lowercase }), input_value!({ "key": lowercase }),
V::object(indexmap! {"key" => V::enum_value("lowercase")}), V::object(indexmap! {"key" => V::enum_value("lowercase")}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": @val}), input_value!({"key": @val}),
V::object(indexmap! {"key" => V::variable("val")}), V::object(indexmap! {"key" => V::variable("val")}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({"key": @array }), input_value!({"key": @array }),
V::object(indexmap! {"key" => V::variable("array")}), V::object(indexmap! {"key" => V::variable("array")}),
); );
assert_eq!( assert_eq!(
graphql_input_value!({ input_value!({
"inner": { "inner": {
"key1": (val), "key1": (val),
"key2": "val", "key2": "val",
@ -606,8 +599,8 @@ mod tests {
fn option() { fn option() {
let val = Some(42); let val = Some(42);
assert_eq!(graphql_input_value!(None), V::Null); assert_eq!(input_value!(None), V::Null);
assert_eq!(graphql_input_value!(Some(42)), V::scalar(42)); assert_eq!(input_value!(Some(42)), V::scalar(42));
assert_eq!(graphql_input_value!((val)), V::scalar(42)); assert_eq!(input_value!((val)), V::scalar(42));
} }
} }

View file

@ -1,19 +1,18 @@
//! [`graphql_value!`] macro implementation. //! [`value!`] macro implementation.
//!
//! [`graphql_value!`]: graphql_value
/// 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`]`>`. /// [`Value::Object`] key should implement [`AsRef`]`<`[`str`]`>`.
/// ```rust /// ```rust
/// # use juniper::{graphql_value, Value}; /// # use juniper::graphql;
/// # /// #
/// let code = 200; /// let code = 200;
/// let features = ["key", "value"]; /// let features = ["key", "value"];
/// ///
/// let value: Value = graphql_value!({ /// let value: graphql::Value = graphql::value!({
/// "code": code, /// "code": code,
/// "success": code == 200, /// "success": code == 200,
/// "payload": { /// "payload": {
@ -26,24 +25,24 @@
/// ///
/// Resulting JSON will look just like what you passed in. /// Resulting JSON will look just like what you passed in.
/// ```rust /// ```rust
/// # use juniper::{graphql_value, DefaultScalarValue, Value}; /// # use juniper::graphql;
/// # /// #
/// # type V = Value<DefaultScalarValue>; /// # type V = graphql::Value;
/// # /// #
/// # let _: V = /// # let _: V =
/// graphql_value!(null); /// graphql::value!(null);
/// # let _: V = /// # let _: V =
/// graphql_value!(1234); /// graphql::value!(1234);
/// # let _: V = /// # let _: V =
/// graphql_value!("test"); /// graphql::value!("test");
/// # let _: V = /// # let _: V =
/// graphql_value!([1234, "test", true]); /// graphql::value!([1234, "test", true]);
/// # let _: V = /// # let _: V =
/// graphql_value!({"key": "value", "foo": 1234}); /// graphql::value!({"key": "value", "foo": 1234});
/// ``` /// ```
/// ///
/// [`Value`]: crate::Value /// [`graphql::Value`]: crate::graphql::Value
/// [`Value::Object`]: crate::Value::Object /// [`Value::Object`]: crate::graphql::Value::Object
#[macro_export] #[macro_export]
macro_rules! graphql_value { macro_rules! graphql_value {
/////////// ///////////
@ -52,68 +51,68 @@ macro_rules! graphql_value {
// Done with trailing comma. // Done with trailing comma.
(@array [$($elems:expr,)*]) => { (@array [$($elems:expr,)*]) => {
$crate::Value::list(vec![ $crate::graphql::Value::list(::std::vec![
$( $elems, )* $( $elems, )*
]) ])
}; };
// Done without trailing comma. // Done without trailing comma.
(@array [$($elems:expr),*]) => { (@array [$($elems:expr),*]) => {
$crate::Value::list(vec![ $crate::graphql::Value::list(::std::vec![
$( $crate::graphql_value!($elems), )* $( $crate::graphql::value!($elems), )*
]) ])
}; };
// Next element is `null`. // Next element is `null`.
(@array [$($elems:expr,)*] null $($rest:tt)*) => { (@array [$($elems:expr,)*] null $($rest:tt)*) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!(null)] $($rest)* @array [$($elems,)* $crate::graphql::value!(null)] $($rest)*
) )
}; };
// Next element is `None`. // Next element is `None`.
(@array [$($elems:expr,)*] None $($rest:tt)*) => { (@array [$($elems:expr,)*] None $($rest:tt)*) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!(None)] $($rest)* @array [$($elems,)* $crate::graphql::value!(None)] $($rest)*
) )
}; };
// Next element is an array. // Next element is an array.
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!([$($array)*])] $($rest)* @array [$($elems,)* $crate::graphql::value!([$($array)*])] $($rest)*
) )
}; };
// Next element is a map. // Next element is a map.
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!({$($map)*})] $($rest)* @array [$($elems,)* $crate::graphql::value!({$($map)*})] $($rest)*
) )
}; };
// Next element is an expression followed by comma. // Next element is an expression followed by comma.
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!($next),] $($rest)* @array [$($elems,)* $crate::graphql::value!($next),] $($rest)*
) )
}; };
// Last element is an expression with no trailing comma. // Last element is an expression with no trailing comma.
(@array [$($elems:expr,)*] $last:expr) => { (@array [$($elems:expr,)*] $last:expr) => {
$crate::graphql_value!( $crate::graphql::value!(
@array [$($elems,)* $crate::graphql_value!($last)] @array [$($elems,)* $crate::graphql::value!($last)]
) )
}; };
// Comma after the most recent element. // Comma after the most recent element.
(@array [$($elems:expr),*] , $($rest:tt)*) => { (@array [$($elems:expr),*] , $($rest:tt)*) => {
$crate::graphql_value!(@array [$($elems,)*] $($rest)*) $crate::graphql::value!(@array [$($elems,)*] $($rest)*)
}; };
// Unexpected token after most recent element. // Unexpected token after most recent element.
(@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { (@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. // Insert the current entry followed by trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
let _ = $object.add_field(($($key)+), $value); 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. // Current entry followed by unexpected token.
(@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { (@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. // Insert the last entry without trailing comma.
@ -141,97 +140,97 @@ macro_rules! graphql_value {
// Next value is `null`. // Next value is `null`.
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!(null)) $($rest)* ($crate::graphql::value!(null)) $($rest)*
); );
}; };
// Next value is `None`. // Next value is `None`.
(@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!(None)) $($rest)* ($crate::graphql::value!(None)) $($rest)*
); );
}; };
// Next value is an array. // Next value is an array.
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!([$($array)*])) $($rest)* ($crate::graphql::value!([$($array)*])) $($rest)*
); );
}; };
// Next value is a map. // Next value is a map.
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!({$($map)*})) $($rest)* ($crate::graphql::value!({$($map)*})) $($rest)*
); );
}; };
// Next value is an expression followed by comma. // Next value is an expression followed by comma.
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!($value)) , $($rest)* ($crate::graphql::value!($value)) , $($rest)*
); );
}; };
// Last value is an expression with no trailing comma. // Last value is an expression with no trailing comma.
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_value!($value)) ($crate::graphql::value!($value))
); );
}; };
// Missing value for last entry. Trigger a reasonable error message. // Missing value for last entry. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => { (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_value!(); $crate::graphql::value!();
}; };
// Missing colon and value for last entry. Trigger a reasonable error // Missing colon and value for last entry. Trigger a reasonable error
// message. // message.
(@object $object:ident ($($key:tt)+) () $copy:tt) => { (@object $object:ident ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_value!(); $crate::graphql::value!();
}; };
// Misplaced colon. Trigger a reasonable error message. // Misplaced colon. Trigger a reasonable error message.
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`". // 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. // Found a comma inside a key. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`". // 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 // Key is fully parenthesized. This avoids `clippy::double_parens` false
// positives because the parenthesization may be necessary here. // positives because the parenthesization may be necessary here.
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { (@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. // Refuse to absorb colon token into key expression.
(@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { (@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. // Munch a token into the current key.
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
$crate::graphql_value!( $crate::graphql::value!(
@object $object @object $object
($($key)* $tt) ($($key)* $tt)
($($rest)*) ($($rest)*) ($($rest)*) ($($rest)*)
@ -249,63 +248,70 @@ macro_rules! graphql_value {
////////////// //////////////
([ $($arr:tt)* ]$(,)?) => { ([ $($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)+ }$(,)?) => { ({ $($map:tt)+ }$(,)?) => {
$crate::Value::object({ $crate::graphql::Value::object({
let mut object = $crate::Object::with_capacity(0); let mut object = $crate::Object::with_capacity(0);
$crate::graphql_value!(@object object () ($($map)*) ($($map)*)); $crate::graphql::value!(@object object () ($($map)*) ($($map)*));
object 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)] #[cfg(test)]
mod tests { mod tests {
type V = crate::Value; use crate::graphql;
use super::value;
type V = graphql::Value;
#[test] #[test]
fn null() { fn null() {
assert_eq!(graphql_value!(null), V::Null); assert_eq!(value!(null), V::Null);
} }
#[test] #[test]
fn scalar() { fn scalar() {
let val = 42; let val = 42;
assert_eq!(graphql_value!(1), V::scalar(1)); assert_eq!(value!(1), V::scalar(1));
assert_eq!(graphql_value!("val"), V::scalar("val")); assert_eq!(value!("val"), V::scalar("val"));
assert_eq!(graphql_value!(1.34), V::scalar(1.34)); assert_eq!(value!(1.34), V::scalar(1.34));
assert_eq!(graphql_value!(false), V::scalar(false)); assert_eq!(value!(false), V::scalar(false));
assert_eq!(graphql_value!(1 + 2), V::scalar(3)); assert_eq!(value!(1 + 2), V::scalar(3));
assert_eq!(graphql_value!(val), V::scalar(42)); assert_eq!(value!(val), V::scalar(42));
} }
#[test] #[test]
fn list() { fn list() {
let val = 42; 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!(value!([1]), V::list(vec![V::scalar(1)]));
assert_eq!(graphql_value!([1 + 2]), V::list(vec![V::scalar(3)])); assert_eq!(value!([1 + 2]), V::list(vec![V::scalar(3)]));
assert_eq!(graphql_value!([val]), V::list(vec![V::scalar(42)])); assert_eq!(value!([val]), V::list(vec![V::scalar(42)]));
assert_eq!( assert_eq!(
graphql_value!([1, [2], 3]), value!([1, [2], 3]),
V::list(vec![ V::list(vec![
V::scalar(1), V::scalar(1),
V::list(vec![V::scalar(2)]), V::list(vec![V::scalar(2)]),
@ -313,7 +319,7 @@ mod tests {
]), ]),
); );
assert_eq!( assert_eq!(
graphql_value!(["string", [2 + 3], true]), value!(["string", [2 + 3], true]),
V::list(vec![ V::list(vec![
V::scalar("string"), V::scalar("string"),
V::list(vec![V::scalar(5)]), V::list(vec![V::scalar(5)]),
@ -327,31 +333,31 @@ mod tests {
let val = 42; let val = 42;
assert_eq!( assert_eq!(
graphql_value!({}), value!({}),
V::object(Vec::<(String, _)>::new().into_iter().collect()), V::object(Vec::<(String, _)>::new().into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": null }), value!({ "key": null }),
V::object(vec![("key", V::Null)].into_iter().collect()), V::object(vec![("key", V::Null)].into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": 123 }), value!({ "key": 123 }),
V::object(vec![("key", V::scalar(123))].into_iter().collect()), V::object(vec![("key", V::scalar(123))].into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": 1 + 2 }), value!({ "key": 1 + 2 }),
V::object(vec![("key", V::scalar(3))].into_iter().collect()), V::object(vec![("key", V::scalar(3))].into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": [] }), value!({ "key": [] }),
V::object(vec![("key", V::list(vec![]))].into_iter().collect()), V::object(vec![("key", V::list(vec![]))].into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": [null] }), value!({ "key": [null] }),
V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()), V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": [1] }), value!({ "key": [1] }),
V::object( V::object(
vec![("key", V::list(vec![V::scalar(1)]))] vec![("key", V::list(vec![V::scalar(1)]))]
.into_iter() .into_iter()
@ -359,7 +365,7 @@ mod tests {
), ),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": [1 + 2] }), value!({ "key": [1 + 2] }),
V::object( V::object(
vec![("key", V::list(vec![V::scalar(3)]))] vec![("key", V::list(vec![V::scalar(3)]))]
.into_iter() .into_iter()
@ -367,7 +373,7 @@ mod tests {
), ),
); );
assert_eq!( assert_eq!(
graphql_value!({ "key": [val] }), value!({ "key": [val] }),
V::object( V::object(
vec![("key", V::list(vec![V::scalar(42)]))] vec![("key", V::list(vec![V::scalar(42)]))]
.into_iter() .into_iter()
@ -380,8 +386,8 @@ mod tests {
fn option() { fn option() {
let val = Some(42); let val = Some(42);
assert_eq!(graphql_value!(None), V::Null); assert_eq!(value!(None), V::Null);
assert_eq!(graphql_value!(Some(42)), V::scalar(42)); assert_eq!(value!(Some(42)), V::scalar(42));
assert_eq!(graphql_value!(val), V::scalar(42)); assert_eq!(value!(val), V::scalar(42));
} }
} }

View file

@ -1,20 +1,18 @@
//! [`graphql_vars!`] macro implementation. //! [`vars!`] macro implementation.
//!
//! [`graphql_vars!`]: graphql_vars
/// 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 /// ```rust
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # /// #
/// # use juniper::{graphql_vars, Variables}; /// # use juniper::graphql;
/// # /// #
/// let code = 200; /// let code = 200;
/// let features = vec!["key", "value"]; /// let features = vec!["key", "value"];
/// let key: Cow<'static, str> = "key".into(); /// let key: Cow<'static, str> = "key".into();
/// ///
/// let value: Variables = graphql_vars! { /// let value: graphql::Variables = graphql::vars! {
/// "code": code, /// "code": code,
/// "success": code == 200, /// "success": code == 200,
/// features[0]: features[1], /// 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 /// [`graphql::input_value!`]: crate::graphql::input_value
/// [`Variables`]: crate::Variables /// [`graphql::Variables`]: crate::graphql::Variables
#[macro_export] #[macro_export]
macro_rules! graphql_vars { macro_rules! graphql_vars {
//////////// ////////////
@ -38,12 +36,12 @@ macro_rules! graphql_vars {
// Insert the current entry followed by trailing comma. // Insert the current entry followed by trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
let _ = $object.insert(($($key)+).into(), $value); 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. // Current entry followed by unexpected token.
(@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { (@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. // Insert the last entry without trailing comma.
@ -53,7 +51,7 @@ macro_rules! graphql_vars {
// Next value is `null`. // Next value is `null`.
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(null)) $($rest)* ($crate::graphql_input_value!(null)) $($rest)*
@ -62,7 +60,7 @@ macro_rules! graphql_vars {
// Next value is `None`. // Next value is `None`.
(@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(None)) $($rest)* ($crate::graphql_input_value!(None)) $($rest)*
@ -71,7 +69,7 @@ macro_rules! graphql_vars {
// Next value is a variable. // Next value is a variable.
(@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!(@$var)) $($rest)* ($crate::graphql_input_value!(@$var)) $($rest)*
@ -80,7 +78,7 @@ macro_rules! graphql_vars {
// Next value is an array. // Next value is an array.
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!([$($array)*])) $($rest)* ($crate::graphql_input_value!([$($array)*])) $($rest)*
@ -89,7 +87,7 @@ macro_rules! graphql_vars {
// Next value is a map. // Next value is a map.
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!({$($map)*})) $($rest)* ($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. // Next value is `true`, `false` or enum ident followed by a comma.
(@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($ident)) , $($rest)* ($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. // Next value is `true`, `false` or enum ident without trailing comma.
(@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($last)) ($crate::graphql_input_value!($last))
@ -116,7 +114,7 @@ macro_rules! graphql_vars {
// Next value is an expression followed by comma. // Next value is an expression followed by comma.
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($value)) , $($rest)* ($crate::graphql_input_value!($value)) , $($rest)*
@ -125,7 +123,7 @@ macro_rules! graphql_vars {
// Last value is an expression with no trailing comma. // Last value is an expression with no trailing comma.
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
[$($key)+] [$($key)+]
($crate::graphql_input_value!($value)) ($crate::graphql_input_value!($value))
@ -135,44 +133,44 @@ macro_rules! graphql_vars {
// Missing value for last entry. Trigger a reasonable error message. // Missing value for last entry. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => { (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_vars! {}; $crate::graphql::vars! {};
}; };
// Missing colon and value for last entry. Trigger a reasonable error // Missing colon and value for last entry. Trigger a reasonable error
// message. // message.
(@object $object:ident ($($key:tt)+) () $copy:tt) => { (@object $object:ident ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation" // "unexpected end of macro invocation"
$crate::graphql_vars! {}; $crate::graphql::vars! {};
}; };
// Misplaced colon. Trigger a reasonable error message. // Misplaced colon. Trigger a reasonable error message.
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`". // 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. // Found a comma inside a key. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`". // 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 // Key is fully parenthesized. This avoids clippy double_parens false
// positives because the parenthesization may be necessary here. // positives because the parenthesization may be necessary here.
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object ($key) (: $($rest)*) (: $($rest)*) @object $object ($key) (: $($rest)*) (: $($rest)*)
}; };
}; };
// Refuse to absorb colon token into key expression. // Refuse to absorb colon token into key expression.
(@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { (@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. // Munch a token into the current key.
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
$crate::graphql_vars! { $crate::graphql::vars! {
@object $object @object $object
($($key)* $tt) ($($key)* $tt)
($($rest)*) ($($rest)*) ($($rest)*) ($($rest)*)
@ -189,26 +187,33 @@ macro_rules! graphql_vars {
// Defaults // // Defaults //
////////////// //////////////
() => {{ $crate::Variables::<_>::new() }}; () => {{ $crate::graphql::Variables::<_>::new() }};
( $($map:tt)+ ) => {{ ( $($map:tt)+ ) => {{
let mut object = $crate::Variables::<_>::new(); let mut object = $crate::graphql::Variables::<_>::new();
$crate::graphql_vars! {@object object () ($($map)*) ($($map)*)}; $crate::graphql::vars! {@object object () ($($map)*) ($($map)*)};
object object
}}; }};
} }
#[doc(inline)]
pub use graphql_vars as vars;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use indexmap::{indexmap, IndexMap}; use indexmap::{indexmap, IndexMap};
type V = crate::Variables; use crate::graphql;
type IV = crate::InputValue; use super::vars;
type V = graphql::Variables;
type IV = graphql::InputValue;
#[test] #[test]
fn empty() { fn empty() {
assert_eq!(graphql_vars! {}, V::new()); assert_eq!(vars! {}, V::new());
} }
#[test] #[test]
@ -216,35 +221,35 @@ mod tests {
let val = 42; let val = 42;
assert_eq!( assert_eq!(
graphql_vars! {"key": 123}, vars! {"key": 123},
vec![("key".into(), IV::scalar(123))] vec![("key".into(), IV::scalar(123))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": "val"}, vars! {"key": "val"},
vec![("key".into(), IV::scalar("val"))] vec![("key".into(), IV::scalar("val"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": 1.23}, vars! {"key": 1.23},
vec![("key".into(), IV::scalar(1.23))] vec![("key".into(), IV::scalar(1.23))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": 1 + 2}, vars! {"key": 1 + 2},
vec![("key".into(), IV::scalar(3))].into_iter().collect(), vec![("key".into(), IV::scalar(3))].into_iter().collect(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": false}, vars! {"key": false},
vec![("key".into(), IV::scalar(false))] vec![("key".into(), IV::scalar(false))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": (val)}, vars! {"key": (val)},
vec![("key".into(), IV::scalar(42))] vec![("key".into(), IV::scalar(42))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
@ -254,13 +259,13 @@ mod tests {
#[test] #[test]
fn r#enum() { fn r#enum() {
assert_eq!( assert_eq!(
graphql_vars! {"key": ENUM}, vars! {"key": ENUM},
vec![("key".into(), IV::enum_value("ENUM"))] vec![("key".into(), IV::enum_value("ENUM"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": lowercase}, vars! {"key": lowercase},
vec![("key".into(), IV::enum_value("lowercase"))] vec![("key".into(), IV::enum_value("lowercase"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
@ -270,19 +275,19 @@ mod tests {
#[test] #[test]
fn variable() { fn variable() {
assert_eq!( assert_eq!(
graphql_vars! {"key": @var}, vars! {"key": @var},
vec![("key".into(), IV::variable("var"))] vec![("key".into(), IV::variable("var"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": @array}, vars! {"key": @array},
vec![("key".into(), IV::variable("array"))] vec![("key".into(), IV::variable("array"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": @object}, vars! {"key": @object},
vec![("key".into(), IV::variable("object"))] vec![("key".into(), IV::variable("object"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
@ -294,72 +299,72 @@ mod tests {
let val = 42; let val = 42;
assert_eq!( assert_eq!(
graphql_vars! {"key": []}, vars! {"key": []},
vec![("key".into(), IV::list(vec![]))] vec![("key".into(), IV::list(vec![]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [null]}, vars! {"key": [null]},
vec![("key".into(), IV::list(vec![IV::Null]))] vec![("key".into(), IV::list(vec![IV::Null]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1]}, vars! {"key": [1]},
vec![("key".into(), IV::list(vec![IV::scalar(1)]))] vec![("key".into(), IV::list(vec![IV::scalar(1)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1 + 2]}, vars! {"key": [1 + 2]},
vec![("key".into(), IV::list(vec![IV::scalar(3)]))] vec![("key".into(), IV::list(vec![IV::scalar(3)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [(val)]}, vars! {"key": [(val)]},
vec![("key".into(), IV::list(vec![IV::scalar(42)]))] vec![("key".into(), IV::list(vec![IV::scalar(42)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [ENUM]}, vars! {"key": [ENUM]},
vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))] vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [lowercase]}, vars! {"key": [lowercase]},
vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))] vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@var]}, vars! {"key": [@var]},
vec![("key".into(), IV::list(vec![IV::variable("var")]))] vec![("key".into(), IV::list(vec![IV::variable("var")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@array]}, vars! {"key": [@array]},
vec![("key".into(), IV::list(vec![IV::variable("array")]))] vec![("key".into(), IV::list(vec![IV::variable("array")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@object]}, vars! {"key": [@object]},
vec![("key".into(), IV::list(vec![IV::variable("object")]))] vec![("key".into(), IV::list(vec![IV::variable("object")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [2], 3]}, vars! {"key": [1, [2], 3]},
vec![( vec![(
"key".into(), "key".into(),
IV::list(vec![ IV::list(vec![
@ -372,7 +377,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [2 + 3], 3]}, vars! {"key": [1, [2 + 3], 3]},
vec![( vec![(
"key".into(), "key".into(),
IV::list(vec![ IV::list(vec![
@ -385,7 +390,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [ENUM], (val)]}, vars! {"key": [1, [ENUM], (val)]},
vec![( vec![(
"key".into(), "key".into(),
IV::list(vec![ IV::list(vec![
@ -398,7 +403,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1 + 2, [(val)], @val]}, vars! {"key": [1 + 2, [(val)], @val]},
vec![( vec![(
"key".into(), "key".into(),
IV::list(vec![ IV::list(vec![
@ -411,7 +416,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [@val], ENUM]}, vars! {"key": [1, [@val], ENUM]},
vec![( vec![(
"key".into(), "key".into(),
IV::list(vec![ IV::list(vec![
@ -430,21 +435,21 @@ mod tests {
let val = 42; let val = 42;
assert_eq!( assert_eq!(
graphql_vars! {"key": {}}, vars! {"key": {}},
vec![("key".into(), IV::object(IndexMap::<String, _>::new()))] vec![("key".into(), IV::object(IndexMap::<String, _>::new()))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": null}}, vars! {"key": {"key": null}},
vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))] vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": 123}}, vars! {"key": {"key": 123}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::scalar(123)}), IV::object(indexmap! {"key" => IV::scalar(123)}),
@ -453,13 +458,13 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": 1 + 2}}, vars! {"key": {"key": 1 + 2}},
vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)] vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": (val)}}, vars! {"key": {"key": (val)}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::scalar(42)}), IV::object(indexmap! {"key" => IV::scalar(42)}),
@ -469,7 +474,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": []}}, vars! {"key": {"key": []}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![])}), IV::object(indexmap! {"key" => IV::list(vec![])}),
@ -478,7 +483,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [null]}}, vars! {"key": {"key": [null]}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}), IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}),
@ -487,7 +492,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [1]}}, vars! {"key": {"key": [1]}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}),
@ -496,7 +501,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [1 + 2]}}, vars! {"key": {"key": [1 + 2]}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}),
@ -505,7 +510,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [(val)]}}, vars! {"key": {"key": [(val)]}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}),
@ -514,7 +519,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": ENUM}}, vars! {"key": {"key": ENUM}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::enum_value("ENUM")}), IV::object(indexmap! {"key" => IV::enum_value("ENUM")}),
@ -523,7 +528,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": lowercase}}, vars! {"key": {"key": lowercase}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::enum_value("lowercase")}), IV::object(indexmap! {"key" => IV::enum_value("lowercase")}),
@ -532,7 +537,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": @val}}, vars! {"key": {"key": @val}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::variable("val")}), IV::object(indexmap! {"key" => IV::variable("val")}),
@ -541,7 +546,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": @array}}, vars! {"key": {"key": @array}},
vec![( vec![(
"key".into(), "key".into(),
IV::object(indexmap! {"key" => IV::variable("array")}), IV::object(indexmap! {"key" => IV::variable("array")}),
@ -550,7 +555,7 @@ mod tests {
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! { vars! {
"inner": { "inner": {
"key1": (val), "key1": (val),
"key2": "val", "key2": "val",

View file

@ -6,9 +6,9 @@ pub mod helper;
#[macro_use] #[macro_use]
pub mod reflect; pub mod reflect;
#[macro_use]
mod graphql_input_value; mod graphql_input_value;
#[macro_use]
mod graphql_value; mod graphql_value;
#[macro_use]
mod graphql_vars; mod graphql_vars;
#[doc(inline)]
pub use self::{graphql_input_value::input_value, graphql_value::value, graphql_vars::vars};

View file

@ -8,20 +8,10 @@ use crate::{
Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue,
}; };
/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name pub use crate::reflect::{
/// in a GraphQL schema. can_be_subtype, fnv1a128, str_eq, str_exists_in_arr, type_len_with_wrapped_val, Argument,
/// Arguments, FieldName, Name, Names, Type, Types, WrappedValue,
/// 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];
/// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// 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; 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. // TODO: Just use `&str`s once they're allowed in `const` generics.
/// Encoding of a composed GraphQL type in numbers. /// 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 /// because of the [wrapping types][2]. To work around this we use a
/// [`WrappedValue`] which is represented via [`u128`] number in the following /// [`WrappedValue`] which is represented via [`u128`] number in the following
/// encoding: /// 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 /// - To represent nullability we "append" `2` to the [`VALUE`], so
/// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`.
/// - To represent list we "append" `3` to the [`VALUE`], so /// - To represent list we "append" `3` to the [`VALUE`], so
@ -177,7 +164,7 @@ pub type WrappedValue = u128;
/// ///
/// ```rust /// ```rust
/// # use juniper::{ /// # use juniper::{
/// # format_type, /// # reflect::format_type,
/// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type}, /// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type},
/// # DefaultScalarValue, /// # DefaultScalarValue,
/// # }; /// # };
@ -253,34 +240,6 @@ impl<S, T: WrappedType<S> + ?Sized> WrappedType<S> for Rc<T> {
const VALUE: u128 = T::VALUE; 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`]. /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`].
/// ///
/// [1]: https://spec.graphql.org/October2021#sec-Objects /// [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>>; ) -> 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 /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing
/// this interface in the `impl = ...` attribute argument. /// this interface in the `impl = ...` attribute argument.
/// ///
@ -509,7 +367,7 @@ macro_rules! assert_implemented_for {
<$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
); );
if !is_present { if !is_present {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
<$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME, <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `", "` on `",
@ -537,7 +395,7 @@ macro_rules! assert_interfaces_impls {
<$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES, <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES,
); );
if !is_present { if !is_present {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
<$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `", "` on `",
@ -622,7 +480,7 @@ macro_rules! assert_subtype {
<$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const IMPL_TY: $crate::macros::reflect::Type = const IMPL_TY: $crate::macros::reflect::Type =
<$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; <$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 `", "Failed to implement interface `",
BASE_TY, BASE_TY,
"` on `", "` 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) 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); && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL);
if !is_subtype { if !is_subtype {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
ERR_PREFIX, ERR_PREFIX,
"Field `", "Field `",
FIELD_NAME, FIELD_NAME,
"`: implementor is expected to return a subtype of interface's return object: `", "`: 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 `", "` 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); ::std::panic!("{}", MSG);
@ -695,7 +553,7 @@ macro_rules! assert_field_args {
const _: () = { const _: () = {
const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; 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 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 `", "Failed to implement interface `",
BASE_NAME, BASE_NAME,
"` on `", "` on `",
@ -818,13 +676,14 @@ macro_rules! assert_field_args {
const BASE_ARG_NAME: &str = ERROR.base.0; const BASE_ARG_NAME: &str = ERROR.base.0;
const IMPL_ARG_NAME: &str = ERROR.implementation.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 = 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 { const MSG: &str = match ERROR.cause {
Cause::TypeMismatch => { Cause::TypeMismatch => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
BASE_ARG_NAME, BASE_ARG_NAME,
"`: expected type `", "`: expected type `",
@ -835,7 +694,7 @@ macro_rules! assert_field_args {
) )
} }
Cause::RequiredField => { Cause::RequiredField => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
BASE_ARG_NAME, BASE_ARG_NAME,
"` of type `", "` of type `",
@ -844,7 +703,7 @@ macro_rules! assert_field_args {
) )
} }
Cause::AdditionalNonNullableField => { Cause::AdditionalNonNullableField => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
IMPL_ARG_NAME, IMPL_ARG_NAME,
"` of type `", "` of type `",
@ -854,43 +713,13 @@ macro_rules! assert_field_args {
} }
}; };
const ERROR_MSG: &str = 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); ::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 /// Ensures that the given `$impl_ty` implements [`Field`] and returns a
/// [`fnv1a128`] hash for it, otherwise panics with understandable message. /// [`fnv1a128`] hash for it, otherwise panics with understandable message.
#[macro_export] #[macro_export]
@ -903,7 +732,7 @@ macro_rules! checked_hash {
if exists { if exists {
$crate::macros::reflect::fnv1a128(FIELD_NAME) $crate::macros::reflect::fnv1a128(FIELD_NAME)
} else { } else {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
$($prefix,)? $($prefix,)?
"Field `", "Field `",
$field_name, $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
}};
}

874
juniper/src/reflect/mod.rs Normal file
View file

@ -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,
};
}

211
juniper/src/resolve/mod.rs Normal file
View file

@ -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>;
}

View file

@ -1,6 +1,5 @@
//! Types used to describe a `GraphQL` schema //! Types used to describe a `GraphQL` schema
use juniper::IntoFieldError;
use std::{ use std::{
borrow::{Cow, ToOwned}, borrow::{Cow, ToOwned},
fmt, fmt,
@ -9,10 +8,11 @@ use std::{
use crate::{ use crate::{
ast::{FromInputValue, InputValue, Type}, ast::{FromInputValue, InputValue, Type},
parser::{ParseError, ScalarToken}, parser::{ParseError, ScalarToken},
resolve,
schema::model::SchemaType, schema::model::SchemaType,
types::base::TypeKind, types::base::TypeKind,
value::{DefaultScalarValue, ParseScalarValue}, value::{DefaultScalarValue, ParseScalarValue},
FieldError, FieldError, IntoFieldError,
}; };
/// Whether an item is deprecated, with context. /// Whether an item is deprecated, with context.
@ -28,16 +28,16 @@ impl DeprecationStatus {
/// If this deprecation status indicates the item is deprecated. /// If this deprecation status indicates the item is deprecated.
pub fn is_deprecated(&self) -> bool { pub fn is_deprecated(&self) -> bool {
match self { match self {
DeprecationStatus::Current => false, Self::Current => false,
DeprecationStatus::Deprecated(_) => true, Self::Deprecated(_) => true,
} }
} }
/// An optional reason for the deprecation, or none if `Current`. /// An optional reason for the deprecation, or none if `Current`.
pub fn reason(&self) -> Option<&str> { pub fn reason(&self) -> Option<&str> {
match self { match self {
DeprecationStatus::Current => None, Self::Current => None,
DeprecationStatus::Deprecated(rsn) => rsn.as_deref(), Self::Deprecated(rsn) => rsn.as_deref(),
} }
} }
} }
@ -54,6 +54,20 @@ pub struct ScalarMeta<'a, S> {
pub(crate) parse_fn: ScalarTokenParseFn<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. /// Shortcut for an [`InputValue`] parsing function.
pub type InputValueParseFn<S> = for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>; 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>; pub type ScalarTokenParseFn<S> = for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError>;
/// List type metadata /// List type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ListMeta<'a> { pub struct ListMeta<'a> {
#[doc(hidden)] #[doc(hidden)]
pub of_type: Type<'a>, pub of_type: Type<'a>,
@ -71,14 +85,14 @@ pub struct ListMeta<'a> {
} }
/// Nullable type metadata /// Nullable type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct NullableMeta<'a> { pub struct NullableMeta<'a> {
#[doc(hidden)] #[doc(hidden)]
pub of_type: Type<'a>, pub of_type: Type<'a>,
} }
/// Object type metadata /// Object type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ObjectMeta<'a, S> { pub struct ObjectMeta<'a, S> {
#[doc(hidden)] #[doc(hidden)]
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
@ -101,8 +115,21 @@ pub struct EnumMeta<'a, S> {
pub(crate) try_parse_fn: InputValueParseFn<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 /// Interface type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct InterfaceMeta<'a, S> { pub struct InterfaceMeta<'a, S> {
#[doc(hidden)] #[doc(hidden)]
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
@ -115,7 +142,7 @@ pub struct InterfaceMeta<'a, S> {
} }
/// Union type metadata /// Union type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct UnionMeta<'a> { pub struct UnionMeta<'a> {
#[doc(hidden)] #[doc(hidden)]
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
@ -126,6 +153,7 @@ pub struct UnionMeta<'a> {
} }
/// Input object metadata /// Input object metadata
#[derive(Clone)]
pub struct InputObjectMeta<'a, S> { pub struct InputObjectMeta<'a, S> {
#[doc(hidden)] #[doc(hidden)]
pub name: Cow<'a, str>, 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 /// 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. /// is inserted into a registry to indicate existence.
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct PlaceholderMeta<'a> { pub struct PlaceholderMeta<'a> {
#[doc(hidden)] #[doc(hidden)]
pub of_type: Type<'a>, pub of_type: Type<'a>,
} }
/// Generic type metadata /// Generic type metadata
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum MetaType<'a, S = DefaultScalarValue> { pub enum MetaType<'a, S = DefaultScalarValue> {
#[doc(hidden)] #[doc(hidden)]
Scalar(ScalarMeta<'a, S>), Scalar(ScalarMeta<'a, S>),
@ -170,7 +198,7 @@ pub enum MetaType<'a, S = DefaultScalarValue> {
} }
/// Metadata for a field /// Metadata for a field
#[derive(Debug, Clone)] #[derive(Clone, Debug)]
pub struct Field<'a, S> { pub struct Field<'a, S> {
#[doc(hidden)] #[doc(hidden)]
pub name: smartstring::alias::String, pub name: smartstring::alias::String,
@ -193,7 +221,7 @@ impl<'a, S> Field<'a, S> {
} }
/// Metadata for an argument to a field /// Metadata for an argument to a field
#[derive(Debug, Clone)] #[derive(Clone, Debug)]
pub struct Argument<'a, S> { pub struct Argument<'a, S> {
#[doc(hidden)] #[doc(hidden)]
pub name: String, 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> { impl<'a, S> ScalarMeta<'a, S> {
/// Builds a new [`ScalarMeta`] type with the specified `name`. /// Builds a new [`ScalarMeta`] type with the specified `name`.
pub fn new<T>(name: Cow<'a, str>) -> Self 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. /// Sets the `description` of this [`ScalarMeta`] type.
/// ///
/// Overwrites any previously set description. /// Overwrites any previously set description.
@ -477,7 +557,7 @@ impl<'a, S> ScalarMeta<'a, S> {
} }
impl<'a> ListMeta<'a> { 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 /// Specifying `expected_size` will be used to ensure that values of this
/// type will always match it. /// type will always match it.
@ -495,7 +575,7 @@ impl<'a> ListMeta<'a> {
} }
impl<'a> NullableMeta<'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 { pub fn new(of_type: Type<'a>) -> Self {
Self { of_type } 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. /// Sets the `description` of this [`EnumMeta`] type.
/// ///
/// Overwrites any previously set description. /// 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. /// Set the `description` of this [`InputObjectMeta`] type.
/// ///
/// Overwrites any previously set description. /// Overwrites any previously set description.
@ -811,3 +920,21 @@ where
.map(drop) .map(drop)
.map_err(T::Error::into_field_error) .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)
}

View file

@ -141,7 +141,7 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
self.description.as_deref() self.description.as_deref()
} }
fn types(&self) -> Vec<TypeType<S>> { fn types(&self) -> Vec<TypeType<'_, S>> {
self.type_list() self.type_list()
.into_iter() .into_iter()
.filter(|t| { .filter(|t| {
@ -156,21 +156,21 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
} }
#[graphql(name = "queryType")] #[graphql(name = "queryType")]
fn query_type_(&self) -> TypeType<S> { fn query_type_(&self) -> TypeType<'_, S> {
self.query_type() self.query_type()
} }
#[graphql(name = "mutationType")] #[graphql(name = "mutationType")]
fn mutation_type_(&self) -> Option<TypeType<S>> { fn mutation_type_(&self) -> Option<TypeType<'_, S>> {
self.mutation_type() self.mutation_type()
} }
#[graphql(name = "subscriptionType")] #[graphql(name = "subscriptionType")]
fn subscription_type_(&self) -> Option<TypeType<S>> { fn subscription_type_(&self) -> Option<TypeType<'_, S>> {
self.subscription_type() self.subscription_type()
} }
fn directives(&self) -> Vec<&DirectiveType<S>> { fn directives(&self) -> Vec<&DirectiveType<'_, S>> {
self.directive_list() self.directive_list()
} }
} }
@ -214,7 +214,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
fn fields( fn fields(
&self, &self,
#[graphql(default = false)] include_deprecated: Option<bool>, #[graphql(default = false)] include_deprecated: Option<bool>,
) -> Option<Vec<&Field<S>>> { ) -> Option<Vec<&Field<'_, S>>> {
match self { match self {
TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
| TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( | 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 { match self {
TypeType::Concrete(_) => None, TypeType::Concrete(_) => None,
TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l), TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l),
} }
} }
fn input_fields(&self) -> Option<&[Argument<S>]> { fn input_fields(&self) -> Option<&[Argument<'_, S>]> {
match self { match self {
TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { TypeType::Concrete(&MetaType::InputObject(InputObjectMeta {
ref input_fields, ref input_fields,
@ -343,7 +343,7 @@ impl<'a, S: ScalarValue + 'a> Field<'a, S> {
self.description.as_deref() self.description.as_deref()
} }
fn args(&self) -> Vec<&Argument<S>> { fn args(&self) -> Vec<&Argument<'_, S>> {
self.arguments self.arguments
.as_ref() .as_ref()
.map_or_else(Vec::new, |v| v.iter().collect()) .map_or_else(Vec::new, |v| v.iter().collect())
@ -434,7 +434,7 @@ impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> {
self.is_repeatable self.is_repeatable
} }
fn args(&self) -> &[Argument<S>] { fn args(&self) -> &[Argument<'_, S>] {
&self.arguments &self.arguments
} }

View file

@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{
graphql_vars, graphql_value, graphql_vars,
introspection::IntrospectionFormat, introspection::IntrospectionFormat,
schema::model::RootNode, schema::model::RootNode,
tests::fixtures::starwars::schema::{Database, Query}, tests::fixtures::starwars::schema::{Database, Query},

View file

@ -1,4 +1,4 @@
use crate::value::Value; use crate::graphql::{self, Value};
// Sort a nested schema Value. // Sort a nested schema Value.
// In particular, lists are sorted by the "name" key of children, if present. // 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 { pub(crate) fn schema_introspection_result() -> Value {
let mut v = graphql_value!({ let mut v = graphql::value!({
"__schema": { "__schema": {
"description": null, "description": null,
"queryType": { "queryType": {
@ -1451,7 +1451,7 @@ pub(crate) fn schema_introspection_result() -> Value {
} }
pub(crate) fn schema_introspection_result_without_descriptions() -> Value { pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
let mut v = graphql_value!({ let mut v = graphql::value!({
"__schema": { "__schema": {
"queryType": { "queryType": {
"name": "Query" "name": "Query"

305
juniper/src/types/arc.rs Normal file
View file

@ -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;
}

448
juniper/src/types/array.rs Normal file
View file

@ -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])),
);
}
}

View file

@ -4,6 +4,7 @@ use crate::{
ast::{Directive, FromInputValue, InputValue, Selection}, ast::{Directive, FromInputValue, InputValue, Selection},
executor::{ExecutionResult, Executor, Registry, Variables}, executor::{ExecutionResult, Executor, Registry, Variables},
parser::Spanning, parser::Spanning,
resolve,
schema::meta::{Argument, MetaType}, schema::meta::{Argument, MetaType},
value::{DefaultScalarValue, Object, ScalarValue, Value}, value::{DefaultScalarValue, Object, ScalarValue, Value},
FieldResult, GraphQLEnum, IntoFieldError, FieldResult, GraphQLEnum, IntoFieldError,
@ -98,6 +99,8 @@ impl<'a, S> Arguments<'a, S> {
Self { args } Self { args }
} }
/// TODO: DEPRECATED!
///
/// Gets an argument by the given `name` and converts it into the desired /// Gets an argument by the given `name` and converts it into the desired
/// type. /// type.
/// ///
@ -121,6 +124,38 @@ impl<'a, S> Arguments<'a, S> {
.transpose() .transpose()
.map_err(IntoFieldError::into_field_error) .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. /// Primary trait used to resolve GraphQL values.

303
juniper/src/types/box.rs Normal file
View file

@ -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;
}

View file

@ -183,7 +183,7 @@ where
S: ScalarValue, S: ScalarValue,
{ {
fn to_input_value(&self) -> InputValue<S> { 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, S: ScalarValue,
{ {
fn to_input_value(&self) -> InputValue<S> { 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, S: ScalarValue,
{ {
fn to_input_value(&self) -> InputValue<S> { 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))
} }
} }

304
juniper/src/types/cow.rs Normal file
View file

@ -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;
}

72
juniper/src/types/iter.rs Normal file
View file

@ -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))
}

View file

@ -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 async_await;
pub mod base; pub mod base;
pub mod containers; pub mod containers;
pub mod marker; pub mod marker;
pub mod name; pub mod name;
pub mod nullable;
pub mod pointers; pub mod pointers;
pub mod scalars; pub mod scalars;
pub mod subscriptions; pub mod subscriptions;
pub mod utilities; pub mod utilities;
#[doc(inline)]
pub use self::nullable::Nullable;

View file

@ -1,42 +1,53 @@
//! GraphQL implementation for [`Nullable`].
use std::mem;
use futures::future;
use crate::{ use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue}, ast::{FromInputValue, InputValue, ToInputValue},
behavior,
executor::{ExecutionResult, Executor, Registry}, executor::{ExecutionResult, Executor, Registry},
graphql, reflect, resolve,
schema::meta::MetaType, schema::meta::MetaType,
types::{ types::{
async_await::GraphQLValueAsync, async_await::GraphQLValueAsync,
base::{GraphQLType, GraphQLValue}, base::{GraphQLType, GraphQLValue},
marker::IsInputType, marker::IsInputType,
}, },
value::{ScalarValue, Value}, BoxFuture, FieldResult, ScalarValue, Selection,
}; };
/// `Nullable` can be used in situations where you need to distinguish between an implicitly and /// [`Nullable`] wrapper allowing to distinguish between an implicit and
/// explicitly null input value. /// 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 /// > ```graphql
/// { /// > {
/// field(arg: null) /// > field(arg: null)
/// field /// > 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 /// In cases where there is no need to distinguish between the two types of
/// not provided a value to the argument “arg”. These two forms may be interpreted differently. For /// `null`, it's better to simply use [`Option`].
/// example, a mutation representing deleting a field vs not altering a field, respectively.
/// ///
/// In cases where you do not need to be able to distinguish between the two types of null, you /// [0]: https://spec.graphql.org/October2021#example-1c7eb
/// should simply use `Option<T>`.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Nullable<T> { pub enum Nullable<T> {
/// No value /// No value specified.
ImplicitNull, ImplicitNull,
/// No value, explicitly specified to be null /// Value explicitly specified to be `null`.
ExplicitNull, ExplicitNull,
/// Some value `T` /// Explicitly specified non-`null` value of `T`.
Some(T), Some(T),
} }
@ -49,101 +60,134 @@ impl<T> Default for Nullable<T> {
} }
impl<T> 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] #[inline]
pub fn is_explicit_null(&self) -> bool { pub fn is_explicit_null(&self) -> bool {
matches!(self, Self::ExplicitNull) matches!(self, Self::ExplicitNull)
} }
/// Returns `true` if the nullable is a `ImplicitNull` value. /// Indicates whether this [`Nullable`] represents an [`ImplicitNull`].
///
/// [`ImplicitNull`]: Nullable::ImplicitNull
#[inline] #[inline]
pub fn is_implicit_null(&self) -> bool { pub fn is_implicit_null(&self) -> bool {
matches!(self, Self::ImplicitNull) matches!(self, Self::ImplicitNull)
} }
/// Returns `true` if the nullable is a `Some` value. /// Indicates whether this [`Nullable`] contains a non-`null` value.
#[inline] #[inline]
pub fn is_some(&self) -> bool { pub fn is_some(&self) -> bool {
matches!(self, Self::Some(_)) matches!(self, Self::Some(_))
} }
/// Returns `true` if the nullable is not a `Some` value. /// Indicates whether this [`Nullable`] represents a `null`.
#[inline] #[inline]
pub fn is_null(&self) -> bool { pub fn is_null(&self) -> bool {
!matches!(self, Self::Some(_)) !matches!(self, Self::Some(_))
} }
/// Converts from `&mut Nullable<T>` to `Nullable<&mut T>`. /// Converts from `&Nullable<T>` to `Nullable<&T>`.
#[inline] #[inline]
pub fn as_mut(&mut self) -> Nullable<&mut T> { pub fn as_ref(&self) -> Nullable<&T> {
match *self { match self {
Self::Some(ref mut x) => Nullable::Some(x), Self::Some(x) => Nullable::Some(x),
Self::ImplicitNull => Nullable::ImplicitNull, Self::ImplicitNull => Nullable::ImplicitNull,
Self::ExplicitNull => Nullable::ExplicitNull, Self::ExplicitNull => Nullable::ExplicitNull,
} }
} }
/// Returns the contained `Some` value, consuming the `self` value. /// Converts from `&mut Nullable<T>` to `Nullable<&mut T>`.
#[inline]
pub fn as_mut(&mut self) -> Nullable<&mut T> {
match self {
Self::Some(x) => Nullable::Some(x),
Self::ImplicitNull => Nullable::ImplicitNull,
Self::ExplicitNull => Nullable::ExplicitNull,
}
}
/// Returns the contained non-`null` value, consuming the `self` value.
/// ///
/// # Panics /// # 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] #[inline]
#[track_caller] #[track_caller]
pub fn expect(self, msg: &str) -> T { pub fn expect(self, msg: &str) -> T {
self.some().expect(msg) 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] #[inline]
pub fn unwrap_or(self, default: T) -> T { pub fn unwrap_or(self, default: T) -> T {
self.some().unwrap_or(default) 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] #[inline]
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T { pub fn unwrap_or_else<F: FnOnce() -> T>(self, func: F) -> T {
self.some().unwrap_or_else(f) self.some().unwrap_or_else(func)
} }
/// Maps a `Nullable<T>` to `Nullable<U>` by applying a function to a contained value. /// Returns the contained non-`null` value or the [`Default`] one.
#[inline] #[inline]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Nullable<U> { pub fn unwrap_or_default(self) -> T
where
T: Default,
{
self.some().unwrap_or_default()
}
/// 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, func: F) -> Nullable<U> {
match self { match self {
Self::Some(x) => Nullable::Some(f(x)), Self::Some(x) => Nullable::Some(func(x)),
Self::ImplicitNull => Nullable::ImplicitNull, Self::ImplicitNull => Nullable::ImplicitNull,
Self::ExplicitNull => Nullable::ExplicitNull, Self::ExplicitNull => Nullable::ExplicitNull,
} }
} }
/// Applies a function to the contained value (if any), or returns the provided default (if /// Applies the provided `func`tion to the contained non-`null` value (if
/// not). /// any), or returns the provided `default` value (if not).
#[inline] #[inline]
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U { pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, func: F) -> U {
self.some().map_or(default, f) 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] #[inline]
pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { 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, f) self.some().map_or_else(default, func)
} }
/// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and /// Transforms this `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)`
/// `ImplicitNull` or `ExplicitNull` to `Err(err)`. /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err)`.
///
/// [`ExplicitNull`]: Nullable::ExplicitNull
/// [`ImplicitNull`]: Nullable::ImplicitNull
#[inline] #[inline]
pub fn ok_or<E>(self, err: E) -> Result<T, E> { pub fn ok_or<E>(self, err: E) -> Result<T, E> {
self.some().ok_or(err) self.some().ok_or(err)
} }
/// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and /// Transforms this `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)`
/// `ImplicitNull` or `ExplicitNull` to `Err(err())`. /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err())`.
///
/// [`ExplicitNull`]: Nullable::ExplicitNull
/// [`ImplicitNull`]: Nullable::ImplicitNull
#[inline] #[inline]
pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> { pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
self.some().ok_or_else(err) 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] #[inline]
#[must_use] #[must_use]
pub fn or(self, b: Self) -> Self { 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 this [`Nullable`] if it contains a non-`null` value, otherwise
/// returns the result. /// computes a [`Nullable`] value from the specified `func`tion.
#[inline] #[inline]
#[must_use] #[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 { match self {
Self::Some(_) => self, Self::Some(_) => self,
_ => f(), _ => func(),
} }
} }
/// Replaces the actual value in the nullable by the value given in parameter, returning the /// Replaces the contained non-`null` value in this [`Nullable`] by the
/// old value if present, leaving a `Some` in its place without deinitializing either one. /// provided `value`, returning the old one if present, leaving a [`Some`]
/// in its place without deinitializing either one.
///
/// [`Some`]: Nullable::Some
#[inline] #[inline]
#[must_use] #[must_use]
pub fn replace(&mut self, value: T) -> Self { 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> { pub fn some(self) -> Option<T> {
match self { match self {
Self::Some(v) => Some(v), 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))`, /// Converts this [`Nullable`] to `Option<Option<T>>`, mapping `Some(v)` to
/// `ExplicitNull` to `Some(None)`, and `ImplicitNull` to `None`. /// `Some(Some(v))`, [`ExplicitNull`] to `Some(None)`, and [`ImplicitNull`]
/// to [`None`].
///
/// [`ExplicitNull`]: Nullable::ExplicitNull
/// [`ImplicitNull`]: Nullable::ImplicitNull
pub fn explicit(self) -> Option<Option<T>> { pub fn explicit(self) -> Option<Option<T>> {
match self { match self {
Self::Some(v) => Some(Some(v)), Self::Some(v) => Some(Some(v)),
@ -192,33 +244,188 @@ impl<T> Nullable<T> {
} }
impl<T: Copy> 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> { pub fn copied(self) -> Nullable<T> {
self.map(|&t| t) self.map(|t| *t)
} }
} }
impl<T: Copy> Nullable<&mut 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> { pub fn copied(self) -> Nullable<T> {
self.map(|&mut t| t) self.map(|t| *t)
} }
} }
impl<T: Clone> Nullable<&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::clone)
}
}
impl<T: Clone> Nullable<&mut T> {
/// Maps this `Nullable<&mut T>` to a `Nullable<T>` by [`Clone`]ing the
/// contents of this [`Nullable`].
pub fn cloned(self) -> Nullable<T> { pub fn cloned(self) -> Nullable<T> {
self.map(|t| t.clone()) self.map(|t| t.clone())
} }
} }
impl<T: Clone> Nullable<&mut T> { impl<T, TI, SV, BH> resolve::Type<TI, SV, BH> for Nullable<T>
/// Maps a `Nullable<&mut T>` to a `Nullable<T>` by cloning the contents of the nullable. where
pub fn cloned(self) -> Nullable<T> { T: resolve::Type<TI, SV, BH>,
self.map(|t| t.clone()) 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> impl<S, T> GraphQLType<S> for Nullable<T>
where where
T: GraphQLType<S>, T: GraphQLType<S>,
@ -256,7 +463,7 @@ where
) -> ExecutionResult<S> { ) -> ExecutionResult<S> {
match *self { match *self {
Self::Some(ref obj) => executor.resolve(info, obj), Self::Some(ref obj) => executor.resolve(info, obj),
_ => Ok(Value::null()), _ => Ok(graphql::Value::null()),
} }
} }
} }
@ -273,11 +480,11 @@ where
info: &'a Self::TypeInfo, info: &'a Self::TypeInfo,
_: Option<&'a [Selection<S>]>, _: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>, executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> { ) -> BoxFuture<'a, ExecutionResult<S>> {
let f = async move { let f = async move {
let value = match self { let value = match self {
Self::Some(obj) => executor.resolve_into_value_async(info, obj).await, Self::Some(obj) => executor.resolve_into_value_async(info, obj).await,
_ => Value::null(), _ => graphql::Value::null(),
}; };
Ok(value) Ok(value)
}; };

175
juniper/src/types/option.rs Normal file
View file

@ -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)));
}
}

305
juniper/src/types/rc.rs Normal file
View file

@ -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;
}

284
juniper/src/types/ref.rs Normal file
View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

235
juniper/src/types/slice.rs Normal file
View file

@ -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);
}

273
juniper/src/types/str.rs Normal file
View file

@ -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;
}

313
juniper/src/types/vec.rs Normal file
View file

@ -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])),
);
}
}

View file

@ -6,6 +6,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem};
use crate::{ use crate::{
ast::{InputValue, ToInputValue}, ast::{InputValue, ToInputValue},
parser::Spanning, parser::Spanning,
resolve,
}; };
pub use self::{ 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> { impl<S: ScalarValue> fmt::Display for Value<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {

View file

@ -31,5 +31,5 @@ url = "2.0"
[dev-dependencies] [dev-dependencies]
derive_more = "0.99.7" derive_more = "0.99.7"
futures = "0.3.22" futures = "0.3.22"
juniper = { path = "../juniper" } #juniper = { path = "../juniper" }
serde = "1.0" serde = "1.0"

View file

@ -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()
}
}

View file

@ -15,7 +15,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
default, diagnostic, filter_attrs, behavior, default, diagnostic, filter_attrs,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
ParseBufferExt as _, TypeExt as _, ParseBufferExt as _, TypeExt as _,
@ -52,6 +52,19 @@ pub(crate) struct Attr {
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
pub(crate) default: Option<SpanContainer<default::Value>>, 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 /// Explicitly specified marker indicating that this method argument doesn't
/// represent a [GraphQL argument][1], but is a [`Context`] being injected /// represent a [GraphQL argument][1], but is a [`Context`] being injected
/// into a [GraphQL field][2] resolving function. /// into a [GraphQL field][2] resolving function.
@ -103,6 +116,13 @@ impl Parse for Attr {
.replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .replace(SpanContainer::new(ident.span(), Some(val.span()), val))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "ctx" | "context" | "Context" => {
let span = ident.span(); let span = ident.span();
out.context out.context
@ -133,6 +153,7 @@ impl Attr {
name: try_merge_opt!(name: self, another), name: try_merge_opt!(name: self, another),
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
default: try_merge_opt!(default: self, another), default: try_merge_opt!(default: self, another),
behavior: try_merge_opt!(behavior: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
executor: try_merge_opt!(executor: 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 /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
pub(crate) default: Option<default::Value>, 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. /// Possible kinds of Rust method arguments for code generation.
@ -433,6 +462,7 @@ impl OnMethod {
ty: argument.ty.as_ref().clone(), ty: argument.ty.as_ref().clone(),
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
default: attr.default.map(SpanContainer::into_inner), default: attr.default.map(SpanContainer::into_inner),
behavior: attr.behavior.into(),
}))) })))
} }
} }

View file

@ -15,7 +15,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
deprecation, filter_attrs, behavior, deprecation, filter_attrs,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
ParseBufferExt as _, ParseBufferExt as _,
@ -56,6 +56,19 @@ pub(crate) struct Attr {
/// [2]: https://spec.graphql.org/October2021#sec-Deprecation /// [2]: https://spec.graphql.org/October2021#sec-Deprecation
pub(crate) deprecated: Option<SpanContainer<deprecation::Directive>>, 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 /// Explicitly specified marker indicating that this method (or struct
/// field) should be omitted by code generation and not considered as the /// field) should be omitted by code generation and not considered as the
/// [GraphQL field][1] definition. /// [GraphQL field][1] definition.
@ -94,6 +107,13 @@ impl Parse for Attr {
)) ))
.none_or_else(|_| err::dup_arg(&ident))? .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" | "skip" => out
.ignore .ignore
.replace(SpanContainer::new(ident.span(), None, ident.clone())) .replace(SpanContainer::new(ident.span(), None, ident.clone()))
@ -116,6 +136,7 @@ impl Attr {
name: try_merge_opt!(name: self, another), name: try_merge_opt!(name: self, another),
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
deprecated: try_merge_opt!(deprecated: self, another), deprecated: try_merge_opt!(deprecated: self, another),
behavior: try_merge_opt!(behavior: self, another),
ignore: try_merge_opt!(ignore: 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 /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
pub(crate) ident: syn::Ident, 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 /// Rust [`MethodArgument`]s required to call the method representing this
/// [GraphQL field][1]. /// [GraphQL field][1].
/// ///

View file

@ -1,7 +1,81 @@
//! Common code generated parts, used by this crate. //! Common code generated parts, used by this crate.
use proc_macro2::TokenStream; 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. /// Generate the code resolving some [GraphQL type][1] in a synchronous manner.
/// ///

View file

@ -1,5 +1,6 @@
//! Common functions, definitions and extensions for code generation, used by this crate. //! Common functions, definitions and extensions for code generation, used by this crate.
pub(crate) mod behavior;
pub(crate) mod default; pub(crate) mod default;
pub(crate) mod deprecation; pub(crate) mod deprecation;
mod description; mod description;

View file

@ -105,15 +105,37 @@ pub(crate) trait TypeExt {
#[must_use] #[must_use]
fn unreferenced(&self) -> &Self; fn unreferenced(&self) -> &Self;
/// Iterates mutably over all the lifetime parameters of this [`syn::Type`] /// Iterates mutably over all the lifetime parameters (even elided) of this
/// with the given `func`tion. /// [`syn::Type`] with the given `func`tion.
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);
/// 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 /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except
/// the `'static` ones), making it suitable for using in contexts with /// the `'static` ones), making it suitable for using in contexts with
/// inferring. /// inferring.
fn lifetimes_anonymized(&mut self); 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. /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any.
#[must_use] #[must_use]
fn topmost_ident(&self) -> Option<&syn::Ident>; 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}; 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() { for seg in path.segments.iter_mut() {
match &mut seg.arguments { match &mut seg.arguments {
syn::PathArguments::AngleBracketed(angle) => { syn::PathArguments::AngleBracketed(angle) => {
for arg in angle.args.iter_mut() { for arg in angle.args.iter_mut() {
match arg { match arg {
GA::Lifetime(lt) => func(lt), GA::Lifetime(lt) => func(lt.into()),
GA::Type(ty) => ty.lifetimes_iter_mut(func), GA::Type(ty) => ty.lifetimes_iter_mut(func),
GA::Binding(b) => b.ty.lifetimes_iter_mut(func), GA::Binding(b) => b.ty.lifetimes_iter_mut(func),
GA::Constraint(_) | GA::Const(_) => {} GA::Constraint(_) | GA::Const(_) => {}
@ -181,7 +203,7 @@ impl TypeExt for syn::Type {
| T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
for bound in bounds.iter_mut() { for bound in bounds.iter_mut() {
match bound { match bound {
syn::TypeParamBound::Lifetime(lt) => func(lt), syn::TypeParamBound::Lifetime(lt) => func(lt.into()),
syn::TypeParamBound::Trait(bound) => { syn::TypeParamBound::Trait(bound) => {
if bound.lifetimes.is_some() { if bound.lifetimes.is_some() {
todo!("Iterating over HRTB lifetimes in trait is not yet supported") todo!("Iterating over HRTB lifetimes in trait is not yet supported")
@ -193,9 +215,7 @@ impl TypeExt for syn::Type {
} }
T::Reference(ref_ty) => { T::Reference(ref_ty) => {
if let Some(lt) = ref_ty.lifetime.as_mut() { func((&mut ref_ty.lifetime).into());
func(lt)
}
(*ref_ty.elem).lifetimes_iter_mut(func) (*ref_ty.elem).lifetimes_iter_mut(func)
} }
@ -213,13 +233,48 @@ impl TypeExt for syn::Type {
} }
fn lifetimes_anonymized(&mut self) { fn lifetimes_anonymized(&mut self) {
self.lifetimes_iter_mut(&mut |lt| { self.named_lifetimes_iter_mut(&mut |lt| {
if lt.ident != "_" && lt.ident != "static" { if lt.ident != "_" && lt.ident != "static" {
lt.ident = syn::Ident::new("_", Span::call_site()); 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> { fn topmost_ident(&self) -> Option<&syn::Ident> {
match self.unparenthesized() { match self.unparenthesized() {
syn::Type::Path(p) => Some(&p.path), 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. /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing.
pub(crate) trait GenericsExt { pub(crate) trait GenericsExt {
/// Removes all default types out of type parameters and const parameters in these /// 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(),
);
}
}
}

View file

@ -63,15 +63,17 @@ pub(crate) enum Type {
/// [`ScalarValue`]: juniper::ScalarValue /// [`ScalarValue`]: juniper::ScalarValue
Concrete(syn::Type), 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. /// The original type is the type that the code is generated for.
/// ///
/// [`ScalarValue`]: juniper::ScalarValue /// [`ScalarValue`]: juniper::ScalarValue
ExplicitGeneric(syn::Ident), ExplicitGeneric(syn::Ident),
/// [`ScalarValue`] parametrization is assumed to be generic and is not specified /// [`ScalarValue`] parametrization is assumed to be generic and is not
/// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`). /// specified explicitly, or specified as bound predicate (like
/// `S: ScalarValue + Send + Sync`).
/// ///
/// [`ScalarValue`]: juniper::ScalarValue /// [`ScalarValue`]: juniper::ScalarValue
ImplicitGeneric(Option<syn::PredicateType>), ImplicitGeneric(Option<syn::PredicateType>),

View file

@ -87,6 +87,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
values, values,
has_ignored_variants, has_ignored_variants,
}; };

View file

@ -15,7 +15,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
deprecation, filter_attrs, behavior, deprecation, filter_attrs, gen,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
ParseBufferExt as _, ParseBufferExt as _,
@ -65,6 +65,17 @@ struct ContainerAttr {
/// [0]: https://spec.graphql.org/October2021#sec-Enums /// [0]: https://spec.graphql.org/October2021#sec-Enums
scalar: Option<SpanContainer<scalar::AttrValue>>, 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 /// Explicitly specified [`rename::Policy`] for all [values][1] of this
/// [GraphQL enum][0]. /// [GraphQL enum][0].
/// ///
@ -118,6 +129,13 @@ impl Parse for ContainerAttr {
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "rename_all" => {
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
let val = input.parse::<syn::LitStr>()?; let val = input.parse::<syn::LitStr>()?;
@ -151,6 +169,7 @@ impl ContainerAttr {
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: 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), rename_values: try_merge_opt!(rename_values: self, another),
is_internal: self.is_internal || another.is_internal, is_internal: self.is_internal || another.is_internal,
}) })
@ -364,6 +383,13 @@ struct Definition {
/// [0]: https://spec.graphql.org/October2021#sec-Enums /// [0]: https://spec.graphql.org/October2021#sec-Enums
scalar: scalar::Type, 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]. /// [Values][1] of this [GraphQL enum][0].
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Enums /// [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_from_input_value_tokens().to_tokens(into);
self.impl_to_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_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 /// Returns generated code implementing [`GraphQLType`] trait for this
/// [GraphQL enum][0]. /// [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 /// Returns generated code implementing [`GraphQLValue`] trait for this
/// [GraphQL enum][0]. /// [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 /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
/// [GraphQL enum][0]. /// [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 /// Returns generated code implementing [`FromInputValue`] trait for this
/// [GraphQL enum][0]. /// [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 /// Returns generated code implementing [`ToInputValue`] trait for this
/// [GraphQL enum][0]. /// [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 /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
/// [`WrappedType`] traits for this [GraphQL enum][0]. /// [`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 /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
/// similar) implementation of this enum. /// similar) implementation of this enum.
/// ///
@ -742,4 +1189,16 @@ impl Definition {
generics 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)
}
} }

View file

@ -80,6 +80,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
}; };
@ -94,13 +95,13 @@ fn parse_field(
renaming: rename::Policy, renaming: rename::Policy,
is_internal: bool, is_internal: bool,
) -> Option<FieldDefinition> { ) -> 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)) .map_err(|e| proc_macro_error::emit_error!(e))
.ok()?; .ok()?;
let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?; let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?;
let name = field_attr let name = attr
.name .name
.map_or_else( .map_or_else(
|| renaming.apply(&ident.unraw().to_string()), || renaming.apply(&ident.unraw().to_string()),
@ -114,10 +115,11 @@ fn parse_field(
Some(FieldDefinition { Some(FieldDefinition {
ident: ident.clone(), ident: ident.clone(),
ty: f.ty.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, name,
description: field_attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
ignored: field_attr.ignore.is_some(), ignored: attr.ignore.is_some(),
}) })
} }

View file

@ -15,7 +15,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
default, filter_attrs, behavior, default, filter_attrs, gen,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
ParseBufferExt as _, ParseBufferExt as _,
@ -66,6 +66,17 @@ struct ContainerAttr {
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
scalar: Option<SpanContainer<scalar::AttrValue>>, 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 /// Explicitly specified [`rename::Policy`] for all fields of this
/// [GraphQL input object][0]. /// [GraphQL input object][0].
/// ///
@ -118,6 +129,13 @@ impl Parse for ContainerAttr {
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "rename_all" => {
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
let val = input.parse::<syn::LitStr>()?; let val = input.parse::<syn::LitStr>()?;
@ -151,6 +169,7 @@ impl ContainerAttr {
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: 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), rename_fields: try_merge_opt!(rename_fields: self, another),
is_internal: self.is_internal || another.is_internal, is_internal: self.is_internal || another.is_internal,
}) })
@ -185,6 +204,16 @@ struct FieldAttr {
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
name: Option<SpanContainer<String>>, 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 /// Explicitly specified [default value][2] of this
/// [GraphQL input object field][1] to be used used in case a field value is /// [GraphQL input object field][1] to be used used in case a field value is
/// not provided. /// not provided.
@ -195,15 +224,18 @@ struct FieldAttr {
/// [2]: https://spec.graphql.org/October2021#DefaultValue /// [2]: https://spec.graphql.org/October2021#DefaultValue
default: Option<SpanContainer<default::Value>>, default: Option<SpanContainer<default::Value>>,
/// Explicitly specified [description][2] of this /// Explicitly specified type of the custom [`Behavior`] this
/// [GraphQL input object field][1]. /// [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 [`None`], then [`behavior::Standard`] will be used for the generated
/// if any. /// code.
/// ///
/// [`Behavior`]: juniper::behavior
/// [`behavior::Standard`]: juniper::behavior::Standard
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions /// [coerce]: juniper::behavior::Coerce
description: Option<SpanContainer<Description>>, behavior: Option<SpanContainer<behavior::Type>>,
/// Explicitly specified marker for the Rust struct field to be ignored and /// Explicitly specified marker for the Rust struct field to be ignored and
/// not included into the code generated for a [GraphQL input object][0] /// 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))? .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" => { "default" => {
let val = input.parse::<default::Value>()?; let val = input.parse::<default::Value>()?;
out.default out.default
.replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .replace(SpanContainer::new(ident.span(), Some(val.span()), val))
.none_or_else(|_| err::dup_arg(&ident))? .none_or_else(|_| err::dup_arg(&ident))?
} }
"desc" | "description" => { "behave" | "behavior" => {
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
let desc = input.parse::<Description>()?; let bh = input.parse::<behavior::Type>()?;
out.description out.behavior
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
.none_or_else(|_| err::dup_arg(&ident))? .none_or_else(|_| err::dup_arg(&ident))?
} }
"ignore" | "skip" => out "ignore" | "skip" => out
@ -267,8 +306,9 @@ impl FieldAttr {
fn try_merge(self, mut another: Self) -> syn::Result<Self> { fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self { Ok(Self {
name: try_merge_opt!(name: self, another), name: try_merge_opt!(name: self, another),
default: try_merge_opt!(default: self, another),
description: try_merge_opt!(description: 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), ignore: try_merge_opt!(ignore: self, another),
}) })
} }
@ -314,6 +354,14 @@ struct FieldDefinition {
/// [2]: https://spec.graphql.org/October2021#DefaultValue /// [2]: https://spec.graphql.org/October2021#DefaultValue
default: Option<default::Value>, 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. /// Name of this [GraphQL input object field][1] in GraphQL schema.
/// ///
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
@ -338,6 +386,14 @@ struct FieldDefinition {
ignored: bool, 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. /// Representation of [GraphQL input object][0] for code generation.
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
@ -383,6 +439,13 @@ struct Definition {
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
scalar: scalar::Type, 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]. /// [Fields][1] of this [GraphQL input object][0].
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [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_from_input_value_tokens().to_tokens(into);
self.impl_to_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_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 /// Returns generated code implementing [`GraphQLType`] trait for this
/// [GraphQL input object][0]. /// [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 /// Returns generated code implementing [`GraphQLValue`] trait for this
/// [GraphQL input object][0]. /// [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 /// Returns generated code implementing [`ToInputValue`] trait for this
/// [GraphQL input object][0]. /// [GraphQL input object][0].
/// ///
@ -663,11 +1019,52 @@ impl Definition {
#where_clause #where_clause
{ {
fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
::juniper::InputValue::object( ::juniper::InputValue::object([#( #fields ),*])
#[allow(deprecated)] }
::std::array::IntoIter::new([#( #fields ),*]) }
.collect() }
) }
/// 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 /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
/// similar) implementation of this struct. /// similar) implementation of this struct.
/// ///
@ -775,4 +1213,16 @@ impl Definition {
generics 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)
}
} }

View file

@ -120,6 +120,7 @@ fn expand_on_trait(
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
implemented_for: attr implemented_for: attr
.implemented_for .implemented_for
@ -207,6 +208,7 @@ fn parse_trait_method(
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
deprecated: attr.deprecated.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner),
ident: method_ident.clone(), ident: method_ident.clone(),
behavior: attr.behavior.into(),
arguments: Some(arguments), arguments: Some(arguments),
has_receiver: method.sig.receiver().is_some(), has_receiver: method.sig.receiver().is_some(),
is_async: method.sig.asyncness.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), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
implemented_for: attr implemented_for: attr
.implemented_for .implemented_for
@ -372,6 +375,7 @@ fn parse_struct_field(
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
deprecated: attr.deprecated.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner),
ident: field_ident.clone(), ident: field_ident.clone(),
behavior: attr.behavior.into(),
arguments: None, arguments: None,
has_receiver: false, has_receiver: false,
is_async: false, is_async: false,

View file

@ -93,6 +93,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
implemented_for: attr implemented_for: attr
.implemented_for .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), description: attr.description.map(SpanContainer::into_inner),
deprecated: attr.deprecated.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner),
ident: field_ident.clone(), ident: field_ident.clone(),
behavior: attr.behavior.into(),
arguments: None, arguments: None,
has_receiver: false, has_receiver: false,
is_async: false, is_async: false,

View file

@ -20,7 +20,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
field, filter_attrs, gen, behavior, field, filter_attrs, gen,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
GenericsExt as _, ParseBufferExt as _, GenericsExt as _, ParseBufferExt as _,
@ -116,6 +116,17 @@ struct Attr {
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
scalar: Option<SpanContainer<scalar::AttrValue>>, 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 /// Explicitly specified marker indicating that the Rust trait should be
/// transformed into [`async_trait`]. /// transformed into [`async_trait`].
/// ///
@ -175,6 +186,13 @@ impl Parse for Attr {
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "for" | "implementers" => {
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
for impler in input.parse_maybe_wrapped_and_punctuated::< for impler in input.parse_maybe_wrapped_and_punctuated::<
@ -245,6 +263,7 @@ impl Attr {
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: 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), implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined),
implements: try_merge_hashset!(implements: self, another => span_joined), implements: try_merge_hashset!(implements: self, another => span_joined),
r#enum: try_merge_opt!(r#enum: self, another), r#enum: try_merge_opt!(r#enum: self, another),
@ -324,6 +343,13 @@ struct Definition {
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
scalar: scalar::Type, 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]. /// Defined [GraphQL fields][2] of this [GraphQL interface][1].
/// ///
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [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_meta_tokens().to_tokens(into);
self.impl_field_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into);
self.impl_async_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 /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
/// [GraphQL interface][1]. /// [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 /// Returns generated code implementing [`FieldMeta`] for each field of this
/// [GraphQL interface][1]. /// [GraphQL interface][1].
/// ///
@ -1373,6 +1496,20 @@ impl Definition {
generics 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 /// Indicates whether this enum has non-exhaustive phantom variant to hold
/// type parameters. /// type parameters.
#[must_use] #[must_use]

View file

@ -117,6 +117,7 @@ where
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
interfaces: attr interfaces: attr
.interfaces .interfaces
@ -218,6 +219,7 @@ fn parse_field(
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
deprecated: attr.deprecated.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner),
ident: method_ident.clone(), ident: method_ident.clone(),
behavior: attr.behavior.into(),
arguments: Some(arguments), arguments: Some(arguments),
has_receiver: method.sig.receiver().is_some(), has_receiver: method.sig.receiver().is_some(),
is_async: method.sig.asyncness.is_some(), is_async: method.sig.asyncness.is_some(),

View file

@ -94,6 +94,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
.map(SpanContainer::into_inner) .map(SpanContainer::into_inner)
.unwrap_or_else(|| parse_quote! { () }), .unwrap_or_else(|| parse_quote! { () }),
scalar, scalar,
behavior: attr.behavior.into(),
fields, fields,
interfaces: attr interfaces: attr
.interfaces .interfaces
@ -143,6 +144,7 @@ fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option<field::D
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
deprecated: attr.deprecated.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner),
ident: field_ident.clone(), ident: field_ident.clone(),
behavior: attr.behavior.into(),
arguments: None, arguments: None,
has_receiver: false, has_receiver: false,
is_async: false, is_async: false,

View file

@ -18,10 +18,10 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
field, filter_attrs, gen, behavior, field, filter_attrs, gen,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
GenericsExt as _, ParseBufferExt as _, TypeExt, GenericsExt as _, ParseBufferExt as _, TypeExt as _,
}, },
rename, scalar, Description, SpanContainer, rename, scalar, Description, SpanContainer,
}; };
@ -71,6 +71,17 @@ pub(crate) struct Attr {
/// [1]: https://spec.graphql.org/October2021#sec-Objects /// [1]: https://spec.graphql.org/October2021#sec-Objects
pub(crate) scalar: Option<SpanContainer<scalar::AttrValue>>, 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] /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1]
/// type implements. /// type implements.
/// ///
@ -130,6 +141,13 @@ impl Parse for Attr {
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "impl" | "implements" | "interfaces" => {
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
for iface in input.parse_maybe_wrapped_and_punctuated::< for iface in input.parse_maybe_wrapped_and_punctuated::<
@ -175,6 +193,7 @@ impl Attr {
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: 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), interfaces: try_merge_hashset!(interfaces: self, another => span_joined),
rename_fields: try_merge_opt!(rename_fields: self, another), rename_fields: try_merge_opt!(rename_fields: self, another),
is_internal: self.is_internal || another.is_internal, 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 /// [1]: https://spec.graphql.org/October2021#sec-Objects
pub(crate) scalar: scalar::Type, 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]. /// Defined [GraphQL fields][2] of this [GraphQL object][1].
/// ///
/// [1]: https://spec.graphql.org/October2021#sec-Objects /// [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 // Modify lifetime names to omit "lifetime name `'a` shadows a
// lifetime name that is already in scope" error. // lifetime name that is already in scope" error.
let mut ty = self.ty.clone(); let mut ty = self.ty.clone();
ty.lifetimes_iter_mut(&mut |lt| { ty.named_lifetimes_iter_mut(&mut |lt| {
let ident = lt.ident.unraw(); let ident = lt.ident.unraw();
lt.ident = format_ident!("__fa__{ident}"); lt.ident = format_ident!("__fa__{ident}");
lifetimes.push(lt.clone()); lifetimes.push(lt.clone());
@ -324,6 +350,12 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
(quote! { #impl_generics }, where_clause.cloned()) (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 /// Returns generated code implementing [`marker::IsOutputType`] trait for
/// this [GraphQL object][1]. /// 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 /// Returns generated code implementing [`GraphQLType`] trait for this
/// [GraphQL object][1]. /// [GraphQL object][1].
/// ///
@ -496,6 +796,12 @@ impl ToTokens for Definition<Query> {
self.impl_field_meta_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into);
self.impl_field_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into);
self.impl_async_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);
} }
} }

View file

@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned};
use crate::common::{diagnostic, parse, scalar, SpanContainer}; 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. /// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro.
const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr; 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 scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
let def = Definition { let def = Definition {
ty: TypeOrIdent::Type(ast.ty.clone()), ident: ast.ident.clone(),
where_clause: attr where_clause: attr
.where_clause .where_clause
.map_or_else(Vec::new, |cl| cl.into_inner()), .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), description: attr.description.map(SpanContainer::into_inner),
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
scalar, scalar,
scalar_value: attr.scalar.as_deref().into(),
behavior: attr.behavior.into(),
}; };
Ok(quote! { Ok(quote! {
@ -74,11 +76,12 @@ fn expand_on_derive_input(
ast: syn::DeriveInput, ast: syn::DeriveInput,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let attr = Attr::from_attrs("graphql_scalar", &attrs)?; let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
let methods = parse_derived_methods(&ast, &attr)?; let methods = parse_derived_methods(&ast, &attr)?;
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
let def = Definition { let def = Definition {
ty: TypeOrIdent::Ident(ast.ident.clone()), ident: ast.ident.clone(),
where_clause: attr where_clause: attr
.where_clause .where_clause
.map_or_else(Vec::new, |cl| cl.into_inner()), .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), description: attr.description.map(SpanContainer::into_inner),
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
scalar, scalar,
scalar_value: attr.scalar.as_deref().into(),
behavior: attr.behavior.into(),
}; };
Ok(quote! { Ok(quote! {

View file

@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned};
use crate::common::{diagnostic, scalar, SpanContainer}; 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. /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro.
const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive; 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> { pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
let ast = syn::parse2::<syn::DeriveInput>(input)?; let ast = syn::parse2::<syn::DeriveInput>(input)?;
let attr = Attr::from_attrs("graphql", &ast.attrs)?; let attr = Attr::from_attrs("graphql", &ast.attrs)?;
let methods = parse_derived_methods(&ast, &attr)?; let methods = parse_derived_methods(&ast, &attr)?;
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); 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 { Ok(Definition {
ty: TypeOrIdent::Ident(ast.ident.clone()), ident: ast.ident,
where_clause: attr where_clause: attr
.where_clause .where_clause
.map_or_else(Vec::new, |cl| cl.into_inner()), .map_or_else(Vec::new, |cl| cl.into_inner()),
generics: ast.generics.clone(), generics: ast.generics,
methods, methods,
name: attr name,
.name
.map(SpanContainer::into_inner)
.unwrap_or_else(|| ast.ident.to_string()),
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
scalar, scalar,
scalar_value: attr.scalar.as_deref().into(),
behavior: attr.behavior.into(),
} }
.to_token_stream()) .to_token_stream())
} }
@ -81,7 +86,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
.first() .first()
.filter(|_| fields.unnamed.len() == 1) .filter(|_| fields.unnamed.len() == 1)
.cloned() .cloned()
.map(Field::Unnamed) .map(Field::try_from)
.transpose()?
.ok_or_else(|| { .ok_or_else(|| {
ERR.custom_error( ERR.custom_error(
ast.span(), ast.span(),
@ -94,7 +100,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
.first() .first()
.filter(|_| fields.named.len() == 1) .filter(|_| fields.named.len() == 1)
.cloned() .cloned()
.map(Field::Named) .map(Field::try_from)
.transpose()?
.ok_or_else(|| { .ok_or_else(|| {
ERR.custom_error( ERR.custom_error(
ast.span(), ast.span(),

File diff suppressed because it is too large Load diff

View file

@ -93,6 +93,7 @@ fn expand_on_trait(
description: attr.description.map(SpanContainer::into_inner), description: attr.description.map(SpanContainer::into_inner),
context, context,
scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
behavior: attr.behavior.into(),
generics: ast.generics.clone(), generics: ast.generics.clone(),
variants, variants,
}; };
@ -210,6 +211,7 @@ fn parse_variant_from_trait_method(
ty, ty,
resolver_code, resolver_code,
resolver_check, resolver_check,
behavior: attr.behavior.into(),
context: method_context_ty, context: method_context_ty,
}) })
} }

View file

@ -84,6 +84,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<Definition> {
.map(SpanContainer::into_inner) .map(SpanContainer::into_inner)
.unwrap_or_else(|| parse_quote! { () }), .unwrap_or_else(|| parse_quote! { () }),
scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
behavior: attr.behavior.into(),
generics: ast.generics, generics: ast.generics,
variants, variants,
}) })
@ -163,6 +164,7 @@ fn parse_variant_from_enum_variant(
ty, ty,
resolver_code, resolver_code,
resolver_check, resolver_check,
behavior: attr.behavior.into(),
context: None, context: None,
}) })
} }
@ -214,6 +216,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition> {
.map(SpanContainer::into_inner) .map(SpanContainer::into_inner)
.unwrap_or_else(|| parse_quote! { () }), .unwrap_or_else(|| parse_quote! { () }),
scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
behavior: attr.behavior.into(),
generics: ast.generics, generics: ast.generics,
variants, variants,
}) })

View file

@ -18,7 +18,7 @@ use syn::{
}; };
use crate::common::{ use crate::common::{
filter_attrs, gen, behavior, filter_attrs, gen,
parse::{ parse::{
attr::{err, OptionExt as _}, attr::{err, OptionExt as _},
ParseBufferExt as _, ParseBufferExt as _,
@ -74,6 +74,17 @@ struct Attr {
/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [1]: https://spec.graphql.org/October2021#sec-Unions
scalar: Option<SpanContainer<scalar::AttrValue>>, 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] /// Explicitly specified external resolver functions for [GraphQL union][1]
/// variants. /// variants.
/// ///
@ -128,6 +139,13 @@ impl Parse for Attr {
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| err::dup_arg(&ident))? .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" => { "on" => {
let ty = input.parse::<syn::Type>()?; let ty = input.parse::<syn::Type>()?;
input.parse::<token::Eq>()?; input.parse::<token::Eq>()?;
@ -160,6 +178,7 @@ impl Attr {
description: try_merge_opt!(description: self, another), description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another), context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: self, another), scalar: try_merge_opt!(scalar: self, another),
behavior: try_merge_opt!(behavior: self, another),
external_resolvers: try_merge_hashmap!( external_resolvers: try_merge_hashmap!(
external_resolvers: self, another => span_joined external_resolvers: self, another => span_joined
), ),
@ -188,6 +207,19 @@ impl Attr {
/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [1]: https://spec.graphql.org/October2021#sec-Unions
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct VariantAttr { 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 /// Explicitly specified marker for the variant/field being ignored and not
/// included into [GraphQL union][1]. /// included into [GraphQL union][1].
/// ///
@ -210,6 +242,13 @@ impl Parse for VariantAttr {
while !input.is_empty() { while !input.is_empty() {
let ident = input.parse::<syn::Ident>()?; let ident = input.parse::<syn::Ident>()?;
match ident.to_string().as_str() { 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" | "skip" => out
.ignore .ignore
.replace(SpanContainer::new(ident.span(), None, ident.clone())) .replace(SpanContainer::new(ident.span(), None, ident.clone()))
@ -236,6 +275,7 @@ impl VariantAttr {
/// duplicates, if any. /// duplicates, if any.
fn try_merge(self, mut another: Self) -> syn::Result<Self> { fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self { Ok(Self {
behavior: try_merge_opt!(behavior: self, another),
ignore: try_merge_opt!(ignore: self, another), ignore: try_merge_opt!(ignore: self, another),
external_resolver: try_merge_opt!(external_resolver: 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 /// [1]: https://spec.graphql.org/October2021#sec-Unions
scalar: scalar::Type, 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]. /// Variants definitions of this [GraphQL union][1].
/// ///
/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [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_tokens().to_tokens(into);
self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into);
self.impl_reflection_traits_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 /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
/// [GraphQL union][1]. /// [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. /// Definition of [GraphQL union][1] variant for code generation.
@ -667,6 +812,14 @@ struct VariantDefinition {
/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [1]: https://spec.graphql.org/October2021#sec-Unions
resolver_check: syn::Expr, 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 /// Rust type of [`Context`] that this [GraphQL union][1] variant requires
/// for resolution. /// for resolution.
/// ///
@ -783,6 +936,7 @@ fn emerge_union_variants_from_attr(
ty, ty,
resolver_code, resolver_code,
resolver_check, resolver_check,
behavior: behavior::Type::default(), // TODO: remove at all
context: None, context: None,
}) })
} }