Reworking base traits, vol.2

This commit is contained in:
tyranron 2022-06-07 18:19:44 +02:00
parent 8235ac22c0
commit 97c88d219c
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
3 changed files with 843 additions and 296 deletions

View file

@ -3,7 +3,10 @@
use futures::future::BoxFuture; use futures::future::BoxFuture;
use crate::{ use crate::{
reflect::{Type, Types, WrappedValue}, reflect::{
can_be_subtype, fnv1a128, str_eq, str_exists_in_arr, type_len_with_wrapped_val, wrap,
Argument, Arguments, FieldName, Name, Names, Type, Types, WrappedValue,
},
Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, ScalarValue, Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, ScalarValue,
}; };
@ -78,7 +81,7 @@ where
/// ///
/// ```rust /// ```rust
/// # use juniper::{ /// # use juniper::{
/// # format_type, /// # reflect::format_type,
/// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type}, /// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type},
/// # DefaultScalarValue, /// # DefaultScalarValue,
/// # }; /// # };
@ -114,48 +117,6 @@ where
const VALUE: u128 = T::VALUE; const VALUE: u128 = T::VALUE;
} }
pub mod wrap {
use super::WrappedValue;
pub const SINGULAR: WrappedValue = 1;
pub const fn nullable(val: WrappedValue) -> WrappedValue {
val * 10 + 2
}
pub const fn list(val: WrappedValue) -> WrappedValue {
val * 10 + 3
}
}
/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name.
///
/// See [`Fields`] for more info.
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Name = &'static str;
/// Alias for a slice of [`Name`]s.
///
/// See [`Fields`] for more info.
pub type Names = &'static [Name];
/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Argument = (Name, Type, WrappedValue);
/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and
/// [`WrappedValue`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Arguments = &'static [(Name, Type, WrappedValue)];
/// Alias for a `const`-hashed [`Name`] used in a `const` context.
pub type FieldName = u128;
/// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`].
/// ///
/// [1]: https://spec.graphql.org/October2021#sec-Objects /// [1]: https://spec.graphql.org/October2021#sec-Objects
@ -269,107 +230,6 @@ pub trait AsyncField<S, const N: FieldName>: FieldMeta<S, N> {
) -> BoxFuture<'b, ExecutionResult<S>>; ) -> BoxFuture<'b, ExecutionResult<S>>;
} }
/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in
/// `const` generics. See [spec] for more info.
///
/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
#[must_use]
pub const fn fnv1a128(str: Name) -> u128 {
const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d;
const FNV_PRIME: u128 = 0x0000000001000000000000000000013b;
let bytes = str.as_bytes();
let mut hash = FNV_OFFSET_BASIS;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u128;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
/// Length __in bytes__ of the [`format_type!`] macro result.
#[must_use]
pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize {
let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type!
let mut curr = val;
while curr % 10 != 0 {
match curr % 10 {
2 => len -= "!".as_bytes().len(), // remove !
3 => len += "[]!".as_bytes().len(), // [Type]!
_ => {}
}
curr /= 10;
}
len
}
/// Checks whether the given GraphQL [object][1] represents a `subtype` of the
/// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding.
///
/// To fully determine the sub-typing relation the [`Type`] should be one of the
/// [`BaseSubTypes::NAMES`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
#[must_use]
pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool {
let ty_curr = ty % 10;
let sub_curr = subtype % 10;
if ty_curr == sub_curr {
if ty_curr == 1 {
true
} else {
can_be_subtype(ty / 10, subtype / 10)
}
} else if ty_curr == 2 {
can_be_subtype(ty / 10, subtype)
} else {
false
}
}
/// Checks whether the given `val` exists in the given `arr`.
#[must_use]
pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool {
let mut i = 0;
while i < arr.len() {
if str_eq(val, arr[i]) {
return true;
}
i += 1;
}
false
}
/// Compares strings in a `const` context.
///
/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to
/// write custom comparison function.
///
/// [`Eq`]: std::cmp::Eq
// TODO: Remove once `Eq` trait is allowed in `const` context.
pub const fn str_eq(l: &str, r: &str) -> bool {
let (l, r) = (l.as_bytes(), r.as_bytes());
if l.len() != r.len() {
return false;
}
let mut i = 0;
while i < l.len() {
if l[i] != r[i] {
return false;
}
i += 1;
}
true
}
/// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing
/// this interface in the `impl = ...` attribute argument. /// this interface in the `impl = ...` attribute argument.
/// ///
@ -384,7 +244,7 @@ macro_rules! assert_implemented_for {
<$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
); );
if !is_present { if !is_present {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
<$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME, <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `", "` on `",
@ -412,7 +272,7 @@ macro_rules! assert_interfaces_impls {
<$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES, <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES,
); );
if !is_present { if !is_present {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
<$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `", "` on `",
@ -465,7 +325,7 @@ macro_rules! assert_subtype {
<$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const IMPL_TY: $crate::macros::reflect::Type = const IMPL_TY: $crate::macros::reflect::Type =
<$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const ERR_PREFIX: &str = $crate::const_concat!( const ERR_PREFIX: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
BASE_TY, BASE_TY,
"` on `", "` on `",
@ -507,14 +367,14 @@ macro_rules! assert_subtype {
let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES)
&& $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL);
if !is_subtype { if !is_subtype {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
ERR_PREFIX, ERR_PREFIX,
"Field `", "Field `",
FIELD_NAME, FIELD_NAME,
"`: implementor is expected to return a subtype of interface's return object: `", "`: implementor is expected to return a subtype of interface's return object: `",
$crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), $crate::reflect::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL),
"` is not a subtype of `", "` is not a subtype of `",
$crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), $crate::reflect::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL),
"`.", "`.",
); );
::std::panic!("{}", MSG); ::std::panic!("{}", MSG);
@ -538,7 +398,7 @@ macro_rules! assert_field_args {
const _: () = { const _: () = {
const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const ERR_PREFIX: &str = $crate::const_concat!( const ERR_PREFIX: &str = $crate::reflect::const_concat!(
"Failed to implement interface `", "Failed to implement interface `",
BASE_NAME, BASE_NAME,
"` on `", "` on `",
@ -661,13 +521,14 @@ macro_rules! assert_field_args {
const BASE_ARG_NAME: &str = ERROR.base.0; const BASE_ARG_NAME: &str = ERROR.base.0;
const IMPL_ARG_NAME: &str = ERROR.implementation.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0;
const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); const BASE_TYPE_FORMATTED: &str =
$crate::reflect::format_type!(ERROR.base.1, ERROR.base.2);
const IMPL_TYPE_FORMATTED: &str = const IMPL_TYPE_FORMATTED: &str =
$crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); $crate::reflect::format_type!(ERROR.implementation.1, ERROR.implementation.2);
const MSG: &str = match ERROR.cause { const MSG: &str = match ERROR.cause {
Cause::TypeMismatch => { Cause::TypeMismatch => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
BASE_ARG_NAME, BASE_ARG_NAME,
"`: expected type `", "`: expected type `",
@ -678,7 +539,7 @@ macro_rules! assert_field_args {
) )
} }
Cause::RequiredField => { Cause::RequiredField => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
BASE_ARG_NAME, BASE_ARG_NAME,
"` of type `", "` of type `",
@ -687,7 +548,7 @@ macro_rules! assert_field_args {
) )
} }
Cause::AdditionalNonNullableField => { Cause::AdditionalNonNullableField => {
$crate::const_concat!( $crate::reflect::const_concat!(
"Argument `", "Argument `",
IMPL_ARG_NAME, IMPL_ARG_NAME,
"` of type `", "` of type `",
@ -697,43 +558,13 @@ macro_rules! assert_field_args {
} }
}; };
const ERROR_MSG: &str = const ERROR_MSG: &str =
$crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); $crate::reflect::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG);
::std::panic!("{}", ERROR_MSG); ::std::panic!("{}", ERROR_MSG);
} }
}; };
}; };
} }
/// Concatenates `const` [`str`](prim@str)s in a `const` context.
#[macro_export]
macro_rules! const_concat {
($($s:expr),* $(,)?) => {{
const LEN: usize = 0 $(+ $s.as_bytes().len())*;
const CNT: usize = [$($s),*].len();
const fn concat(input: [&str; CNT]) -> [u8; LEN] {
let mut bytes = [0; LEN];
let (mut i, mut byte) = (0, 0);
while i < CNT {
let mut b = 0;
while b < input[i].len() {
bytes[byte] = input[i].as_bytes()[b];
byte += 1;
b += 1;
}
i += 1;
}
bytes
}
const CON: [u8; LEN] = concat([$($s),*]);
// TODO: Use `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one
// after another byte-by-byte.
#[allow(unsafe_code)]
unsafe { ::std::str::from_utf8_unchecked(&CON) }
}};
}
/// Ensures that the given `$impl_ty` implements [`Field`] and returns a /// Ensures that the given `$impl_ty` implements [`Field`] and returns a
/// [`fnv1a128`] hash for it, otherwise panics with understandable message. /// [`fnv1a128`] hash for it, otherwise panics with understandable message.
#[macro_export] #[macro_export]
@ -746,7 +577,7 @@ macro_rules! checked_hash {
if exists { if exists {
$crate::macros::reflect::fnv1a128(FIELD_NAME) $crate::macros::reflect::fnv1a128(FIELD_NAME)
} else { } else {
const MSG: &str = $crate::const_concat!( const MSG: &str = $crate::reflect::const_concat!(
$($prefix,)? $($prefix,)?
"Field `", "Field `",
$field_name, $field_name,
@ -758,103 +589,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 `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one
// after another byte-by-byte.
#[allow(unsafe_code)]
const TYPE_FORMATTED: &str =
unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) };
TYPE_FORMATTED
}};
}

View file

@ -2,16 +2,22 @@
use crate::behavior; use crate::behavior;
/// Alias for a [GraphQL type][0]'s name in a GraphQL schema. #[doc(inline)]
pub use self::macros::{
assert_field, assert_field_args, assert_field_type, assert_has_field, assert_implemented_for,
assert_interfaces_impls, checked_hash, const_concat, format_type,
};
/// Name of a [GraphQL type][0] in a GraphQL schema.
/// ///
/// See [`BaseType`] for more info. /// See [`BaseType`] for details.
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Types /// [0]: https://spec.graphql.org/October2021#sec-Types
pub type Type = &'static str; pub type Type = &'static str;
/// Alias for a slice of [`Type`]s. /// List of [`Type`]s.
/// ///
/// See [`BaseSubTypes`] for more info. /// See [`BaseSubTypes`] for details.
pub type Types = &'static [Type]; pub type Types = &'static [Type];
/// Basic reflection of a [GraphQL type][0]. /// Basic reflection of a [GraphQL type][0].
@ -47,8 +53,786 @@ pub trait BaseSubTypes<Behavior: ?Sized = behavior::Standard> {
const NAMES: Types; const NAMES: Types;
} }
/// Alias for a value of a [`WrappedType`] (composed /// Reflection of [GraphQL interfaces][1] implementations for a
/// [GraphQL wrapping type][0]). /// [GraphQL type][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Types
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait Implements<Behavior: ?Sized = behavior::Standard> {
/// [`Types`] of the [GraphQL interfaces][1] implemented by this
/// [GraphQL type][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Types
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
const NAMES: Types;
}
/// Encoded value of a [`WrappedType`] (composed [GraphQL wrapping type][0]).
///
/// See [`WrappedType`] for details.
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Wrapping-Types /// [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; pub type WrappedValue = u128;
/// [`WrappedValue`] encoding helpers.
pub mod wrap {
use super::WrappedValue;
/// [`WrappedValue`] of a singular non-nullable [GraphQL type][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Types
pub const SINGULAR: WrappedValue = 1;
/// Performs wrapping into a nullable [`WrappedValue`].
pub const fn nullable(val: WrappedValue) -> WrappedValue {
val * 10 + 2
}
/// Performs wrapping into a list [`WrappedValue`].
pub const fn list(val: WrappedValue) -> WrappedValue {
val * 10 + 3
}
}
/// Reflection of a composed [GraphQL wrapping type][1], encoded in numbers.
///
/// To fully represent a [GraphQL type][0] it's not enough to use [`Type`],
/// because of the [wrapping types][1]. To work around this, a [`WrappedValue`]
/// is used, which is represented via [`u128`] number in the following encoding:
/// - In base case of non-nullable singular [type][0] [`VALUE`] is `1`.
/// - To represent nullability we "append" `2` to the [`VALUE`], so
/// [`Option`]`<`[type][0]`>` has [`VALUE`] of `12`.
/// - To represent a list we "append" `3` to the [`VALUE`], so
/// [`Vec`]`<`[type][0]`>` has [`VALUE`] of `13`.
///
/// Note, that due to Rust type system, the encoding here differs from the one
/// of [GraphQL wrapping types][1], as it takes nullability as wrapping, while
/// GraphQL [does the opposite][1] (takes non-nullability as wrapping).
///
/// This approach allows to uniquely represent any [GraphQL type][0] with a
/// combination of a [`Type`] and a [`WrappedValue`], and even format it via
/// [`format_type!`] macro in a `const` context.
///
/// # Example
///
/// ```rust
/// # use juniper::reflect::{
/// # format_type, BaseType, Type, WrappedType, WrappedValue,
/// # };
/// #
/// assert_eq!(<Option<i32> as WrappedType>::VALUE, 12);
/// assert_eq!(<Vec<i32> as WrappedType>::VALUE, 13);
/// assert_eq!(<Vec<Option<i32>> as WrappedType>::VALUE, 123);
/// assert_eq!(<Option<Vec<i32>> as WrappedType>::VALUE, 132);
/// assert_eq!(<Option<Vec<Option<i32>>> as WrappedType>::VALUE, 1232);
///
/// const TYPE_STRING: Type = <Option<Vec<Option<String>>> as BaseType>::NAME;
/// const WRAP_VAL_STRING: WrappedValue = <Option<Vec<Option<String>>> as WrappedType>::VALUE;
/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]");
///
/// const TYPE_STR: Type = <Option<Vec<Option<&str>>> as BaseType>::NAME;
/// const WRAP_VAL_STR: WrappedValue = <Option<Vec<Option<&str>>> as WrappedType>::VALUE;
/// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]");
/// ```
///
/// [`VALUE`]: Self::VALUE
/// [0]: https://spec.graphql.org/October2021#sec-Types
/// [1]: https://spec.graphql.org/October2021#sec-Wrapping-Types
pub trait WrappedType<Behavior: ?Sized = behavior::Standard> {
/// [`WrappedValue`] of this this [GraphQL type][0], encoded in a number.
///
/// Use [`format_type!`] macro on this number to represent it as a
/// human-readable [GraphQL type][0] string.
///
/// [0]: https://spec.graphql.org/October2021#sec-Types
const VALUE: WrappedValue;
}
/// Name of a [GraphQL field][0] or a [field argument][1].
///
/// See [`Fields`] for details.
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Name = &'static str;
/// List of [`Name`]s.
///
/// See [`Fields`] for details.
pub type Names = &'static [Name];
/// Reflection of [fields][0] for a [GraphQL object][1] or an [interface][2].
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait Fields<Behavior: ?Sized = behavior::Standard> {
/// [`Names`] of this [GraphQL object][1]/[interface][2] [fields][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
const NAMES: Names;
}
/// [GraphQL field argument][0], represented as its [`Name`], [`Type`] and
/// [`WrappedValue`].
///
/// See [`Field`] for details.
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Argument = (Name, Type, WrappedValue);
/// List of [`Argument`]s.
///
/// See [`Field`] for details.
pub type Arguments = &'static [(Name, Type, WrappedValue)];
/// Alias for a `const`-hashed [`Name`] used in a `const` context.
// TODO: Just use `&str`s once they're allowed in `const` generics.
pub type FieldName = u128;
/// Reflection of a single [GraphQL field][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
pub trait Field<const N: FieldName, Behavior: ?Sized = behavior::Standard> {
/// [`Type`] of this [GraphQL field][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
const TYPE: Type;
/// [Sub-types][1] this [GraphQL field][0] is coercible into.
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
/// [1]: BaseSubTypes
const SUB_TYPES: Types;
/// [`WrappedValue`] of this [GraphQL field][0].
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
const WRAPPED_VALUE: WrappedValue;
/// [`Arguments`] of this [GraphQL field][0] .
///
/// [0]: https://spec.graphql.org/October2021#sec-Language.Fields
const ARGUMENTS: Arguments;
}
/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in
/// `const` generics. See [spec] for more info.
///
/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
#[must_use]
pub const fn fnv1a128(str: Name) -> FieldName {
const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d;
const FNV_PRIME: u128 = 0x0000000001000000000000000000013b;
let bytes = str.as_bytes();
let mut hash = FNV_OFFSET_BASIS;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u128;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
/// Length __in bytes__ of the [`format_type!`] macro result.
#[must_use]
pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize {
let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type!
let mut curr = val;
while curr % 10 != 0 {
match curr % 10 {
2 => len -= "!".as_bytes().len(), // remove !
3 => len += "[]!".as_bytes().len(), // [Type]!
_ => {}
}
curr /= 10;
}
len
}
/// Checks whether the specified `subtype` [GraphQL type][0] represents a
/// [sub-type][1] of the specified `supertype`, basing on the [`WrappedType`]
/// encoding.
///
/// To fully determine the [sub-typing][1] relation the [`Type`] should be one
/// of the [`BaseSubTypes::NAMES`].
///
/// [0]: https://spec.graphql.org/October2021#sec-Types
/// [1]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC
#[must_use]
pub const fn can_be_subtype(supertype: WrappedValue, subtype: WrappedValue) -> bool {
let super_curr = supertype % 10;
let sub_curr = subtype % 10;
if super_curr == sub_curr {
if super_curr == 1 {
true
} else {
can_be_subtype(supertype / 10, subtype / 10)
}
} else if super_curr == 2 {
can_be_subtype(supertype / 10, subtype)
} else {
false
}
}
/// Checks whether the given `val` exists in the given `arr`.
// TODO: Remove once `slice::contains()` method is allowed in `const` context.
#[must_use]
pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool {
let mut i = 0;
while i < arr.len() {
if str_eq(val, arr[i]) {
return true;
}
i += 1;
}
false
}
/// Compares strings in a `const` context.
///
/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to
/// provide a custom comparison function.
///
/// [`Eq`]: std::cmp::Eq
// TODO: Remove once `Eq` trait is allowed in `const` context.
#[must_use]
pub const fn str_eq(l: &str, r: &str) -> bool {
let (l, r) = (l.as_bytes(), r.as_bytes());
if l.len() != r.len() {
return false;
}
let mut i = 0;
while i < l.len() {
if l[i] != r[i] {
return false;
}
i += 1;
}
true
}
mod macros {
/// Asserts that `#[graphql::interface(for = ...)]` attribute has all the
/// types referencing this interface in the `impl = ...` attribute argument.
///
/// Symmetrical to [`assert_interfaces_impls!`].
macro_rules! 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_rules! 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 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_rules! 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! 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! assert_field_args {
(
$base_ty: ty,
$impl_ty: ty,
$behavior: ty,
$field_name: expr $(,)?
) => {
const _: () = {
const BASE_TY: $crate::reflect::Type =
<$base_ty as $crate::reflect::BaseType<$behavior>>::NAME;
const IMPL_TY: $crate::reflect::Type =
<$impl_ty as $crate::reflect::BaseType<$behavior>>::NAME;
const ERR_PREFIX: &str = $crate::reflect::const_concat!(
"Failed to implement interface `",
BASE_TY,
"` on `",
IMPL_TY,
"`: ",
);
const FIELD_NAME: $crate::reflect::Name = $field_name;
const FIELD_NAME_HASH: $crate::reflect::FieldName =
$crate::reflect::fnv1a128(FIELD_NAME);
$crate::reflect::assert_has_field!(FIELD_NAME, $base_ty, $behavior, ERR_PREFIX);
$crate::reflect::assert_has_field!(FIELD_NAME, $impl_ty, $behavior, ERR_PREFIX);
const BASE_ARGS: ::juniper::reflect::Arguments =
<$base_ty as $crate::reflect::Field<FIELD_NAME_HASH, $behavior>>::ARGUMENTS;
const IMPL_ARGS: ::juniper::reflect::Arguments =
<$impl_ty as $crate::reflect::Field<FIELD_NAME_HASH, $behavior>>::ARGUMENTS;
struct Error {
cause: Cause,
base: ::juniper::reflect::Argument,
implementation: ::juniper::reflect::Argument,
}
enum Cause {
RequiredField,
AdditionalNonNullableField,
TypeMismatch,
}
const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error {
match v {
// Unfortunately, we cannot use `unreachable!()` here,
// as this branch will be executed either way.
Ok(()) => Error {
cause: Cause::RequiredField,
base: ("unreachable", "unreachable", 1),
implementation: ("unreachable", "unreachable", 1),
},
Err(e) => e,
}
}
const fn check() -> Result<(), Error> {
let mut base_i = 0;
while base_i < BASE_ARGS.len() {
let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i];
let mut impl_i = 0;
let mut was_found = false;
while impl_i < IMPL_ARGS.len() {
let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i];
if $crate::reflect::str_eq(base_name, impl_name) {
if $crate::reflect::str_eq(base_type, impl_type)
&& base_wrap_val == impl_wrap_val
{
was_found = true;
break;
} else {
return Err(Error {
cause: Cause::TypeMismatch,
base: (base_name, base_type, base_wrap_val),
implementation: (impl_name, impl_type, impl_wrap_val),
});
}
}
impl_i += 1;
}
if !was_found {
return Err(Error {
cause: Cause::RequiredField,
base: (base_name, base_type, base_wrap_val),
implementation: (base_name, base_type, base_wrap_val),
});
}
base_i += 1;
}
let mut impl_i = 0;
while impl_i < IMPL_ARGS.len() {
let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i];
impl_i += 1;
if impl_wrapped_val % 10 == 2 {
continue;
}
let mut base_i = 0;
let mut was_found = false;
while base_i < BASE_ARGS.len() {
let (base_name, _, _) = BASE_ARGS[base_i];
if $crate::reflect::str_eq(base_name, impl_name) {
was_found = true;
break;
}
base_i += 1;
}
if !was_found {
return Err(Error {
cause: Cause::AdditionalNonNullableField,
base: (impl_name, impl_type, impl_wrapped_val),
implementation: (impl_name, impl_type, impl_wrapped_val),
});
}
}
Ok(())
}
const RES: ::std::result::Result<(), Error> = check();
if RES.is_err() {
const ERROR: Error = unwrap_error(RES);
const BASE_ARG_NAME: $crate::reflect::Name = ERROR.base.0;
const IMPL_ARG_NAME: $crate::reflect::Name = ERROR.implementation.0;
const BASE_TYPE_FORMATTED: &str =
$crate::reflect::format_type!(ERROR.base.1, ERROR.base.2,);
const IMPL_TYPE_FORMATTED: &str = $crate::reflect::format_type!(
ERROR.implementation.1,
ERROR.implementation.2,
);
const MSG: &str = match ERROR.cause {
Cause::TypeMismatch => {
$crate::reflect::const_concat!(
"Argument `",
BASE_ARG_NAME,
"`: expected type `",
BASE_TYPE_FORMATTED,
"`, found: `",
IMPL_TYPE_FORMATTED,
"`.",
)
}
Cause::RequiredField => {
$crate::reflect::const_concat!(
"Argument `",
BASE_ARG_NAME,
"` of type `",
BASE_TYPE_FORMATTED,
"` was expected, but not found.",
)
}
Cause::AdditionalNonNullableField => {
$crate::reflect::const_concat!(
"Argument `",
IMPL_ARG_NAME,
"` of type `",
IMPL_TYPE_FORMATTED,
"` isn't present on the interface and so has to be nullable.",
)
}
};
const ERROR_MSG: &str = $crate::reflect::const_concat!(
ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG,
);
::std::panic!("{}", ERROR_MSG);
}
};
};
}
/// Ensures that the given `$impl_ty` has the specified [`Field`].
///
/// [`Field`]: super::Field
/// [`fnv1a128`]: super::fnv1a128
macro_rules! 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_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 `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings
// one after another byte-by-byte.
#[allow(unsafe_code)]
unsafe { ::std::str::from_utf8_unchecked(&CON) }
}};
}
/// 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_rules! 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 `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one
// after another byte-by-byte.
#[allow(unsafe_code)]
const TYPE_FORMATTED: &str =
unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) };
TYPE_FORMATTED
}};
}
#[doc(inline)]
pub(super) use {
assert_field, assert_field_args, assert_field_type, assert_has_field,
assert_implemented_for, assert_interfaces_impls, checked_hash, const_concat, format_type,
};
}

View file

@ -2,7 +2,7 @@ use crate::{
behavior, graphql, behavior, graphql,
meta::MetaType, meta::MetaType,
parser::{self, ParseError}, parser::{self, ParseError},
Arguments, BoxFuture, ExecutionResult, Executor, IntoFieldError, Registry, Selection, reflect, Arguments, BoxFuture, ExecutionResult, Executor, IntoFieldError, Registry, Selection,
}; };
#[doc(inline)] #[doc(inline)]
@ -100,6 +100,22 @@ pub trait Field<
) -> ExecutionResult<ScalarValue>; ) -> ExecutionResult<ScalarValue>;
} }
pub trait StaticField<
const N: reflect::FieldName,
TypeInfo: ?Sized,
Context: ?Sized,
ScalarValue,
Behavior: ?Sized = behavior::Standard,
>
{
fn resolve_static_field(
&self,
arguments: &Arguments<ScalarValue>,
type_info: &TypeInfo,
executor: &Executor<Context, ScalarValue>,
) -> ExecutionResult<ScalarValue>;
}
pub trait FieldAsync< pub trait FieldAsync<
TypeInfo: ?Sized, TypeInfo: ?Sized,
Context: ?Sized, Context: ?Sized,
@ -116,6 +132,22 @@ pub trait FieldAsync<
) -> BoxFuture<'r, ExecutionResult<ScalarValue>>; ) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
} }
pub trait StaticFieldAsync<
const N: reflect::FieldName,
TypeInfo: ?Sized,
Context: ?Sized,
ScalarValue,
Behavior: ?Sized = behavior::Standard,
>
{
fn resolve_static_field_async<'r>(
&'r self,
arguments: &'r Arguments<ScalarValue>,
type_info: &'r TypeInfo,
executor: &'r Executor<Context, ScalarValue>,
) -> BoxFuture<'r, ExecutionResult<ScalarValue>>;
}
pub trait ToInputValue<ScalarValue, Behavior: ?Sized = behavior::Standard> { pub trait ToInputValue<ScalarValue, Behavior: ?Sized = behavior::Standard> {
fn to_input_value(&self) -> graphql::InputValue<ScalarValue>; fn to_input_value(&self) -> graphql::InputValue<ScalarValue>;
} }