Reworking base traits, vol.2
This commit is contained in:
parent
8235ac22c0
commit
97c88d219c
3 changed files with 843 additions and 296 deletions
|
@ -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
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue