Impl new machinery for array
This commit is contained in:
parent
d648181b26
commit
17af7c5ac5
2 changed files with 231 additions and 48 deletions
|
@ -1,118 +1,301 @@
|
||||||
//! GraphQL implementation for [array].
|
//! GraphQL implementation for [array].
|
||||||
//!
|
//!
|
||||||
//! [array]: primitive@std::array
|
//! [array]: prim@array
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
mem::{self, MaybeUninit},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
behavior,
|
||||||
executor::{ExecutionResult, Executor, Registry},
|
executor::{ExecutionResult, Executor, Registry},
|
||||||
graphql, reflect, resolve,
|
graphql, reflect, resolve,
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
BoxFuture, Selection,
|
BoxFuture, FieldError, IntoFieldError, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::iter;
|
use super::iter;
|
||||||
|
|
||||||
/*
|
impl<T, TI, SV, BH, const N: usize> resolve::Type<TI, SV, BH> for [T; N]
|
||||||
impl<T, Info, S, const N: usize> resolve::Type<Info, S> for [T; N]
|
|
||||||
where
|
where
|
||||||
T: resolve::Type<Info, S>,
|
T: resolve::Type<TI, SV, BH>,
|
||||||
Info: ?Sized,
|
TI: ?Sized,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
fn meta<'r>(registry: &mut Registry<'r, S>, info: &Info) -> MetaType<'r, S>
|
fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
|
||||||
where
|
where
|
||||||
S: 'r,
|
SV: 'r,
|
||||||
{
|
{
|
||||||
registry
|
registry.wrap_list::<behavior::Coerce<T, BH>, _>(type_info, None)
|
||||||
.build_list_type_new::<T, _>(info, Some(N))
|
|
||||||
.into_meta()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Info, Ctx, S, const N: usize> resolve::Value<Info, Ctx, S> for [T; N]
|
impl<T, TI, CX, SV, BH, const N: usize> resolve::Value<TI, CX, SV, BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: resolve::Value<Info, Ctx, S>,
|
T: resolve::Value<TI, CX, SV, BH>,
|
||||||
Info: ?Sized,
|
TI: ?Sized,
|
||||||
Ctx: ?Sized,
|
CX: ?Sized,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
fn resolve_value(
|
fn resolve_value(
|
||||||
&self,
|
&self,
|
||||||
selection_set: Option<&[Selection<'_, S>]>,
|
selection_set: Option<&[Selection<'_, SV>]>,
|
||||||
info: &Info,
|
type_info: &TI,
|
||||||
executor: &Executor<Ctx, S>,
|
executor: &Executor<CX, SV>,
|
||||||
) -> ExecutionResult<S> {
|
) -> ExecutionResult<SV> {
|
||||||
iter::resolve_list(self.iter(), selection_set, info, executor)
|
iter::resolve_list(self.iter(), selection_set, type_info, executor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Info, Ctx, S, const N: usize> resolve::ValueAsync<Info, Ctx, S> for [T; N]
|
impl<T, TI, CX, SV, BH, const N: usize> resolve::ValueAsync<TI, CX, SV, BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: resolve::ValueAsync<Info, Ctx, S> + Sync,
|
T: resolve::ValueAsync<TI, CX, SV, BH> + Sync,
|
||||||
Info: Sync + ?Sized,
|
TI: Sync + ?Sized,
|
||||||
Ctx: Sync + ?Sized,
|
CX: Sync + ?Sized,
|
||||||
S: Send + Sync,
|
SV: Send + Sync,
|
||||||
|
BH: ?Sized + 'static, // TODO: Lift `'static` bound if possible.
|
||||||
{
|
{
|
||||||
fn resolve_value_async<'r>(
|
fn resolve_value_async<'r>(
|
||||||
&'r self,
|
&'r self,
|
||||||
selection_set: Option<&'r [Selection<'_, S>]>,
|
selection_set: Option<&'r [Selection<'_, SV>]>,
|
||||||
info: &'r Info,
|
type_info: &'r TI,
|
||||||
executor: &'r Executor<Ctx, S>,
|
executor: &'r Executor<CX, SV>,
|
||||||
) -> BoxFuture<'r, ExecutionResult<S>> {
|
) -> BoxFuture<'r, ExecutionResult<SV>> {
|
||||||
Box::pin(iter::resolve_list_async(
|
Box::pin(iter::resolve_list_async(
|
||||||
self.iter(),
|
self.iter(),
|
||||||
selection_set,
|
selection_set,
|
||||||
info,
|
type_info,
|
||||||
executor,
|
executor,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, const N: usize> resolve::ToInputValue<S> for [T; N]
|
impl<T, SV, BH, const N: usize> resolve::ToInputValue<SV, BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: resolve::ToInputValue<S>,
|
T: resolve::ToInputValue<SV, BH>,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
fn to_input_value(&self) -> graphql::InputValue<S> {
|
fn to_input_value(&self) -> graphql::InputValue<SV> {
|
||||||
graphql::InputValue::list(self.iter().map(T::to_input_value))
|
graphql::InputValue::list(self.iter().map(T::to_input_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
impl<'i, T, SV, BH, const N: usize> resolve::InputValue<'i, SV, BH> for [T; N]
|
||||||
impl<'i, T, Info, S, const N: usize> graphql::InputType<'i, Info, S> for [T; N]
|
|
||||||
where
|
where
|
||||||
T: graphql::InputType<'i, Info, S>,
|
T: resolve::InputValue<'i, SV, BH>,
|
||||||
Info: ?Sized,
|
SV: 'i,
|
||||||
|
BH: ?Sized,
|
||||||
|
{
|
||||||
|
type Error = TryFromInputValueError<T::Error>;
|
||||||
|
|
||||||
|
fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
|
||||||
|
struct PartiallyInitializedArray<T, const N: usize> {
|
||||||
|
arr: [MaybeUninit<T>; N],
|
||||||
|
init_len: usize,
|
||||||
|
no_drop: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Drop for PartiallyInitializedArray<T, N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.no_drop {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Dropping a `MaybeUninit` does nothing, thus we need to drop
|
||||||
|
// the initialized elements manually, otherwise we may introduce
|
||||||
|
// a memory/resource leak if `T: Drop`.
|
||||||
|
for elem in &mut self.arr[0..self.init_len] {
|
||||||
|
// SAFETY: This is safe, because `self.init_len` represents
|
||||||
|
// the number of the initialized elements exactly.
|
||||||
|
unsafe {
|
||||||
|
ptr::drop_in_place(elem.as_mut_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match v {
|
||||||
|
graphql::InputValue::List(ls) => {
|
||||||
|
if ls.len() != N {
|
||||||
|
return Err(TryFromInputValueError::WrongCount {
|
||||||
|
actual: ls.len(),
|
||||||
|
expected: N,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if N == 0 {
|
||||||
|
// TODO: Use `mem::transmute` instead of
|
||||||
|
// `mem::transmute_copy` below, once it's allowed
|
||||||
|
// for const generics:
|
||||||
|
// https://github.com/rust-lang/rust/issues/61956
|
||||||
|
// SAFETY: `mem::transmute_copy` is safe here, because we
|
||||||
|
// check `N` to be `0`. It's no-op, actually.
|
||||||
|
return Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: The reason we're using a wrapper struct implementing
|
||||||
|
// `Drop` here is to be panic safe:
|
||||||
|
// `T: resolve::InputValue` implementation is not
|
||||||
|
// controlled by us, so calling
|
||||||
|
// `T::try_from_input_value(&i.item)` below may cause a
|
||||||
|
// panic when our array is initialized only partially.
|
||||||
|
// In such situation we need to drop already initialized
|
||||||
|
// values to avoid possible memory/resource leaks if
|
||||||
|
// `T: Drop`.
|
||||||
|
let mut out = PartiallyInitializedArray::<T, N> {
|
||||||
|
// SAFETY: The `.assume_init()` here is safe, because the
|
||||||
|
// type we are claiming to have initialized here is
|
||||||
|
// a bunch of `MaybeUninit`s, which do not require
|
||||||
|
// any initialization.
|
||||||
|
arr: unsafe { MaybeUninit::uninit().assume_init() },
|
||||||
|
init_len: 0,
|
||||||
|
no_drop: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut items = ls.iter().map(|i| T::try_from_input_value(&i.item));
|
||||||
|
for elem in &mut out.arr[..] {
|
||||||
|
if let Some(i) = items
|
||||||
|
.next()
|
||||||
|
.transpose()
|
||||||
|
.map_err(TryFromInputValueError::Item)?
|
||||||
|
{
|
||||||
|
*elem = MaybeUninit::new(i);
|
||||||
|
out.init_len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not drop collected `items`, because we're going to return
|
||||||
|
// them.
|
||||||
|
out.no_drop = true;
|
||||||
|
|
||||||
|
// TODO: Use `mem::transmute` instead of `mem::transmute_copy`
|
||||||
|
// below, once it's allowed for const generics:
|
||||||
|
// https://github.com/rust-lang/rust/issues/61956
|
||||||
|
// SAFETY: `mem::transmute_copy` is safe here, because we have
|
||||||
|
// exactly `N` initialized `items`.
|
||||||
|
// Also, despite `mem::transmute_copy` copies the value,
|
||||||
|
// we won't have a double-free when `T: Drop` here,
|
||||||
|
// because original array elements are `MaybeUninit`, so
|
||||||
|
// do nothing on `Drop`.
|
||||||
|
Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
|
||||||
|
}
|
||||||
|
// See "Input Coercion" on List types:
|
||||||
|
// https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
|
||||||
|
graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
|
||||||
|
other => T::try_from_input_value(other)
|
||||||
|
.map_err(TryFromInputValueError::Item)
|
||||||
|
.and_then(|e: T| {
|
||||||
|
// TODO: Use `mem::transmute` instead of
|
||||||
|
// `mem::transmute_copy` below, once it's allowed
|
||||||
|
// for const generics:
|
||||||
|
// https://github.com/rust-lang/rust/issues/61956
|
||||||
|
if N == 1 {
|
||||||
|
// SAFETY: `mem::transmute_copy` is safe here, because
|
||||||
|
// we check `N` to be `1`. Also, despite
|
||||||
|
// `mem::transmute_copy` copies the value, we
|
||||||
|
// won't have a double-free when `T: Drop` here,
|
||||||
|
// because original `e: T` value is wrapped into
|
||||||
|
// `mem::ManuallyDrop`, so does nothing on
|
||||||
|
// `Drop`.
|
||||||
|
Ok(unsafe { mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)]) })
|
||||||
|
} else {
|
||||||
|
Err(TryFromInputValueError::WrongCount {
|
||||||
|
actual: 1,
|
||||||
|
expected: N,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i, T, TI, SV, BH, const N: usize> graphql::InputType<'i, TI, SV, BH> for [T; N]
|
||||||
|
where
|
||||||
|
T: graphql::InputType<'i, TI, SV, BH>,
|
||||||
|
TI: ?Sized,
|
||||||
|
SV: 'i,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
fn assert_input_type() {
|
fn assert_input_type() {
|
||||||
T::assert_input_type()
|
T::assert_input_type()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
impl<T, S, const N: usize> graphql::OutputType<S> for [T; N]
|
impl<T, TI, CX, SV, BH, const N: usize> graphql::OutputType<TI, CX, SV, BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: graphql::OutputType<S>,
|
T: graphql::OutputType<TI, CX, SV, BH>,
|
||||||
|
TI: ?Sized,
|
||||||
|
CX: ?Sized,
|
||||||
|
BH: ?Sized,
|
||||||
|
Self: resolve::ValueAsync<TI, CX, SV, BH>,
|
||||||
{
|
{
|
||||||
fn assert_output_type() {
|
fn assert_output_type() {
|
||||||
T::assert_output_type()
|
T::assert_output_type()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, const N: usize> reflect::BaseType<S> for [T; N]
|
impl<T, BH, const N: usize> reflect::BaseType<BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: reflect::BaseType<S>,
|
T: reflect::BaseType<BH>,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
const NAME: reflect::Type = T::NAME;
|
const NAME: reflect::Type = T::NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, const N: usize> reflect::BaseSubTypes<S> for [T; N]
|
impl<T, BH, const N: usize> reflect::BaseSubTypes<BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: reflect::BaseSubTypes<S>,
|
T: reflect::BaseSubTypes<BH>,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
const NAMES: reflect::Types = T::NAMES;
|
const NAMES: reflect::Types = T::NAMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, const N: usize> reflect::WrappedType<S> for [T; N]
|
impl<T, BH, const N: usize> reflect::WrappedType<BH> for [T; N]
|
||||||
where
|
where
|
||||||
T: reflect::WrappedType<S>,
|
T: reflect::WrappedType<BH>,
|
||||||
|
BH: ?Sized,
|
||||||
{
|
{
|
||||||
const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
|
const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
/// Possible errors of converting a [`graphql::InputValue`] into an exact-size
|
||||||
|
/// [array](prim@array).
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum TryFromInputValueError<E> {
|
||||||
|
/// [`graphql::InputValue`] cannot be [`Null`].
|
||||||
|
///
|
||||||
|
/// See ["Combining List and Non-Null" section of spec][0].
|
||||||
|
///
|
||||||
|
/// [`Null`]: [`InputValue::Null`]
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
|
||||||
|
IsNull,
|
||||||
|
|
||||||
|
/// Wrong count of items.
|
||||||
|
WrongCount {
|
||||||
|
/// Actual count of items.
|
||||||
|
actual: usize,
|
||||||
|
|
||||||
|
/// Expected count of items ([array](prim@array) size).
|
||||||
|
expected: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Error of converting a [`graphql::InputValue::List`]'s item.
|
||||||
|
Item(E),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, SV> IntoFieldError<SV> for TryFromInputValueError<E>
|
||||||
|
where
|
||||||
|
E: IntoFieldError<SV>,
|
||||||
|
{
|
||||||
|
fn into_field_error(self) -> FieldError<SV> {
|
||||||
|
const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
|
||||||
|
match self {
|
||||||
|
Self::IsNull => format!("{}: Value cannot be `null`", ERROR_PREFIX).into(),
|
||||||
|
Self::WrongCount { actual, expected } => format!(
|
||||||
|
"{}: wrong elements count: {} instead of {}",
|
||||||
|
ERROR_PREFIX, actual, expected,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
Self::Item(s) => s.into_field_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod arc;
|
mod arc;
|
||||||
mod array;
|
pub mod array;
|
||||||
mod r#box;
|
mod r#box;
|
||||||
pub mod iter;
|
pub mod iter;
|
||||||
mod nullable;
|
mod nullable;
|
||||||
|
|
Loading…
Reference in a new issue