async-graphql/src/context.rs

685 lines
20 KiB
Rust
Raw Normal View History

//! Query context.
use std::any::{Any, TypeId};
use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
2020-11-30 08:17:32 +00:00
use async_graphql_value::Value as InputValue;
use fnv::FnvHashMap;
2020-10-15 06:38:10 +00:00
use serde::de::{Deserialize, Deserializer};
2020-10-15 06:39:53 +00:00
use serde::ser::{SerializeSeq, Serializer};
2020-10-15 06:38:10 +00:00
use serde::Serialize;
2020-05-22 03:58:49 +00:00
use crate::extensions::Extensions;
2020-09-08 08:30:29 +00:00
use crate::parser::types::{
2020-11-30 08:17:32 +00:00
Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
};
2020-09-06 05:38:31 +00:00
use crate::schema::SchemaEnv;
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
use crate::{
Error, InputType, Lookahead, Name, Pos, Positioned, Result, ServerError, ServerResult,
2020-10-10 02:32:43 +00:00
UploadValue, Value,
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
};
2020-03-01 10:54:34 +00:00
2020-09-06 05:38:31 +00:00
/// Variables of a query.
2020-10-15 06:38:10 +00:00
#[derive(Debug, Clone, Default, Serialize)]
2020-09-12 16:07:46 +00:00
#[serde(transparent)]
pub struct Variables(pub BTreeMap<Name, Value>);
2020-03-01 10:54:34 +00:00
impl Display for Variables {
2020-09-06 05:38:31 +00:00
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("{")?;
for (i, (name, value)) in self.0.iter().enumerate() {
write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?;
2020-03-14 03:46:20 +00:00
}
2020-09-06 05:38:31 +00:00
f.write_str("}")
2020-03-05 00:39:56 +00:00
}
}
2020-10-15 06:38:10 +00:00
impl<'de> Deserialize<'de> for Variables {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2020-10-15 06:39:53 +00:00
Ok(Self(
<Option<BTreeMap<Name, Value>>>::deserialize(deserializer)?.unwrap_or_default(),
))
2020-10-15 06:38:10 +00:00
}
}
2020-03-04 03:51:42 +00:00
impl Variables {
2020-09-12 16:07:46 +00:00
/// Get the variables from a GraphQL value.
///
2020-09-12 16:07:46 +00:00
/// If the value is not a map, then no variables will be returned.
#[must_use]
pub fn from_value(value: Value) -> Self {
match value {
Value::Object(obj) => Self(obj),
_ => Self::default(),
2020-03-04 03:51:42 +00:00
}
}
2020-03-14 03:46:20 +00:00
2020-09-12 16:07:46 +00:00
/// Get the values from a JSON value.
///
/// If the value is not a map or the keys of a map are not valid GraphQL names, then no
/// variables will be returned.
#[must_use]
pub fn from_json(value: serde_json::Value) -> Self {
Value::from_json(value)
.map(Self::from_value)
.unwrap_or_default()
}
/// Get the variables as a GraphQL value.
#[must_use]
pub fn into_value(self) -> Value {
Value::Object(self.0)
}
2020-09-06 05:38:31 +00:00
pub(crate) fn variable_path(&mut self, path: &str) -> Option<&mut Value> {
let mut parts = path.strip_prefix("variables.")?.split('.');
2020-03-14 03:46:20 +00:00
2020-09-06 05:38:31 +00:00
let initial = self.0.get_mut(parts.next().unwrap())?;
parts.try_fold(initial, |current, part| match current {
Value::List(list) => part
.parse::<u32>()
.ok()
.and_then(|idx| usize::try_from(idx).ok())
.and_then(move |idx| list.get_mut(idx)),
Value::Object(obj) => obj.get_mut(part),
_ => None,
})
2020-03-14 03:46:20 +00:00
}
}
2020-09-12 16:07:46 +00:00
impl From<Variables> for Value {
fn from(variables: Variables) -> Self {
variables.into_value()
}
}
2020-09-06 05:38:31 +00:00
/// Schema/Context data.
2020-09-12 16:07:46 +00:00
///
/// This is a type map, allowing you to store anything inside it.
2020-03-01 10:54:34 +00:00
#[derive(Default)]
2020-04-23 13:36:04 +00:00
pub struct Data(FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>);
impl Deref for Data {
type Target = FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
2020-03-01 10:54:34 +00:00
impl Data {
2020-09-12 16:07:46 +00:00
/// Insert data.
2020-03-01 10:54:34 +00:00
pub fn insert<D: Any + Send + Sync>(&mut self, data: D) {
self.0.insert(TypeId::of::<D>(), Box::new(data));
}
}
2020-09-12 16:07:46 +00:00
impl Debug for Data {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("Data").finish()
}
}
2020-03-20 03:56:08 +00:00
/// Context for `SelectionSet`
2020-05-10 02:59:51 +00:00
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a Positioned<SelectionSet>>;
2020-03-09 10:05:52 +00:00
2020-03-20 03:56:08 +00:00
/// Context object for resolve field
2020-05-10 02:59:51 +00:00
pub type Context<'a> = ContextBase<'a, &'a Positioned<Field>>;
2020-03-01 10:54:34 +00:00
/// A segment in the path to the current query.
///
/// This is a borrowed form of [`PathSegment`](enum.PathSegment.html) used during execution instead
/// of passed back when errors occur.
2020-10-15 06:38:10 +00:00
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(untagged)]
2020-03-26 10:30:29 +00:00
pub enum QueryPathSegment<'a> {
/// We are currently resolving an element in a list.
2020-03-26 03:34:28 +00:00
Index(usize),
/// We are currently resolving a field in an object.
2020-03-26 10:30:29 +00:00
Name(&'a str),
2020-03-26 03:34:28 +00:00
}
/// A path to the current query.
///
/// The path is stored as a kind of reverse linked list.
#[derive(Debug, Clone, Copy)]
2020-03-26 10:30:29 +00:00
pub struct QueryPathNode<'a> {
/// The parent node to this, if there is one.
2020-03-26 10:30:29 +00:00
pub parent: Option<&'a QueryPathNode<'a>>,
2020-03-26 03:34:28 +00:00
/// The current path segment being resolved.
2020-03-26 10:30:29 +00:00
pub segment: QueryPathSegment<'a>,
2020-03-26 03:34:28 +00:00
}
2020-07-15 10:05:24 +00:00
impl<'a> serde::Serialize for QueryPathNode<'a> {
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2020-07-15 10:05:24 +00:00
let mut seq = serializer.serialize_seq(None)?;
2020-10-15 06:38:10 +00:00
self.try_for_each(|segment| seq.serialize_element(segment))?;
2020-07-15 10:05:24 +00:00
seq.end()
}
}
2020-09-06 05:38:31 +00:00
impl<'a> Display for QueryPathNode<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut first = true;
2020-10-15 06:38:10 +00:00
self.try_for_each(|segment| {
if !first {
2020-10-15 06:38:10 +00:00
write!(f, ".")?;
}
2020-10-15 06:38:10 +00:00
first = false;
match segment {
2020-10-15 06:39:53 +00:00
QueryPathSegment::Index(idx) => write!(f, "{}", *idx),
QueryPathSegment::Name(name) => write!(f, "{}", name),
}
2020-10-15 06:38:10 +00:00
})
}
}
2020-03-26 10:30:29 +00:00
impl<'a> QueryPathNode<'a> {
2020-09-09 10:42:10 +00:00
/// Get the current field name.
///
/// This traverses all the parents of the node until it finds one that is a field name.
2020-09-09 10:42:10 +00:00
pub fn field_name(&self) -> &str {
2020-11-23 04:43:31 +00:00
std::iter::once(self)
.chain(self.parents())
2020-10-15 05:56:17 +00:00
.find_map(|node| match node.segment {
QueryPathSegment::Name(name) => Some(name),
QueryPathSegment::Index(_) => None,
})
.unwrap()
2020-03-26 03:34:28 +00:00
}
/// Get the path represented by `Vec<String>`; numbers will be stringified.
#[must_use]
pub fn to_string_vec(&self) -> Vec<String> {
let mut res = Vec::new();
2020-10-15 05:56:17 +00:00
self.for_each(|s| {
res.push(match s {
QueryPathSegment::Name(name) => (*name).to_string(),
2020-10-15 05:56:17 +00:00
QueryPathSegment::Index(idx) => idx.to_string(),
2020-10-15 06:38:10 +00:00
});
2020-10-15 05:56:17 +00:00
});
res
}
/// Iterate over the parents of the node.
pub fn parents(&self) -> Parents<'_> {
Parents(self)
}
2020-03-26 10:30:29 +00:00
pub(crate) fn for_each<F: FnMut(&QueryPathSegment<'a>)>(&self, mut f: F) {
2020-10-15 06:38:10 +00:00
let _ = self.try_for_each::<std::convert::Infallible, _>(|segment| {
f(segment);
Ok(())
});
}
pub(crate) fn try_for_each<E, F: FnMut(&QueryPathSegment<'a>) -> Result<(), E>>(
&self,
mut f: F,
) -> Result<(), E> {
self.try_for_each_ref(&mut f)
2020-03-26 03:34:28 +00:00
}
2020-10-15 06:38:10 +00:00
fn try_for_each_ref<E, F: FnMut(&QueryPathSegment<'a>) -> Result<(), E>>(
&self,
f: &mut F,
) -> Result<(), E> {
2020-03-26 10:30:29 +00:00
if let Some(parent) = &self.parent {
2020-10-15 06:38:10 +00:00
parent.try_for_each_ref(f)?;
2020-03-26 03:34:28 +00:00
}
2020-10-15 06:38:10 +00:00
f(&self.segment)
2020-03-26 03:34:28 +00:00
}
}
/// An iterator over the parents of a [`QueryPathNode`](struct.QueryPathNode.html).
#[derive(Debug, Clone)]
pub struct Parents<'a>(&'a QueryPathNode<'a>);
impl<'a> Parents<'a> {
/// Get the current query path node, which the next call to `next` will get the parents of.
#[must_use]
pub fn current(&self) -> &'a QueryPathNode<'a> {
self.0
}
}
impl<'a> Iterator for Parents<'a> {
type Item = &'a QueryPathNode<'a>;
fn next(&mut self) -> Option<Self::Item> {
let parent = self.0.parent;
if let Some(parent) = parent {
self.0 = parent;
}
parent
}
}
impl<'a> std::iter::FusedIterator for Parents<'a> {}
2020-10-15 06:38:10 +00:00
/// The unique id of the current resolution.
#[derive(Debug, Clone, Copy)]
2020-04-28 07:01:19 +00:00
pub struct ResolveId {
/// The unique ID of the parent resolution.
2020-04-28 07:01:19 +00:00
pub parent: Option<usize>,
/// The current unique id.
2020-04-28 07:01:19 +00:00
pub current: usize,
}
impl ResolveId {
2020-09-26 07:52:59 +00:00
#[doc(hidden)]
pub fn root() -> ResolveId {
2020-04-28 07:01:19 +00:00
ResolveId {
parent: None,
current: 0,
}
}
}
2020-09-06 05:38:31 +00:00
impl Display for ResolveId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2020-04-28 07:01:19 +00:00
if let Some(parent) = self.parent {
write!(f, "{}:{}", parent, self.current)
} else {
write!(f, "{}", self.current)
}
}
}
/// Query context.
///
/// **This type is not stable and should not be used directly.**
2020-03-25 03:39:28 +00:00
#[derive(Clone)]
2020-03-03 03:48:00 +00:00
pub struct ContextBase<'a, T> {
2020-10-16 06:49:22 +00:00
/// The current path node being resolved.
pub path_node: Option<QueryPathNode<'a>>,
2020-04-28 07:01:19 +00:00
pub(crate) resolve_id: ResolveId,
pub(crate) inc_resolve_id: &'a AtomicUsize,
2020-05-20 00:18:28 +00:00
#[doc(hidden)]
pub item: T,
#[doc(hidden)]
pub schema_env: &'a SchemaEnv,
#[doc(hidden)]
pub query_env: &'a QueryEnv,
2020-03-01 10:54:34 +00:00
}
#[doc(hidden)]
pub struct QueryEnvInner {
2020-10-12 06:49:32 +00:00
pub extensions: Extensions,
pub variables: Variables,
pub operation: Positioned<OperationDefinition>,
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
2020-10-10 02:32:43 +00:00
pub uploads: Vec<UploadValue>,
pub ctx_data: Arc<Data>,
}
#[doc(hidden)]
#[derive(Clone)]
pub struct QueryEnv(Arc<QueryEnvInner>);
impl Deref for QueryEnv {
type Target = QueryEnvInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl QueryEnv {
#[doc(hidden)]
2020-09-29 12:47:37 +00:00
pub fn new(inner: QueryEnvInner) -> QueryEnv {
QueryEnv(Arc::new(inner))
}
#[doc(hidden)]
pub fn create_context<'a, T>(
&'a self,
schema_env: &'a SchemaEnv,
path_node: Option<QueryPathNode<'a>>,
item: T,
2020-09-26 07:52:59 +00:00
resolve_id: ResolveId,
2020-04-28 07:01:19 +00:00
inc_resolve_id: &'a AtomicUsize,
) -> ContextBase<'a, T> {
ContextBase {
path_node,
2020-09-26 07:52:59 +00:00
resolve_id,
2020-04-28 07:01:19 +00:00
inc_resolve_id,
item,
schema_env,
query_env: self,
}
}
}
2020-03-03 03:48:00 +00:00
impl<'a, T> ContextBase<'a, T> {
2020-09-26 07:52:59 +00:00
#[doc(hidden)]
pub fn get_child_resolve_id(&self) -> ResolveId {
2020-04-28 07:01:19 +00:00
let id = self
.inc_resolve_id
2020-03-26 03:34:28 +00:00
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
2020-04-28 07:01:19 +00:00
+ 1;
ResolveId {
parent: Some(self.resolve_id.current),
current: id,
}
2020-03-26 03:34:28 +00:00
}
#[doc(hidden)]
2020-05-10 02:59:51 +00:00
pub fn with_field(
&'a self,
field: &'a Positioned<Field>,
) -> ContextBase<'a, &'a Positioned<Field>> {
2020-03-26 03:34:28 +00:00
ContextBase {
2020-03-26 10:30:29 +00:00
path_node: Some(QueryPathNode {
parent: self.path_node.as_ref(),
2020-09-06 05:38:31 +00:00
segment: QueryPathSegment::Name(&field.node.response_key().node),
2020-03-26 10:30:29 +00:00
}),
2020-03-26 03:34:28 +00:00
item: field,
2020-04-28 07:01:19 +00:00
resolve_id: self.get_child_resolve_id(),
inc_resolve_id: self.inc_resolve_id,
schema_env: self.schema_env,
query_env: self.query_env,
2020-03-26 03:34:28 +00:00
}
}
#[doc(hidden)]
pub fn with_selection_set(
&self,
2020-05-10 02:59:51 +00:00
selection_set: &'a Positioned<SelectionSet>,
) -> ContextBase<'a, &'a Positioned<SelectionSet>> {
2020-03-03 03:48:00 +00:00
ContextBase {
path_node: self.path_node,
2020-03-26 03:34:28 +00:00
item: selection_set,
resolve_id: self.resolve_id,
2020-04-28 07:01:19 +00:00
inc_resolve_id: &self.inc_resolve_id,
schema_env: self.schema_env,
query_env: self.query_env,
2020-03-01 10:54:34 +00:00
}
}
2020-03-31 03:19:18 +00:00
/// Gets the global data defined in the `Context` or `Schema`.
2020-05-22 06:02:28 +00:00
///
/// If both `Schema` and `Query` have the same data type, the data in the `Query` is obtained.
///
2020-09-06 05:38:31 +00:00
/// # Errors
///
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
/// Returns a `Error` if the specified type data does not exist.
pub fn data<D: Any + Send + Sync>(&self) -> Result<&D> {
self.data_opt::<D>().ok_or_else(|| {
Error::new(format!(
"Data `{}` does not exist.",
std::any::type_name::<D>()
))
})
}
/// Gets the global data defined in the `Context` or `Schema`.
///
2020-05-22 06:02:28 +00:00
/// # Panics
///
/// It will panic if the specified data type does not exist.
pub fn data_unchecked<D: Any + Send + Sync>(&self) -> &D {
self.data_opt::<D>()
.unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::<D>()))
}
2020-09-06 05:38:31 +00:00
/// Gets the global data defined in the `Context` or `Schema` or `None` if the specified type data does not exist.
pub fn data_opt<D: Any + Send + Sync>(&self) -> Option<&D> {
self.query_env
.ctx_data
.0
.get(&TypeId::of::<D>())
.or_else(|| self.schema_env.data.0.get(&TypeId::of::<D>()))
2020-03-05 00:39:56 +00:00
.and_then(|d| d.downcast_ref::<D>())
2020-03-01 10:54:34 +00:00
}
2020-03-05 07:50:57 +00:00
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
fn var_value(&self, name: &str, pos: Pos) -> ServerResult<Value> {
2020-09-06 05:38:31 +00:00
self.query_env
.operation
.node
2020-03-10 06:14:09 +00:00
.variable_definitions
2020-04-01 08:53:49 +00:00
.iter()
2020-09-06 05:38:31 +00:00
.find(|def| def.node.name.node == name)
.and_then(|def| {
self.query_env
.variables
.0
.get(&def.node.name.node)
.or_else(|| def.node.default_value())
})
.cloned()
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
.ok_or_else(|| ServerError::new(format!("Variable {} is not defined.", name)).at(pos))
2020-03-10 06:14:09 +00:00
}
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
fn resolve_input_value(&self, value: Positioned<InputValue>) -> ServerResult<Value> {
let pos = value.pos;
2020-09-08 08:30:29 +00:00
value
.node
.into_const_with(|name| self.var_value(&name, pos))
2020-03-04 03:51:42 +00:00
}
2020-08-06 06:52:54 +00:00
#[doc(hidden)]
pub fn is_ifdef(&self, directives: &[Positioned<Directive>]) -> bool {
2020-09-06 05:38:31 +00:00
directives
.iter()
.any(|directive| directive.node.name.node == "ifdef")
2020-08-06 06:52:54 +00:00
}
#[doc(hidden)]
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
pub fn is_skip(&self, directives: &[Positioned<Directive>]) -> ServerResult<bool> {
2020-03-05 09:06:14 +00:00
for directive in directives {
2020-09-06 05:38:31 +00:00
let include = match &*directive.node.name.node {
"skip" => false,
"include" => true,
_ => continue,
};
let condition_input = directive
2020-09-06 06:16:36 +00:00
.node
.get_argument("if")
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
.ok_or_else(|| ServerError::new(format!(r#"Directive @{} requires argument `if` of type `Boolean!` but it was not provided."#, if include { "include" } else { "skip" })).at(directive.pos))?
2020-09-06 06:16:36 +00:00
.clone();
2020-09-06 05:38:31 +00:00
let pos = condition_input.pos;
let condition_input = self.resolve_input_value(condition_input)?;
2020-09-06 05:38:31 +00:00
2020-09-08 08:30:29 +00:00
if include
!= <bool as InputType>::parse(Some(condition_input))
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
.map_err(|e| e.into_server_error().at(pos))?
2020-09-06 06:16:36 +00:00
{
2020-09-06 05:38:31 +00:00
return Ok(true);
}
}
Ok(false)
}
2020-03-01 10:54:34 +00:00
}
2020-03-05 09:06:14 +00:00
2020-05-10 02:59:51 +00:00
impl<'a> ContextBase<'a, &'a Positioned<SelectionSet>> {
2020-03-26 03:34:28 +00:00
#[doc(hidden)]
2020-05-10 02:59:51 +00:00
pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, &'a Positioned<SelectionSet>> {
2020-03-26 03:34:28 +00:00
ContextBase {
2020-03-26 10:30:29 +00:00
path_node: Some(QueryPathNode {
parent: self.path_node.as_ref(),
segment: QueryPathSegment::Index(idx),
}),
2020-03-26 03:34:28 +00:00
item: self.item,
2020-04-28 07:01:19 +00:00
resolve_id: self.get_child_resolve_id(),
inc_resolve_id: self.inc_resolve_id,
schema_env: self.schema_env,
query_env: self.query_env,
2020-03-26 03:34:28 +00:00
}
}
}
2020-05-10 02:59:51 +00:00
impl<'a> ContextBase<'a, &'a Positioned<Field>> {
2020-03-05 09:06:14 +00:00
#[doc(hidden)]
pub fn param_value<T: InputType>(
2020-03-05 09:06:14 +00:00
&self,
name: &str,
default: Option<fn() -> T>,
Rework errors This completely overhauls the error system used in async-graphql. - `Error` has been renamed to `ServerError` and `FieldError` has been renamed to just `Error`. This is because `FieldError` is by far the most common error that users will have to use so it makes sense to use the most obvious error name. Also, the current name didn't make sense as it was used for things other than field errors, such as the data callback for websockets. - `ServerError` has been made completely opaque. Before it was an enum of all the possible errors, but now it just contains an error message, the locations, the path and extensions. It is a shame that we lose information, it makes more sense as _conceptually_ GraphQL does not provide that information. It also frees us to change the internals of async-graphql a lot more. - The path of errors is no longer an opaque JSON value but a regular type, `Vec<PathSegment>`. The type duplication of `PathSegment` and `QueryPathSegment` is unfortunate, I plan to work on this in the future. - Now that `ServerError` is opaque, `RuleError` has been removed from the public API, making it simpler. - Additionally `QueryError` has been completely removed. Instead the error messages are constructed ad-hoc; I took care to never repeat an error message. - Instead of constructing field-not-found errors inside the implementations of field resolvers they now return `Option`s, where a `None` value is representative of the field not being found. - As an unfortunate consequence of the last change, self-referential types based on the output of a subscription resolver can no longer be created. This does not mean anything for users, but causes lifetime issues in the implementation of merged objects. I fixed it with a bit of a hack, but this'll have to be looked into further. - `InputValueError` now has a generic parameter - it's kind of weird but it's necessary for ergonomics. It also improves error messages. - The `ErrorExtensions` trait has been removed. I didn't think the `extend` method was necessary since `From` impls exist. But the ergonomics are still there with a new trait `ExtendError`, which is implemented for both errors and results. - `Response` now supports serializing multiple errors. This allows for nice things like having multiple validation errors not be awkwardly shoved into a single error. - When an error occurs in execution, data is sent as `null`. This is slightly more compliant with the spec but the algorithm described in <https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability> has yet to be implemented.
2020-09-29 19:06:44 +00:00
) -> ServerResult<T> {
2020-09-06 05:38:31 +00:00
let value = self.item.node.get_argument(name).cloned();
if value.is_none() {
if let Some(default) = default {
return Ok(default());
2020-03-05 09:06:14 +00:00
}
}
2020-09-06 05:38:31 +00:00
let (pos, value) = match value {
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
2020-09-06 05:38:31 +00:00
None => (Pos::default(), None),
};
InputType::parse(value).map_err(|e| e.into_server_error().at(pos))
2020-03-06 15:58:43 +00:00
}
2020-05-14 09:35:25 +00:00
2020-05-14 14:13:28 +00:00
/// Creates a uniform interface to inspect the forthcoming selections.
///
/// # Examples
///
/// ```no_run
/// use async_graphql::*;
///
/// #[derive(SimpleObject)]
2020-05-14 14:13:28 +00:00
/// struct Detail {
/// c: i32,
/// d: i32,
/// }
///
/// #[derive(SimpleObject)]
2020-05-14 14:13:28 +00:00
/// struct MyObj {
/// a: i32,
/// b: i32,
/// detail: Detail,
/// }
///
/// struct Query;
///
/// #[Object]
2020-05-14 14:13:28 +00:00
/// impl Query {
/// async fn obj(&self, ctx: &Context<'_>) -> MyObj {
/// if ctx.look_ahead().field("a").exists() {
/// // This is a query like `obj { a }`
/// } else if ctx.look_ahead().field("detail").field("c").exists() {
/// // This is a query like `obj { detail { c } }`
/// } else {
/// // This query doesn't have `a`
/// }
/// unimplemented!()
/// }
/// }
/// ```
pub fn look_ahead(&self) -> Lookahead {
Lookahead::new(&self.query_env.fragments, &self.item.node)
2020-05-14 14:13:28 +00:00
}
2020-11-30 08:17:32 +00:00
/// Get the current field.
///
/// # Examples
///
/// ```rust
/// use async_graphql::*;
///
/// #[derive(SimpleObject)]
/// struct MyObj {
/// a: i32,
/// b: i32,
/// c: i32,
/// }
///
/// pub struct Query;
///
/// #[Object]
/// impl Query {
/// async fn obj(&self, ctx: &Context<'_>) -> MyObj {
/// let fields = ctx.field().selection_set().map(|field| field.name()).collect::<Vec<_>>();
/// assert_eq!(fields, vec!["a", "b", "c"]);
/// MyObj { a: 1, b: 2, c: 3 }
/// }
/// }
///
/// async_std::task::block_on(async move {
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
/// assert!(schema.execute("{ obj { a b c }}").await.is_ok());
/// assert!(schema.execute("{ obj { a ... { b c } }}").await.is_ok());
/// assert!(schema.execute("{ obj { a ... BC }} fragment BC on MyObj { b c }").await.is_ok());
/// });
///
/// ```
pub fn field(&self) -> SelectionField<'a> {
SelectionField {
fragments: &self.query_env.fragments,
field: &self.item.node,
}
}
}
/// Selection field.
pub struct SelectionField<'a> {
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
field: &'a Field,
}
impl<'a> SelectionField<'a> {
/// Get the name of this field.
pub fn name(&self) -> &'a str {
self.field.name.node.as_str()
}
/// Get all subfields of the current selection set.
pub fn selection_set(&self) -> impl Iterator<Item = SelectionField<'a>> {
SelectionFieldsIter {
fragments: self.fragments,
iter: vec![self.field.selection_set.node.items.iter()],
}
}
}
struct SelectionFieldsIter<'a> {
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
iter: Vec<std::slice::Iter<'a, Positioned<Selection>>>,
}
impl<'a> Iterator for SelectionFieldsIter<'a> {
type Item = SelectionField<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let it = self.iter.last_mut()?;
match it.next() {
Some(selection) => match &selection.node {
Selection::Field(field) => {
return Some(SelectionField {
fragments: self.fragments,
field: &field.node,
});
}
Selection::FragmentSpread(fragment_spread) => {
if let Some(fragment) =
self.fragments.get(&fragment_spread.node.fragment_name.node)
{
self.iter
.push(fragment.node.selection_set.node.items.iter());
}
}
Selection::InlineFragment(inline_fragment) => {
self.iter
.push(inline_fragment.node.selection_set.node.items.iter());
}
},
None => {
self.iter.pop();
}
}
}
}
2020-03-05 09:06:14 +00:00
}