async-graphql/src/context.rs

513 lines
14 KiB
Rust
Raw Normal View History

2020-05-22 03:58:49 +00:00
use crate::extensions::Extensions;
2020-09-08 08:30:29 +00:00
use crate::parser::types::{
Directive, Field, FragmentDefinition, Name, OperationDefinition, SelectionSet,
Value as InputValue,
};
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, InputValueType, Lookahead, Pos, Positioned, Result, ServerError, ServerResult, Value,
};
2020-04-24 02:05:41 +00:00
use fnv::FnvHashMap;
2020-09-12 16:07:46 +00:00
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
2020-03-01 10:54:34 +00:00
use std::any::{Any, TypeId};
use std::collections::{BTreeMap, HashMap};
2020-09-06 05:38:31 +00:00
use std::convert::TryFrom;
2020-09-12 16:07:46 +00:00
use std::fmt::{self, Debug, Display, Formatter};
2020-09-06 05:38:31 +00:00
use std::ops::Deref;
2020-03-26 03:34:28 +00:00
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
2020-03-01 10:54:34 +00:00
2020-09-06 05:38:31 +00:00
/// Variables of a query.
2020-09-12 16:07:46 +00:00
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[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-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
2020-03-26 03:34:28 +00:00
/// The query path segment
#[derive(Clone)]
2020-03-26 10:30:29 +00:00
pub enum QueryPathSegment<'a> {
2020-03-26 03:34:28 +00:00
/// Index
Index(usize),
/// Field name
2020-03-26 10:30:29 +00:00
Name(&'a str),
2020-03-26 03:34:28 +00:00
}
2020-03-26 10:30:29 +00:00
/// The query path node
#[derive(Clone)]
pub struct QueryPathNode<'a> {
/// Parent node
pub parent: Option<&'a QueryPathNode<'a>>,
2020-03-26 03:34:28 +00:00
2020-03-26 10:30:29 +00:00
/// Current path segment
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)?;
self.for_each(|segment| match segment {
QueryPathSegment::Index(idx) => {
seq.serialize_element(&idx).ok();
}
QueryPathSegment::Name(name) => {
seq.serialize_element(name).ok();
}
});
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;
self.for_each(|segment| {
if !first {
write!(f, ".").ok();
}
match segment {
QueryPathSegment::Index(idx) => {
write!(f, "{}", *idx).ok();
}
QueryPathSegment::Name(name) => {
write!(f, "{}", name).ok();
}
}
first = false;
});
Ok(())
}
}
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.
pub fn field_name(&self) -> &str {
2020-03-26 10:30:29 +00:00
let mut p = self;
loop {
if let QueryPathSegment::Name(name) = &p.segment {
return name;
2020-03-26 03:34:28 +00:00
}
2020-03-26 10:30:29 +00:00
p = p.parent.unwrap();
2020-03-26 03:34:28 +00:00
}
}
2020-03-26 10:30:29 +00:00
pub(crate) fn for_each<F: FnMut(&QueryPathSegment<'a>)>(&self, mut f: F) {
self.for_each_ref(&mut f);
2020-03-26 03:34:28 +00:00
}
2020-03-26 10:30:29 +00:00
fn for_each_ref<F: FnMut(&QueryPathSegment<'a>)>(&self, f: &mut F) {
if let Some(parent) = &self.parent {
parent.for_each_ref(f);
2020-03-26 03:34:28 +00:00
}
2020-03-26 10:30:29 +00:00
f(&self.segment);
2020-03-26 03:34:28 +00:00
}
}
2020-04-28 07:01:19 +00:00
/// Represents the unique id of the resolve
#[derive(Copy, Clone, Debug)]
pub struct ResolveId {
/// Parent id
pub parent: Option<usize>,
/// Current id
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)
}
}
}
2020-03-20 03:56:08 +00:00
/// Query context
2020-03-25 03:39:28 +00:00
#[derive(Clone)]
2020-03-03 03:48:00 +00:00
pub struct ContextBase<'a, T> {
#[allow(missing_docs)]
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 {
pub extensions: spin::Mutex<Extensions>,
pub variables: Variables,
pub operation: Positioned<OperationDefinition>,
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
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 {
2020-03-26 10:30:29 +00:00
path_node: self.path_node.clone(),
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 InputValueType>::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: InputValueType>(
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),
};
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
InputValueType::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-03-05 09:06:14 +00:00
}