async-graphql/src/context.rs

856 lines
26 KiB
Rust
Raw Normal View History

//! Query context.
2022-04-19 04:25:11 +00:00
use std::{
any::{Any, TypeId},
collections::HashMap,
fmt::{self, Debug, Display, Formatter},
ops::Deref,
sync::{Arc, Mutex},
};
use async_graphql_value::{Value as InputValue, Variables};
use fnv::FnvHashMap;
2022-04-19 04:25:11 +00:00
use http::{
header::{AsHeaderName, HeaderMap, IntoHeaderName},
HeaderValue,
};
use serde::{
ser::{SerializeSeq, Serializer},
Serialize,
};
2022-04-19 04:25:11 +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
use crate::{
2022-04-19 04:25:11 +00:00
extensions::Extensions,
parser::types::{
Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
},
schema::{IntrospectionMode, SchemaEnv},
Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result,
ServerError, ServerResult, 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
2022-01-18 09:49:47 +00:00
/// Data related functions of the context.
pub trait DataContext<'a> {
/// Gets the global data defined in the `Context` or `Schema`.
///
2022-04-19 04:25:11 +00:00
/// If both `Schema` and `Query` have the same data type, the data in the
/// `Query` is obtained.
2022-01-18 09:49:47 +00:00
///
/// # Errors
///
/// Returns a `Error` if the specified type data does not exist.
fn data<D: Any + Send + Sync>(&self) -> Result<&'a D>;
/// Gets the global data defined in the `Context` or `Schema`.
///
/// # Panics
///
/// It will panic if the specified data type does not exist.
fn data_unchecked<D: Any + Send + Sync>(&self) -> &'a D;
2022-04-19 04:25:11 +00:00
/// Gets the global data defined in the `Context` or `Schema` or `None` if
/// the specified type data does not exist.
2022-01-18 09:49:47 +00:00
fn data_opt<D: Any + Send + Sync>(&self) -> Option<&'a D>;
}
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));
}
2021-11-09 09:01:51 +00:00
pub(crate) fn merge(&mut self, other: Data) {
self.0.extend(other.0);
}
2020-03-01 10:54:34 +00:00
}
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
2021-11-19 10:49:37 +00:00
/// Context object for execute directive.
pub type ContextDirective<'a> = ContextBase<'a, &'a Positioned<Directive>>;
/// A segment in the path to the current query.
///
2022-04-19 04:25:11 +00:00
/// 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.
///
2022-04-19 04:25:11 +00:00
/// 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]
2021-06-18 02:43:34 +00:00
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
}
}
2022-04-19 04:25:11 +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> {
2022-04-19 04:25:11 +00:00
/// 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> {}
/// 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>>,
/// If `true` means the current field is for introspection.
pub(crate) is_for_introspection: bool,
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_name: Option<String>,
pub operation: Positioned<OperationDefinition>,
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
2020-10-10 02:32:43 +00:00
pub uploads: Vec<UploadValue>,
pub session_data: Arc<Data>,
pub ctx_data: Arc<Data>,
pub extension_data: Arc<Data>,
pub http_headers: Mutex<HeaderMap>,
2022-04-13 03:00:24 +00:00
pub introspection_mode: IntrospectionMode,
pub errors: Mutex<Vec<ServerError>>,
}
#[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,
) -> ContextBase<'a, T> {
ContextBase {
path_node,
is_for_introspection: false,
item,
schema_env,
query_env: self,
}
}
}
2022-01-18 09:49:47 +00:00
impl<'a, T> DataContext<'a> for ContextBase<'a, T> {
fn data<D: Any + Send + Sync>(&self) -> Result<&'a D> {
ContextBase::data::<D>(self)
}
fn data_unchecked<D: Any + Send + Sync>(&self) -> &'a D {
ContextBase::data_unchecked::<D>(self)
}
fn data_opt<D: Any + Send + Sync>(&self) -> Option<&'a D> {
ContextBase::data_opt::<D>(self)
}
}
2020-03-03 03:48:00 +00:00
impl<'a, T> ContextBase<'a, T> {
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
}),
is_for_introspection: self.is_for_introspection,
2020-03-26 03:34:28 +00:00
item: field,
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,
is_for_introspection: self.is_for_introspection,
2020-03-26 03:34:28 +00:00
item: selection_set,
schema_env: self.schema_env,
query_env: self.query_env,
2020-03-01 10:54:34 +00:00
}
}
2021-06-07 12:51:20 +00:00
#[doc(hidden)]
pub fn set_error_path(&self, error: ServerError) -> ServerError {
if let Some(node) = self.path_node {
let mut path = Vec::new();
node.for_each(|current_node| {
path.push(match current_node {
QueryPathSegment::Name(name) => PathSegment::Field((*name).to_string()),
QueryPathSegment::Index(idx) => PathSegment::Index(*idx),
})
});
ServerError { path, ..error }
} else {
error
}
}
2021-06-07 06:22:45 +00:00
/// Report a resolver error.
///
2022-04-19 04:25:11 +00:00
/// When implementing `OutputType`, if an error occurs, call this function
/// to report this error and return `Value::Null`.
pub fn add_error(&self, error: ServerError) {
2021-06-07 12:51:20 +00:00
self.query_env.errors.lock().unwrap().push(error);
}
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
///
2022-04-19 04:25:11 +00:00
/// If both `Schema` and `Query` have the same data type, the data in the
/// `Query` is obtained.
2020-05-22 06:02:28 +00:00
///
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<&'a D> {
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
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) -> &'a D {
self.data_opt::<D>()
.unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::<D>()))
}
2022-04-19 04:25:11 +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<&'a D> {
self.query_env
.extension_data
.0
.get(&TypeId::of::<D>())
.or_else(|| self.query_env.ctx_data.0.get(&TypeId::of::<D>()))
.or_else(|| self.query_env.session_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
/// Returns whether the HTTP header `key` is currently set on the response
///
/// # Examples
///
/// ```no_run
/// use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
2022-04-19 04:25:11 +00:00
/// use async_graphql::*;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
/// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
/// assert!(!header_exists);
///
/// ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
///
/// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
/// assert!(header_exists);
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn http_header_contains(&self, key: impl AsHeaderName) -> bool {
2021-04-08 03:41:15 +00:00
self.query_env
.http_headers
.lock()
.unwrap()
.contains_key(key)
}
/// Sets a HTTP header to response.
///
2022-04-19 04:25:11 +00:00
/// If the header was not currently set on the response, then `None` is
/// returned.
///
2022-04-19 04:25:11 +00:00
/// If the response already contained this header then the new value is
/// associated with this key and __all the previous values are
/// removed__, however only a the first previous value is returned.
///
2022-04-19 04:25:11 +00:00
/// See [`http::HeaderMap`] for more details on the underlying
/// implementation
///
/// # Examples
///
/// ```no_run
2022-04-19 04:25:11 +00:00
/// use ::http::{header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue};
/// use async_graphql::*;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
/// // Headers can be inserted using the `http` constants
/// let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
/// assert_eq!(was_in_headers, None);
///
/// // They can also be inserted using &str
/// let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
/// assert_eq!(was_in_headers, None);
///
/// // If multiple headers with the same key are `inserted` then the most recent
/// // one overwrites the previous. If you want multiple headers for the same key, use
/// // `append_http_header` for subsequent headers
/// let was_in_headers = ctx.insert_http_header("Custom-Header", "Hello World");
/// assert_eq!(was_in_headers, Some(HeaderValue::from_static("1234")));
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn insert_http_header(
&self,
name: impl IntoHeaderName,
value: impl TryInto<HeaderValue>,
) -> Option<HeaderValue> {
if let Ok(value) = value.try_into() {
self.query_env
.http_headers
.lock()
.unwrap()
.insert(name, value)
} else {
None
}
}
/// Sets a HTTP header to response.
///
2022-04-19 04:25:11 +00:00
/// If the header was not currently set on the response, then `false` is
/// returned.
///
2022-04-19 04:25:11 +00:00
/// If the response did have this header then the new value is appended to
/// the end of the list of values currently associated with the key,
/// however the key is not updated _(which is important for types that
/// can be `==` without being identical)_.
///
2022-04-19 04:25:11 +00:00
/// See [`http::HeaderMap`] for more details on the underlying
/// implementation
///
/// # Examples
///
/// ```no_run
/// use ::http::header::SET_COOKIE;
2022-04-19 04:25:11 +00:00
/// use async_graphql::*;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
/// // Insert the first instance of the header
/// ctx.insert_http_header(SET_COOKIE, "Chocolate Chip");
///
/// // Subsequent values should be appended
/// let header_already_exists = ctx.append_http_header("Set-Cookie", "Macadamia");
/// assert!(header_already_exists);
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn append_http_header(
&self,
name: impl IntoHeaderName,
value: impl TryInto<HeaderValue>,
) -> bool {
if let Ok(value) = value.try_into() {
self.query_env
.http_headers
.lock()
.unwrap()
.append(name, value)
} else {
false
}
}
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
.get(&def.node.name.node)
.or_else(|| def.node.default_value())
})
.cloned()
.ok_or_else(|| {
ServerError::new(format!("Variable {} is not defined.", name), Some(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
}
2021-11-19 10:49:37 +00:00
#[doc(hidden)]
fn get_param_value<Q: InputType>(
&self,
arguments: &[(Positioned<Name>, Positioned<InputValue>)],
name: &str,
default: Option<fn() -> Q>,
) -> ServerResult<(Pos, Q)> {
let value = arguments
.iter()
.find(|(n, _)| n.node.as_str() == name)
.map(|(_, value)| value)
.cloned();
if value.is_none() {
if let Some(default) = default {
return Ok((Pos::default(), default()));
}
}
let (pos, value) = match value {
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
None => (Pos::default(), None),
};
InputType::parse(value)
.map(|value| (pos, value))
.map_err(|e| e.into_server_error(pos))
}
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)]
#[must_use]
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),
}),
is_for_introspection: self.is_for_introspection,
2020-03-26 03:34:28 +00:00
item: self.item,
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>,
2021-11-15 01:12:13 +00:00
) -> ServerResult<(Pos, T)> {
2021-11-19 10:49:37 +00:00
self.get_param_value(&self.item.node.arguments, name, default)
2020-03-06 15:58:43 +00:00
}
2020-05-14 09:35:25 +00:00
#[doc(hidden)]
pub fn oneof_param_value<T: OneofObjectType>(&self) -> ServerResult<(Pos, T)> {
use indexmap::IndexMap;
let mut map = IndexMap::new();
for (name, value) in &self.item.node.arguments {
let value = self.resolve_input_value(value.clone())?;
map.insert(name.node.clone(), value);
}
InputType::parse(Some(Value::Object(map)))
.map(|value| (self.item.pos, value))
.map_err(|e| e.into_server_error(self.item.pos))
}
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, self)
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 {
2022-04-19 04:25:11 +00:00
/// let fields = ctx
/// .field()
/// .selection_set()
/// .map(|field| field.name())
/// .collect::<Vec<_>>();
2020-11-30 08:17:32 +00:00
/// assert_eq!(fields, vec!["a", "b", "c"]);
/// MyObj { a: 1, b: 2, c: 3 }
/// }
/// }
///
2021-11-20 03:16:48 +00:00
/// # tokio::runtime::Runtime::new().unwrap().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());
2022-04-19 04:25:11 +00:00
/// assert!(schema
/// .execute("{ obj { a ... BC }} fragment BC on MyObj { b c }")
/// .await
/// .is_ok());
2021-11-20 03:16:48 +00:00
/// # });
2020-11-30 08:17:32 +00:00
/// ```
pub fn field(&self) -> SelectionField {
2020-11-30 08:17:32 +00:00
SelectionField {
fragments: &self.query_env.fragments,
field: &self.item.node,
context: self,
2020-11-30 08:17:32 +00:00
}
}
}
2021-11-19 10:49:37 +00:00
impl<'a> ContextBase<'a, &'a Positioned<Directive>> {
#[doc(hidden)]
pub fn param_value<T: InputType>(
&self,
name: &str,
default: Option<fn() -> T>,
) -> ServerResult<(Pos, T)> {
self.get_param_value(&self.item.node.arguments, name, default)
}
}
2020-11-30 08:17:32 +00:00
/// Selection field.
#[derive(Clone, Copy)]
2020-11-30 08:17:32 +00:00
pub struct SelectionField<'a> {
pub(crate) fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
pub(crate) field: &'a Field,
pub(crate) context: &'a Context<'a>,
2020-11-30 08:17:32 +00:00
}
impl<'a> SelectionField<'a> {
/// Get the name of this field.
#[inline]
2020-11-30 08:17:32 +00:00
pub fn name(&self) -> &'a str {
self.field.name.node.as_str()
}
/// Get the alias of this field.
#[inline]
pub fn alias(&self) -> Option<&'a str> {
self.field.alias.as_ref().map(|alias| alias.node.as_str())
}
/// Get the arguments of this field.
pub fn arguments(&self) -> ServerResult<Vec<(Name, Value)>> {
let mut arguments = Vec::with_capacity(self.field.arguments.len());
for (name, value) in &self.field.arguments {
let pos = name.pos;
arguments.push((
name.node.clone(),
value
.clone()
.node
.into_const_with(|name| self.context.var_value(&name, pos))?,
));
}
Ok(arguments)
}
2020-11-30 08:17:32 +00:00
/// 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()],
context: self.context,
2020-11-30 08:17:32 +00:00
}
}
}
impl<'a> Debug for SelectionField<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
struct DebugSelectionSet<'a>(Vec<SelectionField<'a>>);
impl<'a> Debug for DebugSelectionSet<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(&self.0).finish()
}
}
f.debug_struct(self.name())
.field("name", &self.name())
.field(
"selection_set",
&DebugSelectionSet(self.selection_set().collect()),
)
.finish()
}
}
2020-11-30 08:17:32 +00:00
struct SelectionFieldsIter<'a> {
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
iter: Vec<std::slice::Iter<'a, Positioned<Selection>>>,
context: &'a Context<'a>,
2020-11-30 08:17:32 +00:00
}
impl<'a> Iterator for SelectionFieldsIter<'a> {
type Item = SelectionField<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let it = self.iter.last_mut()?;
let item = it.next();
match item {
2020-11-30 08:17:32 +00:00
Some(selection) => match &selection.node {
Selection::Field(field) => {
return Some(SelectionField {
fragments: self.fragments,
field: &field.node,
context: self.context,
2020-11-30 08:17:32 +00:00
});
}
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
}