diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 68eb1be43..790df06cc 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, fmt, hash::Hash, slice, vec}; +use std::{any::TypeId, borrow::Cow, convert::Into, fmt, hash::Hash, mem, slice, vec}; use indexmap::IndexMap; @@ -256,13 +256,17 @@ impl InputValue { Self::Variable(v.as_ref().into()) } - /// Construct a [`Spanning::unlocated`] list. + /// Constructs a [`Spanning::unlocated`] [`InputValue::List`]. /// - /// Convenience function to make each [`InputValue`] in the input vector - /// not contain any location information. Can be used from [`ToInputValue`] - /// implementations, where no source code position information is available. - pub fn list(l: Vec) -> Self { - Self::List(l.into_iter().map(Spanning::unlocated).collect()) + /// Convenience function to make each [`InputValue`] in the input `list` to + /// not contain any location information. + /// + /// Intended for [`resolve::ToInputValue`] implementations, where no source + /// code position information is available. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + pub fn list(list: impl IntoIterator) -> Self { + Self::List(list.into_iter().map(Spanning::unlocated).collect()) } /// Construct a located list. @@ -270,16 +274,25 @@ impl InputValue { Self::List(l) } - /// Construct aa [`Spanning::unlocated`] object. + /// Construct a [`Spanning::unlocated`] [`InputValue::Onject`]. + /// + /// Similarly to [`InputValue::list()`] it makes each key and value in the + /// given `obj`ect to not contain any location information. /// - /// Similarly to [`InputValue::list`] it makes each key and value in the - /// given hash map not contain any location information. - pub fn object(o: IndexMap) -> Self + /// Intended for [`resolve::ToInputValue`] implementations, where no source + /// code position information is available. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + // TODO: Use `impl IntoIterator` argument once feature + // `explicit_generic_args_with_impl_trait` hits stable: + // https://github.com/rust-lang/rust/issues/83701 + pub fn object(obj: O) -> Self where K: AsRef + Eq + Hash, + O: IntoIterator, { Self::Object( - o.into_iter() + obj.into_iter() .map(|(k, v)| { ( Spanning::unlocated(k.as_ref().into()), @@ -459,6 +472,42 @@ impl InputValue { _ => false, } } + + /// Maps the [`ScalarValue`] type of this [`InputValue`] into the specified + /// one. + pub fn map_scalar_value(self) -> InputValue + where + S: ScalarValue, + Into: ScalarValue, + { + if TypeId::of::() == TypeId::of::() { + // 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 fmt::Display for InputValue { diff --git a/juniper/src/behavior.rs b/juniper/src/behavior.rs new file mode 100644 index 000000000..cb1976cd4 --- /dev/null +++ b/juniper/src/behavior.rs @@ -0,0 +1,130 @@ +//! GraphQL types behavior machinery. + +use std::{marker::PhantomData, sync::atomic::AtomicPtr}; + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Registry, +}; + +/// Default standard behavior of GraphQL types implementation. +#[derive(Debug)] +pub enum Standard {} + +/// Transparent wrapper allowing coercion of behavior types and type parameters. +#[repr(transparent)] +pub struct Coerce(PhantomData>>, T); + +impl Coerce { + /// 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(value: T) -> Coerce { + Coerce::wrap(value) +} + +impl resolve::Type for Coerce +where + T: resolve::Type + ?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 resolve::TypeName for Coerce +where + T: resolve::TypeName + ?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 +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) -> Result { + T::try_from_input_value(v).map(Self::wrap) + } + + fn try_from_implicit_null() -> Result { + T::try_from_implicit_null().map(Self::wrap) + } +} + +impl resolve::ScalarToken for Coerce +where + T: resolve::ScalarToken + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + T::parse_scalar_token(token) + } +} + +impl reflect::BaseType for Coerce +where + T: reflect::BaseType + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Coerce +where + T: reflect::BaseSubTypes + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Coerce +where + T: reflect::WrappedType + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} + +impl reflect::Implements for Coerce +where + T: reflect::Implements + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 9ab412d4f..cd550d7bb 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -3,7 +3,8 @@ use std::{ borrow::Cow, cmp::Ordering, - collections::HashMap, + collections::{hash_map, HashMap}, + convert, fmt::{Debug, Display}, sync::{Arc, RwLock}, }; @@ -17,6 +18,7 @@ use crate::{ Selection, ToInputValue, Type, }, parser::{SourcePosition, Spanning}, + resolve, schema::{ meta::{ Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta, @@ -69,7 +71,7 @@ pub enum FieldPath<'a> { /// of the current field stack, context, variables, and errors. pub struct Executor<'r, 'a, CtxT, S = DefaultScalarValue> where - CtxT: 'a, + CtxT: ?Sized + 'a, S: 'a, { fragments: &'r HashMap<&'a str, Fragment<'a, S>>, @@ -83,6 +85,41 @@ where field_path: Arc>, } +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(&self, value: &Type, type_info: &TI) -> ExecutionResult + where + Type: resolve::Value + ?Sized, + TI: ?Sized, + BH: ?Sized, + { + value.resolve_value(self.current_selection_set, type_info, self) + } + + /// Returns the current context of this [`Executor`]. + /// + /// Context is usually provided when the top-level [`execute()`] function is + /// called. + /// + /// [`execute()`]: crate::execute + #[must_use] + pub fn context(&self) -> &'r CX { + self.context + } +} + /// Error type for errors that occur during query execution /// /// All execution errors contain the source position in the query of the field @@ -627,14 +664,6 @@ where self.current_selection_set } - /// Access the current context - /// - /// You usually provide the context when calling the top-level `execute` - /// function, or using the context factory in the Iron integration. - pub fn context(&self) -> &'r CtxT { - self.context - } - /// The currently executing schema pub fn schema(&self) -> &'a SchemaType { 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( + &mut self, + type_info: &TI, + ) -> hash_map::Entry<'_, Name, MetaType<'r, S>> + where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + { + self.types.entry(T::type_name(type_info).parse().unwrap()) + } + /// Creates a [`Field`] with the provided `name`. pub fn field(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S> where @@ -1240,6 +1284,16 @@ impl<'r, S: 'r> Registry<'r, S> { Argument::new(name, self.get_type::(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 + resolve::InputValueOwned, + TI: ?Sized, + 'ti: 'r, + { + Argument::new(name, T::meta(self, type_info).as_type()) + } + fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) { self.types .entry(name) @@ -1258,6 +1312,84 @@ impl<'r, S: 'r> Registry<'r, S> { ScalarMeta::new::(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 + resolve::InputValueOwned + resolve::ScalarToken, + TI: ?Sized, + 'ti: 'r, + S: Clone, + { + self.entry_type::(type_info) + .or_insert_with(move || { + customize(ScalarMeta::new_reworked::(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 + resolve::InputValueAsRef + resolve::ScalarToken + ?Sized, + TI: ?Sized, + 'ti: 'r, + S: Clone, + { + self.register_scalar_unsized_with::(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 + resolve::InputValueAsRef + resolve::ScalarToken + ?Sized, + TI: ?Sized, + 'ti: 'r, + S: Clone, + { + self.entry_type::(type_info) + .or_insert_with(move || { + customize(ScalarMeta::new_unsized::(T::type_name(type_info))).into_meta() + }) + .clone() + } + /// Creates a [`ListMeta`] type. /// /// Specifying `expected_size` will be used to ensure that values of this @@ -1275,6 +1407,25 @@ impl<'r, S: 'r> Registry<'r, S> { ListMeta::new(of_type, expected_size) } + /// Builds a [`ListMeta`] information for the specified [`graphql::Type`]. + /// + /// Specifying `expected_size` will be used in validation to ensure that + /// values of this type matches it. + /// + /// [`graphql::Type`]: resolve::Type + pub fn wrap_list<'ti, T, TI>( + &mut self, + type_info: &'ti TI, + expected_size: Option, + ) -> MetaType<'r, S> + where + T: resolve::Type + ?Sized, + TI: ?Sized, + 'ti: 'r, + { + ListMeta::new(T::meta(self, type_info).into(), expected_size).into_meta() + } + /// Creates a [`NullableMeta`] type. pub fn build_nullable_type(&mut self, info: &T::TypeInfo) -> NullableMeta<'r> where @@ -1285,6 +1436,19 @@ impl<'r, S: 'r> Registry<'r, S> { NullableMeta::new(of_type) } + /// Builds a [`NullableMeta`] information for the specified + /// [`graphql::Type`]. + /// + /// [`graphql::Type`]: resolve::Type + pub fn wrap_nullable<'ti, T, TI>(&mut self, type_info: &'ti TI) -> MetaType<'r, S> + where + T: resolve::Type + ?Sized, + TI: ?Sized, + 'ti: 'r, + { + NullableMeta::new(T::meta(self, type_info).into()).into_meta() + } + /// Creates an [`ObjectMeta`] type with the given `fields`. pub fn build_object_type( &mut self, @@ -1318,6 +1482,36 @@ impl<'r, S: 'r> Registry<'r, S> { EnumMeta::new::(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 + resolve::InputValueOwned, + TI: ?Sized, + 'ti: 'r, + S: Clone, + { + self.entry_type::(type_info) + .or_insert_with(move || { + customize(EnumMeta::new_reworked::(T::type_name(type_info), values)).into_meta() + }) + .clone() + } + /// Creates an [`InterfaceMeta`] type with the given `fields`. pub fn build_interface_type( &mut self, @@ -1361,4 +1555,38 @@ impl<'r, S: 'r> Registry<'r, S> { InputObjectMeta::new::(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 + resolve::InputValueOwned, + TI: ?Sized, + 'ti: 'r, + S: Clone, + { + self.entry_type::(type_info) + .or_insert_with(move || { + customize(InputObjectMeta::new_reworked::( + T::type_name(type_info), + fields, + )) + .into_meta() + }) + .clone() + } } diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index 06d93fc0e..088974187 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -369,6 +369,8 @@ mod threads_context_correctly { } } +// TODO: Remove as should be unnecessary with generic context. +/* mod dynamic_context_switching { use indexmap::IndexMap; @@ -672,7 +674,7 @@ mod dynamic_context_switching { assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } } - +*/ mod propagates_errors_to_nullable_fields { use crate::{ executor::{ExecutionError, FieldError, FieldResult, IntoFieldError}, diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 10d5e0889..c1cd3b43f 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -1,6 +1,6 @@ mod interface { use crate::{ - graphql_interface, graphql_object, + graphql_interface, graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, GraphQLObject, @@ -96,19 +96,16 @@ mod interface { mod union { use crate::{ - graphql_object, graphql_union, + graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, + GraphQLUnion, }; - #[graphql_union] - trait Pet { - fn as_dog(&self) -> Option<&Dog> { - None - } - fn as_cat(&self) -> Option<&Cat> { - None - } + #[derive(GraphQLUnion)] + enum Pet { + Dog(Dog), + Cat(Cat), } struct Dog { @@ -116,12 +113,6 @@ mod union { woofs: bool, } - impl Pet for Dog { - fn as_dog(&self) -> Option<&Dog> { - Some(self) - } - } - #[graphql_object] impl Dog { fn name(&self) -> &str { @@ -137,12 +128,6 @@ mod union { meows: bool, } - impl Pet for Cat { - fn as_cat(&self) -> Option<&Cat> { - Some(self) - } - } - #[graphql_object] impl Cat { fn name(&self) -> &str { @@ -154,13 +139,13 @@ mod union { } struct Schema { - pets: Vec>, + pets: Vec, } #[graphql_object] impl Schema { - fn pets(&self) -> Vec<&(dyn Pet + Send + Sync)> { - self.pets.iter().map(|p| p.as_ref()).collect() + fn pets(&self) -> &[Pet] { + &self.pets } } @@ -169,11 +154,11 @@ mod union { let schema = RootNode::new( Schema { pets: vec![ - Box::new(Dog { + Pet::Dog(Dog { name: "Odie".into(), woofs: true, }), - Box::new(Cat { + Pet::Cat(Cat { name: "Garfield".into(), meows: false, }), diff --git a/juniper/src/extract.rs b/juniper/src/extract.rs new file mode 100644 index 000000000..3b02845b5 --- /dev/null +++ b/juniper/src/extract.rs @@ -0,0 +1,9 @@ +pub trait Extract { + fn extract(&self) -> &T; +} + +impl Extract for T { + fn extract(&self) -> &Self { + self + } +} diff --git a/juniper/src/graphql/mod.rs b/juniper/src/graphql/mod.rs new file mode 100644 index 000000000..387690d16 --- /dev/null +++ b/juniper/src/graphql/mod.rs @@ -0,0 +1,140 @@ +use crate::{behavior, resolve}; + +pub use crate::{ + ast::InputValue, + executor::Variables, + macros::{input_value, value, vars}, + resolve::Type, + value::Value, + GraphQLEnum as Enum, GraphQLScalar as Scalar, +}; + +pub trait Enum< + 'inp, + TypeInfo: ?Sized, + Context: ?Sized, + ScalarValue: 'inp, + Behavior: ?Sized = behavior::Standard, +>: + InputType<'inp, TypeInfo, ScalarValue, Behavior> + + OutputType +{ + fn assert_enum(); +} + +/* +pub trait Interface: OutputType + + resolve::TypeName + + resolve::ConcreteTypeName + + resolve::Value + + resolve::ValueAsync + + resolve::ConcreteValue + + resolve::ConcreteValueAsync + + resolve::Field + + resolve::FieldAsync +{ + fn assert_interface(); +} + +pub trait Object: OutputType + + resolve::TypeName + + resolve::ConcreteTypeName + + resolve::Value + + resolve::ValueAsync + + resolve::Field + + resolve::FieldAsync +{ + 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 + + resolve::ScalarToken +{ + 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 + + resolve::ScalarToken +{ + fn assert_scalar(); +} + +/* +pub trait Union + OutputType ++ resolve::TypeName ++ resolve::ConcreteTypeName ++ resolve::Value ++ resolve::ValueAsync ++ resolve::ConcreteValue ++ resolve::ConcreteValueAsync +{ + fn assert_union(); +}*/ + +pub trait InputType< + 'inp, + TypeInfo: ?Sized, + ScalarValue: 'inp, + Behavior: ?Sized = behavior::Standard, +>: + Type + + resolve::ToInputValue + + resolve::InputValue<'inp, ScalarValue, Behavior> +{ + fn assert_input_type(); +} + +pub trait InputTypeAs< + 'inp, + Wrapper, + TypeInfo: ?Sized, + ScalarValue: 'inp, + Behavior: ?Sized = behavior::Standard, +>: + Type + + resolve::ToInputValue + + resolve::InputValueAs<'inp, Wrapper, ScalarValue, Behavior> +{ + fn assert_input_type(); +} + +pub trait OutputType< + TypeInfo: ?Sized, + Context: ?Sized, + ScalarValue, + Behavior: ?Sized = behavior::Standard, +>: + Type + + resolve::Value + + resolve::ValueAsync +{ + fn assert_output_type(); +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index ac1e0329e..9e54f70f0 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -29,19 +29,23 @@ pub use juniper_codegen::{ #[doc(hidden)] #[macro_use] pub mod macros; + mod ast; +pub mod behavior; pub mod executor; +pub mod extract; +pub mod graphql; +pub mod http; +pub mod integrations; mod introspection; pub mod parser; +pub mod reflect; +pub mod resolve; pub(crate) mod schema; mod types; mod util; pub mod validation; -mod value; -// This needs to be public until docs have support for private modules: -// https://github.com/rust-lang/cargo/issues/1520 -pub mod http; -pub mod integrations; +pub(crate) mod value; #[cfg(all(test, not(feature = "expose-test-schema")))] mod tests; @@ -71,6 +75,7 @@ pub use crate::{ FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods, LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, }, + extract::Extract, introspection::IntrospectionFormat, macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult}, parser::{ParseError, ScalarToken, Spanning}, @@ -82,12 +87,12 @@ pub use crate::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion}, - nullable::Nullable, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue, SubscriptionConnection, SubscriptionCoordinator, }, + Nullable, }, validation::RuleError, value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, diff --git a/juniper/src/macros/graphql_input_value.rs b/juniper/src/macros/graphql_input_value.rs index 69641e376..73233a799 100644 --- a/juniper/src/macros/graphql_input_value.rs +++ b/juniper/src/macros/graphql_input_value.rs @@ -1,45 +1,43 @@ -//! [`graphql_input_value!`] macro implementation. -//! -//! [`graphql_input_value!`]: graphql_input_value +//! [`input_value!`] macro implementation. -/// Constructs [`InputValue`]s via JSON-like syntax. +/// Constructs [`graphql::InputValue`]s via JSON-like syntax. /// /// # Differences from [`graphql_value!`] /// /// - [`InputValue::Enum`] is constructed with `ident`, so to capture outer /// variable as [`InputValue::Scalar`] surround it with parens: `(var)`. /// ```rust -/// # use juniper::{graphql_input_value, graphql_value}; +/// # use juniper::graphql; /// # -/// # type InputValue = juniper::InputValue; -/// # type Value = juniper::Value; +/// # type InputValue = graphql::InputValue; +/// # type Value = graphql::Value; /// # /// const OUTER_VAR: i32 = 42; -/// assert_eq!(graphql_value!(OUTER_VAR), Value::scalar(42)); -/// assert_eq!(graphql_input_value!(OUTER_VAR), InputValue::enum_value("OUTER_VAR")); -/// assert_eq!(graphql_input_value!((OUTER_VAR)), InputValue::scalar(42)); +/// assert_eq!(graphql::value!(OUTER_VAR), Value::scalar(42)); +/// assert_eq!(graphql::input_value!(OUTER_VAR), InputValue::enum_value("OUTER_VAR")); +/// assert_eq!(graphql::input_value!((OUTER_VAR)), InputValue::scalar(42)); /// ``` /// /// - [`InputValue::Variable`] is constructed by prefixing `ident` with `@`. /// ```rust -/// # use juniper::graphql_input_value; +/// # use juniper::graphql; /// # -/// # type InputValue = juniper::InputValue; +/// # type InputValue = graphql::InputValue; /// # -/// assert_eq!(graphql_input_value!(@var), InputValue::variable("var")); +/// assert_eq!(graphql::input_value!(@var), InputValue::variable("var")); /// ``` /// /// - [`InputValue::Object`] key should implement [`Into`]`<`[`String`]`>`. /// ```rust /// # use std::borrow::Cow; /// # -/// # use juniper::{graphql_input_value, InputValue}; +/// # use juniper::graphql; /// # /// let code = 200; /// let features = vec!["key", "value"]; /// let key: Cow<'static, str> = "key".into(); /// -/// let value: InputValue = graphql_input_value!({ +/// let value: graphql::InputValue = graphql::input_value!({ /// "code": code, /// "success": code == 200, /// "payload": { @@ -55,35 +53,35 @@ /// # Example /// /// ```rust -/// # use juniper::{graphql_input_value, InputValue}; +/// # use juniper::graphql; /// # -/// # type V = InputValue; +/// # type V = graphql::InputValue; /// # /// # let _: V = -/// graphql_input_value!(null); +/// graphql::input_value!(null); /// # let _: V = -/// graphql_input_value!(1234); +/// graphql::input_value!(1234); /// # let _: V = -/// graphql_input_value!("test"); +/// graphql::input_value!("test"); /// # let _: V = -/// graphql_input_value!([1234, "test", true]); +/// graphql::input_value!([1234, "test", true]); /// # let _: V = -/// graphql_input_value!({"key": "value", "foo": 1234}); +/// graphql::input_value!({"key": "value", "foo": 1234}); /// # let _: V = -/// graphql_input_value!({"key": ENUM}); +/// graphql::input_value!({"key": ENUM}); /// let captured_var = 42; /// # let _: V = -/// graphql_input_value!({"key": (captured_var)}); +/// graphql::input_value!({"key": (captured_var)}); /// # let _: V = -/// graphql_input_value!({"key": @variable}); +/// graphql::input_value!({"key": @variable}); /// ``` /// -/// [`InputValue`]: crate::InputValue -/// [`InputValue::Enum`]: crate::InputValue::Enum -/// [`InputValue::List`]: crate::InputValue::List -/// [`InputValue::Object`]: crate::InputValue::Object -/// [`InputValue::Scalar`]: crate::InputValue::Scalar -/// [`InputValue::Variable`]: crate::InputValue::Variable +/// [`graphql::InputValue`]: crate::graphql::InputValue +/// [`InputValue::Enum`]: crate::graphql::InputValue::Enum +/// [`InputValue::List`]: crate::graphql::InputValue::List +/// [`InputValue::Object`]: crate::graphql::InputValue::Object +/// [`InputValue::Scalar`]: crate::graphql::InputValue::Scalar +/// [`InputValue::Variable`]: crate::graphql::InputValue::Variable /// [`Spanning::unlocated`]: crate::Spanning::unlocated #[macro_export] macro_rules! graphql_input_value { @@ -93,90 +91,90 @@ macro_rules! graphql_input_value { // Done with trailing comma. (@@array [$($elems:expr,)*]) => { - $crate::InputValue::list(vec![ + $crate::graphql::InputValue::list(::std::vec![ $( $elems, )* ]) }; // Done without trailing comma. (@@array [$($elems:expr),*]) => { - $crate::InputValue::list(vec![ + $crate::graphql::InputValue::list(::std::vec![ $( $elems, )* ]) }; // Next element is `null`. (@@array [$($elems:expr,)*] null $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!(null)] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!(null)] $($rest)* ) }; // Next element is `None`. (@@array [$($elems:expr,)*] None $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!(None)] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!(None)] $($rest)* ) }; // Next element is a variable. (@@array [$($elems:expr,)*] @$var:ident $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!(@$var)] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!(@$var)] $($rest)* ) }; // Next element is an array. (@@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!([$($array)*])] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!([$($array)*])] $($rest)* ) }; // Next element is a map. (@@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!({$($map)*})] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!({$($map)*})] $($rest)* ) }; // Next element is `true`, `false` or enum ident followed by comma. (@@array [$($elems:expr,)*] $ident:ident, $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!($ident),] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!($ident),] $($rest)* ) }; // Next element is `true`, `false` or enum ident without trailing comma. (@@array [$($elems:expr,)*] $last:ident ) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!($last)] + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!($last)] ) }; // Next element is an expression followed by comma. (@@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!($next),] $($rest)* + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!($next),] $($rest)* ) }; // Last element is an expression with no trailing comma. (@@array [$($elems:expr,)*] $last:expr) => { - $crate::graphql_input_value!( - @@array [$($elems,)* $crate::graphql_input_value!($last)] + $crate::graphql::input_value!( + @@array [$($elems,)* $crate::graphql::input_value!($last)] ) }; // Comma after the most recent element. (@@array [$($elems:expr),*] , $($rest:tt)*) => { - $crate::graphql_input_value!(@@array [$($elems,)*] $($rest)*) + $crate::graphql::input_value!(@@array [$($elems,)*] $($rest)*) }; // Unexpected token after most recent element. (@@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { - $crate::graphql_input_value!(@unexpected $unexpected) + $crate::graphql::input_value!(@unexpected $unexpected) }; //////////// @@ -192,12 +190,12 @@ macro_rules! graphql_input_value { $crate::Spanning::unlocated(($($key)+).into()), $crate::Spanning::unlocated($value), )); - $crate::graphql_input_value!(@@object $object () ($($rest)*) ($($rest)*)); + $crate::graphql::input_value!(@@object $object () ($($rest)*) ($($rest)*)); }; // Current entry followed by unexpected token. (@@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { - $crate::graphql_input_value!(@unexpected $unexpected); + $crate::graphql::input_value!(@unexpected $unexpected); }; // Insert the last entry without trailing comma. @@ -210,114 +208,114 @@ macro_rules! graphql_input_value { // Next value is `null`. (@@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!(null)) $($rest)* + ($crate::graphql::input_value!(null)) $($rest)* ); }; // Next value is `None`. (@@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!(None)) $($rest)* + ($crate::graphql::input_value!(None)) $($rest)* ); }; // Next value is a variable. (@@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!(@$var)) $($rest)* + ($crate::graphql::input_value!(@$var)) $($rest)* ); }; // Next value is an array. (@@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!([$($array)*])) $($rest)* + ($crate::graphql::input_value!([$($array)*])) $($rest)* ); }; // Next value is a map. (@@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!({$($map)*})) $($rest)* + ($crate::graphql::input_value!({$($map)*})) $($rest)* ); }; // Next value is `true`, `false` or enum ident followed by comma. (@@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!($ident)) , $($rest)* + ($crate::graphql::input_value!($ident)) , $($rest)* ); }; // Next value is `true`, `false` or enum ident without trailing comma. (@@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!($last)) + ($crate::graphql::input_value!($last)) ); }; // Next value is an expression followed by comma. (@@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!($value)) , $($rest)* + ($crate::graphql::input_value!($value)) , $($rest)* ); }; // Last value is an expression with no trailing comma. (@@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object [$($key)+] - ($crate::graphql_input_value!($value)) + ($crate::graphql::input_value!($value)) ); }; // Missing value for last entry. Trigger a reasonable error message. (@@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_input_value!(); + $crate::graphql::input_value!(); }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_input_value!(); + $crate::graphql::input_value!(); }; // Misplaced colon. Trigger a reasonable error message. (@@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". - $crate::graphql_input_value!(@unexpected $colon); + $crate::graphql::input_value!(@unexpected $colon); }; // Found a comma inside a key. Trigger a reasonable error message. (@@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". - $crate::graphql_input_value!(@unexpected $comma); + $crate::graphql::input_value!(@unexpected $comma); }; // Key is fully parenthesized. This avoids `clippy::double_parens` false // positives because the parenthesization may be necessary here. (@@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object ($key) (: $($rest)*) (: $($rest)*) @@ -326,12 +324,12 @@ macro_rules! graphql_input_value { // Refuse to absorb colon token into key expression. (@@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - $crate::graphql_input_value!(@@unexpected $($unexpected)+); + $crate::graphql::input_value!(@@unexpected $($unexpected)+); }; // Munch a token into the current key. (@@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { - $crate::graphql_input_value!( + $crate::graphql::input_value!( @@object $object ($($key)* $tt) ($($rest)*) ($($rest)*) @@ -349,109 +347,107 @@ macro_rules! graphql_input_value { ////////////// ([ $($arr:tt)* ]$(,)?) => { - $crate::graphql_input_value!(@@array [] $($arr)*) + $crate::graphql::input_value!(@@array [] $($arr)*) }; ({}$(,)?) => { - $crate::InputValue::parsed_object(vec![]) + $crate::graphql::InputValue::parsed_object(vec![]) }; ({ $($map:tt)+ }$(,)?) => { - $crate::InputValue::parsed_object({ + $crate::graphql::InputValue::parsed_object({ let mut object = vec![]; - $crate::graphql_input_value!(@@object object () ($($map)*) ($($map)*)); + $crate::graphql::input_value!(@@object object () ($($map)*) ($($map)*)); object }) }; - (null$(,)?) => ($crate::InputValue::null()); + (null$(,)?) => ($crate::graphql::InputValue::null()); - (None$(,)?) => ($crate::InputValue::null()); + (None$(,)?) => ($crate::graphql::InputValue::null()); - (true$(,)?) => ($crate::InputValue::from(true)); + (true$(,)?) => ($crate::graphql::InputValue::from(true)); - (false$(,)?) => ($crate::InputValue::from(false)); + (false$(,)?) => ($crate::graphql::InputValue::from(false)); - (@$var:ident$(,)?) => ($crate::InputValue::variable(stringify!($var))); + (@$var:ident$(,)?) => ($crate::graphql::InputValue::variable(stringify!($var))); - ($enum:ident$(,)?) => ($crate::InputValue::enum_value(stringify!($enum))); + ($enum:ident$(,)?) => ($crate::graphql::InputValue::enum_value(stringify!($enum))); - (($e:expr)$(,)?) => ($crate::InputValue::from($e)); + (($e:expr)$(,)?) => ($crate::graphql::InputValue::from($e)); - ($e:expr$(,)?) => ($crate::InputValue::from($e)); + ($e:expr$(,)?) => ($crate::graphql::InputValue::from($e)); } +#[doc(inline)] +pub use graphql_input_value as input_value; + #[cfg(test)] mod tests { use indexmap::{indexmap, IndexMap}; - type V = crate::InputValue; + use crate::graphql; + + use super::input_value; + + type V = graphql::InputValue; #[test] fn null() { - assert_eq!(graphql_input_value!(null), V::Null); + assert_eq!(input_value!(null), V::Null); } #[test] fn scalar() { let val = 42; - assert_eq!(graphql_input_value!(1), V::scalar(1)); - assert_eq!(graphql_input_value!("val"), V::scalar("val")); - assert_eq!(graphql_input_value!(1.34), V::scalar(1.34)); - assert_eq!(graphql_input_value!(false), V::scalar(false)); - assert_eq!(graphql_input_value!(1 + 2), V::scalar(3)); - assert_eq!(graphql_input_value!((val)), V::scalar(42)); + assert_eq!(input_value!(1), V::scalar(1)); + assert_eq!(input_value!("val"), V::scalar("val")); + assert_eq!(input_value!(1.34), V::scalar(1.34)); + assert_eq!(input_value!(false), V::scalar(false)); + assert_eq!(input_value!(1 + 2), V::scalar(3)); + assert_eq!(input_value!((val)), V::scalar(42)); } #[test] fn r#enum() { - assert_eq!(graphql_input_value!(ENUM), V::enum_value("ENUM")); - assert_eq!(graphql_input_value!(lowercase), V::enum_value("lowercase")); + assert_eq!(input_value!(ENUM), V::enum_value("ENUM")); + assert_eq!(input_value!(lowercase), V::enum_value("lowercase")); } #[test] fn variable() { - assert_eq!(graphql_input_value!(@var), V::variable("var")); - assert_eq!(graphql_input_value!(@array), V::variable("array")); - assert_eq!(graphql_input_value!(@object), V::variable("object")); + assert_eq!(input_value!(@var), V::variable("var")); + assert_eq!(input_value!(@array), V::variable("array")); + assert_eq!(input_value!(@object), V::variable("object")); } #[test] fn list() { let val = 42; - assert_eq!(graphql_input_value!([]), V::list(vec![])); + assert_eq!(input_value!([]), V::list(vec![])); - assert_eq!(graphql_input_value!([null]), V::list(vec![V::Null])); + assert_eq!(input_value!([null]), V::list(vec![V::Null])); - assert_eq!(graphql_input_value!([1]), V::list(vec![V::scalar(1)])); - assert_eq!(graphql_input_value!([1 + 2]), V::list(vec![V::scalar(3)])); - assert_eq!(graphql_input_value!([(val)]), V::list(vec![V::scalar(42)])); + assert_eq!(input_value!([1]), V::list(vec![V::scalar(1)])); + assert_eq!(input_value!([1 + 2]), V::list(vec![V::scalar(3)])); + assert_eq!(input_value!([(val)]), V::list(vec![V::scalar(42)])); + assert_eq!(input_value!([ENUM]), V::list(vec![V::enum_value("ENUM")])); assert_eq!( - graphql_input_value!([ENUM]), - V::list(vec![V::enum_value("ENUM")]), - ); - assert_eq!( - graphql_input_value!([lowercase]), + input_value!([lowercase]), V::list(vec![V::enum_value("lowercase")]), ); + assert_eq!(input_value!([@var]), V::list(vec![V::variable("var")]),); + assert_eq!(input_value!([@array]), V::list(vec![V::variable("array")])); assert_eq!( - graphql_input_value!([@var]), - V::list(vec![V::variable("var")]), - ); - assert_eq!( - graphql_input_value!([@array]), - V::list(vec![V::variable("array")]), - ); - assert_eq!( - graphql_input_value!([@object]), + input_value!([@object]), V::list(vec![V::variable("object")]), ); assert_eq!( - graphql_input_value!([1, [2], 3]), + input_value!([1, [2], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(2)]), @@ -459,7 +455,7 @@ mod tests { ]), ); assert_eq!( - graphql_input_value!([1, [2 + 3], 3]), + input_value!([1, [2 + 3], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(5)]), @@ -467,7 +463,7 @@ mod tests { ]), ); assert_eq!( - graphql_input_value!([1, [ENUM], (val)]), + input_value!([1, [ENUM], (val)]), V::list(vec![ V::scalar(1), V::list(vec![V::enum_value("ENUM")]), @@ -475,7 +471,7 @@ mod tests { ]), ); assert_eq!( - graphql_input_value!([1 + 2, [(val)], @val]), + input_value!([1 + 2, [(val)], @val]), V::list(vec![ V::scalar(3), V::list(vec![V::scalar(42)]), @@ -483,7 +479,7 @@ mod tests { ]), ); assert_eq!( - graphql_input_value!([1, [@val], ENUM]), + input_value!([1, [@val], ENUM]), V::list(vec![ V::scalar(1), V::list(vec![V::variable("val")]), @@ -495,68 +491,65 @@ mod tests { #[test] fn object() { let val = 42; - assert_eq!( - graphql_input_value!({}), - V::object(IndexMap::::new()), - ); + assert_eq!(input_value!({}), V::object(IndexMap::::new())); assert_eq!( - graphql_input_value!({ "key": null }), + input_value!({ "key": null }), V::object(indexmap! {"key" => V::Null}), ); assert_eq!( - graphql_input_value!({"key": 123}), + input_value!({"key": 123}), V::object(indexmap! {"key" => V::scalar(123)}), ); assert_eq!( - graphql_input_value!({"key": 1 + 2}), + input_value!({"key": 1 + 2}), V::object(indexmap! {"key" => V::scalar(3)}), ); assert_eq!( - graphql_input_value!({ "key": (val) }), + input_value!({ "key": (val) }), V::object(indexmap! {"key" => V::scalar(42)}), ); assert_eq!( - graphql_input_value!({"key": []}), + input_value!({"key": []}), V::object(indexmap! {"key" => V::list(vec![])}), ); assert_eq!( - graphql_input_value!({ "key": [null] }), + input_value!({ "key": [null] }), V::object(indexmap! {"key" => V::list(vec![V::Null])}), ); assert_eq!( - graphql_input_value!({"key": [1] }), + input_value!({"key": [1] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(1)])}), ); assert_eq!( - graphql_input_value!({"key": [1 + 2] }), + input_value!({"key": [1 + 2] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(3)])}), ); assert_eq!( - graphql_input_value!({ "key": [(val)] }), + input_value!({ "key": [(val)] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(42)])}), ); assert_eq!( - graphql_input_value!({ "key": ENUM }), + input_value!({ "key": ENUM }), V::object(indexmap! {"key" => V::enum_value("ENUM")}), ); assert_eq!( - graphql_input_value!({ "key": lowercase }), + input_value!({ "key": lowercase }), V::object(indexmap! {"key" => V::enum_value("lowercase")}), ); assert_eq!( - graphql_input_value!({"key": @val}), + input_value!({"key": @val}), V::object(indexmap! {"key" => V::variable("val")}), ); assert_eq!( - graphql_input_value!({"key": @array }), + input_value!({"key": @array }), V::object(indexmap! {"key" => V::variable("array")}), ); assert_eq!( - graphql_input_value!({ + input_value!({ "inner": { "key1": (val), "key2": "val", @@ -606,8 +599,8 @@ mod tests { fn option() { let val = Some(42); - assert_eq!(graphql_input_value!(None), V::Null); - assert_eq!(graphql_input_value!(Some(42)), V::scalar(42)); - assert_eq!(graphql_input_value!((val)), V::scalar(42)); + assert_eq!(input_value!(None), V::Null); + assert_eq!(input_value!(Some(42)), V::scalar(42)); + assert_eq!(input_value!((val)), V::scalar(42)); } } diff --git a/juniper/src/macros/graphql_value.rs b/juniper/src/macros/graphql_value.rs index cc4e34d38..aa72a7fbd 100644 --- a/juniper/src/macros/graphql_value.rs +++ b/juniper/src/macros/graphql_value.rs @@ -1,19 +1,18 @@ -//! [`graphql_value!`] macro implementation. -//! -//! [`graphql_value!`]: graphql_value +//! [`value!`] macro implementation. -/// Constructs [`Value`]s via JSON-like syntax. +/// Constructs [`graphql::Value`]s via JSON-like syntax. /// -/// [`Value`] objects are used mostly when creating custom errors from fields. +/// [`graphql::Value`] objects are used mostly when creating custom errors from +/// fields. /// /// [`Value::Object`] key should implement [`AsRef`]`<`[`str`]`>`. /// ```rust -/// # use juniper::{graphql_value, Value}; +/// # use juniper::graphql; /// # /// let code = 200; /// let features = ["key", "value"]; /// -/// let value: Value = graphql_value!({ +/// let value: graphql::Value = graphql::value!({ /// "code": code, /// "success": code == 200, /// "payload": { @@ -26,24 +25,24 @@ /// /// Resulting JSON will look just like what you passed in. /// ```rust -/// # use juniper::{graphql_value, DefaultScalarValue, Value}; +/// # use juniper::graphql; /// # -/// # type V = Value; +/// # type V = graphql::Value; /// # /// # let _: V = -/// graphql_value!(null); +/// graphql::value!(null); /// # let _: V = -/// graphql_value!(1234); +/// graphql::value!(1234); /// # let _: V = -/// graphql_value!("test"); +/// graphql::value!("test"); /// # let _: V = -/// graphql_value!([1234, "test", true]); +/// graphql::value!([1234, "test", true]); /// # let _: V = -/// graphql_value!({"key": "value", "foo": 1234}); +/// graphql::value!({"key": "value", "foo": 1234}); /// ``` /// -/// [`Value`]: crate::Value -/// [`Value::Object`]: crate::Value::Object +/// [`graphql::Value`]: crate::graphql::Value +/// [`Value::Object`]: crate::graphql::Value::Object #[macro_export] macro_rules! graphql_value { /////////// @@ -52,68 +51,68 @@ macro_rules! graphql_value { // Done with trailing comma. (@array [$($elems:expr,)*]) => { - $crate::Value::list(vec![ + $crate::graphql::Value::list(::std::vec![ $( $elems, )* ]) }; // Done without trailing comma. (@array [$($elems:expr),*]) => { - $crate::Value::list(vec![ - $( $crate::graphql_value!($elems), )* + $crate::graphql::Value::list(::std::vec![ + $( $crate::graphql::value!($elems), )* ]) }; // Next element is `null`. (@array [$($elems:expr,)*] null $($rest:tt)*) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!(null)] $($rest)* + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!(null)] $($rest)* ) }; // Next element is `None`. (@array [$($elems:expr,)*] None $($rest:tt)*) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!(None)] $($rest)* + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!(None)] $($rest)* ) }; // Next element is an array. (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!([$($array)*])] $($rest)* + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!([$($array)*])] $($rest)* ) }; // Next element is a map. (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!({$($map)*})] $($rest)* + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!({$($map)*})] $($rest)* ) }; // Next element is an expression followed by comma. (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!($next),] $($rest)* + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!($next),] $($rest)* ) }; // Last element is an expression with no trailing comma. (@array [$($elems:expr,)*] $last:expr) => { - $crate::graphql_value!( - @array [$($elems,)* $crate::graphql_value!($last)] + $crate::graphql::value!( + @array [$($elems,)* $crate::graphql::value!($last)] ) }; // Comma after the most recent element. (@array [$($elems:expr),*] , $($rest:tt)*) => { - $crate::graphql_value!(@array [$($elems,)*] $($rest)*) + $crate::graphql::value!(@array [$($elems,)*] $($rest)*) }; // Unexpected token after most recent element. (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { - $crate::graphql_value!(@unexpected $unexpected) + $crate::graphql::value!(@unexpected $unexpected) }; //////////// @@ -126,12 +125,12 @@ macro_rules! graphql_value { // Insert the current entry followed by trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { let _ = $object.add_field(($($key)+), $value); - $crate::graphql_value!(@object $object () ($($rest)*) ($($rest)*)); + $crate::graphql::value!(@object $object () ($($rest)*) ($($rest)*)); }; // Current entry followed by unexpected token. (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { - $crate::graphql_value!(@unexpected $unexpected); + $crate::graphql::value!(@unexpected $unexpected); }; // Insert the last entry without trailing comma. @@ -141,97 +140,97 @@ macro_rules! graphql_value { // Next value is `null`. (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!(null)) $($rest)* + ($crate::graphql::value!(null)) $($rest)* ); }; // Next value is `None`. (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!(None)) $($rest)* + ($crate::graphql::value!(None)) $($rest)* ); }; // Next value is an array. (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!([$($array)*])) $($rest)* + ($crate::graphql::value!([$($array)*])) $($rest)* ); }; // Next value is a map. (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!({$($map)*})) $($rest)* + ($crate::graphql::value!({$($map)*})) $($rest)* ); }; // Next value is an expression followed by comma. (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!($value)) , $($rest)* + ($crate::graphql::value!($value)) , $($rest)* ); }; // Last value is an expression with no trailing comma. (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object [$($key)+] - ($crate::graphql_value!($value)) + ($crate::graphql::value!($value)) ); }; // Missing value for last entry. Trigger a reasonable error message. (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_value!(); + $crate::graphql::value!(); }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_value!(); + $crate::graphql::value!(); }; // Misplaced colon. Trigger a reasonable error message. (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". - $crate::graphql_value!(@unexpected $colon); + $crate::graphql::value!(@unexpected $colon); }; // Found a comma inside a key. Trigger a reasonable error message. (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". - $crate::graphql_value!(@unexpected $comma); + $crate::graphql::value!(@unexpected $comma); }; // Key is fully parenthesized. This avoids `clippy::double_parens` false // positives because the parenthesization may be necessary here. (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!(@object $object ($key) (: $($rest)*) (: $($rest)*)); + $crate::graphql::value!(@object $object ($key) (: $($rest)*) (: $($rest)*)); }; // Refuse to absorb colon token into key expression. (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - $crate::graphql_value!(@unexpected $($unexpected)+); + $crate::graphql::value!(@unexpected $($unexpected)+); }; // Munch a token into the current key. (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { - $crate::graphql_value!( + $crate::graphql::value!( @object $object ($($key)* $tt) ($($rest)*) ($($rest)*) @@ -249,63 +248,70 @@ macro_rules! graphql_value { ////////////// ([ $($arr:tt)* ]$(,)?) => { - $crate::graphql_value!(@array [] $($arr)*) + $crate::graphql::value!(@array [] $($arr)*) }; ({}$(,)?) => { - $crate::Value::object($crate::Object::with_capacity(0)) + $crate::graphql::Value::object($crate::Object::with_capacity(0)) }; ({ $($map:tt)+ }$(,)?) => { - $crate::Value::object({ + $crate::graphql::Value::object({ let mut object = $crate::Object::with_capacity(0); - $crate::graphql_value!(@object object () ($($map)*) ($($map)*)); + $crate::graphql::value!(@object object () ($($map)*) ($($map)*)); object }) }; - (null$(,)?) => ($crate::Value::null()); + (null$(,)?) => ($crate::graphql::Value::null()); - (None$(,)?) => ($crate::Value::null()); + (None$(,)?) => ($crate::graphql::Value::null()); - ($e:expr$(,)?) => ($crate::Value::from($e)); + ($e:expr$(,)?) => ($crate::graphql::Value::from($e)); } +#[doc(inline)] +pub use graphql_value as value; + #[cfg(test)] mod tests { - type V = crate::Value; + use crate::graphql; + + use super::value; + + type V = graphql::Value; #[test] fn null() { - assert_eq!(graphql_value!(null), V::Null); + assert_eq!(value!(null), V::Null); } #[test] fn scalar() { let val = 42; - assert_eq!(graphql_value!(1), V::scalar(1)); - assert_eq!(graphql_value!("val"), V::scalar("val")); - assert_eq!(graphql_value!(1.34), V::scalar(1.34)); - assert_eq!(graphql_value!(false), V::scalar(false)); - assert_eq!(graphql_value!(1 + 2), V::scalar(3)); - assert_eq!(graphql_value!(val), V::scalar(42)); + assert_eq!(value!(1), V::scalar(1)); + assert_eq!(value!("val"), V::scalar("val")); + assert_eq!(value!(1.34), V::scalar(1.34)); + assert_eq!(value!(false), V::scalar(false)); + assert_eq!(value!(1 + 2), V::scalar(3)); + assert_eq!(value!(val), V::scalar(42)); } #[test] fn list() { let val = 42; - assert_eq!(graphql_value!([]), V::list(vec![])); + assert_eq!(value!([]), V::list(vec![])); - assert_eq!(graphql_value!([null]), V::list(vec![V::Null])); + assert_eq!(value!([null]), V::list(vec![V::Null])); - assert_eq!(graphql_value!([1]), V::list(vec![V::scalar(1)])); - assert_eq!(graphql_value!([1 + 2]), V::list(vec![V::scalar(3)])); - assert_eq!(graphql_value!([val]), V::list(vec![V::scalar(42)])); + assert_eq!(value!([1]), V::list(vec![V::scalar(1)])); + assert_eq!(value!([1 + 2]), V::list(vec![V::scalar(3)])); + assert_eq!(value!([val]), V::list(vec![V::scalar(42)])); assert_eq!( - graphql_value!([1, [2], 3]), + value!([1, [2], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(2)]), @@ -313,7 +319,7 @@ mod tests { ]), ); assert_eq!( - graphql_value!(["string", [2 + 3], true]), + value!(["string", [2 + 3], true]), V::list(vec![ V::scalar("string"), V::list(vec![V::scalar(5)]), @@ -327,31 +333,31 @@ mod tests { let val = 42; assert_eq!( - graphql_value!({}), + value!({}), V::object(Vec::<(String, _)>::new().into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": null }), + value!({ "key": null }), V::object(vec![("key", V::Null)].into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": 123 }), + value!({ "key": 123 }), V::object(vec![("key", V::scalar(123))].into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": 1 + 2 }), + value!({ "key": 1 + 2 }), V::object(vec![("key", V::scalar(3))].into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": [] }), + value!({ "key": [] }), V::object(vec![("key", V::list(vec![]))].into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": [null] }), + value!({ "key": [null] }), V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()), ); assert_eq!( - graphql_value!({ "key": [1] }), + value!({ "key": [1] }), V::object( vec![("key", V::list(vec![V::scalar(1)]))] .into_iter() @@ -359,7 +365,7 @@ mod tests { ), ); assert_eq!( - graphql_value!({ "key": [1 + 2] }), + value!({ "key": [1 + 2] }), V::object( vec![("key", V::list(vec![V::scalar(3)]))] .into_iter() @@ -367,7 +373,7 @@ mod tests { ), ); assert_eq!( - graphql_value!({ "key": [val] }), + value!({ "key": [val] }), V::object( vec![("key", V::list(vec![V::scalar(42)]))] .into_iter() @@ -380,8 +386,8 @@ mod tests { fn option() { let val = Some(42); - assert_eq!(graphql_value!(None), V::Null); - assert_eq!(graphql_value!(Some(42)), V::scalar(42)); - assert_eq!(graphql_value!(val), V::scalar(42)); + assert_eq!(value!(None), V::Null); + assert_eq!(value!(Some(42)), V::scalar(42)); + assert_eq!(value!(val), V::scalar(42)); } } diff --git a/juniper/src/macros/graphql_vars.rs b/juniper/src/macros/graphql_vars.rs index 5d0f55627..debd4201b 100644 --- a/juniper/src/macros/graphql_vars.rs +++ b/juniper/src/macros/graphql_vars.rs @@ -1,20 +1,18 @@ -//! [`graphql_vars!`] macro implementation. -//! -//! [`graphql_vars!`]: graphql_vars +//! [`vars!`] macro implementation. -/// Constructs [`Variables`] via JSON-like syntax. +/// Constructs [`graphql::Variables`] via JSON-like syntax. /// -/// [`Variables`] key should implement [`Into`]`<`[`String`]`>`. +/// [`graphql::Variables`] key should implement [`Into`]`<`[`String`]`>`. /// ```rust /// # use std::borrow::Cow; /// # -/// # use juniper::{graphql_vars, Variables}; +/// # use juniper::graphql; /// # /// let code = 200; /// let features = vec!["key", "value"]; /// let key: Cow<'static, str> = "key".into(); /// -/// let value: Variables = graphql_vars! { +/// let value: graphql::Variables = graphql::vars! { /// "code": code, /// "success": code == 200, /// features[0]: features[1], @@ -22,10 +20,10 @@ /// }; /// ``` /// -/// See [`graphql_input_value!`] for more info on syntax of value after `:`. +/// See [`graphql::input_value!`] for more info on syntax of value after `:`. /// -/// [`graphql_input_value!`]: crate::graphql_input_value -/// [`Variables`]: crate::Variables +/// [`graphql::input_value!`]: crate::graphql::input_value +/// [`graphql::Variables`]: crate::graphql::Variables #[macro_export] macro_rules! graphql_vars { //////////// @@ -38,12 +36,12 @@ macro_rules! graphql_vars { // Insert the current entry followed by trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { let _ = $object.insert(($($key)+).into(), $value); - $crate::graphql_vars! {@object $object () ($($rest)*) ($($rest)*)}; + $crate::graphql::vars! {@object $object () ($($rest)*) ($($rest)*)}; }; // Current entry followed by unexpected token. (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { - $crate::graphql_vars! {@unexpected $unexpected}; + $crate::graphql::vars! {@unexpected $unexpected}; }; // Insert the last entry without trailing comma. @@ -53,7 +51,7 @@ macro_rules! graphql_vars { // Next value is `null`. (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!(null)) $($rest)* @@ -62,7 +60,7 @@ macro_rules! graphql_vars { // Next value is `None`. (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!(None)) $($rest)* @@ -71,7 +69,7 @@ macro_rules! graphql_vars { // Next value is a variable. (@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!(@$var)) $($rest)* @@ -80,7 +78,7 @@ macro_rules! graphql_vars { // Next value is an array. (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!([$($array)*])) $($rest)* @@ -89,7 +87,7 @@ macro_rules! graphql_vars { // Next value is a map. (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!({$($map)*})) $($rest)* @@ -98,7 +96,7 @@ macro_rules! graphql_vars { // Next value is `true`, `false` or enum ident followed by a comma. (@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!($ident)) , $($rest)* @@ -107,7 +105,7 @@ macro_rules! graphql_vars { // Next value is `true`, `false` or enum ident without trailing comma. (@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!($last)) @@ -116,7 +114,7 @@ macro_rules! graphql_vars { // Next value is an expression followed by comma. (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!($value)) , $($rest)* @@ -125,7 +123,7 @@ macro_rules! graphql_vars { // Last value is an expression with no trailing comma. (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object [$($key)+] ($crate::graphql_input_value!($value)) @@ -135,44 +133,44 @@ macro_rules! graphql_vars { // Missing value for last entry. Trigger a reasonable error message. (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_vars! {}; + $crate::graphql::vars! {}; }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" - $crate::graphql_vars! {}; + $crate::graphql::vars! {}; }; // Misplaced colon. Trigger a reasonable error message. (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". - $crate::graphql_vars! {@unexpected $colon}; + $crate::graphql::vars! {@unexpected $colon}; }; // Found a comma inside a key. Trigger a reasonable error message. (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". - $crate::graphql_vars! {@unexpected $comma}; + $crate::graphql::vars! {@unexpected $comma}; }; // Key is fully parenthesized. This avoids clippy double_parens false // positives because the parenthesization may be necessary here. (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object ($key) (: $($rest)*) (: $($rest)*) }; }; // Refuse to absorb colon token into key expression. (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - $crate::graphql_vars! {@unexpected $($unexpected)+}; + $crate::graphql::vars! {@unexpected $($unexpected)+}; }; // Munch a token into the current key. (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { - $crate::graphql_vars! { + $crate::graphql::vars! { @object $object ($($key)* $tt) ($($rest)*) ($($rest)*) @@ -189,26 +187,33 @@ macro_rules! graphql_vars { // Defaults // ////////////// - () => {{ $crate::Variables::<_>::new() }}; + () => {{ $crate::graphql::Variables::<_>::new() }}; ( $($map:tt)+ ) => {{ - let mut object = $crate::Variables::<_>::new(); - $crate::graphql_vars! {@object object () ($($map)*) ($($map)*)}; + let mut object = $crate::graphql::Variables::<_>::new(); + $crate::graphql::vars! {@object object () ($($map)*) ($($map)*)}; object }}; } +#[doc(inline)] +pub use graphql_vars as vars; + #[cfg(test)] mod tests { use indexmap::{indexmap, IndexMap}; - type V = crate::Variables; + use crate::graphql; + + use super::vars; + + type V = graphql::Variables; - type IV = crate::InputValue; + type IV = graphql::InputValue; #[test] fn empty() { - assert_eq!(graphql_vars! {}, V::new()); + assert_eq!(vars! {}, V::new()); } #[test] @@ -216,35 +221,35 @@ mod tests { let val = 42; assert_eq!( - graphql_vars! {"key": 123}, + vars! {"key": 123}, vec![("key".into(), IV::scalar(123))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": "val"}, + vars! {"key": "val"}, vec![("key".into(), IV::scalar("val"))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": 1.23}, + vars! {"key": 1.23}, vec![("key".into(), IV::scalar(1.23))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": 1 + 2}, + vars! {"key": 1 + 2}, vec![("key".into(), IV::scalar(3))].into_iter().collect(), ); assert_eq!( - graphql_vars! {"key": false}, + vars! {"key": false}, vec![("key".into(), IV::scalar(false))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": (val)}, + vars! {"key": (val)}, vec![("key".into(), IV::scalar(42))] .into_iter() .collect::(), @@ -254,13 +259,13 @@ mod tests { #[test] fn r#enum() { assert_eq!( - graphql_vars! {"key": ENUM}, + vars! {"key": ENUM}, vec![("key".into(), IV::enum_value("ENUM"))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": lowercase}, + vars! {"key": lowercase}, vec![("key".into(), IV::enum_value("lowercase"))] .into_iter() .collect::(), @@ -270,19 +275,19 @@ mod tests { #[test] fn variable() { assert_eq!( - graphql_vars! {"key": @var}, + vars! {"key": @var}, vec![("key".into(), IV::variable("var"))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": @array}, + vars! {"key": @array}, vec![("key".into(), IV::variable("array"))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": @object}, + vars! {"key": @object}, vec![("key".into(), IV::variable("object"))] .into_iter() .collect::(), @@ -294,72 +299,72 @@ mod tests { let val = 42; assert_eq!( - graphql_vars! {"key": []}, + vars! {"key": []}, vec![("key".into(), IV::list(vec![]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [null]}, + vars! {"key": [null]}, vec![("key".into(), IV::list(vec![IV::Null]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [1]}, + vars! {"key": [1]}, vec![("key".into(), IV::list(vec![IV::scalar(1)]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [1 + 2]}, + vars! {"key": [1 + 2]}, vec![("key".into(), IV::list(vec![IV::scalar(3)]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [(val)]}, + vars! {"key": [(val)]}, vec![("key".into(), IV::list(vec![IV::scalar(42)]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [ENUM]}, + vars! {"key": [ENUM]}, vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [lowercase]}, + vars! {"key": [lowercase]}, vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [@var]}, + vars! {"key": [@var]}, vec![("key".into(), IV::list(vec![IV::variable("var")]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [@array]}, + vars! {"key": [@array]}, vec![("key".into(), IV::list(vec![IV::variable("array")]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [@object]}, + vars! {"key": [@object]}, vec![("key".into(), IV::list(vec![IV::variable("object")]))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": [1, [2], 3]}, + vars! {"key": [1, [2], 3]}, vec![( "key".into(), IV::list(vec![ @@ -372,7 +377,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": [1, [2 + 3], 3]}, + vars! {"key": [1, [2 + 3], 3]}, vec![( "key".into(), IV::list(vec![ @@ -385,7 +390,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": [1, [ENUM], (val)]}, + vars! {"key": [1, [ENUM], (val)]}, vec![( "key".into(), IV::list(vec![ @@ -398,7 +403,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": [1 + 2, [(val)], @val]}, + vars! {"key": [1 + 2, [(val)], @val]}, vec![( "key".into(), IV::list(vec![ @@ -411,7 +416,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": [1, [@val], ENUM]}, + vars! {"key": [1, [@val], ENUM]}, vec![( "key".into(), IV::list(vec![ @@ -430,21 +435,21 @@ mod tests { let val = 42; assert_eq!( - graphql_vars! {"key": {}}, + vars! {"key": {}}, vec![("key".into(), IV::object(IndexMap::::new()))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": null}}, + vars! {"key": {"key": null}}, vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": 123}}, + vars! {"key": {"key": 123}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::scalar(123)}), @@ -453,13 +458,13 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": 1 + 2}}, + vars! {"key": {"key": 1 + 2}}, vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)] .into_iter() .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": (val)}}, + vars! {"key": {"key": (val)}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::scalar(42)}), @@ -469,7 +474,7 @@ mod tests { ); assert_eq!( - graphql_vars! {"key": {"key": []}}, + vars! {"key": {"key": []}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![])}), @@ -478,7 +483,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": [null]}}, + vars! {"key": {"key": [null]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}), @@ -487,7 +492,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": [1]}}, + vars! {"key": {"key": [1]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}), @@ -496,7 +501,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": [1 + 2]}}, + vars! {"key": {"key": [1 + 2]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}), @@ -505,7 +510,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": [(val)]}}, + vars! {"key": {"key": [(val)]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}), @@ -514,7 +519,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": ENUM}}, + vars! {"key": {"key": ENUM}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::enum_value("ENUM")}), @@ -523,7 +528,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": lowercase}}, + vars! {"key": {"key": lowercase}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::enum_value("lowercase")}), @@ -532,7 +537,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": @val}}, + vars! {"key": {"key": @val}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::variable("val")}), @@ -541,7 +546,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! {"key": {"key": @array}}, + vars! {"key": {"key": @array}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::variable("array")}), @@ -550,7 +555,7 @@ mod tests { .collect::(), ); assert_eq!( - graphql_vars! { + vars! { "inner": { "key1": (val), "key2": "val", diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 4bbcfb5f6..57f1a4c74 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -6,9 +6,9 @@ pub mod helper; #[macro_use] pub mod reflect; -#[macro_use] mod graphql_input_value; -#[macro_use] mod graphql_value; -#[macro_use] mod graphql_vars; + +#[doc(inline)] +pub use self::{graphql_input_value::input_value, graphql_value::value, graphql_vars::vars}; diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 563b28dfe..33ea79ab9 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -8,20 +8,10 @@ use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; -/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name -/// in a GraphQL schema. -/// -/// See [`BaseType`] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Scalars -/// [3]: https://spec.graphql.org/October2021#sec-Interfaces -pub type Type = &'static str; - -/// Alias for a slice of [`Type`]s. -/// -/// See [`BaseSubTypes`] for more info. -pub type Types = &'static [Type]; +pub use crate::reflect::{ + can_be_subtype, fnv1a128, str_eq, str_exists_in_arr, type_len_with_wrapped_val, Argument, + Arguments, FieldName, Name, Names, Type, Types, WrappedValue, +}; /// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// @@ -153,9 +143,6 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// Alias for a value of a [`WrappedType`] (composed GraphQL type). -pub type WrappedValue = u128; - // TODO: Just use `&str`s once they're allowed in `const` generics. /// Encoding of a composed GraphQL type in numbers. /// @@ -163,7 +150,7 @@ pub type WrappedValue = u128; /// because of the [wrapping types][2]. To work around this we use a /// [`WrappedValue`] which is represented via [`u128`] number in the following /// encoding: -/// - In base case of non-nullable [object][1] [`VALUE`] is `1`. +/// - In base case of non-nullable singular [object][1] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so @@ -177,7 +164,7 @@ pub type WrappedValue = u128; /// /// ```rust /// # use juniper::{ -/// # format_type, +/// # reflect::format_type, /// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type}, /// # DefaultScalarValue, /// # }; @@ -253,34 +240,6 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name. -/// -/// See [`Fields`] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Name = &'static str; - -/// Alias for a slice of [`Name`]s. -/// -/// See [`Fields`] for more info. -pub type Names = &'static [Name]; - -/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Argument = (Name, Type, WrappedValue); - -/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Arguments = &'static [(Name, Type, WrappedValue)]; - -/// Alias for a `const`-hashed [`Name`] used in a `const` context. -pub type FieldName = u128; - /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects @@ -394,107 +353,6 @@ pub trait AsyncField: FieldMeta { ) -> BoxFuture<'b, ExecutionResult>; } -/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in -/// `const` generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -#[must_use] -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -/// Length __in bytes__ of the [`format_type!`] macro result. -#[must_use] -pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut curr = val; - while curr % 10 != 0 { - match curr % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - curr /= 10; - } - - len -} - -/// Checks whether the given GraphQL [object][1] represents a `subtype` of the -/// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding. -/// -/// To fully determine the sub-typing relation the [`Type`] should be one of the -/// [`BaseSubTypes::NAMES`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -#[must_use] -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_curr = ty % 10; - let sub_curr = subtype % 10; - - if ty_curr == sub_curr { - if ty_curr == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_curr == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -/// Checks whether the given `val` exists in the given `arr`. -#[must_use] -pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} - -/// Compares strings in a `const` context. -/// -/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to -/// write custom comparison function. -/// -/// [`Eq`]: std::cmp::Eq -// TODO: Remove once `Eq` trait is allowed in `const` context. -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. /// @@ -509,7 +367,7 @@ macro_rules! assert_implemented_for { <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, ); if !is_present { - const MSG: &str = $crate::const_concat!( + const MSG: &str = $crate::reflect::const_concat!( "Failed to implement interface `", <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", @@ -537,7 +395,7 @@ macro_rules! assert_interfaces_impls { <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES, ); if !is_present { - const MSG: &str = $crate::const_concat!( + const MSG: &str = $crate::reflect::const_concat!( "Failed to implement interface `", <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", @@ -622,7 +480,7 @@ macro_rules! assert_subtype { <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const IMPL_TY: $crate::macros::reflect::Type = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( + const ERR_PREFIX: &str = $crate::reflect::const_concat!( "Failed to implement interface `", BASE_TY, "` on `", @@ -664,14 +522,14 @@ macro_rules! assert_subtype { let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { - const MSG: &str = $crate::const_concat!( + const MSG: &str = $crate::reflect::const_concat!( ERR_PREFIX, "Field `", FIELD_NAME, "`: implementor is expected to return a subtype of interface's return object: `", - $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), + $crate::reflect::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), "` is not a subtype of `", - $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), + $crate::reflect::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), "`.", ); ::std::panic!("{}", MSG); @@ -695,7 +553,7 @@ macro_rules! assert_field_args { const _: () = { const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( + const ERR_PREFIX: &str = $crate::reflect::const_concat!( "Failed to implement interface `", BASE_NAME, "` on `", @@ -818,13 +676,14 @@ macro_rules! assert_field_args { const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); + const BASE_TYPE_FORMATTED: &str = + $crate::reflect::format_type!(ERROR.base.1, ERROR.base.2); const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); + $crate::reflect::format_type!(ERROR.implementation.1, ERROR.implementation.2); const MSG: &str = match ERROR.cause { Cause::TypeMismatch => { - $crate::const_concat!( + $crate::reflect::const_concat!( "Argument `", BASE_ARG_NAME, "`: expected type `", @@ -835,7 +694,7 @@ macro_rules! assert_field_args { ) } Cause::RequiredField => { - $crate::const_concat!( + $crate::reflect::const_concat!( "Argument `", BASE_ARG_NAME, "` of type `", @@ -844,7 +703,7 @@ macro_rules! assert_field_args { ) } Cause::AdditionalNonNullableField => { - $crate::const_concat!( + $crate::reflect::const_concat!( "Argument `", IMPL_ARG_NAME, "` of type `", @@ -854,43 +713,13 @@ macro_rules! assert_field_args { } }; const ERROR_MSG: &str = - $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); + $crate::reflect::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); ::std::panic!("{}", ERROR_MSG); } }; }; } -/// Concatenates `const` [`str`](prim@str)s in a `const` context. -#[macro_export] -macro_rules! const_concat { - ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.as_bytes().len())*; - const CNT: usize = [$($s),*].len(); - const fn concat(input: [&str; CNT]) -> [u8; LEN] { - let mut bytes = [0; LEN]; - let (mut i, mut byte) = (0, 0); - while i < CNT { - let mut b = 0; - while b < input[i].len() { - bytes[byte] = input[i].as_bytes()[b]; - byte += 1; - b += 1; - } - i += 1; - } - bytes - } - const CON: [u8; LEN] = concat([$($s),*]); - - // TODO: Use `.unwrap()` once it becomes `const`. - match ::std::str::from_utf8(&CON) { - ::std::result::Result::Ok(s) => s, - _ => unreachable!(), - } - }}; -} - /// Ensures that the given `$impl_ty` implements [`Field`] and returns a /// [`fnv1a128`] hash for it, otherwise panics with understandable message. #[macro_export] @@ -903,7 +732,7 @@ macro_rules! checked_hash { if exists { $crate::macros::reflect::fnv1a128(FIELD_NAME) } else { - const MSG: &str = $crate::const_concat!( + const MSG: &str = $crate::reflect::const_concat!( $($prefix,)? "Field `", $field_name, @@ -915,102 +744,3 @@ macro_rules! checked_hash { } }}; } - -/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type -/// name. -/// -/// # Examples -/// -/// ```rust -/// # use juniper::format_type; -/// # -/// assert_eq!(format_type!("String", 123), "[String]!"); -/// assert_eq!(format_type!("🦀", 123), "[🦀]!"); -/// ``` -#[macro_export] -macro_rules! format_type { - ($ty: expr, $wrapped_value: expr $(,)?) => {{ - const TYPE: ( - $crate::macros::reflect::Type, - $crate::macros::reflect::WrappedValue, - ) = ($ty, $wrapped_value); - const RES_LEN: usize = $crate::macros::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1); - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - const fn format_type_arr() -> [u8; RES_LEN] { - let (ty, wrap_val) = TYPE; - let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; - - let mut current_start = 0; - let mut current_end = RES_LEN - 1; - let mut current_wrap_val = wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, // Skips writing `BANG` later. - 3 => { - // Write `OPENING_BRACKET` at `current_start`. - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - if !is_null { - // Write `BANG` at `current_end`. - i = 0; - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - // Write `CLOSING_BRACKET` at `current_end`. - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - is_null = false; - } - _ => {} - } - - current_wrap_val /= 10; - } - - // Writes `Type` at `current_start`. - let mut i = 0; - while i < ty.as_bytes().len() { - type_arr[current_start + i] = ty.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - // Writes `BANG` at `current_end`. - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - type_arr - } - - const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - - // TODO: Use `.unwrap()` once it becomes `const`. - const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) { - ::std::result::Result::Ok(s) => s, - _ => unreachable!(), - }; - - TYPE_FORMATTED - }}; -} diff --git a/juniper/src/reflect/mod.rs b/juniper/src/reflect/mod.rs new file mode 100644 index 000000000..49fff1689 --- /dev/null +++ b/juniper/src/reflect/mod.rs @@ -0,0 +1,874 @@ +//! Compile-time reflection of Rust types into GraphQL types. + +use crate::behavior; + +#[doc(inline)] +pub use self::macros::{ + assert_field, assert_field_args, assert_field_type, assert_has_field, assert_implemented_for, + assert_interfaces_impls, assert_transitive_impls, const_concat, format_type, +}; + +/// Name of a [GraphQL type][0] in a GraphQL schema. +/// +/// See [`BaseType`] for details. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Types +pub type Type = &'static str; + +/// List of [`Type`]s. +/// +/// See [`BaseSubTypes`] for details. +pub type Types = &'static [Type]; + +/// Basic reflection of a [GraphQL type][0]. +/// +/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to +/// fully represent a [GraphQL object][1] we additionally use [`WrappedType`]. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Types +pub trait BaseType { + /// [`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 { + /// 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 { + /// [`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!( as WrappedType>::VALUE, 12); +/// assert_eq!( as WrappedType>::VALUE, 13); +/// assert_eq!(> as WrappedType>::VALUE, 123); +/// assert_eq!(> as WrappedType>::VALUE, 132); +/// assert_eq!(>> as WrappedType>::VALUE, 1232); +/// +/// const TYPE_STRING: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// +/// const TYPE_STR: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STR: WrappedValue = >> 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 { + /// [`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 { + /// [`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 { + /// [`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>::ARGUMENTS; + const IMPL_ARGS: ::juniper::reflect::Arguments = + <$impl_ty as $crate::reflect::Field>::ARGUMENTS; + + struct Error { + cause: Cause, + base: ::juniper::reflect::Argument, + implementation: ::juniper::reflect::Argument, + } + + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, + } + + const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { + match v { + // Unfortunately, we cannot use `unreachable!()` here, + // as this branch will be executed either way. + Ok(()) => Error { + cause: Cause::RequiredField, + base: ("unreachable", "unreachable", 1), + implementation: ("unreachable", "unreachable", 1), + }, + Err(e) => e, + } + } + + const fn check() -> Result<(), Error> { + let mut base_i = 0; + while base_i < BASE_ARGS.len() { + let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; + + if $crate::reflect::str_eq(base_name, impl_name) { + if $crate::reflect::str_eq(base_type, impl_type) + && base_wrap_val == impl_wrap_val + { + was_found = true; + break; + } else { + return Err(Error { + cause: Cause::TypeMismatch, + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + }); + } + } + + impl_i += 1; + } + + if !was_found { + return Err(Error { + cause: Cause::RequiredField, + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + }); + } + + base_i += 1; + } + + let mut impl_i = 0; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; + impl_i += 1; + + if impl_wrapped_val % 10 == 2 { + continue; + } + + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE_ARGS.len() { + let (base_name, _, _) = BASE_ARGS[base_i]; + if $crate::reflect::str_eq(base_name, impl_name) { + was_found = true; + break; + } + base_i += 1; + } + if !was_found { + return Err(Error { + cause: Cause::AdditionalNonNullableField, + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + }); + } + } + + Ok(()) + } + + const RES: ::std::result::Result<(), Error> = check(); + if RES.is_err() { + const ERROR: Error = unwrap_error(RES); + + const BASE_ARG_NAME: $crate::reflect::Name = ERROR.base.0; + const IMPL_ARG_NAME: $crate::reflect::Name = ERROR.implementation.0; + + const BASE_TYPE_FORMATTED: &str = + $crate::reflect::format_type!(ERROR.base.1, ERROR.base.2,); + const IMPL_TYPE_FORMATTED: &str = $crate::reflect::format_type!( + ERROR.implementation.1, + ERROR.implementation.2, + ); + + const MSG: &str = match ERROR.cause { + Cause::TypeMismatch => { + $crate::reflect::const_concat!( + "Argument `", + BASE_ARG_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + ) + } + Cause::RequiredField => { + $crate::reflect::const_concat!( + "Argument `", + BASE_ARG_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found.", + ) + } + Cause::AdditionalNonNullableField => { + $crate::reflect::const_concat!( + "Argument `", + IMPL_ARG_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` isn't present on the interface and so has to be nullable.", + ) + } + }; + const ERROR_MSG: &str = $crate::reflect::const_concat!( + ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG, + ); + ::std::panic!("{}", ERROR_MSG); + } + }; + }; + } + + /// Ensures that the given `$impl_ty` has the specified [`Field`]. + /// + /// [`Field`]: super::Field + /// [`fnv1a128`]: super::fnv1a128 + #[macro_export] + macro_rules! reflect_assert_has_field { + ( + $field_name: expr, + $impl_ty: ty, + $behavior: ty + $(, $prefix: expr)? $(,)? + ) => {{ + let exists = $crate::reflect::str_exists_in_arr( + $field_name, + <$impl_ty as $crate::reflect::Fields<$behavior>>::NAMES, + ); + if !exists { + const MSG: &str = $crate::reflect::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::reflect::BaseType<$behavior>>::NAME, + "`." + ); + ::std::panic!("{}", MSG); + } + }}; + } + + /// Concatenates `const` [`str`](prim@str)s in a `const` context. + #[macro_export] + macro_rules! reflect_const_concat { + ($($s:expr),* $(,)?) => {{ + const LEN: usize = 0 $(+ $s.as_bytes().len())*; + const CNT: usize = [$($s),*].len(); + const fn concat(input: [&str; CNT]) -> [u8; LEN] { + let mut bytes = [0; LEN]; + let (mut i, mut byte) = (0, 0); + while i < CNT { + let mut b = 0; + while b < input[i].len() { + bytes[byte] = input[i].as_bytes()[b]; + byte += 1; + b += 1; + } + i += 1; + } + bytes + } + const CON: [u8; LEN] = concat([$($s),*]); + + // TODO: Use `.unwrap()` once it becomes `const`. + match ::std::str::from_utf8(&CON) { + ::std::result::Result::Ok(s) => s, + _ => unreachable!(), + } + }}; + } + + /// Formats the given [`Type`] and [`WrappedValue`] into a human-readable + /// GraphQL type name string. + /// + /// # Example + /// + /// ```rust + /// # use juniper::reflect::format_type; + /// # + /// assert_eq!(format_type!("String", 123), "[String]!"); + /// assert_eq!(format_type!("🦀", 123), "[🦀]!"); + /// ``` + /// + /// [`Type`]: super::Type + /// [`WrappedValue`]: super::WrappedValue + #[macro_export] + macro_rules! reflect_format_type { + ($ty: expr, $wrapped_value: expr $(,)?) => {{ + const TYPE: ($crate::reflect::Type, $crate::reflect::WrappedValue) = + ($ty, $wrapped_value); + const RES_LEN: usize = $crate::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1); + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn format_type_arr() -> [u8; RES_LEN] { + let (ty, wrap_val) = TYPE; + let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; + + let mut current_start = 0; + let mut current_end = RES_LEN - 1; + let mut current_wrap_val = wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, // Skips writing `BANG` later. + 3 => { + // Write `OPENING_BRACKET` at `current_start`. + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + if !is_null { + // Write `BANG` at `current_end`. + i = 0; + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + // Write `CLOSING_BRACKET` at `current_end`. + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + is_null = false; + } + _ => {} + } + + current_wrap_val /= 10; + } + + // Writes `Type` at `current_start`. + let mut i = 0; + while i < ty.as_bytes().len() { + type_arr[current_start + i] = ty.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + // Writes `BANG` at `current_end`. + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } + } + + type_arr + } + + const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); + + // TODO: Use `.unwrap()` once it becomes `const`. + const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) { + ::std::result::Result::Ok(s) => s, + _ => unreachable!(), + }; + TYPE_FORMATTED + }}; + } + + #[doc(inline)] + pub use { + reflect_assert_field as assert_field, reflect_assert_field_args as assert_field_args, + reflect_assert_field_type as assert_field_type, + reflect_assert_has_field as assert_has_field, + reflect_assert_implemented_for as assert_implemented_for, + reflect_assert_interfaces_impls as assert_interfaces_impls, + reflect_assert_transitive_impls as assert_transitive_impls, + reflect_const_concat as const_concat, reflect_format_type as format_type, + }; +} diff --git a/juniper/src/resolve/mod.rs b/juniper/src/resolve/mod.rs new file mode 100644 index 000000000..3c782e92a --- /dev/null +++ b/juniper/src/resolve/mod.rs @@ -0,0 +1,211 @@ +use crate::{ + behavior, graphql, + meta::MetaType, + parser::{self, ParseError}, + reflect, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, IntoFieldError, + Registry, Selection, +}; + +pub trait Type { + 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 { + fn type_name(type_info: &TypeInfo) -> &str; +} + +pub trait ConcreteTypeName { + 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, + ) -> ExecutionResult; +} + +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, + ) -> BoxFuture<'r, ExecutionResult>; +} + +pub trait Resolvable { + type Value; + + fn into_value(self) -> FieldResult; +} + +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, + ) -> ExecutionResult; +} + +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, + ) -> BoxFuture<'r, ExecutionResult>; +} + +pub trait Field< + TypeInfo: ?Sized, + Context: ?Sized, + ScalarValue, + Behavior: ?Sized = behavior::Standard, +> +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TypeInfo, + executor: &Executor, + ) -> ExecutionResult; +} + +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; +} + +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, + type_info: &'r TypeInfo, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult>; +} + +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, + type_info: &'r TypeInfo, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult>; +} + +pub trait ToInputValue { + fn to_input_value(&self) -> graphql::InputValue; +} + +pub trait InputValue<'input, ScalarValue: 'input, Behavior: ?Sized = behavior::Standard>: + Sized +{ + type Error: IntoFieldError; + + fn try_from_input_value( + v: &'input graphql::InputValue, + ) -> Result; + + fn try_from_implicit_null() -> Result { + Self::try_from_input_value(&graphql::InputValue::Null) + } +} + +pub trait InputValueOwned: + for<'i> InputValue<'i, ScalarValue, Behavior> +{ +} + +impl InputValueOwned for T where T: for<'i> InputValue<'i, SV, BH> {} + +pub trait InputValueAs<'input, Wrapper, ScalarValue: 'input, Behavior: ?Sized = behavior::Standard> +{ + type Error: IntoFieldError; + + fn try_from_input_value( + v: &'input graphql::InputValue, + ) -> Result; + + fn try_from_implicit_null() -> Result { + Self::try_from_input_value(&graphql::InputValue::Null) + } +} + +pub trait InputValueAsRef { + type Error: IntoFieldError; + + fn try_from_input_value(v: &graphql::InputValue) -> 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 { + fn parse_scalar_token(token: parser::ScalarToken<'_>) -> Result; +} diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 04f5eda96..870e23321 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -1,6 +1,5 @@ //! Types used to describe a `GraphQL` schema -use juniper::IntoFieldError; use std::{ borrow::{Cow, ToOwned}, fmt, @@ -9,10 +8,11 @@ use std::{ use crate::{ ast::{FromInputValue, InputValue, Type}, parser::{ParseError, ScalarToken}, + resolve, schema::model::SchemaType, types::base::TypeKind, value::{DefaultScalarValue, ParseScalarValue}, - FieldError, + FieldError, IntoFieldError, }; /// Whether an item is deprecated, with context. @@ -28,16 +28,16 @@ impl DeprecationStatus { /// If this deprecation status indicates the item is deprecated. pub fn is_deprecated(&self) -> bool { match self { - DeprecationStatus::Current => false, - DeprecationStatus::Deprecated(_) => true, + Self::Current => false, + Self::Deprecated(_) => true, } } /// An optional reason for the deprecation, or none if `Current`. pub fn reason(&self) -> Option<&str> { match self { - DeprecationStatus::Current => None, - DeprecationStatus::Deprecated(rsn) => rsn.as_deref(), + Self::Current => None, + Self::Deprecated(rsn) => rsn.as_deref(), } } } @@ -54,6 +54,20 @@ pub struct ScalarMeta<'a, S> { pub(crate) parse_fn: ScalarTokenParseFn, } +// Manual implementation is required here to omit redundant `S: Clone` trait +// bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for ScalarMeta<'a, S> { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + description: self.description.clone(), + specified_by_url: self.specified_by_url.clone(), + try_parse_fn: self.try_parse_fn, + parse_fn: self.parse_fn, + } + } +} + /// Shortcut for an [`InputValue`] parsing function. pub type InputValueParseFn = for<'b> fn(&'b InputValue) -> Result<(), FieldError>; @@ -61,7 +75,7 @@ pub type InputValueParseFn = for<'b> fn(&'b InputValue) -> Result<(), Fiel pub type ScalarTokenParseFn = for<'b> fn(ScalarToken<'b>) -> Result; /// List type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ListMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, @@ -71,14 +85,14 @@ pub struct ListMeta<'a> { } /// Nullable type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct NullableMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, } /// Object type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ObjectMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, @@ -101,8 +115,21 @@ pub struct EnumMeta<'a, S> { pub(crate) try_parse_fn: InputValueParseFn, } +// Manual implementation is required here to omit redundant `S: Clone` trait +// bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for EnumMeta<'a, S> { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + description: self.description.clone(), + values: self.values.clone(), + try_parse_fn: self.try_parse_fn, + } + } +} + /// Interface type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct InterfaceMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, @@ -115,7 +142,7 @@ pub struct InterfaceMeta<'a, S> { } /// Union type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct UnionMeta<'a> { #[doc(hidden)] pub name: Cow<'a, str>, @@ -126,6 +153,7 @@ pub struct UnionMeta<'a> { } /// Input object metadata +#[derive(Clone)] pub struct InputObjectMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, @@ -140,14 +168,14 @@ pub struct InputObjectMeta<'a, S> { /// /// After a type's `meta` method has been called but before it has returned, a placeholder type /// is inserted into a registry to indicate existence. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PlaceholderMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, } /// Generic type metadata -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum MetaType<'a, S = DefaultScalarValue> { #[doc(hidden)] Scalar(ScalarMeta<'a, S>), @@ -170,7 +198,7 @@ pub enum MetaType<'a, S = DefaultScalarValue> { } /// Metadata for a field -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Field<'a, S> { #[doc(hidden)] pub name: smartstring::alias::String, @@ -193,7 +221,7 @@ impl<'a, S> Field<'a, S> { } /// Metadata for an argument to a field -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Argument<'a, S> { #[doc(hidden)] pub name: String, @@ -434,6 +462,29 @@ impl<'a, S> MetaType<'a, S> { } } +impl<'a, S> From> for Type<'a> { + fn from(meta: MetaType<'a, S>) -> Self { + match meta { + MetaType::Scalar(ScalarMeta { name, .. }) + | MetaType::Object(ObjectMeta { name, .. }) + | MetaType::Enum(EnumMeta { name, .. }) + | MetaType::Interface(InterfaceMeta { name, .. }) + | MetaType::Union(UnionMeta { name, .. }) + | MetaType::InputObject(InputObjectMeta { name, .. }) => Type::NonNullNamed(name), + MetaType::List(ListMeta { + of_type, + expected_size, + }) => Type::NonNullList(Box::new(of_type), expected_size), + MetaType::Nullable(NullableMeta { of_type }) => match of_type { + Type::NonNullNamed(inner) => Type::Named(inner), + Type::NonNullList(inner, expected_size) => Type::List(inner, expected_size), + t => t, + }, + MetaType::Placeholder(PlaceholderMeta { of_type }) => of_type, + } + } +} + impl<'a, S> ScalarMeta<'a, S> { /// Builds a new [`ScalarMeta`] type with the specified `name`. pub fn new(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(name: impl Into>) -> Self + where + T: resolve::InputValueOwned + resolve::ScalarToken, + { + Self { + name: name.into(), + description: None, + specified_by_url: None, + try_parse_fn: try_parse_fn_reworked::, + parse_fn: >::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(name: impl Into>) -> Self + where + T: resolve::InputValueAsRef + resolve::ScalarToken + ?Sized, + { + Self { + name: name.into(), + description: None, + specified_by_url: None, + try_parse_fn: try_parse_unsized_fn::, + parse_fn: >::parse_scalar_token, + } + } + /// Sets the `description` of this [`ScalarMeta`] type. /// /// Overwrites any previously set description. @@ -477,7 +557,7 @@ impl<'a, S> ScalarMeta<'a, S> { } impl<'a> ListMeta<'a> { - /// Build a new [`ListMeta`] type by wrapping the specified [`Type`]. + /// Builds a new [`ListMeta`] type by wrapping the specified [`Type`]. /// /// Specifying `expected_size` will be used to ensure that values of this /// type will always match it. @@ -495,7 +575,7 @@ impl<'a> ListMeta<'a> { } impl<'a> NullableMeta<'a> { - /// Build a new [`NullableMeta`] type by wrapping the specified [`Type`]. + /// Builds a new [`NullableMeta`] type by wrapping the specified [`Type`]. pub fn new(of_type: Type<'a>) -> Self { Self { of_type } } @@ -563,6 +643,20 @@ impl<'a, S> EnumMeta<'a, S> { } } + /// Builds a new [`EnumMeta`] information with the specified `name` and + /// possible `values`. + pub fn new_reworked(name: impl Into>, values: &[EnumValue]) -> Self + where + T: resolve::InputValueOwned, + { + Self { + name: name.into(), + description: None, + values: values.to_owned(), + try_parse_fn: try_parse_fn_reworked::, + } + } + /// Sets the `description` of this [`EnumMeta`] type. /// /// Overwrites any previously set description. @@ -663,6 +757,21 @@ impl<'a, S> InputObjectMeta<'a, S> { } } + /// Builds a new [`InputObjectMeta`] information with the specified `name` + /// and its `fields`. + pub fn new_reworked(name: impl Into>, fields: &[Argument<'a, S>]) -> Self + where + T: resolve::InputValueOwned, + S: Clone, + { + Self { + name: name.into(), + description: None, + input_fields: fields.to_vec(), + try_parse_fn: try_parse_fn_reworked::, + } + } + /// Set the `description` of this [`InputObjectMeta`] type. /// /// Overwrites any previously set description. @@ -811,3 +920,21 @@ where .map(drop) .map_err(T::Error::into_field_error) } + +fn try_parse_fn_reworked(v: &InputValue) -> Result<(), FieldError> +where + T: resolve::InputValueOwned, +{ + T::try_from_input_value(v) + .map(drop) + .map_err(T::Error::into_field_error) +} + +fn try_parse_unsized_fn(v: &InputValue) -> Result<(), FieldError> +where + T: resolve::InputValueAsRef + ?Sized, +{ + T::try_from_input_value(v) + .map(drop) + .map_err(T::Error::into_field_error) +} diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 618227462..311bcab26 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -141,7 +141,7 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> { self.description.as_deref() } - fn types(&self) -> Vec> { + fn types(&self) -> Vec> { self.type_list() .into_iter() .filter(|t| { @@ -156,21 +156,21 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> { } #[graphql(name = "queryType")] - fn query_type_(&self) -> TypeType { + fn query_type_(&self) -> TypeType<'_, S> { self.query_type() } #[graphql(name = "mutationType")] - fn mutation_type_(&self) -> Option> { + fn mutation_type_(&self) -> Option> { self.mutation_type() } #[graphql(name = "subscriptionType")] - fn subscription_type_(&self) -> Option> { + fn subscription_type_(&self) -> Option> { self.subscription_type() } - fn directives(&self) -> Vec<&DirectiveType> { + fn directives(&self) -> Vec<&DirectiveType<'_, S>> { self.directive_list() } } @@ -214,7 +214,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn fields( &self, #[graphql(default = false)] include_deprecated: Option, - ) -> Option>> { + ) -> Option>> { match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -231,14 +231,14 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn of_type(&self) -> Option<&TypeType> { + fn of_type(&self) -> Option<&TypeType<'_, S>> { match self { TypeType::Concrete(_) => None, TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l), } } - fn input_fields(&self) -> Option<&[Argument]> { + fn input_fields(&self) -> Option<&[Argument<'_, S>]> { match self { TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, @@ -343,7 +343,7 @@ impl<'a, S: ScalarValue + 'a> Field<'a, S> { self.description.as_deref() } - fn args(&self) -> Vec<&Argument> { + fn args(&self) -> Vec<&Argument<'_, S>> { self.arguments .as_ref() .map_or_else(Vec::new, |v| v.iter().collect()) @@ -434,7 +434,7 @@ impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> { self.is_repeatable } - fn args(&self) -> &[Argument] { + fn args(&self) -> &[Argument<'_, S>] { &self.arguments } diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs index f4f064914..a887907ce 100644 --- a/juniper/src/tests/introspection_tests.rs +++ b/juniper/src/tests/introspection_tests.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use crate::{ - graphql_vars, + graphql_value, graphql_vars, introspection::IntrospectionFormat, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index d2e4643e5..06a79325b 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -1,4 +1,4 @@ -use crate::value::Value; +use crate::graphql::{self, Value}; // Sort a nested schema Value. // In particular, lists are sorted by the "name" key of children, if present. @@ -34,7 +34,7 @@ pub(super) fn sort_schema_value(value: &mut Value) { } pub(crate) fn schema_introspection_result() -> Value { - let mut v = graphql_value!({ + let mut v = graphql::value!({ "__schema": { "description": null, "queryType": { @@ -1451,7 +1451,7 @@ pub(crate) fn schema_introspection_result() -> Value { } pub(crate) fn schema_introspection_result_without_descriptions() -> Value { - let mut v = graphql_value!({ + let mut v = graphql::value!({ "__schema": { "queryType": { "name": "Query" diff --git a/juniper/src/types/arc.rs b/juniper/src/types/arc.rs new file mode 100644 index 000000000..2d3a75000 --- /dev/null +++ b/juniper/src/types/arc.rs @@ -0,0 +1,305 @@ +//! GraphQL implementation for [`Arc`]. + +use std::sync::Arc; + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl resolve::Type for Arc +where + T: resolve::Type + ?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 resolve::TypeName for Arc +where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl resolve::ConcreteTypeName for Arc +where + T: resolve::ConcreteTypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str { + (**self).concrete_type_name(type_info) + } +} + +impl resolve::Value for Arc +where + T: resolve::Value + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for Arc +where + T: resolve::ValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl resolve::Resolvable for Arc +where + T: ?Sized, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ConcreteValue for Arc +where + T: resolve::ConcreteValue + ?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, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl resolve::ConcreteValueAsync for Arc +where + T: resolve::ConcreteValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl resolve::Field for Arc +where + T: resolve::Field + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl resolve::FieldAsync for Arc +where + T: resolve::FieldAsync + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl resolve::ToInputValue for Arc +where + T: resolve::ToInputValue + ?Sized, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**self).to_input_value() + } +} + +impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Arc +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) -> Result { + T::try_from_input_value(v) + } + + fn try_from_implicit_null() -> Result { + T::try_from_implicit_null() + } +} + +impl<'i, T, SV, BH> resolve::InputValueAs<'i, Arc, 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) -> Result, Self::Error> { + T::try_from_input_value(v).map(Arc::new) + } + + fn try_from_implicit_null() -> Result, Self::Error> { + T::try_from_implicit_null().map(Arc::new) + } +} + +impl resolve::ScalarToken for Arc +where + T: resolve::ScalarToken + ?Sized, + BH: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + T::parse_scalar_token(token) + } +} + +impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Arc +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, 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 graphql::OutputType for Arc +where + T: graphql::OutputType + ?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 +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, 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 reflect::BaseType for Arc +where + T: reflect::BaseType + ?Sized, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Arc +where + T: reflect::BaseSubTypes + ?Sized, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Arc +where + T: reflect::WrappedType + ?Sized, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/array.rs b/juniper/src/types/array.rs new file mode 100644 index 000000000..ad1812513 --- /dev/null +++ b/juniper/src/types/array.rs @@ -0,0 +1,448 @@ +//! GraphQL implementation for [array]. +//! +//! [array]: prim@array + +use std::{ + mem::{self, MaybeUninit}, + ptr, +}; + +use crate::{ + behavior, + executor::{ExecutionResult, Executor, Registry}, + graphql, reflect, resolve, + schema::meta::MetaType, + BoxFuture, FieldError, FieldResult, IntoFieldError, Selection, +}; + +use super::iter; + +impl resolve::Type for [T; N] +where + T: resolve::Type, + 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::, _>(type_info, None) + } +} + +impl resolve::Value for [T; N] +where + T: resolve::Value, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + iter::resolve_list(self.iter(), selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for [T; N] +where + T: resolve::ValueAsync + 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, + ) -> BoxFuture<'r, ExecutionResult> { + Box::pin(iter::resolve_list_async( + self.iter(), + selection_set, + type_info, + executor, + )) + } +} + +impl resolve::Resolvable for [T; N] +where + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ToInputValue for [T; N] +where + T: resolve::ToInputValue, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + 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; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result { + struct PartiallyInitializedArray { + arr: [MaybeUninit; N], + init_len: usize, + no_drop: bool, + } + + impl Drop for PartiallyInitializedArray { + 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:: { + // 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 graphql::OutputType for [T; N] +where + T: graphql::OutputType, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: resolve::ValueAsync, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl reflect::BaseType for [T; N] +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for [T; N] +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for [T; N] +where + T: reflect::WrappedType, + 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 { + /// [`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 IntoFieldError for TryFromInputValueError +where + E: IntoFieldError, +{ + fn into_field_error(self) -> FieldError { + 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; 0]>::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!( + <[Option; 1]>::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!(>::try_from_input_value(&v), Ok(None)); + assert_eq!(>::try_from_input_value(&v), Ok(None)); + assert_eq!( + ; 0]>>::try_from_input_value(&v), + Ok(None), + ); + assert_eq!( + ; 1]>>::try_from_input_value(&v), + Ok(None), + ); + assert_eq!( + <[[i32; 1]; 1]>::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!( + ; 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; 1]>::try_from_input_value(&v), Ok([Some(1)])); + assert_eq!(>::try_from_input_value(&v), Ok(Some([1]))); + assert_eq!( + ; 1]>>::try_from_input_value(&v), + Ok(Some([Some(1)])), + ); + assert_eq!(<[[i32; 1]; 1]>::try_from_input_value(&v), Ok([[1]])); + assert_eq!( + ; 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!( + >::try_from_input_value(&v), + Ok(Some([1, 2, 3])), + ); + assert_eq!( + <[Option; 3]>::try_from_input_value(&v), + Ok([Some(1), Some(2), Some(3)]), + ); + assert_eq!( + ; 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!( + ; 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!( + >::try_from_input_value(&v), + Err(TryFromInputValueError::Item( + "Expected `Int`, found: null".into_field_error(), + )), + ); + assert_eq!( + <[Option; 3]>::try_from_input_value(&v), + Ok([Some(1), Some(2), None]), + ); + assert_eq!( + ; 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!( + ; 1]>; 3]>>::try_from_input_value(&v), + Ok(Some([Some([Some(1)]), Some([Some(2)]), None])), + ); + } +} diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 9d54fee08..aab0c6df4 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -4,6 +4,7 @@ use crate::{ ast::{Directive, FromInputValue, InputValue, Selection}, executor::{ExecutionResult, Executor, Registry, Variables}, parser::Spanning, + resolve, schema::meta::{Argument, MetaType}, value::{DefaultScalarValue, Object, ScalarValue, Value}, FieldResult, GraphQLEnum, IntoFieldError, @@ -98,6 +99,8 @@ impl<'a, S> Arguments<'a, S> { Self { args } } + /// TODO: DEPRECATED! + /// /// Gets an argument by the given `name` and converts it into the desired /// type. /// @@ -121,6 +124,38 @@ impl<'a, S> Arguments<'a, S> { .transpose() .map_err(IntoFieldError::into_field_error) } + + /// Resolves an argument with the provided `name` as the specified type `T`. + /// + /// If [`None`] argument is found, then `T` is + /// [tried to be resolved from implicit `null`][0]. + /// + /// # Errors + /// + /// If the [`resolve::InputValue`] conversion fails. + /// + /// [0]: resolve::InputValue::try_from_implicit_null + pub fn resolve<'s, T, BH>(&'s self, name: &str) -> FieldResult + where + T: resolve::InputValue<'s, S, BH>, + BH: ?Sized, + { + self.args + .as_ref() + .and_then(|args| args.get(name)) + .map(>::try_from_input_value) + .transpose() + .map_err(IntoFieldError::into_field_error)? + .map_or_else( + || { + >::try_from_implicit_null().map_err(|e| { + IntoFieldError::into_field_error(e) + .map_message(|m| format!("Missing argument `{name}`: {m}")) + }) + }, + Ok, + ) + } } /// Primary trait used to resolve GraphQL values. diff --git a/juniper/src/types/box.rs b/juniper/src/types/box.rs new file mode 100644 index 000000000..c6f2fcb46 --- /dev/null +++ b/juniper/src/types/box.rs @@ -0,0 +1,303 @@ +//! GraphQL implementation for [`Box`]. + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl resolve::Type for Box +where + T: resolve::Type + ?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 resolve::TypeName for Box +where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl resolve::ConcreteTypeName for Box +where + T: resolve::ConcreteTypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str { + (**self).concrete_type_name(type_info) + } +} + +impl resolve::Value for Box +where + T: resolve::Value + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for Box +where + T: resolve::ValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl resolve::Resolvable for Box +where + T: ?Sized, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ConcreteValue for Box +where + T: resolve::ConcreteValue + ?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, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl resolve::ConcreteValueAsync for Box +where + T: resolve::ConcreteValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl resolve::Field for Box +where + T: resolve::Field + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl resolve::FieldAsync for Box +where + T: resolve::FieldAsync + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl resolve::ToInputValue for Box +where + T: resolve::ToInputValue + ?Sized, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**self).to_input_value() + } +} + +impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Box +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) -> Result { + T::try_from_input_value(v) + } + + fn try_from_implicit_null() -> Result { + T::try_from_implicit_null() + } +} + +impl<'i, T, SV, BH> resolve::InputValueAs<'i, Box, 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) -> Result, Self::Error> { + T::try_from_input_value(v).map(Box::new) + } + + fn try_from_implicit_null() -> Result, Self::Error> { + T::try_from_implicit_null().map(Box::new) + } +} + +impl resolve::ScalarToken for Box +where + T: resolve::ScalarToken + ?Sized, + BH: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + T::parse_scalar_token(token) + } +} + +impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Box +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, 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 graphql::OutputType for Box +where + T: graphql::OutputType + ?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 +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, 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 reflect::BaseType for Box +where + T: reflect::BaseType + ?Sized, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Box +where + T: reflect::BaseSubTypes + ?Sized, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Box +where + T: reflect::WrappedType + ?Sized, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 108f0e021..379cbae27 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -183,7 +183,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } @@ -283,7 +283,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } @@ -481,7 +481,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } diff --git a/juniper/src/types/cow.rs b/juniper/src/types/cow.rs new file mode 100644 index 000000000..0701eb314 --- /dev/null +++ b/juniper/src/types/cow.rs @@ -0,0 +1,304 @@ +//! GraphQL implementation for [`Cow`]. + +use std::{borrow::Cow, ops::Deref}; + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl<'me, T, TI, SV, BH> resolve::Type for Cow<'me, T> +where + T: resolve::Type + ToOwned + ?Sized + 'me, + TI: ?Sized, + BH: ?Sized, + Self: Deref, +{ + 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 for Cow<'me, T> +where + T: resolve::TypeName + ToOwned + ?Sized + 'me, + TI: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl<'me, T, TI, BH> resolve::ConcreteTypeName for Cow<'me, T> +where + T: resolve::ConcreteTypeName + ToOwned + ?Sized + 'me, + TI: ?Sized, + BH: ?Sized, + Self: Deref, +{ + 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 for Cow<'me, T> +where + T: resolve::Value + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync for Cow<'me, T> +where + T: resolve::ValueAsync + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_value_async<'r>( + &'r self, + selection_set: Option<&'r [Selection<'_, SV>]>, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::Resolvable for Cow<'me, T> +where + T: ToOwned + ?Sized + 'me, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue for Cow<'me, T> +where + T: resolve::ConcreteValue + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_concrete_value( + &self, + type_name: &str, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync for Cow<'me, T> +where + T: resolve::ConcreteValueAsync + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_concrete_value_async<'r>( + &'r self, + type_name: &str, + selection_set: Option<&'r [Selection<'_, SV>]>, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::Field for Cow<'me, T> +where + T: resolve::Field + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync for Cow<'me, T> +where + T: resolve::FieldAsync + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::ToInputValue for Cow<'me, T> +where + T: resolve::ToInputValue + ToOwned + ?Sized + 'me, + BH: ?Sized, + Self: Deref, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**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, +{ + type Error = T::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result { + T::try_from_input_value(v) + } + + fn try_from_implicit_null() -> Result { + 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 + ToOwned + 'me, + SV: 'i, + BH: ?Sized, + Cow<'me, Self>: Deref, +{ + type Error = T::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + T::try_from_input_value(v).map(Cow::Borrowed) + } + + fn try_from_implicit_null() -> Result, Self::Error> { + T::try_from_implicit_null().map(Cow::Borrowed) + } +} + +impl<'me, T, SV, BH> resolve::ScalarToken for Cow<'me, T> +where + T: resolve::ScalarToken + ToOwned + ?Sized + 'me, + BH: ?Sized, + Self: Deref, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + 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, +{ + fn assert_input_type() { + T::assert_input_type() + } +} + +impl<'me, T, TI, CX, SV, BH> graphql::OutputType for Cow<'me, T> +where + T: graphql::OutputType + ToOwned + ?Sized + 'me, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: Deref, +{ + 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, +{ + fn assert_scalar() { + T::assert_scalar() + } +} + +impl<'me, T, BH> reflect::BaseType for Cow<'me, T> +where + T: reflect::BaseType + ToOwned + ?Sized + 'me, + BH: ?Sized, + Self: Deref, +{ + const NAME: reflect::Type = T::NAME; +} + +impl<'me, T, BH> reflect::BaseSubTypes for Cow<'me, T> +where + T: reflect::BaseSubTypes + ToOwned + ?Sized + 'me, + BH: ?Sized, + Self: Deref, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl<'me, T, BH> reflect::WrappedType for Cow<'me, T> +where + T: reflect::WrappedType + ToOwned + ?Sized + 'me, + BH: ?Sized, + Self: Deref, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/iter.rs b/juniper/src/types/iter.rs new file mode 100644 index 000000000..f0f599a04 --- /dev/null +++ b/juniper/src/types/iter.rs @@ -0,0 +1,72 @@ +//! GraphQL implementation for [`Iterator`]. + +use crate::{graphql, resolve, ExecutionResult, Executor, Selection}; + +pub fn resolve_list<'t, T, TI, CX, SV, BH, I>( + iter: I, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, +) -> ExecutionResult +where + I: Iterator + ExactSizeIterator, + T: resolve::Value + ?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 +where + I: Iterator + ExactSizeIterator, + T: resolve::ValueAsync + ?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::>(); + + let mut values = Vec::with_capacity(futs.len()); + while let Some(res) = futs.next().await { + let val = res?; + if is_non_null && val.is_null() { + return Err("Resolved `null` on non-null type".into()); + } + values.push(val); + } + Ok(graphql::Value::list(values)) +} diff --git a/juniper/src/types/mod.rs b/juniper/src/types/mod.rs index 8ac005ba0..fd5e0abbf 100644 --- a/juniper/src/types/mod.rs +++ b/juniper/src/types/mod.rs @@ -1,10 +1,27 @@ +mod arc; +pub mod array; +mod r#box; +mod cow; +pub mod iter; +mod nullable; +mod option; +mod rc; +mod r#ref; +mod ref_mut; +mod result; +mod slice; +mod r#str; +pub mod vec; + pub mod async_await; pub mod base; pub mod containers; pub mod marker; pub mod name; -pub mod nullable; pub mod pointers; pub mod scalars; pub mod subscriptions; pub mod utilities; + +#[doc(inline)] +pub use self::nullable::Nullable; diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 20f16708b..e2e6524bb 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -1,42 +1,53 @@ +//! GraphQL implementation for [`Nullable`]. + +use std::mem; + +use futures::future; + use crate::{ - ast::{FromInputValue, InputValue, Selection, ToInputValue}, + ast::{FromInputValue, InputValue, ToInputValue}, + behavior, executor::{ExecutionResult, Executor, Registry}, + graphql, reflect, resolve, schema::meta::MetaType, types::{ async_await::GraphQLValueAsync, base::{GraphQLType, GraphQLValue}, marker::IsInputType, }, - value::{ScalarValue, Value}, + BoxFuture, FieldResult, ScalarValue, Selection, }; -/// `Nullable` can be used in situations where you need to distinguish between an implicitly and -/// explicitly null input value. +/// [`Nullable`] wrapper allowing to distinguish between an implicit and +/// explicit `null` input value. /// -/// The GraphQL spec states that these two field calls are similar, but are not identical: +/// [GraphQL spec states][0] that these two field calls are similar, but are not +/// identical: /// -/// ```graphql -/// { -/// field(arg: null) -/// field -/// } -/// ``` +/// > ```graphql +/// > { +/// > field(arg: null) +/// > field +/// > } +/// > ``` +/// > The first has explicitly provided `null` to the argument "arg", while the +/// > second has implicitly not provided a value to the argument "arg". These +/// > two forms may be interpreted differently. For example, a mutation +/// > representing deleting a field vs not altering a field, respectively. /// -/// The first has explicitly provided null to the argument “arg”, while the second has implicitly -/// not provided a value to the argument “arg”. These two forms may be interpreted differently. For -/// example, a mutation representing deleting a field vs not altering a field, respectively. +/// In cases where there is no need to distinguish between the two types of +/// `null`, it's better to simply use [`Option`]. /// -/// In cases where you do not need to be able to distinguish between the two types of null, you -/// should simply use `Option`. +/// [0]: https://spec.graphql.org/October2021#example-1c7eb #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Nullable { - /// No value + /// No value specified. ImplicitNull, - /// No value, explicitly specified to be null + /// Value explicitly specified to be `null`. ExplicitNull, - /// Some value `T` + /// Explicitly specified non-`null` value of `T`. Some(T), } @@ -49,101 +60,134 @@ impl Default for Nullable { } impl Nullable { - /// Returns `true` if the nullable is a `ExplicitNull` value. + /// Indicates whether this [`Nullable`] represents an [`ExplicitNull`]. + /// + /// [`ExplicitNull`]: Nullable::ExplicitNull #[inline] pub fn is_explicit_null(&self) -> bool { matches!(self, Self::ExplicitNull) } - /// Returns `true` if the nullable is a `ImplicitNull` value. + /// Indicates whether this [`Nullable`] represents an [`ImplicitNull`]. + /// + /// [`ImplicitNull`]: Nullable::ImplicitNull #[inline] pub fn is_implicit_null(&self) -> bool { matches!(self, Self::ImplicitNull) } - /// Returns `true` if the nullable is a `Some` value. + /// Indicates whether this [`Nullable`] contains a non-`null` value. #[inline] pub fn is_some(&self) -> bool { matches!(self, Self::Some(_)) } - /// Returns `true` if the nullable is not a `Some` value. + /// Indicates whether this [`Nullable`] represents a `null`. #[inline] pub fn is_null(&self) -> bool { !matches!(self, Self::Some(_)) } + /// Converts from `&Nullable` to `Nullable<&T>`. + #[inline] + pub fn as_ref(&self) -> Nullable<&T> { + match self { + Self::Some(x) => Nullable::Some(x), + Self::ImplicitNull => Nullable::ImplicitNull, + Self::ExplicitNull => Nullable::ExplicitNull, + } + } + /// Converts from `&mut Nullable` to `Nullable<&mut T>`. #[inline] pub fn as_mut(&mut self) -> Nullable<&mut T> { - match *self { - Self::Some(ref mut x) => Nullable::Some(x), + match self { + Self::Some(x) => Nullable::Some(x), Self::ImplicitNull => Nullable::ImplicitNull, Self::ExplicitNull => Nullable::ExplicitNull, } } - /// Returns the contained `Some` value, consuming the `self` value. + /// Returns the contained non-`null` value, consuming the `self` value. /// /// # Panics /// - /// Panics if the value is not a `Some` with a custom panic message provided by `msg`. + /// With a custom `msg` if this [`Nullable`] represents a `null`. #[inline] #[track_caller] pub fn expect(self, msg: &str) -> T { self.some().expect(msg) } - /// Returns the contained `Some` value or a provided default. + /// Returns the contained non-`null` value or the provided `default` one. #[inline] pub fn unwrap_or(self, default: T) -> T { self.some().unwrap_or(default) } - /// Returns the contained `Some` value or computes it from a closure. + /// Returns thecontained non-`null` value or computes it from the provided + /// `func`tion. + #[inline] + pub fn unwrap_or_else T>(self, func: F) -> T { + self.some().unwrap_or_else(func) + } + + /// Returns the contained non-`null` value or the [`Default`] one. #[inline] - pub fn unwrap_or_else T>(self, f: F) -> T { - self.some().unwrap_or_else(f) + pub fn unwrap_or_default(self) -> T + where + T: Default, + { + self.some().unwrap_or_default() } - /// Maps a `Nullable` to `Nullable` by applying a function to a contained value. + /// Maps this `Nullable` to `Nullable` by applying the provided + /// `func`tion to the contained non-`null` value. #[inline] - pub fn map U>(self, f: F) -> Nullable { + pub fn map U>(self, func: F) -> Nullable { match self { - Self::Some(x) => Nullable::Some(f(x)), + Self::Some(x) => Nullable::Some(func(x)), Self::ImplicitNull => Nullable::ImplicitNull, Self::ExplicitNull => Nullable::ExplicitNull, } } - /// Applies a function to the contained value (if any), or returns the provided default (if - /// not). + /// Applies the provided `func`tion to the contained non-`null` value (if + /// any), or returns the provided `default` value (if not). #[inline] - pub fn map_or U>(self, default: U, f: F) -> U { - self.some().map_or(default, f) + pub fn map_or U>(self, default: U, func: F) -> U { + self.some().map_or(default, func) } - /// Applies a function to the contained value (if any), or computes a default (if not). + /// Applies the provided `func`tion to the contained non-`null` value (if + /// any), or computes the provided `default` one (if not). #[inline] - pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { - self.some().map_or_else(default, f) + pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, func: F) -> U { + self.some().map_or_else(default, func) } - /// Transforms the `Nullable` into a `Result`, mapping `Some(v)` to `Ok(v)` and - /// `ImplicitNull` or `ExplicitNull` to `Err(err)`. + /// Transforms this `Nullable` into a `Result`, mapping `Some(v)` + /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err)`. + /// + /// [`ExplicitNull`]: Nullable::ExplicitNull + /// [`ImplicitNull`]: Nullable::ImplicitNull #[inline] pub fn ok_or(self, err: E) -> Result { self.some().ok_or(err) } - /// Transforms the `Nullable` into a `Result`, mapping `Some(v)` to `Ok(v)` and - /// `ImplicitNull` or `ExplicitNull` to `Err(err())`. + /// Transforms this `Nullable` into a `Result`, mapping `Some(v)` + /// to `Ok(v)` and [`ImplicitNull`] or [`ExplicitNull`] to `Err(err())`. + /// + /// [`ExplicitNull`]: Nullable::ExplicitNull + /// [`ImplicitNull`]: Nullable::ImplicitNull #[inline] pub fn ok_or_else E>(self, err: F) -> Result { self.some().ok_or_else(err) } - /// Returns the nullable if it contains a value, otherwise returns `b`. + /// Returns this [`Nullable`] if it contains a non-`null` value, otherwise + /// returns the specified `b` [`Nullable`] value. #[inline] #[must_use] pub fn or(self, b: Self) -> Self { @@ -153,35 +197,43 @@ impl Nullable { } } - /// Returns the nullable if it contains a value, otherwise calls `f` and - /// returns the result. + /// Returns this [`Nullable`] if it contains a non-`null` value, otherwise + /// computes a [`Nullable`] value from the specified `func`tion. #[inline] #[must_use] - pub fn or_else Nullable>(self, f: F) -> Nullable { + pub fn or_else Nullable>(self, func: F) -> Nullable { match self { Self::Some(_) => self, - _ => f(), + _ => func(), } } - /// Replaces the actual value in the nullable by the value given in parameter, returning the - /// old value if present, leaving a `Some` in its place without deinitializing either one. + /// Replaces the contained non-`null` value in this [`Nullable`] by the + /// provided `value`, returning the old one if present, leaving a [`Some`] + /// in its place without deinitializing either one. + /// + /// [`Some`]: Nullable::Some #[inline] #[must_use] pub fn replace(&mut self, value: T) -> Self { - std::mem::replace(self, Self::Some(value)) + mem::replace(self, Self::Some(value)) } - /// Converts from `Nullable` to `Option`. + /// Converts this [`Nullable`] to [Option]. + #[inline] pub fn some(self) -> Option { match self { Self::Some(v) => Some(v), - _ => None, + Self::ExplicitNull | Self::ImplicitNull => None, } } - /// Converts from `Nullable` to `Option>`, mapping `Some(v)` to `Some(Some(v))`, - /// `ExplicitNull` to `Some(None)`, and `ImplicitNull` to `None`. + /// Converts this [`Nullable`] to `Option>`, mapping `Some(v)` to + /// `Some(Some(v))`, [`ExplicitNull`] to `Some(None)`, and [`ImplicitNull`] + /// to [`None`]. + /// + /// [`ExplicitNull`]: Nullable::ExplicitNull + /// [`ImplicitNull`]: Nullable::ImplicitNull pub fn explicit(self) -> Option> { match self { Self::Some(v) => Some(Some(v)), @@ -192,33 +244,188 @@ impl Nullable { } impl Nullable<&T> { - /// Maps a `Nullable<&T>` to a `Nullable` by copying the contents of the nullable. + /// Maps this `Nullable<&T>` to a `Nullable` by [`Copy`]ing the contents + /// of this [`Nullable`]. pub fn copied(self) -> Nullable { - self.map(|&t| t) + self.map(|t| *t) } } impl Nullable<&mut T> { - /// Maps a `Nullable<&mut T>` to a `Nullable` by copying the contents of the nullable. + /// Maps this `Nullable<&mut T>` to a `Nullable` by [`Copy`]ing the + /// contents of this [`Nullable`]. pub fn copied(self) -> Nullable { - self.map(|&mut t| t) + self.map(|t| *t) } } impl Nullable<&T> { - /// Maps a `Nullable<&T>` to a `Nullable` by cloning the contents of the nullable. + /// Maps this `Nullable<&T>` to a `Nullable` by [`Clone`]ing the contents + /// of this [`Nullable`]. pub fn cloned(self) -> Nullable { - self.map(|t| t.clone()) + self.map(T::clone) } } impl Nullable<&mut T> { - /// Maps a `Nullable<&mut T>` to a `Nullable` by cloning the contents of the nullable. + /// Maps this `Nullable<&mut T>` to a `Nullable` by [`Clone`]ing the + /// contents of this [`Nullable`]. pub fn cloned(self) -> Nullable { self.map(|t| t.clone()) } } +impl resolve::Type for Nullable +where + T: resolve::Type, + 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::, _>(type_info) + } +} + +impl resolve::Value for Nullable +where + T: resolve::Value, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + match self { + Self::Some(v) => v.resolve_value(selection_set, type_info, executor), + Self::ImplicitNull | Self::ExplicitNull => Ok(graphql::Value::Null), + } + } +} + +impl resolve::ValueAsync for Nullable +where + T: resolve::ValueAsync, + 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, + ) -> BoxFuture<'r, ExecutionResult> { + 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 resolve::Resolvable for Nullable +where + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ToInputValue for Nullable +where + T: resolve::ToInputValue, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + 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 +where + T: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = T::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result { + if v.is_null() { + Ok(Self::ExplicitNull) + } else { + T::try_from_input_value(v).map(Self::Some) + } + } + + fn try_from_implicit_null() -> Result { + Ok(Self::ImplicitNull) + } +} + +impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Nullable +where + T: graphql::InputType<'i, TI, SV, BH>, + TI: ?Sized, + SV: 'i, + BH: ?Sized, +{ + fn assert_input_type() { + T::assert_input_type() + } +} + +impl graphql::OutputType for Nullable +where + T: graphql::OutputType, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: resolve::ValueAsync, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl reflect::BaseType for Nullable +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Nullable +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Nullable +where + T: reflect::WrappedType, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = reflect::wrap::nullable(T::VALUE); +} + +//////////////////////////////////////////////////////////////////////////////// + impl GraphQLType for Nullable where T: GraphQLType, @@ -256,7 +463,7 @@ where ) -> ExecutionResult { match *self { Self::Some(ref obj) => executor.resolve(info, obj), - _ => Ok(Value::null()), + _ => Ok(graphql::Value::null()), } } } @@ -273,11 +480,11 @@ where info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, - ) -> crate::BoxFuture<'a, ExecutionResult> { + ) -> BoxFuture<'a, ExecutionResult> { let f = async move { let value = match self { Self::Some(obj) => executor.resolve_into_value_async(info, obj).await, - _ => Value::null(), + _ => graphql::Value::null(), }; Ok(value) }; diff --git a/juniper/src/types/option.rs b/juniper/src/types/option.rs new file mode 100644 index 000000000..a999802d9 --- /dev/null +++ b/juniper/src/types/option.rs @@ -0,0 +1,175 @@ +//! GraphQL implementation for [`Option`]. + +use futures::future; + +use crate::{ + behavior, + executor::{ExecutionResult, Executor, Registry}, + graphql, reflect, resolve, + schema::meta::MetaType, + BoxFuture, FieldResult, Selection, +}; + +impl resolve::Type for Option +where + T: resolve::Type, + 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::, _>(type_info) + } +} + +impl resolve::Value for Option +where + T: resolve::Value, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + match self { + Some(v) => v.resolve_value(selection_set, type_info, executor), + None => Ok(graphql::Value::Null), + } + } +} + +impl resolve::ValueAsync for Option +where + T: resolve::ValueAsync, + 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, + ) -> BoxFuture<'r, ExecutionResult> { + match self { + Some(v) => v.resolve_value_async(selection_set, type_info, executor), + None => Box::pin(future::ok(graphql::Value::Null)), + } + } +} + +impl resolve::Resolvable for Option +where + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ToInputValue for Option +where + T: resolve::ToInputValue, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + match self { + Some(v) => v.to_input_value(), + None => graphql::InputValue::Null, + } + } +} + +impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Option +where + T: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = T::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result { + 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 +where + T: graphql::InputType<'i, TI, SV, BH>, + TI: ?Sized, + SV: 'i, + BH: ?Sized, +{ + fn assert_input_type() { + T::assert_input_type() + } +} + +impl graphql::OutputType for Option +where + T: graphql::OutputType, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: resolve::ValueAsync, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl reflect::BaseType for Option +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Option +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Option +where + T: reflect::WrappedType, + 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!(>::try_from_input_value(&v), Ok(None)); + } + + #[test] + fn from_value() { + let v: V = graphql::input_value!(1); + assert_eq!(>::try_from_input_value(&v), Ok(Some(1))); + } +} diff --git a/juniper/src/types/rc.rs b/juniper/src/types/rc.rs new file mode 100644 index 000000000..eddffd74f --- /dev/null +++ b/juniper/src/types/rc.rs @@ -0,0 +1,305 @@ +//! GraphQL implementation for [`Rc`]. + +use std::rc::Rc; + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl resolve::Type for Rc +where + T: resolve::Type + ?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 resolve::TypeName for Rc +where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl resolve::ConcreteTypeName for Rc +where + T: resolve::ConcreteTypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn concrete_type_name<'i>(&self, type_info: &'i TI) -> &'i str { + (**self).concrete_type_name(type_info) + } +} + +impl resolve::Value for Rc +where + T: resolve::Value + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for Rc +where + T: resolve::ValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl resolve::Resolvable for Rc +where + T: ?Sized, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ConcreteValue for Rc +where + T: resolve::ConcreteValue + ?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, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl resolve::ConcreteValueAsync for Rc +where + T: resolve::ConcreteValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl resolve::Field for Rc +where + T: resolve::Field + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl resolve::FieldAsync for Rc +where + T: resolve::FieldAsync + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl resolve::ToInputValue for Rc +where + T: resolve::ToInputValue + ?Sized, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**self).to_input_value() + } +} + +impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Rc +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) -> Result { + T::try_from_input_value(v) + } + + fn try_from_implicit_null() -> Result { + T::try_from_implicit_null() + } +} + +impl<'i, T, SV, BH> resolve::InputValueAs<'i, Rc, 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) -> Result, Self::Error> { + T::try_from_input_value(v).map(Rc::new) + } + + fn try_from_implicit_null() -> Result, Self::Error> { + T::try_from_implicit_null().map(Rc::new) + } +} + +impl resolve::ScalarToken for Rc +where + T: resolve::ScalarToken + ?Sized, + BH: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + T::parse_scalar_token(token) + } +} + +impl<'i, T, TI, SV, BH> graphql::InputType<'i, TI, SV, BH> for Rc +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, 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 graphql::OutputType for Rc +where + T: graphql::OutputType + ?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 +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, 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 reflect::BaseType for Rc +where + T: reflect::BaseType + ?Sized, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Rc +where + T: reflect::BaseSubTypes + ?Sized, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Rc +where + T: reflect::WrappedType + ?Sized, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/ref.rs b/juniper/src/types/ref.rs new file mode 100644 index 000000000..def629f0b --- /dev/null +++ b/juniper/src/types/ref.rs @@ -0,0 +1,284 @@ +//! GraphQL implementation for [reference]. +//! +//! [reference]: primitive@std::reference + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl<'me, T, TI, SV, BH> resolve::Type for &'me T +where + T: resolve::Type + ?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 for &'me T +where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl<'me, T, TI, BH> resolve::ConcreteTypeName for &'me T +where + T: resolve::ConcreteTypeName + ?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 for &'me T +where + T: resolve::Value + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync for &'me T +where + T: resolve::ValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::Resolvable for &'me T +where + T: ?Sized, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue for &'me T +where + T: resolve::ConcreteValue + ?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, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync for &'me T +where + T: resolve::ConcreteValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::Field for &'me T +where + T: resolve::Field + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync for &'me T +where + T: resolve::FieldAsync + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::ToInputValue for &'me T +where + T: resolve::ToInputValue + ?Sized, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**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) -> Result { + T::try_from_input_value(v) + } + + fn try_from_implicit_null() -> Result { + 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 + ?Sized, + SV: 'i, + BH: ?Sized, +{ + type Error = T::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> 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 for &'me T +where + T: resolve::ScalarToken + ?Sized, + BH: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + 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 for &'me T +where + T: graphql::OutputType + ?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 for &'me T +where + T: reflect::BaseType + ?Sized, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl<'me, T, BH> reflect::BaseSubTypes for &'me T +where + T: reflect::BaseSubTypes + ?Sized, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl<'me, T, BH> reflect::WrappedType for &'me T +where + T: reflect::WrappedType + ?Sized, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/ref_mut.rs b/juniper/src/types/ref_mut.rs new file mode 100644 index 000000000..d289e8d82 --- /dev/null +++ b/juniper/src/types/ref_mut.rs @@ -0,0 +1,221 @@ +//! GraphQL implementation for mutable [reference]. +//! +//! [reference]: primitive@std::reference + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, Registry, + Selection, +}; + +impl<'me, T, TI, SV, BH> resolve::Type for &'me mut T +where + T: resolve::Type + ?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 for &'me mut T +where + T: resolve::TypeName + ?Sized, + TI: ?Sized, + BH: ?Sized, +{ + fn type_name(type_info: &TI) -> &str { + T::type_name(type_info) + } +} + +impl<'me, T, TI, BH> resolve::ConcreteTypeName for &'me mut T +where + T: resolve::ConcreteTypeName + ?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 for &'me mut T +where + T: resolve::Value + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_value(selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ValueAsync for &'me mut T +where + T: resolve::ValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_value_async(selection_set, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::Resolvable for &'me mut T +where + T: ?Sized, + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValue for &'me mut T +where + T: resolve::ConcreteValue + ?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, + ) -> ExecutionResult { + (**self).resolve_concrete_value(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::ConcreteValueAsync for &'me mut T +where + T: resolve::ConcreteValueAsync + ?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, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_concrete_value_async(type_name, selection_set, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::Field for &'me mut T +where + T: resolve::Field + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field( + &self, + field_name: &str, + arguments: &Arguments, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + (**self).resolve_field(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, TI, CX, SV, BH> resolve::FieldAsync for &'me mut T +where + T: resolve::FieldAsync + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_field_async<'r>( + &'r self, + field_name: &'r str, + arguments: &'r Arguments, + type_info: &'r TI, + executor: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + (**self).resolve_field_async(field_name, arguments, type_info, executor) + } +} + +impl<'me, T, SV, BH> resolve::ToInputValue for &'me mut T +where + T: resolve::ToInputValue + ?Sized, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + (**self).to_input_value() + } +} + +impl<'me, T, SV, BH> resolve::ScalarToken for &'me mut T +where + T: resolve::ScalarToken + ?Sized, + BH: ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + T::parse_scalar_token(token) + } +} + +impl<'me, T, TI, CX, SV, BH> graphql::OutputType for &'me mut T +where + T: graphql::OutputType + ?Sized, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl<'me, T, BH> reflect::BaseType for &'me mut T +where + T: reflect::BaseType + ?Sized, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl<'me, T, BH> reflect::BaseSubTypes for &'me mut T +where + T: reflect::BaseSubTypes + ?Sized, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl<'me, T, BH> reflect::WrappedType for &'me mut T +where + T: reflect::WrappedType + ?Sized, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/result.rs b/juniper/src/types/result.rs new file mode 100644 index 000000000..ae47d440b --- /dev/null +++ b/juniper/src/types/result.rs @@ -0,0 +1,39 @@ +//! GraphQL implementation for [`Result`]. + +use crate::{reflect, resolve, FieldResult, IntoFieldError}; + +impl resolve::Resolvable for Result +where + E: IntoFieldError, + BH: ?Sized, +{ + type Value = T; + + fn into_value(self) -> FieldResult { + self.map_err(IntoFieldError::into_field_error) + } +} + +impl reflect::BaseType for Result +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Result +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Result +where + T: reflect::WrappedType, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} diff --git a/juniper/src/types/slice.rs b/juniper/src/types/slice.rs new file mode 100644 index 000000000..6a3f0124b --- /dev/null +++ b/juniper/src/types/slice.rs @@ -0,0 +1,235 @@ +//! GraphQL implementation for [slice]. +//! +//! [slice]: prim@slice + +use std::{borrow::Cow, rc::Rc, sync::Arc}; + +use crate::{ + behavior, + executor::{ExecutionResult, Executor, Registry}, + graphql, reflect, resolve, + schema::meta::MetaType, + BoxFuture, Selection, +}; + +use super::{iter, vec::TryFromInputValueError}; + +impl resolve::Type for [T] +where + T: resolve::Type, + 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::, _>(type_info, None) + } +} + +impl resolve::Value for [T] +where + T: resolve::Value, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + iter::resolve_list(self.iter(), selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for [T] +where + T: resolve::ValueAsync + 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, + ) -> BoxFuture<'r, ExecutionResult> { + Box::pin(iter::resolve_list_async( + self.iter(), + selection_set, + type_info, + executor, + )) + } +} + +impl resolve::ToInputValue for [T] +where + T: resolve::ToInputValue, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + 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: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, + Self: ToOwned, +{ + type Error = as resolve::InputValue<'i, SV, BH>>::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + 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, SV, BH> for [T] +where + Vec: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = as resolve::InputValue<'i, SV, BH>>::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + 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, SV, BH> for [T] +where + T: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = TryFromInputValueError; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + // We don't want to reuse `Vec` implementation in the same way we do + // for `Box<[T]>`, because `impl From> 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, SV, BH> for [T] +where + T: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = TryFromInputValueError; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + // We don't want to reuse `Vec` implementation in the same way we do + // for `Box<[T]>`, because `impl From> 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, 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, 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, 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 graphql::OutputType for [T] +where + T: graphql::OutputType, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: resolve::ValueAsync, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl reflect::BaseType for [T] +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for [T] +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for [T] +where + T: reflect::WrappedType, + BH: ?Sized, +{ + const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE); +} diff --git a/juniper/src/types/str.rs b/juniper/src/types/str.rs new file mode 100644 index 000000000..ebcc61495 --- /dev/null +++ b/juniper/src/types/str.rs @@ -0,0 +1,273 @@ +//! GraphQL implementation for [`str`]. +//! +//! [`str`]: primitive@std::str + +use std::{borrow::Cow, rc::Rc, sync::Arc}; + +use futures::future; + +use crate::{ + graphql, + meta::MetaType, + parser::{ParseError, ScalarToken}, + reflect, resolve, BoxFuture, ExecutionResult, Executor, Registry, ScalarValue, Selection, +}; + +impl resolve::Type 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::(type_info) + } +} + +impl resolve::TypeName for str { + fn type_name(_: &TI) -> &'static str { + ::NAME + } +} + +impl resolve::Value for str +where + TI: ?Sized, + CX: ?Sized, + SV: From, +{ + fn resolve_value( + &self, + _: Option<&[Selection<'_, SV>]>, + _: &TI, + _: &Executor, + ) -> ExecutionResult { + // TODO: Remove redundant `.to_owned()` allocation by allowing + // `ScalarValue` creation from reference? + Ok(graphql::Value::scalar(self.to_owned())) + } +} + +impl resolve::ValueAsync for str +where + TI: ?Sized, + CX: ?Sized, + SV: From + Send, +{ + fn resolve_value_async<'r>( + &'r self, + _: Option<&'r [Selection<'_, SV>]>, + _: &'r TI, + _: &'r Executor, + ) -> BoxFuture<'r, ExecutionResult> { + // TODO: Remove redundant `.to_owned()` allocation by allowing + // `ScalarValue` creation from reference? + Box::pin(future::ok(graphql::Value::scalar(self.to_owned()))) + } +} + +impl resolve::ToInputValue for str +where + SV: From, +{ + fn to_input_value(&self) -> graphql::InputValue { + // TODO: Remove redundant `.to_owned()` allocation by allowing + // `ScalarValue` creation from reference? + graphql::InputValue::scalar(self.to_owned()) + } +} + +impl resolve::InputValueAsRef for str { + type Error = String; + + fn try_from_input_value(v: &graphql::InputValue) -> 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, +{ + type Error = >::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + >::try_from_input_value(v).map(Cow::Borrowed) + } +} + +impl<'i, SV> resolve::InputValueAs<'i, Box, SV> for str +where + SV: 'i, + Self: resolve::InputValueAsRef, +{ + type Error = >::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + >::try_from_input_value(v).map(Into::into) + } +} + +impl<'i, SV> resolve::InputValueAs<'i, Rc, SV> for str +where + SV: 'i, + Self: resolve::InputValueAsRef, +{ + type Error = >::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + >::try_from_input_value(v).map(Into::into) + } +} + +impl<'i, SV> resolve::InputValueAs<'i, Arc, SV> for str +where + SV: 'i, + Self: resolve::InputValueAsRef, +{ + type Error = >::Error; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result, Self::Error> { + >::try_from_input_value(v).map(Into::into) + } +} + +impl resolve::ScalarToken for str +where + String: resolve::ScalarToken, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result { + >::parse_scalar_token(token) + } +} + +impl<'me, 'i, TI, SV> graphql::InputTypeAs<'i, &'me Self, TI, SV> for str +where + Self: graphql::Type + + resolve::ToInputValue + + 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 + + resolve::ToInputValue + + resolve::InputValueAs<'i, Cow<'me, Self>, SV>, + TI: ?Sized, + SV: 'i, +{ + fn assert_input_type() {} +} + +impl<'i, TI, SV> graphql::InputTypeAs<'i, Box, TI, SV> for str +where + Self: graphql::Type + + resolve::ToInputValue + + resolve::InputValueAs<'i, Box, SV>, + TI: ?Sized, + SV: 'i, +{ + fn assert_input_type() {} +} + +impl<'i, TI, SV> graphql::InputTypeAs<'i, Rc, TI, SV> for str +where + Self: + graphql::Type + resolve::ToInputValue + resolve::InputValueAs<'i, Rc, SV>, + TI: ?Sized, + SV: 'i, +{ + fn assert_input_type() {} +} + +impl<'i, TI, SV> graphql::InputTypeAs<'i, Arc, TI, SV> for str +where + Self: graphql::Type + + resolve::ToInputValue + + resolve::InputValueAs<'i, Arc, SV>, + TI: ?Sized, + SV: 'i, +{ + fn assert_input_type() {} +} + +impl graphql::OutputType for str +where + Self: graphql::Type + resolve::Value + resolve::ValueAsync, + 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 + + resolve::ScalarToken, + 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 + + resolve::ScalarToken, + TI: ?Sized, + SV: 'i, +{ + fn assert_scalar() {} +} + +impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Box, TI, CX, SV> for str +where + Self: graphql::InputTypeAs<'i, Box, TI, SV> + + graphql::OutputType + + resolve::ScalarToken, + TI: ?Sized, + SV: 'i, +{ + fn assert_scalar() {} +} + +impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Rc, TI, CX, SV> for str +where + Self: graphql::InputTypeAs<'i, Rc, TI, SV> + + graphql::OutputType + + resolve::ScalarToken, + TI: ?Sized, + SV: 'i, +{ + fn assert_scalar() {} +} + +impl<'i, TI, CX, SV> graphql::ScalarAs<'i, Arc, TI, CX, SV> for str +where + Self: graphql::InputTypeAs<'i, Arc, TI, SV> + + graphql::OutputType + + resolve::ScalarToken, + TI: ?Sized, + SV: 'i, +{ + fn assert_scalar() {} +} + +impl reflect::BaseType for str { + const NAME: reflect::Type = ::NAME; +} + +impl reflect::BaseSubTypes for str { + const NAMES: reflect::Types = &[::NAME]; +} + +impl reflect::WrappedType for str { + const VALUE: reflect::WrappedValue = reflect::wrap::SINGULAR; +} diff --git a/juniper/src/types/vec.rs b/juniper/src/types/vec.rs new file mode 100644 index 000000000..cf01b0301 --- /dev/null +++ b/juniper/src/types/vec.rs @@ -0,0 +1,313 @@ +//! GraphQL implementation for [`Vec`]. + +use crate::{ + behavior, + executor::{ExecutionResult, Executor, Registry}, + graphql, reflect, resolve, + schema::meta::MetaType, + BoxFuture, FieldError, FieldResult, IntoFieldError, Selection, +}; + +use super::iter; + +impl resolve::Type for Vec +where + T: resolve::Type, + 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::, _>(type_info, None) + } +} + +impl resolve::Value for Vec +where + T: resolve::Value, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, +{ + fn resolve_value( + &self, + selection_set: Option<&[Selection<'_, SV>]>, + type_info: &TI, + executor: &Executor, + ) -> ExecutionResult { + iter::resolve_list(self.iter(), selection_set, type_info, executor) + } +} + +impl resolve::ValueAsync for Vec +where + T: resolve::ValueAsync + 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, + ) -> BoxFuture<'r, ExecutionResult> { + Box::pin(iter::resolve_list_async( + self.iter(), + selection_set, + type_info, + executor, + )) + } +} + +impl resolve::Resolvable for Vec +where + BH: ?Sized, +{ + type Value = Self; + + fn into_value(self) -> FieldResult { + Ok(self) + } +} + +impl resolve::ToInputValue for Vec +where + T: resolve::ToInputValue, + BH: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + graphql::InputValue::list(self.iter().map(T::to_input_value)) + } +} + +impl<'i, T, SV, BH> resolve::InputValue<'i, SV, BH> for Vec +where + T: resolve::InputValue<'i, SV, BH>, + SV: 'i, + BH: ?Sized, +{ + type Error = TryFromInputValueError; + + fn try_from_input_value(v: &'i graphql::InputValue) -> Result { + 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 +where + T: graphql::InputType<'i, TI, SV, BH>, + TI: ?Sized, + SV: 'i, + BH: ?Sized, +{ + fn assert_input_type() { + T::assert_input_type() + } +} + +impl graphql::OutputType for Vec +where + T: graphql::OutputType, + TI: ?Sized, + CX: ?Sized, + BH: ?Sized, + Self: resolve::ValueAsync, +{ + fn assert_output_type() { + T::assert_output_type() + } +} + +impl reflect::BaseType for Vec +where + T: reflect::BaseType, + BH: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl reflect::BaseSubTypes for Vec +where + T: reflect::BaseSubTypes, + BH: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl reflect::WrappedType for Vec +where + T: reflect::WrappedType, + 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 { + /// [`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 IntoFieldError for TryFromInputValueError +where + E: IntoFieldError, +{ + fn into_field_error(self) -> FieldError { + 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!( + >::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!( + >>::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!(>>::try_from_input_value(&v), Ok(None)); + assert_eq!( + >>>::try_from_input_value(&v), + Ok(None), + ); + assert_eq!( + >>::try_from_input_value(&v), + Err(TryFromInputValueError::IsNull), + ); + assert_eq!( + >>>>>::try_from_input_value(&v), + Ok(None), + ); + } + + #[test] + fn from_value() { + let v: V = graphql::input_value!(1); + assert_eq!(>::try_from_input_value(&v), Ok(vec![1])); + assert_eq!( + >>::try_from_input_value(&v), + Ok(vec![Some(1)]), + ); + assert_eq!( + >>::try_from_input_value(&v), + Ok(Some(vec![1])), + ); + assert_eq!( + >>>::try_from_input_value(&v), + Ok(Some(vec![Some(1)])), + ); + assert_eq!(>>::try_from_input_value(&v), Ok(vec![vec![1]])); + assert_eq!( + >>>>>::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!(>::try_from_input_value(&v), Ok(vec![1, 2, 3])); + assert_eq!( + >>::try_from_input_value(&v), + Ok(Some(vec![1, 2, 3])), + ); + assert_eq!( + >>::try_from_input_value(&v), + Ok(vec![Some(1), Some(2), Some(3)]), + ); + assert_eq!( + >>>::try_from_input_value(&v), + Ok(Some(vec![Some(1), Some(2), Some(3)])), + ); + assert_eq!( + >>::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!( + >>>>>::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!( + >::try_from_input_value(&v), + Err(TryFromInputValueError::Item( + "Expected `Int`, found: null".into_field_error(), + )), + ); + assert_eq!( + >>::try_from_input_value(&v), + Err(TryFromInputValueError::Item( + "Expected `Int`, found: null".into_field_error(), + )), + ); + assert_eq!( + >>::try_from_input_value(&v), + Ok(vec![Some(1), Some(2), None]), + ); + assert_eq!( + >>>::try_from_input_value(&v), + Ok(Some(vec![Some(1), Some(2), None])), + ); + assert_eq!( + >>::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!( + >>>>>::try_from_input_value(&v), + Ok(Some(vec![Some(vec![Some(1)]), Some(vec![Some(2)]), None])), + ); + } +} diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 6a160807d..b4798e90a 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -6,6 +6,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, + resolve, }; pub use self::{ @@ -190,6 +191,27 @@ impl ToInputValue for Value { } } +impl resolve::ToInputValue for Value { + fn to_input_value(&self) -> InputValue { + // 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(>::to_input_value), + ), + Self::Object(o) => InputValue::object(o.iter().map(|(k, v)| { + ( + k.clone(), + >::to_input_value(v), + ) + })), + } + } +} + impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index b22916581..9d696a7a1 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -31,5 +31,5 @@ url = "2.0" [dev-dependencies] derive_more = "0.99.7" futures = "0.3.22" -juniper = { path = "../juniper" } +#juniper = { path = "../juniper" } serde = "1.0" diff --git a/juniper_codegen/src/common/behavior.rs b/juniper_codegen/src/common/behavior.rs new file mode 100644 index 000000000..618ae5bc0 --- /dev/null +++ b/juniper_codegen/src/common/behavior.rs @@ -0,0 +1,60 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! related to [`Behaviour`] type parameter. +//! +//! [`Behaviour`]: juniper::behavior + +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, +}; + +use crate::common::SpanContainer; + +/// [`Behaviour`] parametrization of the code generation. +/// +/// [`Behaviour`]: juniper::behavior +#[derive(Clone, Debug, Default)] +pub(crate) enum Type { + /// [`behavior::Standard`] should be used in the generated code. + /// + /// [`behavior::Standard`]: juniper::behavior::Standard + #[default] + Standard, + + /// Concrete custom Rust type should be used as [`Behaviour`] in the + /// generated code. + /// + /// [`Behaviour`]: juniper::behavior + Custom(syn::Type), +} + +impl Parse for Type { + fn parse(input: ParseStream<'_>) -> syn::Result { + input.parse::().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>> for Type { + fn from(attr: Option>) -> Self { + attr.map(SpanContainer::into_inner).unwrap_or_default() + } +} diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index afbe53ed8..06bedfa02 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - default, diagnostic, filter_attrs, + behavior, default, diagnostic, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, TypeExt as _, @@ -52,6 +52,19 @@ pub(crate) struct Attr { /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments pub(crate) default: Option>, + /// 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>, + /// Explicitly specified marker indicating that this method argument doesn't /// represent a [GraphQL argument][1], but is a [`Context`] being injected /// into a [GraphQL field][2] resolving function. @@ -103,6 +116,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "ctx" | "context" | "Context" => { let span = ident.span(); out.context @@ -133,6 +153,7 @@ impl Attr { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), default: try_merge_opt!(default: self, another), + behavior: try_merge_opt!(behavior: self, another), context: try_merge_opt!(context: self, another), executor: try_merge_opt!(executor: self, another), }) @@ -229,6 +250,14 @@ pub(crate) struct OnField { /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments pub(crate) default: Option, + + /// [`Behavior`] parametrization of this [GraphQL field argument][1] + /// implementation to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [coerce]: juniper::behavior::Coerce + pub(crate) behavior: behavior::Type, } /// Possible kinds of Rust method arguments for code generation. @@ -433,6 +462,7 @@ impl OnMethod { ty: argument.ty.as_ref().clone(), description: attr.description.map(SpanContainer::into_inner), default: attr.default.map(SpanContainer::into_inner), + behavior: attr.behavior.into(), }))) } } diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 8e8cd2973..24751fab6 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - deprecation, filter_attrs, + behavior, deprecation, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -56,6 +56,19 @@ pub(crate) struct Attr { /// [2]: https://spec.graphql.org/October2021#sec-Deprecation pub(crate) deprecated: Option>, + /// 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>, + /// Explicitly specified marker indicating that this method (or struct /// field) should be omitted by code generation and not considered as the /// [GraphQL field][1] definition. @@ -94,6 +107,13 @@ impl Parse for Attr { )) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) @@ -116,6 +136,7 @@ impl Attr { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), + behavior: try_merge_opt!(behavior: self, another), ignore: try_merge_opt!(ignore: self, another), }) } @@ -184,6 +205,14 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ident: syn::Ident, + /// [`Behavior`] parametrization of this [GraphQL field][1] implementation + /// to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [coerce]: juniper::behavior::Coerce + pub(crate) behavior: behavior::Type, + /// Rust [`MethodArgument`]s required to call the method representing this /// [GraphQL field][1]. /// diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs index a0b5e7315..846ed25e1 100644 --- a/juniper_codegen/src/common/gen.rs +++ b/juniper_codegen/src/common/gen.rs @@ -1,7 +1,81 @@ //! Common code generated parts, used by this crate. use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; +use syn::parse_quote; + +use crate::common::behavior; + +/// Returns generated code implementing [`resolve::Resolvable`] trait for the +/// provided [`syn::Type`] with its [`syn::Generics`]. +/// +/// [`resolve::Resolvable`]: juniper::resolve::Resolvable +/// [0]: https://spec.graphql.org/October2021#sec-Interfaces +pub(crate) fn impl_resolvable( + bh: &behavior::Type, + (ty, generics): (syn::Type, syn::Generics), +) -> TokenStream { + let (sv, generics) = mix_scalar_value(generics); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Resolvable<#sv, #bh> + for #ty #where_clause + { + type Value = Self; + + fn into_value(self) -> ::juniper::FieldResult { + ::juniper::FieldResult::Ok(self) + } + } + } +} + +/// Mixes a type info [`syn::GenericParam`] into the provided [`syn::Generics`] +/// and returns its [`syn::Ident`]. +#[must_use] +pub(crate) fn mix_type_info(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let ty = parse_quote! { __TypeInfo }; + generics.params.push(parse_quote! { #ty: ?Sized }); + (ty, generics) +} + +/// Mixes a context [`syn::GenericParam`] into the provided [`syn::Generics`] +/// and returns its [`syn::Ident`]. +pub(crate) fn mix_context(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let ty = parse_quote! { __Context }; + generics.params.push(parse_quote! { #ty: ?Sized }); + (ty, generics) +} + +/// Mixes a [`ScalarValue`] [`syn::GenericParam`] into the provided +/// [`syn::Generics`] and returns it. +/// +/// [`ScalarValue`]: juniper::ScalarValue +pub(crate) fn mix_scalar_value(mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let sv = parse_quote! { __ScalarValue }; + generics.params.push(parse_quote! { #sv }); + (sv, generics) +} + +/// Mixes an [`InputValue`]'s lifetime [`syn::GenericParam`] into the provided +/// [`syn::Generics`] and returns it. +/// +/// [`InputValue`]: juniper::resolve::InputValue +#[must_use] +pub(crate) fn mix_input_lifetime( + mut generics: syn::Generics, + sv: impl ToTokens, +) -> (syn::GenericParam, syn::Generics) { + let lt: syn::GenericParam = parse_quote! { '__inp }; + generics.params.push(lt.clone()); + generics + .make_where_clause() + .predicates + .push(parse_quote! { #sv: #lt }); + (lt, generics) +} /// Generate the code resolving some [GraphQL type][1] in a synchronous manner. /// diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index e6e3dd8fa..9c5f4a117 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,5 +1,6 @@ //! Common functions, definitions and extensions for code generation, used by this crate. +pub(crate) mod behavior; pub(crate) mod default; pub(crate) mod deprecation; mod description; diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 53781675e..ba4e87181 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -105,15 +105,37 @@ pub(crate) trait TypeExt { #[must_use] fn unreferenced(&self) -> &Self; - /// Iterates mutably over all the lifetime parameters of this [`syn::Type`] - /// with the given `func`tion. - fn lifetimes_iter_mut(&mut self, func: &mut F); + /// Iterates mutably over all the lifetime parameters (even elided) of this + /// [`syn::Type`] with the given `func`tion. + fn lifetimes_iter_mut)>(&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(&mut self, func: &mut F) { + self.lifetimes_iter_mut(&mut |lt| { + if let MaybeElidedLifetimeMut::Named(lt) = lt { + func(lt); + } + }) + } /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except /// the `'static` ones), making it suitable for using in contexts with /// inferring. fn lifetimes_anonymized(&mut self); + /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except + /// the `'static` ones), making it suitable for using in contexts with + /// inferring, and returns it as a new type, leaving the original one + /// unchanged. + fn to_anonymized_lifetimes(&self) -> syn::Type; + + /// Lifts all the lifetimes of this [`syn::Type`] (even elided, but except + /// `'static`) to a `for<>` quantifier, preserving the type speciality as + /// much as possible, and returns it as a new type, leaving the original one + /// unchanged. + fn to_hrtb_lifetimes(&self) -> (Option, syn::Type); + /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any. #[must_use] fn topmost_ident(&self) -> Option<&syn::Ident>; @@ -135,16 +157,16 @@ impl TypeExt for syn::Type { } } - fn lifetimes_iter_mut(&mut self, func: &mut F) { + fn lifetimes_iter_mut)>(&mut self, func: &mut F) { use syn::{GenericArgument as GA, Type as T}; - fn iter_path(path: &mut syn::Path, func: &mut F) { + fn iter_path)>(path: &mut syn::Path, func: &mut F) { for seg in path.segments.iter_mut() { match &mut seg.arguments { syn::PathArguments::AngleBracketed(angle) => { for arg in angle.args.iter_mut() { match arg { - GA::Lifetime(lt) => func(lt), + GA::Lifetime(lt) => func(lt.into()), GA::Type(ty) => ty.lifetimes_iter_mut(func), GA::Binding(b) => b.ty.lifetimes_iter_mut(func), GA::Constraint(_) | GA::Const(_) => {} @@ -181,7 +203,7 @@ impl TypeExt for syn::Type { | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { for bound in bounds.iter_mut() { match bound { - syn::TypeParamBound::Lifetime(lt) => func(lt), + syn::TypeParamBound::Lifetime(lt) => func(lt.into()), syn::TypeParamBound::Trait(bound) => { if bound.lifetimes.is_some() { todo!("Iterating over HRTB lifetimes in trait is not yet supported") @@ -193,9 +215,7 @@ impl TypeExt for syn::Type { } T::Reference(ref_ty) => { - if let Some(lt) = ref_ty.lifetime.as_mut() { - func(lt) - } + func((&mut ref_ty.lifetime).into()); (*ref_ty.elem).lifetimes_iter_mut(func) } @@ -213,13 +233,48 @@ impl TypeExt for syn::Type { } fn lifetimes_anonymized(&mut self) { - self.lifetimes_iter_mut(&mut |lt| { + self.named_lifetimes_iter_mut(&mut |lt| { if lt.ident != "_" && lt.ident != "static" { lt.ident = syn::Ident::new("_", Span::call_site()); } }); } + fn to_anonymized_lifetimes(&self) -> syn::Type { + let mut ty = self.clone(); + ty.lifetimes_anonymized(); + ty + } + + fn to_hrtb_lifetimes(&self) -> (Option, syn::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 <_mut { + MaybeElidedLifetimeMut::Elided(v) => { + v.as_ref().map(|l| &l.ident).unwrap_or(anon_ident) + } + MaybeElidedLifetimeMut::Named(l) => &l.ident, + }; + if ident != "static" { + let new_lt = syn::Lifetime::new(&format!("'__fa_f_{ident}"), Span::call_site()); + if !lts.contains(&new_lt) { + lts.push(new_lt.clone()); + } + lt_mut.set(new_lt); + } + }); + + let for_ = (!lts.is_empty()).then(|| { + parse_quote! { + for< #( #lts ),* >} + }); + (for_, ty) + } + fn topmost_ident(&self) -> Option<&syn::Ident> { match self.unparenthesized() { syn::Type::Path(p) => Some(&p.path), @@ -239,6 +294,38 @@ impl TypeExt for syn::Type { } } +/// Mutable reference to a place that may containing a [`syn::Lifetime`]. +pub(crate) enum MaybeElidedLifetimeMut<'a> { + /// [`syn::Lifetime`] may be elided. + Elided(&'a mut Option), + + /// [`syn::Lifetime`] 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> for MaybeElidedLifetimeMut<'a> { + fn from(lt: &'a mut Option) -> Self { + Self::Elided(lt) + } +} + +impl<'a> From<&'a mut syn::Lifetime> for MaybeElidedLifetimeMut<'a> { + fn from(lt: &'a mut syn::Lifetime) -> Self { + Self::Named(lt) + } +} + /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing. pub(crate) trait GenericsExt { /// Removes all default types out of type parameters and const parameters in these @@ -354,3 +441,43 @@ impl<'a> VisitMut for ReplaceWithDefaults<'a> { } } } + +#[cfg(test)] +mod test_type_ext_to_hrtb_lifetimes { + use quote::quote; + use syn::parse_quote; + + use super::TypeExt as _; + + #[test] + fn test() { + for (input, expected) in [ + (parse_quote! { &'static T }, quote! { &'static T }), + (parse_quote! { &T }, quote! { for<'__fa_f__>: &'__fa_f__ T }), + ( + parse_quote! { &'a mut T }, + quote! { for<'__fa_f_a>: &'__fa_f_a mut T }, + ), + ( + parse_quote! { &str }, + quote! { for<'__fa_f__>: &'__fa_f__ str }, + ), + ( + parse_quote! { &Cow<'static, str> }, + quote! { for<'__fa_f__>: &'__fa_f__ Cow<'static, str> }, + ), + ( + parse_quote! { &Cow<'a, str> }, + quote! { for<'__fa_f__, '__fa_f_a>: &'__fa_f__ Cow<'__fa_f_a, str> }, + ), + ] { + let (actual_for, actual_ty) = syn::Type::to_hrtb_lifetimes(&input); + let actual_for = actual_for.map(|for_| quote! { #for_: }); + + assert_eq!( + quote! { #actual_for #actual_ty }.to_string(), + expected.to_string(), + ); + } + } +} diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs index 8f11833de..103c3ac07 100644 --- a/juniper_codegen/src/common/scalar.rs +++ b/juniper_codegen/src/common/scalar.rs @@ -63,15 +63,17 @@ pub(crate) enum Type { /// [`ScalarValue`]: juniper::ScalarValue Concrete(syn::Type), - /// One of type parameters of the original type is specified as [`ScalarValue`]. + /// One of type parameters of the original type is specified as + /// [`ScalarValue`]. /// /// The original type is the type that the code is generated for. /// /// [`ScalarValue`]: juniper::ScalarValue ExplicitGeneric(syn::Ident), - /// [`ScalarValue`] parametrization is assumed to be generic and is not specified - /// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`). + /// [`ScalarValue`] parametrization is assumed to be generic and is not + /// specified explicitly, or specified as bound predicate (like + /// `S: ScalarValue + Send + Sync`). /// /// [`ScalarValue`]: juniper::ScalarValue ImplicitGeneric(Option), diff --git a/juniper_codegen/src/graphql_enum/derive.rs b/juniper_codegen/src/graphql_enum/derive.rs index f0564a264..05b727caf 100644 --- a/juniper_codegen/src/graphql_enum/derive.rs +++ b/juniper_codegen/src/graphql_enum/derive.rs @@ -87,6 +87,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result { description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), values, has_ignored_variants, }; diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs index c14356f65..4718cd87d 100644 --- a/juniper_codegen/src/graphql_enum/mod.rs +++ b/juniper_codegen/src/graphql_enum/mod.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - deprecation, filter_attrs, + behavior, deprecation, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -65,6 +65,17 @@ struct ContainerAttr { /// [0]: https://spec.graphql.org/October2021#sec-Enums scalar: Option>, + /// 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>, + /// Explicitly specified [`rename::Policy`] for all [values][1] of this /// [GraphQL enum][0]. /// @@ -118,6 +129,13 @@ impl Parse for ContainerAttr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -151,6 +169,7 @@ impl ContainerAttr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), rename_values: try_merge_opt!(rename_values: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -364,6 +383,13 @@ struct Definition { /// [0]: https://spec.graphql.org/October2021#sec-Enums scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL enum][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Enums + behavior: behavior::Type, + /// [Values][1] of this [GraphQL enum][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums @@ -386,6 +412,18 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_type().to_tokens(into); + self.impl_resolve_type_name().to_tokens(into); + self.impl_resolve_value().to_tokens(into); + self.impl_resolve_value_async().to_tokens(into); + gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into); + self.impl_resolve_to_input_value().to_tokens(into); + self.impl_resolve_input_value().to_tokens(into); + self.impl_graphql_input_type().to_tokens(into); + self.impl_graphql_output_type().to_tokens(into); + self.impl_graphql_enum().to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -416,6 +454,98 @@ impl Definition { } } + /// Returns generated code implementing [`graphql::InputType`] trait for + /// this [GraphQL enum][0]. + /// + /// [`graphql::InputType`]: juniper::graphql::InputType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + #[must_use] + fn impl_graphql_input_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::resolve::Type<#inf, #sv, #bh> + + ::juniper::resolve::ToInputValue<#sv, #bh> + + ::juniper::resolve::InputValue<#lt, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + for #ty #where_clause + { + fn assert_input_type() {} + } + } + } + + /// Returns generated code implementing [`graphql::OutputType`] trait for + /// this [GraphQL enum][0]. + /// + /// [`graphql::OutputType`]: juniper::graphql::OutputType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + #[must_use] + fn impl_graphql_output_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = gen::mix_scalar_value(generics); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::resolve::Type<#inf, #sv, #bh> + + ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + + ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn assert_output_type() {} + } + } + } + + /// Returns generated code implementing [`graphql::Enum`] trait for this + /// [GraphQL enum][0]. + /// + /// [`graphql::Enum`]: juniper::graphql::Enum + /// [0]: https://spec.graphql.org/October2021#sec-Enums + #[must_use] + fn impl_graphql_enum(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + + ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::Enum<#lt, #inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn assert_enum() { + > + ::assert_input_type(); + > + ::assert_output_type(); + } + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL enum][0]. /// @@ -470,6 +600,86 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Type`] trait for this + /// [GraphQL enum][0]. + /// + /// [`resolve::Type`]: juniper::resolve::Type + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_resolve_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, mut generics) = gen::mix_scalar_value(generics); + let preds = &mut generics.make_where_clause().predicates; + preds.push(parse_quote! { #sv: Clone }); + preds.push(parse_quote! { + ::juniper::behavior::Coerce: + ::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, _, + >(&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 { + >::NAME + } + } + } + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL enum][0]. /// @@ -528,6 +738,64 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Value`] trait for this + /// [GraphQL enum][0]. + /// + /// [`resolve::Value`]: juniper::resolve::Value + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_resolve_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = gen::mix_scalar_value(generics); + generics.make_where_clause().predicates.push(parse_quote! { + #sv: From + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let variant_arms = self.values.iter().map(|v| { + let v_ident = &v.ident; + let v_name = &v.name; + + quote! { + Self::#v_ident => ::std::result::Result::Ok( + ::juniper::Value::<#sv>::scalar( + ::std::string::String::from(#v_name), + ), + ), + } + }); + let ignored_arm = self.has_ignored_variants.then(|| { + let err_msg = format!("Cannot resolve ignored variant of `{}` enum", self.ident); + + quote! { + _ => ::std::result::Result::Err( + ::juniper::FieldError::<#sv>::from(#err_msg), + ), + } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value( + &self, + _: Option<&[::juniper::Selection<'_, #sv>]>, + _: &#inf, + _: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + match self { + #( #variant_arms )* + #ignored_arm + } + } + } + } + } + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL enum][0]. /// @@ -559,6 +827,48 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ValueAsync`] trait for + /// this [GraphQL enum][0]. + /// + /// [`resolve::ValueAsync`]: juniper::resolve::ValueAsync + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_resolve_value_async(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = gen::mix_scalar_value(generics); + let preds = &mut generics.make_where_clause().predicates; + preds.push(parse_quote! { + Self: ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + }); + preds.push(parse_quote! { + #sv: Send + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value_async<'__r>( + &'__r self, + sel_set: Option<&'__r [::juniper::Selection<'_, #sv>]>, + type_info: &'__r #inf, + executor: &'__r ::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::BoxFuture< + '__r, ::juniper::ExecutionResult<#sv>, + > { + let v = + > + ::resolve_value(self, sel_set, type_info, executor); + ::std::boxed::Box::pin(::juniper::futures::future::ready(v)) + } + } + } + } + /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL enum][0]. /// @@ -598,6 +908,55 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::InputValue`] trait for + /// this [GraphQL enum][0]. + /// + /// [`resolve::InputValue`]: juniper::resolve::InputValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_resolve_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + #sv: ::juniper::ScalarValue + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let variant_arms = self.values.iter().map(|v| { + let v_ident = &v.ident; + let v_name = &v.name; + + quote! { + ::std::option::Option::Some(#v_name) => + ::std::result::Result::Ok(Self::#v_ident), + } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh> + for #ty #where_clause + { + type Error = ::std::string::String; + + fn try_from_input_value( + input: &#lt ::juniper::graphql::InputValue<#sv>, + ) -> ::std::result::Result { + match input + .as_enum_value() + .or_else(|| input.as_string_value()) + { + #( #variant_arms )* + _ => ::std::result::Result::Err( + ::std::format!("Unknown enum value: {}", input), + ), + } + } + } + } + } + /// Returns generated code implementing [`ToInputValue`] trait for this /// [GraphQL enum][0]. /// @@ -643,6 +1002,53 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ToInputValue`] trait for + /// this [GraphQL enum][0]. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_resolve_to_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = gen::mix_scalar_value(generics); + generics.make_where_clause().predicates.push(parse_quote! { + #sv: From + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let variant_arms = self.values.iter().map(|v| { + let v_ident = &v.ident; + let v_name = &v.name; + + quote! { + Self::#v_ident => ::juniper::graphql::InputValue::<#sv>::scalar( + ::std::string::String::from(#v_name), + ), + } + }); + let ignored_arm = self.has_ignored_variants.then(|| { + let err_msg = format!("Cannot resolve ignored variant of `{}` enum", self.ident); + + quote! { + _ => ::std::panic!(#err_msg), + } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh> + for #ty #where_clause + { + fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> { + match self { + #( #variant_arms )* + #ignored_arm + } + } + } + } + } + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL enum][0]. /// @@ -684,6 +1090,47 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this + /// [GraphQL enum][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + } + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this enum. /// @@ -742,4 +1189,16 @@ impl Definition { generics } + + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let generics = self.generics.clone(); + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + (ty, generics) + } } diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs index 370fc2002..562343ea2 100644 --- a/juniper_codegen/src/graphql_input_object/derive.rs +++ b/juniper_codegen/src/graphql_input_object/derive.rs @@ -80,6 +80,7 @@ pub fn expand(input: TokenStream) -> syn::Result { description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, }; @@ -94,13 +95,13 @@ fn parse_field( renaming: rename::Policy, is_internal: bool, ) -> Option { - let field_attr = FieldAttr::from_attrs("graphql", &f.attrs) + let attr = FieldAttr::from_attrs("graphql", &f.attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?; - let name = field_attr + let name = attr .name .map_or_else( || renaming.apply(&ident.unraw().to_string()), @@ -114,10 +115,11 @@ fn parse_field( Some(FieldDefinition { ident: ident.clone(), ty: f.ty.clone(), - default: field_attr.default.map(SpanContainer::into_inner), + default: attr.default.map(SpanContainer::into_inner), + behavior: attr.behavior.into(), name, - description: field_attr.description.map(SpanContainer::into_inner), - ignored: field_attr.ignore.is_some(), + description: attr.description.map(SpanContainer::into_inner), + ignored: attr.ignore.is_some(), }) } diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs index 93c21d6f8..c339b4346 100644 --- a/juniper_codegen/src/graphql_input_object/mod.rs +++ b/juniper_codegen/src/graphql_input_object/mod.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - default, filter_attrs, + behavior, default, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -66,6 +66,17 @@ struct ContainerAttr { /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: Option>, + /// 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>, + /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL input object][0]. /// @@ -118,6 +129,13 @@ impl Parse for ContainerAttr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -151,6 +169,7 @@ impl ContainerAttr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -185,6 +204,16 @@ struct FieldAttr { /// [1]: https://spec.graphql.org/October2021#InputValueDefinition name: Option>, + /// 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>, + /// Explicitly specified [default value][2] of this /// [GraphQL input object field][1] to be used used in case a field value is /// not provided. @@ -195,15 +224,18 @@ struct FieldAttr { /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option>, - /// Explicitly specified [description][2] of this - /// [GraphQL input object field][1]. + /// Explicitly specified type of the custom [`Behavior`] this + /// [GraphQL input object field][1] implementation is parametrized with, to + /// [coerce] in the generated code from. /// - /// If [`None`], then Rust doc comment will be used as the [description][2], - /// if any. + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard /// [1]: https://spec.graphql.org/October2021#InputValueDefinition - /// [2]: https://spec.graphql.org/October2021#sec-Descriptions - description: Option>, + /// [coerce]: juniper::behavior::Coerce + behavior: Option>, /// Explicitly specified marker for the Rust struct field to be ignored and /// not included into the code generated for a [GraphQL input object][0] @@ -234,17 +266,24 @@ impl Parse for FieldAttr { )) .none_or_else(|_| err::dup_arg(&ident))? } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } "default" => { let val = input.parse::()?; out.default .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } - "desc" | "description" => { + "behave" | "behavior" => { input.parse::()?; - let desc = input.parse::()?; - out.description - .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) .none_or_else(|_| err::dup_arg(&ident))? } "ignore" | "skip" => out @@ -267,8 +306,9 @@ impl FieldAttr { fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), - default: try_merge_opt!(default: self, another), description: try_merge_opt!(description: self, another), + default: try_merge_opt!(default: self, another), + behavior: try_merge_opt!(behavior: self, another), ignore: try_merge_opt!(ignore: self, another), }) } @@ -314,6 +354,14 @@ struct FieldDefinition { /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option, + /// [`Behavior`] parametrization of this [GraphQL input object field][1] + /// implementation to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [coerce]: juniper::behavior::Coerce + behavior: behavior::Type, + /// Name of this [GraphQL input object field][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition @@ -338,6 +386,14 @@ struct FieldDefinition { ignored: bool, } +impl FieldDefinition { + /// Indicates whether this [`FieldDefinition`] uses [`Default::default()`] + /// ans its [`FieldDefinition::default`] value. + fn needs_default_trait_bound(&self) -> bool { + matches!(self.default, Some(default::Value::Default)) + } +} + /// Representation of [GraphQL input object][0] for code generation. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects @@ -383,6 +439,13 @@ struct Definition { /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL input object][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + behavior: behavior::Type, + /// [Fields][1] of this [GraphQL input object][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects @@ -399,6 +462,14 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_type().to_tokens(into); + self.impl_resolve_type_name().to_tokens(into); + self.impl_resolve_to_input_value().to_tokens(into); + self.impl_resolve_input_value().to_tokens(into); + self.impl_graphql_input_type().to_tokens(into); + self.impl_graphql_input_object().to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -440,6 +511,88 @@ impl Definition { } } + /// Returns generated code implementing [`graphql::InputType`] trait for + /// [GraphQL input object][0]. + /// + /// [`graphql::InputType`]: juniper::graphql::InputType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_graphql_input_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::resolve::Type<#inf, #sv, #bh> + + ::juniper::resolve::ToInputValue<#sv, #bh> + + ::juniper::resolve::InputValue<#lt, #sv, #bh> + }); + for f in self.fields.iter().filter(|f| !f.ignored) { + let field_ty = &f.ty; + let field_bh = &f.behavior; + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: + ::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh> + }); + } + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let fields_assertions = self.fields.iter().filter_map(|f| { + (!f.ignored).then(|| { + let field_ty = &f.ty; + let field_bh = &f.behavior; + + quote! { + <#field_ty as + ::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh>> + ::assert_input_type(); + } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + for #ty #where_clause + { + fn assert_input_type() { + #( #fields_assertions )* + } + } + } + } + + /// Returns generated code implementing [`graphql::InputObject`] trait for + /// this [GraphQL input object][0]. + /// + /// [`graphql::InputObject`]: juniper::graphql::InputObject + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_graphql_input_object(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::InputObject<#lt, #inf, #sv, #bh> + for #ty #where_clause + { + fn assert_input_object() { + > + ::assert_input_type(); + } + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL input object][0]. /// @@ -500,6 +653,119 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Type`] trait for this + /// [GraphQL input object][0]. + /// + /// [`resolve::Type`]: juniper::resolve::Type + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_resolve_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, mut generics) = gen::mix_scalar_value(generics); + let preds = &mut generics.make_where_clause().predicates; + preds.push(parse_quote! { #sv: Clone }); + preds.push(parse_quote! { + ::juniper::behavior::Coerce: + ::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, _, + >(&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 { + >::NAME + } + } + } + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL input object][0]. /// @@ -631,6 +897,96 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::InputValue`] trait for + /// this [GraphQL input object][0]. + /// + /// [`resolve::InputValue`]: juniper::resolve::InputValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_resolve_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, generics) = gen::mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, &sv); + generics.make_where_clause().predicates.push(parse_quote! { + #sv: ::juniper::ScalarValue + }); + for f in self.fields.iter().filter(|f| !f.ignored) { + let field_ty = &f.ty; + let field_bh = &f.behavior; + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::resolve::InputValue<#lt, #sv, #field_bh> + }); + } + for f in self.fields.iter().filter(|f| f.needs_default_trait_bound()) { + let field_ty = &f.ty; + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::std::default::Default, + }); + } + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let fields = self.fields.iter().map(|f| { + let field = &f.ident; + let field_ty = &f.ty; + let field_bh = &f.behavior; + + let constructor = if f.ignored { + let expr = f.default.clone().unwrap_or_default(); + + quote! { #expr } + } else { + let name = &f.name; + + let fallback = f.default.as_ref().map_or_else( + || { + quote! { + <#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>> + ::try_from_implicit_null() + .map_err(::juniper::IntoFieldError::<#sv>::into_field_error)? + } + }, + |expr| quote! { #expr }, + ); + + quote! { + match obj.get(#name) { + ::std::option::Option::Some(v) => { + <#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>> + ::try_from_input_value(v) + .map_err(::juniper::IntoFieldError::<#sv>::into_field_error)? + } + ::std::option::Option::None => { #fallback } + } + } + }; + + quote! { #field: { #constructor }, } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh> + for #ty #where_clause + { + type Error = ::juniper::FieldError<#sv>; + + fn try_from_input_value( + input: &#lt ::juniper::graphql::InputValue<#sv>, + ) -> ::std::result::Result { + let obj = input + .to_object_value() + .ok_or_else(|| ::std::format!( + "Expected input object, found: {}", input, + ))?; + + ::std::result::Result::Ok(Self { + #( #fields )* + }) + } + } + } + } + /// Returns generated code implementing [`ToInputValue`] trait for this /// [GraphQL input object][0]. /// @@ -663,11 +1019,52 @@ impl Definition { #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - ::juniper::InputValue::object( - #[allow(deprecated)] - ::std::array::IntoIter::new([#( #fields ),*]) - .collect() - ) + ::juniper::InputValue::object([#( #fields ),*]) + } + } + } + } + + /// Returns generated code implementing [`resolve::ToInputValue`] trait for + /// this [GraphQL input object][0]. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_resolve_to_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = gen::mix_scalar_value(generics); + for f in self.fields.iter().filter(|f| !f.ignored) { + let field_ty = &f.ty; + let field_bh = &f.behavior; + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh> + }); + } + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let fields = self.fields.iter().filter_map(|f| { + (!f.ignored).then(|| { + let field = &f.ident; + let field_ty = &f.ty; + let field_bh = &f.behavior; + let name = &f.name; + + quote! { + (#name, <#field_ty as + ::juniper::resolve::ToInputValue<#sv, #field_bh>> + ::to_input_value(&self.#field)) + } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh> + for #ty #where_clause + { + fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> { + ::juniper::InputValue::object([#( #fields ),*]) } } } @@ -716,6 +1113,47 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this + /// [GraphQL input object][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + } + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this struct. /// @@ -775,4 +1213,16 @@ impl Definition { generics } + + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let generics = self.generics.clone(); + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + (ty, generics) + } } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 634a1419a..16bf2fbc1 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -120,6 +120,7 @@ fn expand_on_trait( description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, implemented_for: attr .implemented_for @@ -207,6 +208,7 @@ fn parse_trait_method( description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), + behavior: attr.behavior.into(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), is_async: method.sig.asyncness.is_some(), @@ -301,6 +303,7 @@ fn expand_on_derive_input( description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, implemented_for: attr .implemented_for @@ -372,6 +375,7 @@ fn parse_struct_field( description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: field_ident.clone(), + behavior: attr.behavior.into(), arguments: None, has_receiver: false, is_async: false, diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 25bc096bd..52f88792a 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -93,6 +93,7 @@ pub fn expand(input: TokenStream) -> syn::Result { description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, implemented_for: attr .implemented_for @@ -149,6 +150,7 @@ fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option>, + /// 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>, + /// Explicitly specified marker indicating that the Rust trait should be /// transformed into [`async_trait`]. /// @@ -175,6 +186,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "for" | "implementers" => { input.parse::()?; for impler in input.parse_maybe_wrapped_and_punctuated::< @@ -245,6 +263,7 @@ impl Attr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), implements: try_merge_hashset!(implements: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), @@ -324,6 +343,13 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Interfaces scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL interface][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + behavior: behavior::Type, + /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces @@ -368,6 +394,10 @@ impl ToTokens for Definition { self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_value().to_tokens(into); + gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -822,6 +852,36 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Value`] trait for this + /// [GraphQL interface][0]. + /// + /// [`resolve::Value`]: juniper::resolve::Value + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + fn impl_resolve_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value( + &self, + _: Option<&[::juniper::Selection<'_, #sv>]>, + _: &#inf, + _: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + todo!() + } + } + } + } + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL interface][1]. /// @@ -953,6 +1013,69 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`], [`reflect::WrappedType`] and + /// [`reflect::Fields`] traits for this [GraphQL interface][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::Fields`]: juniper::reflect::Fields + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + let implers = &self.implemented_for; + let interfaces = &self.implements; + let fields = self.fields.iter().map(|f| &f.name); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = &[ + >::NAME, + #( <#implers as ::juniper::reflect::BaseType<#bh>>::NAME ),* + ]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::Implements<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = &[#( + <#interfaces as ::juniper::reflect::BaseType<#bh>>::NAME + ),*]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::Fields<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Names = &[#( #fields ),*]; + } + } + } + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// @@ -1373,6 +1496,20 @@ impl Definition { generics } + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let generics = self.generics.clone(); + + let ty = { + let ident = &self.enum_alias_ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + + (ty, generics) + } + /// Indicates whether this enum has non-exhaustive phantom variant to hold /// type parameters. #[must_use] diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index 37b47d166..83c084848 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -117,6 +117,7 @@ where description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, interfaces: attr .interfaces @@ -218,6 +219,7 @@ fn parse_field( description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), + behavior: attr.behavior.into(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), is_async: method.sig.asyncness.is_some(), diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 189ed261d..be1c939b1 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -94,6 +94,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar, + behavior: attr.behavior.into(), fields, interfaces: attr .interfaces @@ -143,6 +144,7 @@ fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option>, + /// 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>, + /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. /// @@ -130,6 +141,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "impl" | "implements" | "interfaces" => { input.parse::()?; for iface in input.parse_maybe_wrapped_and_punctuated::< @@ -175,6 +193,7 @@ impl Attr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), interfaces: try_merge_hashset!(interfaces: self, another => span_joined), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, @@ -240,6 +259,13 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL object][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Objects + pub(crate) behavior: behavior::Type, + /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects @@ -297,7 +323,7 @@ impl Definition { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut ty = self.ty.clone(); - ty.lifetimes_iter_mut(&mut |lt| { + ty.named_lifetimes_iter_mut(&mut |lt| { let ident = lt.ident.unraw(); lt.ident = format_ident!("__fa__{ident}"); lifetimes.push(lt.clone()); @@ -324,6 +350,12 @@ impl Definition { (quote! { #impl_generics }, where_clause.cloned()) } + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + (self.ty.clone(), self.generics.clone()) + } + /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL object][1]. /// @@ -418,6 +450,274 @@ impl Definition { } } + /// 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 = + &[>::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, + >::NAME, + ); + } + }; + + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::StaticField< + { ::juniper::reflect::fnv1a128(#f_name) }, + #inf, #cx, #sv, #bh, + > for #ty #where_clause { + fn resolve_static_field( + &self, + args: &::juniper::Arguments<'_, #sv>, + type_info: &#inf, + executor: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + #body + } + } + } + }) + .collect() + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -496,6 +796,12 @@ impl ToTokens for Definition { self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_value().to_tokens(into); + gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into); + self.impl_resolve_static_field().to_tokens(into); + self.impl_reflect().to_tokens(into); + self.impl_reflect_field().to_tokens(into); } } diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index 90024820f..a336712c1 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned}; use crate::common::{diagnostic, parse, scalar, SpanContainer}; -use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken, TypeOrIdent}; +use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken}; /// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr; @@ -47,7 +47,7 @@ fn expand_on_type_alias( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let def = Definition { - ty: TypeOrIdent::Type(ast.ty.clone()), + ident: ast.ident.clone(), where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), @@ -60,6 +60,8 @@ fn expand_on_type_alias( description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, + scalar_value: attr.scalar.as_deref().into(), + behavior: attr.behavior.into(), }; Ok(quote! { @@ -74,11 +76,12 @@ fn expand_on_derive_input( ast: syn::DeriveInput, ) -> syn::Result { let attr = Attr::from_attrs("graphql_scalar", &attrs)?; + let methods = parse_derived_methods(&ast, &attr)?; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let def = Definition { - ty: TypeOrIdent::Ident(ast.ident.clone()), + ident: ast.ident.clone(), where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), @@ -91,6 +94,8 @@ fn expand_on_derive_input( description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, + scalar_value: attr.scalar.as_deref().into(), + behavior: attr.behavior.into(), }; Ok(quote! { diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index ff4f93b12..53a27ddbf 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -6,7 +6,7 @@ use syn::{parse_quote, spanned::Spanned}; use crate::common::{diagnostic, scalar, SpanContainer}; -use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent}; +use super::{Attr, Definition, Field, Methods, ParseToken}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive; @@ -15,23 +15,28 @@ const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive; pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = Attr::from_attrs("graphql", &ast.attrs)?; + let methods = parse_derived_methods(&ast, &attr)?; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + let name = attr + .name + .map(SpanContainer::into_inner) + .unwrap_or_else(|| ast.ident.to_string()); + Ok(Definition { - ty: TypeOrIdent::Ident(ast.ident.clone()), + ident: ast.ident, where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), - generics: ast.generics.clone(), + generics: ast.generics, methods, - name: attr - .name - .map(SpanContainer::into_inner) - .unwrap_or_else(|| ast.ident.to_string()), + name, description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, + scalar_value: attr.scalar.as_deref().into(), + behavior: attr.behavior.into(), } .to_token_stream()) } @@ -81,7 +86,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn: .first() .filter(|_| fields.unnamed.len() == 1) .cloned() - .map(Field::Unnamed) + .map(Field::try_from) + .transpose()? .ok_or_else(|| { ERR.custom_error( ast.span(), @@ -94,7 +100,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn: .first() .filter(|_| fields.named.len() == 1) .cloned() - .map(Field::Named) + .map(Field::try_from) + .transpose()? .ok_or_else(|| { ERR.custom_error( ast.span(), diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 2280d9c24..eba30018c 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -2,6 +2,9 @@ //! //! [1]: https://spec.graphql.org/October2021#sec-Scalars +pub mod attr; +pub mod derive; + use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ @@ -15,7 +18,7 @@ use syn::{ use url::Url; use crate::common::{ - filter_attrs, + behavior, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -23,9 +26,6 @@ use crate::common::{ scalar, Description, SpanContainer, }; -pub mod attr; -pub mod derive; - /// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when /// generating code for [GraphQL scalar][1]. /// @@ -61,6 +61,17 @@ struct Attr { /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: Option>, + /// Explicitly specified type of the custom [`Behavior`] to parametrize this + /// [GraphQL scalar][0] implementation with. + /// + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. + /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + behavior: Option>, + /// Explicitly specified function to be used as /// [`ToInputValue::to_input_value`] implementation. /// @@ -86,7 +97,7 @@ struct Attr { /// Explicit where clause added to [`syn::WhereClause`]. where_clause: Option>>, - /// Indicator for single-field structs allowing to delegate implmemntations + /// Indicator for single-field structs allowing to delegate implementations /// of non-provided resolvers to that field. transparent: bool, } @@ -132,6 +143,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "to_output_with" => { input.parse::()?; let scl = input.parse::()?; @@ -232,6 +250,7 @@ impl Attr { description: try_merge_opt!(description: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), to_output: try_merge_opt!(to_output: self, another), from_input: try_merge_opt!(from_input: self, another), parse_token: try_merge_opt!(parse_token: self, another), @@ -256,17 +275,6 @@ impl Attr { } } -/// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of -/// `#[derive(GraphQLScalar)]`. -#[derive(Clone)] -enum TypeOrIdent { - /// [`syn::Type`]. - Type(Box), - - /// [`syn::Ident`]. - Ident(syn::Ident), -} - /// Definition of [GraphQL scalar][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars @@ -276,10 +284,10 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Scalars name: String, - /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema. + /// [`syn::Ident`] of the Rust type implementing this [GraphQL scalar][0]. /// - /// [1]: https://spec.graphql.org/October2021#sec-Scalars - ty: TypeOrIdent, + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + ident: syn::Ident, /// Additional [`Self::generics`] [`syn::WhereClause`] predicates. where_clause: Vec, @@ -312,6 +320,20 @@ struct Definition { /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: scalar::Type, + + /// [`ScalarValue`] parametrization to generate code with for this + /// [GraphQL scalar][0]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + scalar_value: ScalarValue, + + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL scalar][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + behavior: behavior::Type, } impl ToTokens for Definition { @@ -324,6 +346,19 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_type().to_tokens(into); + self.impl_resolve_type_name().to_tokens(into); + self.impl_resolve_value().to_tokens(into); + self.impl_resolve_value_async().to_tokens(into); + gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into); + self.impl_resolve_to_input_value().to_tokens(into); + self.impl_resolve_input_value().to_tokens(into); + self.impl_resolve_scalar_token().to_tokens(into); + self.impl_graphql_input_type().to_tokens(into); + self.impl_graphql_output_type().to_tokens(into); + self.impl_graphql_scalar().to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -352,6 +387,99 @@ impl Definition { } } + /// Returns generated code implementing [`graphql::InputType`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`graphql::InputType`]: juniper::graphql::InputType + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + #[must_use] + fn impl_graphql_input_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, generics) = self.mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::resolve::Type<#inf, #sv, #bh> + + ::juniper::resolve::ToInputValue<#sv, #bh> + + ::juniper::resolve::InputValue<#lt, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + for #ty #where_clause + { + fn assert_input_type() {} + } + } + } + + /// Returns generated code implementing [`graphql::OutputType`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`graphql::OutputType`]: juniper::graphql::OutputType + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + #[must_use] + fn impl_graphql_output_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = self.mix_scalar_value(generics); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::resolve::Type<#inf, #sv, #bh> + + ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + + ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn assert_output_type() {} + } + } + } + + /// Returns generated code implementing [`graphql::Scalar`] trait for this + /// [GraphQL scalar][0]. + /// + /// [`graphql::Scalar`]: juniper::graphql::Scalar + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + #[must_use] + fn impl_graphql_scalar(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, generics) = self.mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, sv); + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh> + + ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh> + + ::juniper::resolve::ScalarToken<#sv, #bh> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::graphql::Scalar<#lt, #inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn assert_scalar() { + > + ::assert_input_type(); + > + ::assert_output_type(); + } + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL scalar][1]. /// @@ -395,6 +523,77 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::TypeName`] trait for this + /// [GraphQL scalar][0]. + /// + /// [`resolve::TypeName`]: juniper::resolve::TypeName + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_type_name(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::TypeName<#inf, #bh> + for #ty #where_clause + { + fn type_name(_: &#inf) -> &'static str { + >::NAME + } + } + } + } + + /// Returns generated code implementing [`resolve::Type`] trait for this + /// [GraphQL scalar][0]. + /// + /// [`resolve::Type`]: juniper::resolve::Type + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_type(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (sv, mut generics) = self.mix_scalar_value(generics); + let preds = &mut generics.make_where_clause().predicates; + preds.push(parse_quote! { #sv: Clone }); + preds.push(parse_quote! { + ::juniper::behavior::Coerce: + ::juniper::resolve::TypeName<#inf> + + ::juniper::resolve::ScalarToken<#sv> + + ::juniper::resolve::InputValueOwned<#sv> + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let description = &self.description; + let specified_by_url = self.specified_by_url.as_ref().map(|url| { + let url_lit = url.as_str(); + quote! { .specified_by_url(#url_lit) } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Type<#inf, #sv, #bh> + for #ty #where_clause + { + fn meta<'__r, '__ti: '__r>( + registry: &mut ::juniper::Registry<'__r, #sv>, + type_info: &'__ti #inf, + ) -> ::juniper::meta::MetaType<'__r, #sv> + where + #sv: '__r, + { + registry.register_scalar_with::< + ::juniper::behavior::Coerce, _, + >(type_info, |meta| { + meta #description #specified_by_url + }) + } + } + } + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL scalar][1]. /// @@ -432,6 +631,42 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Value`] trait for this + /// [GraphQL scalar][0]. + /// + /// [`resolve::Value`]: juniper::resolve::Value + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = self.mix_scalar_value(generics); + generics + .make_where_clause() + .predicates + .extend(self.methods.bound_resolve_value(&inf, &cx, sv)); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let body = self.methods.expand_resolve_value(&inf, &cx, sv); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value( + &self, + selection_set: Option<&[::juniper::Selection<'_, #sv>]>, + type_info: &#inf, + executor: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + #body + } + } + } + } + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL scalar][1]. /// @@ -462,6 +697,48 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ValueAsync`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`resolve::ValueAsync`]: juniper::resolve::ValueAsync + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_value_async(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, mut generics) = self.mix_scalar_value(generics); + let preds = &mut generics.make_where_clause().predicates; + preds.push(parse_quote! { + Self: ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + }); + preds.push(parse_quote! { + #sv: Send + }); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ValueAsync<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value_async<'__r>( + &'__r self, + sel_set: Option<&'__r [::juniper::Selection<'_, #sv>]>, + type_info: &'__r #inf, + executor: &'__r ::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::BoxFuture< + '__r, ::juniper::ExecutionResult<#sv>, + > { + let v = + > + ::resolve_value(self, sel_set, type_info, executor); + ::std::boxed::Box::pin(::juniper::futures::future::ready(v)) + } + } + } + } + /// Returns generated code implementing [`InputValue`] trait for this /// [GraphQL scalar][1]. /// @@ -470,7 +747,7 @@ impl Definition { fn impl_to_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let to_input_value = self.methods.expand_to_input_value(scalar); + let to_input_value = self.methods.expand_old_to_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -487,6 +764,35 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ToInputValue`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_to_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = self.mix_scalar_value(generics); + generics + .make_where_clause() + .predicates + .extend(self.methods.bound_to_input_value(sv)); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let body = self.methods.expand_to_input_value(sv); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh> + for #ty #where_clause + { + fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> { + #body + } + } + } + } + /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL scalar][1]. /// @@ -515,6 +821,41 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::InputValue`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`resolve::InputValue`]: juniper::resolve::InputValue + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, generics) = self.mix_scalar_value(generics); + let (lt, mut generics) = gen::mix_input_lifetime(generics, sv); + generics + .make_where_clause() + .predicates + .extend(self.methods.bound_try_from_input_value(<, sv)); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let error_ty = self.methods.expand_try_from_input_value_error(<, sv); + let body = self.methods.expand_try_from_input_value(sv); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh> + for #ty #where_clause + { + type Error = #error_ty; + + fn try_from_input_value( + input: &#lt ::juniper::graphql::InputValue<#sv>, + ) -> ::std::result::Result { + #body + } + } + } + } + /// Returns generated code implementing [`ParseScalarValue`] trait for this /// [GraphQL scalar][1]. /// @@ -542,6 +883,37 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ScalarToken`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_scalar_token(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = self.mix_scalar_value(generics); + generics + .make_where_clause() + .predicates + .extend(self.methods.bound_parse_scalar_token(sv)); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let body = self.methods.expand_parse_scalar_token(sv); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ScalarToken<#sv, #bh> + for #ty #where_clause + { + fn parse_scalar_token( + token: ::juniper::parser::ScalarToken<'_>, + ) -> ::std::result::Result<#sv, ::juniper::parser::ParseError> { + #body + } + } + } + } + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL scalar][1]. /// @@ -581,6 +953,47 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this + /// [GraphQL scalar][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + } + } + /// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`] /// trait (and similar) implementation. /// @@ -593,12 +1006,10 @@ impl Definition { fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) { let mut generics = self.generics.clone(); - let ty = match &self.ty { - TypeOrIdent::Type(ty) => ty.into_token_stream(), - TypeOrIdent::Ident(ident) => { - let (_, ty_gen, _) = self.generics.split_for_impl(); - quote! { #ident #ty_gen } - } + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = self.generics.split_for_impl(); + quote! { #ident #ty_gen } }; if !self.where_clause.is_empty() { @@ -628,15 +1039,10 @@ impl Definition { ModifyLifetimes.visit_generics_mut(&mut generics); let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = match self.ty.clone() { - TypeOrIdent::Type(mut ty) => { - ModifyLifetimes.visit_type_mut(&mut ty); - ty.into_token_stream() - } - TypeOrIdent::Ident(ident) => { - let (_, ty_gens, _) = generics.split_for_impl(); - quote! { #ident #ty_gens } - } + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = generics.split_for_impl(); + quote! { #ident #ty_gen } }; quote! { for<#( #lifetimes ),*> #ty } @@ -658,6 +1064,42 @@ impl Definition { (ty, generics) } + + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + #[must_use] + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let mut generics = self.generics.clone(); + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + + if !self.where_clause.is_empty() { + generics + .make_where_clause() + .predicates + .extend(self.where_clause.clone()) + } + + (ty, generics) + } + + /// Mixes a [`ScalarValue`] [`syn::GenericParam`] into the provided + /// [`syn::Generics`] and returns it. + /// + /// [`ScalarValue`] trait bound is not made here, because some trait + /// implementations may not require it, depending on the generated code or + /// even at all. + /// + /// [`ScalarValue`]: juniper::ScalarValue + #[must_use] + fn mix_scalar_value(&self, mut generics: syn::Generics) -> (&ScalarValue, syn::Generics) { + let sv = &self.scalar_value; + generics.params.push(parse_quote! { #sv }); + (sv, generics) + } } /// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a @@ -670,29 +1112,30 @@ impl VisitMut for ModifyLifetimes { } } -/// Methods representing [GraphQL scalar][1]. +/// User-provided methods for implementing a [GraphQL scalar][0]. /// -/// [1]: https://spec.graphql.org/October2021#sec-Scalars +/// [0]: https://spec.graphql.org/October2021#sec-Scalars enum Methods { - /// [GraphQL scalar][1] represented with only custom resolvers. + /// [GraphQL scalar][0] represented with custom resolving methods only. /// - /// [1]: https://spec.graphql.org/October2021#sec-Scalars + /// [0]: https://spec.graphql.org/October2021#sec-Scalars Custom { - /// Function provided with `#[graphql(to_output_with = ...)]`. + /// Function provided with `#[graphql(to_output_with = ...)]` attribute. to_output: syn::ExprPath, - /// Function provided with `#[graphql(from_input_with = ...)]`. + /// Function provided with `#[graphql(from_input_with = ...)]` + /// attribute. from_input: syn::ExprPath, /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` - /// or `#[graphql(parse_token(...))]`. + /// or `#[graphql(parse_token(...))]` attribute. parse_token: ParseToken, }, - /// [GraphQL scalar][1] maybe partially represented with custom resolver. - /// Other methods are used from [`Field`]. + /// [GraphQL scalar][0] maybe partially represented with custom resolving + /// methods. Other methods are re-used from its inner [`Field`]. /// - /// [1]: https://spec.graphql.org/October2021#sec-Scalars + /// [0]: https://spec.graphql.org/October2021#sec-Scalars Delegated { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: Option, @@ -735,10 +1178,88 @@ impl Methods { } } + /// Expands body of [`resolve::Value::resolve_value()`][0] method. + /// + /// [0]: juniper::resolve::Value::resolve_value + fn expand_resolve_value( + &self, + inf: &syn::Ident, + cx: &syn::Ident, + sv: &ScalarValue, + ) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + let into = sv.custom.is_some().then(|| { + quote! { .map_scalar_value() } + }); + quote! { ::std::result::Result::Ok(#to_output(self)#into) } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + quote! { + <#field_ty as + ::juniper::resolve::Value<#inf, #cx, #sv, #field_bh>> + ::resolve_value( + &self.#field, + selection_set, + type_info, + executor, + ) + } + } + } + } + + /// Generates additional trait bounds for [`resolve::Value`] implementation + /// allowing to execute [`resolve::Value::resolve_value()`][0] method. + /// + /// [`resolve::Value`]: juniper::resolve::Value + /// [0]: juniper::resolve::Value::resolve_value + fn bound_resolve_value( + &self, + inf: &syn::Ident, + cx: &syn::Ident, + sv: &ScalarValue, + ) -> Vec { + match self { + Self::Custom { .. } + | Self::Delegated { + to_output: Some(_), .. + } => { + let mut bounds = vec![parse_quote! { + #sv: ::juniper::ScalarValue + }]; + if let Some(custom_sv) = &sv.custom { + bounds.push(parse_quote! { + #custom_sv: ::juniper::ScalarValue + }); + } + bounds + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + vec![parse_quote! { + #field_ty: + ::juniper::resolve::Value<#inf, #cx, #sv, #field_bh> + }] + } + } + } + /// Expands [`ToInputValue::to_input_value`] method. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value - fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_old_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } | Self::Delegated { @@ -758,6 +1279,72 @@ impl Methods { } } + /// Expands body of [`resolve::ToInputValue::to_input_value()`][0] method. + /// + /// [0]: juniper::resolve::ToInputValue::to_input_value + fn expand_to_input_value(&self, sv: &ScalarValue) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + let into = sv.custom.is_some().then(|| { + quote! { .map_scalar_value() } + }); + quote! { + let v = #to_output(self)#into; + ::juniper::resolve::ToInputValue::<#sv>::to_input_value(&v) + } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + quote! { + <#field_ty as + ::juniper::resolve::ToInputValue<#sv, #field_bh>> + ::to_input_value(&self.#field) + } + } + } + } + + /// Generates additional trait bounds for [`resolve::ToInputValue`] + /// implementation allowing to execute + /// [`resolve::ToInputValue::to_input_value()`][0] method. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: juniper::resolve::ToInputValue::to_input_value + fn bound_to_input_value(&self, sv: &ScalarValue) -> Vec { + match self { + Self::Custom { .. } + | Self::Delegated { + to_output: Some(_), .. + } => { + let mut bounds = vec![parse_quote! { + #sv: ::juniper::ScalarValue + }]; + if let Some(custom_sv) = &sv.custom { + bounds.push(parse_quote! { + #custom_sv: ::juniper::ScalarValue + }); + } + bounds + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + vec![parse_quote! { + #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh> + }] + } + } + } + /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value @@ -781,6 +1368,114 @@ impl Methods { } } + /// Expands body of [`resolve::InputValue::try_from_input_value()`][0] + /// method. + /// + /// [0]: juniper::resolve::InputValue::try_from_input_value + fn expand_try_from_input_value(&self, sv: &ScalarValue) -> TokenStream { + match self { + Self::Custom { from_input, .. } + | Self::Delegated { + from_input: Some(from_input), + .. + } => { + let map_sv = sv.custom.is_some().then(|| { + quote! { .map_scalar_value() } + }); + quote! { + #from_input(input #map_sv) + .map_err( + ::juniper::IntoFieldError::<#sv>::into_field_error, + ) + } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + let self_constructor = field.closure_constructor(); + + quote! { + <#field_ty as + ::juniper::resolve::InputValue<'_, #sv, #field_bh>> + ::try_from_input_value(input) + .map(#self_constructor) + } + } + } + } + + /// Expands error type of [`resolve::InputValue`] trait. + /// + /// [`resolve::InputValue`]: juniper::resolve::InputValue + fn expand_try_from_input_value_error( + &self, + lt: &syn::GenericParam, + sv: &ScalarValue, + ) -> syn::Type { + match self { + Self::Custom { .. } + | Self::Delegated { + from_input: Some(_), + .. + } => { + parse_quote! { + ::juniper::FieldError<#sv> + } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + parse_quote! { + <#field_ty as + ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>::Error + } + } + } + } + + /// Generates additional trait bounds for [`resolve::InputValue`] + /// implementation allowing to execute + /// [`resolve::InputValue::try_from_input_value()`][0] method. + /// + /// [`resolve::InputValue`]: juniper::resolve::InputValue + /// [0]: juniper::resolve::InputValue::try_from_input_value + fn bound_try_from_input_value( + &self, + lt: &syn::GenericParam, + sv: &ScalarValue, + ) -> Vec { + match self { + Self::Custom { .. } + | Self::Delegated { + from_input: Some(_), + .. + } => { + let mut bounds = vec![parse_quote! { + #sv: ::juniper::ScalarValue + }]; + if let Some(custom_sv) = &sv.custom { + bounds.push(parse_quote! { + #custom_sv: ::juniper::ScalarValue + }); + } + bounds + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + vec![parse_quote! { + #field_ty: + ::juniper::resolve::InputValue<#lt, #sv, #field_bh> + }] + } + } + } + /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str @@ -802,6 +1497,56 @@ impl Methods { } } } + + /// Expands body of [`resolve::ScalarToken::parse_scalar_token()`][0] + /// method. + /// + /// [0]: juniper::resolve::ScalarToken::parse_scalar_token + fn expand_parse_scalar_token(&self, sv: &ScalarValue) -> TokenStream { + match self { + Self::Custom { parse_token, .. } + | Self::Delegated { + parse_token: Some(parse_token), + .. + } => parse_token.expand_parse_scalar_token(sv), + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + quote! { + <#field_ty as + ::juniper::resolve::ScalarToken<#sv, #field_bh>> + ::parse_scalar_token(token) + } + } + } + } + + /// Generates additional trait bounds for [`resolve::ScalarToken`] + /// implementation allowing to execute + /// [`resolve::ScalarToken::parse_scalar_token()`][0] method. + /// + /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken + /// [0]: juniper::resolve::ScalarToken::parse_scalar_token + fn bound_parse_scalar_token(&self, sv: &ScalarValue) -> Vec { + match self { + Self::Custom { parse_token, .. } + | Self::Delegated { + parse_token: Some(parse_token), + .. + } => parse_token.bound_parse_scalar_token(sv), + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let field_bh = &field.behavior; + + vec![parse_quote! { + #field_ty: ::juniper::resolve::ScalarToken<#sv, #field_bh> + }] + } + } + } } /// Representation of [`ParseScalarValue::from_str`] method. @@ -845,22 +1590,169 @@ impl ParseToken { .unwrap_or_default(), } } + + /// Expands body of [`resolve::ScalarToken::parse_scalar_token()`][0] + /// method. + /// + /// [0]: juniper::resolve::ScalarToken::parse_scalar_token + fn expand_parse_scalar_token(&self, sv: &ScalarValue) -> TokenStream { + match self { + Self::Custom(parse_token) => { + let into = sv.custom.is_some().then(|| { + quote! { .map(::juniper::ScalarValue::into_another) } + }); + quote! { + #parse_token(token)#into + } + } + + Self::Delegated(delegated) => delegated + .iter() + .fold(None, |acc, ty| { + acc.map_or_else( + || { + Some(quote! { + <#ty as ::juniper::resolve::ScalarToken<#sv>> + ::parse_scalar_token(token) + }) + }, + |prev| { + Some(quote! { + #prev.or_else(|_| { + <#ty as ::juniper::resolve::ScalarToken<#sv>> + ::parse_scalar_token(token) + }) + }) + }, + ) + }) + .unwrap_or_default(), + } + } + + /// Generates additional trait bounds for [`resolve::ScalarToken`] + /// implementation allowing to execute + /// [`resolve::ScalarToken::parse_scalar_token()`][0] method. + /// + /// [`resolve::ScalarToken`]: juniper::resolve::ScalarToken + /// [0]: juniper::resolve::ScalarToken::parse_scalar_token + fn bound_parse_scalar_token(&self, sv: &ScalarValue) -> Vec { + match self { + Self::Custom(_) => { + let mut bounds = vec![parse_quote! { + #sv: ::juniper::ScalarValue + }]; + if let Some(custom_sv) = &sv.custom { + bounds.push(parse_quote! { + #custom_sv: ::juniper::ScalarValue + }); + } + bounds + } + + Self::Delegated(delegated) => delegated + .iter() + .map(|ty| { + parse_quote! { + #ty: ::juniper::resolve::ScalarToken<#sv> + } + }) + .collect(), + } + } +} + +/// Available arguments behind `#[graphql]` attribute on a [`Field`] when +/// generating code for a [GraphQL scalar][0] implementation. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Scalars +#[derive(Debug, Default)] +struct FieldAttr { + /// Explicitly specified type of the custom [`Behavior`] used for + /// [GraphQL scalar][0] implementation by this [`Field`]. + /// + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. + /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + behavior: Option>, +} + +impl Parse for FieldAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl FieldAttr { + /// Tries to merge two [`FieldAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + behavior: try_merge_opt!(behavior: self, another), + }) + } + + /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a field definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) + } } -/// Struct field to resolve not provided methods. -enum Field { - /// Named [`Field`]. - Named(syn::Field), +/// Inner field of a type implementing [GraphQL scalar][0], that the +/// implementation delegates calls to. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Scalars +struct Field { + /// This [`Field`] itself. + itself: syn::Field, - /// Unnamed [`Field`]. - Unnamed(syn::Field), + /// [`Behavior`] parametrization of this [`Field`]. + /// + /// [`Behavior`]: juniper::behavior + behavior: behavior::Type, +} + +impl TryFrom for Field { + type Error = syn::Error; + + fn try_from(field: syn::Field) -> syn::Result { + let attr = FieldAttr::from_attrs("graphql", &field.attrs)?; + Ok(Self { + itself: field, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), + }) + } } impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Named(f) => f.ident.to_tokens(tokens), - Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + if let Some(name) = &self.itself.ident { + name.to_tokens(tokens) + } else { + tokens.append(Literal::u8_unsuffixed(0)) } } } @@ -868,20 +1760,47 @@ impl ToTokens for Field { impl Field { /// [`syn::Type`] of this [`Field`]. fn ty(&self) -> &syn::Type { - match self { - Self::Named(f) | Self::Unnamed(f) => &f.ty, - } + &self.itself.ty } - /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. + /// Generates closure to construct a [GraphQL scalar][0] struct from an + /// inner [`Field`] value. /// - /// [1]: https://spec.graphql.org/October2021#sec-Scalars + /// [0]: https://spec.graphql.org/October2021#sec-Scalars fn closure_constructor(&self) -> TokenStream { - match self { - Field::Named(syn::Field { ident, .. }) => { - quote! { |v| Self { #ident: v } } - } - Field::Unnamed(_) => quote! { Self }, + if let Some(name) = &self.itself.ident { + quote! { |v| Self { #name: v } } + } else { + quote! { Self } + } + } +} + +/// [`ScalarValue`] parametrization of a [GraphQL scalar][0] implementation. +/// +/// [`ScalarValue`]: juniper::ScalarValue +/// [0]: https://spec.graphql.org/October2021#sec-Scalars +struct ScalarValue { + /// Concrete custom Rust type used in user-provided [`Methods`] as + /// [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + custom: Option, +} + +impl ToTokens for ScalarValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + (quote! { __ScalarValue }).to_tokens(tokens) + } +} + +impl<'a> From> for ScalarValue { + fn from(attr: Option<&'a scalar::AttrValue>) -> Self { + Self { + custom: match attr { + Some(scalar::AttrValue::Concrete(ty)) => Some(ty.clone()), + Some(scalar::AttrValue::Generic(_)) | None => None, + }, } } } diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 006ad2263..a07ac78ee 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -93,6 +93,7 @@ fn expand_on_trait( description: attr.description.map(SpanContainer::into_inner), context, scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics.clone(), variants, }; @@ -210,6 +211,7 @@ fn parse_variant_from_trait_method( ty, resolver_code, resolver_check, + behavior: attr.behavior.into(), context: method_context_ty, }) } diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index a10be60cc..0a2818c2b 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -84,6 +84,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics, variants, }) @@ -163,6 +164,7 @@ fn parse_variant_from_enum_variant( ty, resolver_code, resolver_check, + behavior: attr.behavior.into(), context: None, }) } @@ -214,6 +216,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics, variants, }) diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index fa585b004..7931fccee 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -18,7 +18,7 @@ use syn::{ }; use crate::common::{ - filter_attrs, gen, + behavior, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -74,6 +74,17 @@ struct Attr { /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: Option>, + /// 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>, + /// Explicitly specified external resolver functions for [GraphQL union][1] /// variants. /// @@ -128,6 +139,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "on" => { let ty = input.parse::()?; input.parse::()?; @@ -160,6 +178,7 @@ impl Attr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), external_resolvers: try_merge_hashmap!( external_resolvers: self, another => span_joined ), @@ -188,6 +207,19 @@ impl Attr { /// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct VariantAttr { + /// Explicitly specified type of the custom [`Behavior`] this + /// [GraphQL union][0] member implementation is parametrized with, to + /// [coerce] in the generated code from. + /// + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. + /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard + /// [0]: https://spec.graphql.org/October2021#sec-Unions + /// [coerce]: juniper::behavior::Coerce + behavior: Option>, + /// Explicitly specified marker for the variant/field being ignored and not /// included into [GraphQL union][1]. /// @@ -210,6 +242,13 @@ impl Parse for VariantAttr { while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) @@ -236,6 +275,7 @@ impl VariantAttr { /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { + behavior: try_merge_opt!(behavior: self, another), ignore: try_merge_opt!(ignore: self, another), external_resolver: try_merge_opt!(external_resolver: self, another), }) @@ -301,6 +341,13 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL union][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Unions + behavior: behavior::Type, + /// Variants definitions of this [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions @@ -315,6 +362,10 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_resolve_value().to_tokens(into); + gen::impl_resolvable(&self.behavior, self.ty_and_generics()).to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -560,6 +611,36 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::Value`] trait for this + /// [GraphQL union][0]. + /// + /// [`resolve::Value`]: juniper::resolve::Value + /// [0]: https://spec.graphql.org/October2021#sec-Unions + fn impl_resolve_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = gen::mix_type_info(generics); + let (cx, generics) = gen::mix_context(generics); + let (sv, generics) = gen::mix_scalar_value(generics); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::Value<#inf, #cx, #sv, #bh> + for #ty #where_clause + { + fn resolve_value( + &self, + _: Option<&[::juniper::Selection<'_, #sv>]>, + _: &#inf, + _: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + todo!() + } + } + } + } + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL union][1]. /// @@ -645,6 +726,70 @@ impl Definition { } } } + + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this + /// [GraphQL union][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Unions + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + + let member_names = self.variants.iter().map(|m| { + let m_ty = &m.ty; + let m_bh = &m.behavior; + + quote! { + <#m_ty as ::juniper::reflect::BaseType<#m_bh>>::NAME + } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = &[ + >::NAME, + #( #member_names ),* + ]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + } + } + + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let generics = self.generics.clone(); + let ty = { + let ident = &self.ty; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + (ty, generics) + } } /// Definition of [GraphQL union][1] variant for code generation. @@ -667,6 +812,14 @@ struct VariantDefinition { /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_check: syn::Expr, + /// [`Behavior`] parametrization of this [GraphQL union][0] member + /// implementation to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Unions + /// [coerce]: juniper::behavior::Coerce + behavior: behavior::Type, + /// Rust type of [`Context`] that this [GraphQL union][1] variant requires /// for resolution. /// @@ -783,6 +936,7 @@ fn emerge_union_variants_from_attr( ty, resolver_code, resolver_check, + behavior: behavior::Type::default(), // TODO: remove at all context: None, }) }