#![doc = include_str!("../../examples/async_resolvers.rs")]
use crate::collections::HashMap;
use crate::executable;
use crate::executable::Operation;
#[cfg(doc)]
use crate::introspection;
use crate::request::coerce_variable_values;
use crate::request::RequestError;
use crate::resolvers::execution::execute_selection_set;
use crate::resolvers::execution::ExecutionContext;
use crate::resolvers::execution::ExecutionMode;
use crate::resolvers::execution::MaybeLazy;
use crate::resolvers::execution::PropagateNull;
use crate::response::ExecutionResponse;
use crate::response::JsonMap;
use crate::response::JsonValue;
use crate::schema;
use crate::schema::Implementers;
use crate::validation::Valid;
use crate::ExecutableDocument;
use crate::Name;
use crate::Schema;
use futures::future::BoxFuture;
use futures::stream::BoxStream;
use futures::FutureExt as _;
use std::sync::OnceLock;
mod execution;
pub(crate) mod input_coercion;
mod result_coercion;
pub struct Execution<'a> {
schema: &'a Valid<Schema>,
document: &'a Valid<ExecutableDocument>,
operation: Option<&'a Operation>,
implementers_map: Option<&'a HashMap<Name, Implementers>>,
variable_values: Option<VariableValues<'a>>,
enable_schema_introspection: Option<bool>,
}
const DEFAULT_ENABLE_SCHEMA_INTROSPECTION: bool = false;
enum VariableValues<'a> {
Raw(&'a JsonMap),
Coerced(&'a Valid<JsonMap>),
}
#[derive(Clone, Copy)]
pub(crate) enum MaybeAsync<A, S> {
Async(A),
Sync(S),
}
pub(crate) type MaybeAsyncObject<'a> = MaybeAsync<&'a dyn AsyncObjectValue, &'a dyn ObjectValue>;
pub(crate) type MaybeAsyncResolved<'a> = MaybeAsync<AsyncResolvedValue<'a>, ResolvedValue<'a>>;
pub struct ResolveInfo<'a> {
pub(crate) schema: &'a Valid<Schema>,
pub(crate) implementers_map: MaybeLazy<'a, HashMap<Name, Implementers>>,
pub(crate) document: &'a Valid<ExecutableDocument>,
pub(crate) fields: &'a [&'a executable::Field],
pub(crate) arguments: &'a JsonMap,
}
pub struct FieldError {
pub message: String,
}
pub trait ObjectValue {
fn type_name(&self) -> &str;
fn resolve_field<'a>(
&'a self,
info: &'a ResolveInfo<'a>,
) -> Result<ResolvedValue<'a>, FieldError>;
fn unknown_field_error(&self, info: &ResolveInfo<'_>) -> FieldError {
FieldError::unknown_field(info.field_name(), self.type_name())
}
}
pub trait AsyncObjectValue: Send {
fn type_name(&self) -> &str;
fn resolve_field<'a>(
&'a self,
info: &'a ResolveInfo<'a>,
) -> BoxFuture<'a, Result<AsyncResolvedValue<'a>, FieldError>>;
fn unknown_field_error(&self, info: &ResolveInfo<'_>) -> FieldError {
FieldError::unknown_field(info.field_name(), self.type_name())
}
}
pub enum ResolvedValue<'a> {
Leaf(JsonValue),
Object(Box<dyn ObjectValue + 'a>),
List(Box<dyn Iterator<Item = Result<Self, FieldError>> + 'a>),
SkipForPartialExecution,
}
pub enum AsyncResolvedValue<'a> {
Leaf(JsonValue),
Object(Box<dyn AsyncObjectValue + 'a>),
List(BoxStream<'a, Result<Self, FieldError>>),
SkipForPartialExecution,
}
impl<'a> Execution<'a> {
pub fn new(schema: &'a Valid<Schema>, document: &'a Valid<ExecutableDocument>) -> Self {
Self {
schema,
document,
operation: None,
implementers_map: None,
variable_values: None,
enable_schema_introspection: None,
}
}
pub fn operation(mut self, operation: &'a Operation) -> Self {
assert!(
self.operation.is_none(),
"operation to execute already provided"
);
self.operation = Some(operation);
self
}
pub fn operation_name(mut self, operation_name: Option<&str>) -> Result<Self, RequestError> {
assert!(
self.operation.is_none(),
"operation to execute already provided"
);
self.operation = Some(self.document.operations.get(operation_name)?);
Ok(self)
}
pub fn implementers_map(mut self, implementers_map: &'a HashMap<Name, Implementers>) -> Self {
assert!(
self.implementers_map.is_none(),
"implementers map already provided"
);
self.implementers_map = Some(implementers_map);
self
}
pub fn coerced_variable_values(mut self, variable_values: &'a Valid<JsonMap>) -> Self {
assert!(
self.variable_values.is_none(),
"variable values already provided"
);
self.variable_values = Some(VariableValues::Coerced(variable_values));
self
}
pub fn raw_variable_values(mut self, variable_values: &'a JsonMap) -> Self {
assert!(
self.variable_values.is_none(),
"variable values already provided"
);
self.variable_values = Some(VariableValues::Raw(variable_values));
self
}
pub fn enable_schema_introspection(mut self, enable_schema_introspection: bool) -> Self {
assert!(
self.enable_schema_introspection.is_none(),
"schema introspection already configured"
);
self.enable_schema_introspection = Some(enable_schema_introspection);
self
}
pub fn execute_sync(
&self,
initial_value: &dyn ObjectValue,
) -> Result<ExecutionResponse, RequestError> {
let future = self.execute_common(MaybeAsync::Sync(initial_value));
future
.now_or_never()
.expect("expected async fn with sync resolvers to never be pending")
}
pub async fn execute_async(
&self,
initial_value: &dyn AsyncObjectValue,
) -> Result<ExecutionResponse, RequestError> {
self.execute_common(MaybeAsync::Async(initial_value)).await
}
async fn execute_common(
&self,
initial_value: MaybeAsyncObject<'_>,
) -> Result<ExecutionResponse, RequestError> {
let operation = if let Some(op) = self.operation {
op
} else {
self.document.operations.get(None)?
};
let object_type_name = operation.object_type();
let Some(root_operation_object_type_def) = self.schema.get_object(object_type_name) else {
return Err(RequestError {
message: "Undefined root operation type".to_owned(),
location: object_type_name.location(),
is_suspected_validation_bug: true,
});
};
let map;
let variable_values = match self.variable_values {
None => {
map = Valid::assume_valid(JsonMap::new());
&map
}
Some(VariableValues::Raw(v)) => {
map = coerce_variable_values(self.schema, operation, v)?;
&map
}
Some(VariableValues::Coerced(v)) => v,
};
let lock;
let implementers_map = match self.implementers_map {
None => {
lock = OnceLock::new();
MaybeLazy::Lazy(&lock)
}
Some(map) => MaybeLazy::Eager(map),
};
let enable_schema_introspection = self
.enable_schema_introspection
.unwrap_or(DEFAULT_ENABLE_SCHEMA_INTROSPECTION);
let mut errors = Vec::new();
let mut context = ExecutionContext {
schema: self.schema,
document: self.document,
variable_values,
errors: &mut errors,
implementers_map,
enable_schema_introspection,
};
let mode = match operation.operation_type {
executable::OperationType::Query | executable::OperationType::Subscription => {
ExecutionMode::Normal
}
executable::OperationType::Mutation => ExecutionMode::Sequential,
};
let result = execute_selection_set(
&mut context,
None,
mode,
root_operation_object_type_def,
initial_value,
&operation.selection_set.selections,
)
.await;
let data = result
.inspect_err(|_: &PropagateNull| {})
.ok();
Ok(ExecutionResponse { data, errors })
}
}
impl<'a> ResolveInfo<'a> {
pub fn schema(&self) -> &'a Valid<Schema> {
self.schema
}
pub fn implementers_map(&self) -> &'a HashMap<Name, Implementers> {
match self.implementers_map {
MaybeLazy::Eager(map) => map,
MaybeLazy::Lazy(cell) => cell.get_or_init(|| self.schema.implementers_map()),
}
}
pub fn document(&self) -> &'a Valid<ExecutableDocument> {
self.document
}
pub fn field_name(&self) -> &'a str {
&self.fields[0].name
}
pub fn field_definition(&self) -> &'a schema::FieldDefinition {
&self.fields[0].definition
}
pub fn field_selections(&self) -> &'a [&'a executable::Field] {
self.fields
}
pub fn arguments(&self) -> &'a JsonMap {
self.arguments
}
}
impl<'a> ResolvedValue<'a> {
pub fn null() -> Self {
Self::Leaf(JsonValue::Null)
}
pub fn leaf(json: impl Into<JsonValue>) -> Self {
Self::Leaf(json.into())
}
pub fn object(object: impl ObjectValue + 'a) -> Self {
Self::Object(Box::new(object))
}
pub fn nullable_object(opt_object: Option<impl ObjectValue + 'a>) -> Self {
match opt_object {
Some(object) => Self::Object(Box::new(object)),
None => Self::null(),
}
}
pub fn list<I>(iter: I) -> Self
where
I: IntoIterator<Item = Self>,
I::IntoIter: 'a,
{
Self::List(Box::new(iter.into_iter().map(Ok)))
}
}
impl<'a> AsyncResolvedValue<'a> {
pub fn null() -> Self {
Self::Leaf(JsonValue::Null)
}
pub fn leaf(json: impl Into<JsonValue>) -> Self {
Self::Leaf(json.into())
}
pub fn object(object: impl AsyncObjectValue + 'a) -> Self {
Self::Object(Box::new(object))
}
pub fn nullable_object(opt_object: Option<impl AsyncObjectValue + 'a>) -> Self {
match opt_object {
Some(object) => Self::Object(Box::new(object)),
None => Self::null(),
}
}
pub fn list<I>(iter: I) -> Self
where
I: IntoIterator<Item = Self>,
I::IntoIter: 'a + Send,
{
Self::List(Box::pin(futures::stream::iter(iter.into_iter().map(Ok))))
}
}
impl MaybeAsync<Box<dyn AsyncObjectValue + '_>, Box<dyn ObjectValue + '_>> {
pub(crate) fn type_name(&self) -> &str {
match self {
MaybeAsync::Async(obj) => obj.type_name(),
MaybeAsync::Sync(obj) => obj.type_name(),
}
}
}
impl FieldError {
fn unknown_field(field_name: &str, type_name: &str) -> Self {
Self {
message: format!("unexpected field name: {field_name} in type {type_name}"),
}
}
}