Merge branch 'master' into rework-errors
This commit is contained in:
commit
1e30712726
|
@ -14,8 +14,9 @@ categories = ["network-programming", "asynchronous"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["apollo_tracing", "uuid", "bson", "chrono", "chrono-tz", "log", "multipart", "tracing", "url", "unblock", "string_number"]
|
default = ["apollo_tracing", "apollo_persisted_queries", "uuid", "bson", "chrono", "chrono-tz", "log", "multipart", "tracing", "url", "unblock", "string_number"]
|
||||||
apollo_tracing = ["chrono"]
|
apollo_tracing = ["chrono"]
|
||||||
|
apollo_persisted_queries = ["lru"]
|
||||||
multipart = ["multer", "bytes", "tempfile"]
|
multipart = ["multer", "bytes", "tempfile"]
|
||||||
unblock = ["blocking"]
|
unblock = ["blocking"]
|
||||||
string_number = ["num-traits"]
|
string_number = ["num-traits"]
|
||||||
|
@ -50,6 +51,7 @@ log = { version = "0.4.11", optional = true }
|
||||||
tracing = { version = "0.1.19", optional = true }
|
tracing = { version = "0.1.19", optional = true }
|
||||||
url = { version = "2.1.1", optional = true }
|
url = { version = "2.1.1", optional = true }
|
||||||
num-traits = { version = "0.2.12", optional = true }
|
num-traits = { version = "0.2.12", optional = true }
|
||||||
|
lru = { version = "0.6.0", optional = true }
|
||||||
|
|
||||||
bytes = { version = "0.5.4", optional = true }
|
bytes = { version = "0.5.4", optional = true }
|
||||||
multer = { version = "1.2.2", optional = true }
|
multer = { version = "1.2.2", optional = true }
|
||||||
|
|
16
README.md
16
README.md
|
@ -52,21 +52,7 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in
|
||||||
* Error Extensions
|
* Error Extensions
|
||||||
* Apollo Federation
|
* Apollo Federation
|
||||||
* Batch Queries
|
* Batch Queries
|
||||||
|
* Apollo Persisted Queries
|
||||||
# Crate features
|
|
||||||
|
|
||||||
This crate offers the following features, all of which are activated by default:
|
|
||||||
|
|
||||||
- `apollo_tracing`: Enable the [Apollo tracing extension](extensions/struct.ApolloTracing.html).
|
|
||||||
- `log`: Enable the [logger extension](extensions/struct.Logger.html).
|
|
||||||
- `tracing`: Enable the [tracing extension](extensions/struct.Tracing.html).
|
|
||||||
- `multipart`: Support [sending files over HTTP multipart](http/fn.receive_body.html).
|
|
||||||
- `unblock`: Support [asynchronous reader for Upload](types/struct.Upload.html)
|
|
||||||
- `bson`: Integrate with the [`bson` crate](https://crates.io/crates/bson).
|
|
||||||
- `chrono`: Integrate with the [`chrono` crate](https://crates.io/crates/chrono).
|
|
||||||
- `chrono-tz`: Integrate with the [`chrono-tz` crate](https://crates.io/crates/chrono-tz).
|
|
||||||
- `url`: Integrate with the [`url` crate](https://crates.io/crates/url).
|
|
||||||
- `uuid`: Integrate with the [`uuid` crate](https://crates.io/crates/uuid).
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ pub fn generate(
|
||||||
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
));
|
));
|
||||||
pat.attrs.clear();
|
remove_graphql_attrs(&mut pat.attrs);
|
||||||
}
|
}
|
||||||
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
||||||
if let Type::Path(path) = elem.as_ref() {
|
if let Type::Path(path) = elem.as_ref() {
|
||||||
|
@ -279,7 +279,7 @@ pub fn generate(
|
||||||
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
));
|
));
|
||||||
pat.attrs.clear();
|
remove_graphql_attrs(&mut pat.attrs);
|
||||||
}
|
}
|
||||||
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
||||||
if let Type::Path(path) = elem.as_ref() {
|
if let Type::Path(path) = elem.as_ref() {
|
||||||
|
|
|
@ -27,3 +27,4 @@ Comparing Features of Other Rust GraphQL Implementations
|
||||||
| Opentracing | 👍 | ⛔️ |
|
| Opentracing | 👍 | ⛔️ |
|
||||||
| Apollo Federation | 👍 | ⛔️ |
|
| Apollo Federation | 👍 | ⛔️ |
|
||||||
| Apollo Tracing | 👍 | ⛔️ |
|
| Apollo Tracing | 👍 | ⛔️ |
|
||||||
|
| Apollo Persisted Queries | 👍 | ⛔️ |
|
157
src/error.rs
157
src/error.rs
|
@ -237,163 +237,6 @@ impl<T> ExtendError for Result<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// An error processing a GraphQL query.
|
|
||||||
#[derive(Debug, Error, PartialEq)]
|
|
||||||
pub enum QueryError {
|
|
||||||
/// The feature is not supported.
|
|
||||||
#[error("Not supported.")]
|
|
||||||
NotSupported,
|
|
||||||
|
|
||||||
/// The actual input type did not match the expected input type.
|
|
||||||
#[error("Expected input type \"{expect}\", found {actual}.")]
|
|
||||||
ExpectedInputType {
|
|
||||||
/// The expected input type.
|
|
||||||
expect: String,
|
|
||||||
|
|
||||||
/// The actual input type.
|
|
||||||
actual: Value,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Parsing of an input value failed.
|
|
||||||
#[error("Failed to parse input value: {reason}")]
|
|
||||||
ParseInputValue {
|
|
||||||
/// The reason for the failure to resolve.
|
|
||||||
reason: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A field was not found on an object type.
|
|
||||||
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
|
|
||||||
FieldNotFound {
|
|
||||||
/// Field name
|
|
||||||
field_name: String,
|
|
||||||
|
|
||||||
/// Object name
|
|
||||||
object: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// `operation_name` in the request was required but not provided.
|
|
||||||
#[error("Operation name required in request")]
|
|
||||||
RequiredOperationName,
|
|
||||||
|
|
||||||
/// The operation name was unknown.
|
|
||||||
#[error("Unknown operation named \"{name}\"")]
|
|
||||||
UnknownOperationNamed {
|
|
||||||
/// Operation name for query.
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The user attempted to query an object without selecting any subfields.
|
|
||||||
#[error("Type \"{object}\" must have a selection of subfields.")]
|
|
||||||
MustHaveSubFields {
|
|
||||||
/// Object name
|
|
||||||
object: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The schema does not have mutations.
|
|
||||||
#[error("Schema is not configured for mutations.")]
|
|
||||||
NotConfiguredMutations,
|
|
||||||
|
|
||||||
/// The schema does not have subscriptions.
|
|
||||||
#[error("Schema is not configured for subscriptions.")]
|
|
||||||
NotConfiguredSubscriptions,
|
|
||||||
|
|
||||||
/// The value does not exist in the enum.
|
|
||||||
#[error("Invalid value for enum \"{ty}\".")]
|
|
||||||
InvalidEnumValue {
|
|
||||||
/// Enum type name
|
|
||||||
ty: String,
|
|
||||||
|
|
||||||
/// Enum value
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A required field in an input object was not present.
|
|
||||||
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
|
|
||||||
RequiredField {
|
|
||||||
/// Field name
|
|
||||||
field_name: String,
|
|
||||||
|
|
||||||
/// Object name
|
|
||||||
object: &'static str,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A variable is used but not defined.
|
|
||||||
#[error("Variable \"${var_name}\" is not defined")]
|
|
||||||
VarNotDefined {
|
|
||||||
/// Variable name
|
|
||||||
var_name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A directive was required but not provided.
|
|
||||||
#[error(
|
|
||||||
"Directive \"{directive}\" argument \"{arg_name}\" of type \"{arg_type}\" is required, but it was not provided."
|
|
||||||
)]
|
|
||||||
RequiredDirectiveArgs {
|
|
||||||
/// Directive name
|
|
||||||
directive: &'static str,
|
|
||||||
|
|
||||||
/// Argument name
|
|
||||||
arg_name: &'static str,
|
|
||||||
|
|
||||||
/// Argument type
|
|
||||||
arg_type: &'static str,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An unknown directive name was encountered.
|
|
||||||
#[error("Unknown directive \"{name}\".")]
|
|
||||||
UnknownDirective {
|
|
||||||
/// Directive name
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An unknown fragment was encountered.
|
|
||||||
#[error("Unknown fragment \"{name}\".")]
|
|
||||||
UnknownFragment {
|
|
||||||
/// Fragment name
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The query was too complex.
|
|
||||||
// TODO: Expand on this
|
|
||||||
#[error("Too complex")]
|
|
||||||
TooComplex,
|
|
||||||
|
|
||||||
/// The query was nested too deep.
|
|
||||||
#[error("Too deep")]
|
|
||||||
TooDeep,
|
|
||||||
|
|
||||||
/// A field handler errored.
|
|
||||||
#[error("Failed to resolve field: {err}")]
|
|
||||||
Error {
|
|
||||||
/// The error description.
|
|
||||||
err: String,
|
|
||||||
/// Extensions to the error provided through the [`ErrorExtensions`](trait.ErrorExtensions)
|
|
||||||
/// or [`ResultExt`](trait.ResultExt) traits.
|
|
||||||
extended_error: Option<serde_json::Value>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Entity not found.
|
|
||||||
#[error("Entity not found")]
|
|
||||||
EntityNotFound,
|
|
||||||
|
|
||||||
/// "__typename" must be an existing string.
|
|
||||||
#[error("\"__typename\" must be an existing string")]
|
|
||||||
TypeNameNotExists,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QueryError {
|
|
||||||
/// Convert this error to a regular `Error` type.
|
|
||||||
pub fn into_error(self, pos: Pos) -> Error {
|
|
||||||
Error::Query {
|
|
||||||
pos,
|
|
||||||
path: None,
|
|
||||||
err: self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// An error parsing the request.
|
/// An error parsing the request.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
176
src/extensions/apollo_persisted_queries.rs
Normal file
176
src/extensions/apollo_persisted_queries.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
//! Apollo persisted queries extension.
|
||||||
|
|
||||||
|
use crate::extensions::{Error, Extension, ExtensionContext, ExtensionFactory};
|
||||||
|
use crate::{Request, Result};
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PersistedQuery {
|
||||||
|
version: i32,
|
||||||
|
#[serde(rename = "sha256Hash")]
|
||||||
|
sha256_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cache storage for persisted queries.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait CacheStorage: Send + Sync + Clone + 'static {
|
||||||
|
/// Load the query by `key`.
|
||||||
|
async fn get(&self, key: String) -> Option<String>;
|
||||||
|
|
||||||
|
/// Save the query by `key`.
|
||||||
|
async fn set(&self, key: String, query: String);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Memory-based LRU cache.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LruCacheStorage(Arc<Mutex<lru::LruCache<String, String>>>);
|
||||||
|
|
||||||
|
impl LruCacheStorage {
|
||||||
|
/// Creates a new LRU Cache that holds at most `cap` items.
|
||||||
|
pub fn new(cap: usize) -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(lru::LruCache::new(cap))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl CacheStorage for LruCacheStorage {
|
||||||
|
async fn get(&self, key: String) -> Option<String> {
|
||||||
|
let mut cache = self.0.lock().await;
|
||||||
|
cache.get(&key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set(&self, key: String, query: String) {
|
||||||
|
let mut cache = self.0.lock().await;
|
||||||
|
cache.put(key, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apollo persisted queries extension.
|
||||||
|
///
|
||||||
|
/// [Reference](https://www.apollographql.com/docs/react/api/link/persisted-queries/)
|
||||||
|
#[cfg_attr(feature = "nightly", doc(cfg(feature = "apollo_persisted_queries")))]
|
||||||
|
pub struct ApolloPersistedQueries<T>(T);
|
||||||
|
|
||||||
|
impl<T: CacheStorage> ApolloPersistedQueries<T> {
|
||||||
|
/// Creates an apollo persisted queries extension.
|
||||||
|
pub fn new(cache_storage: T) -> ApolloPersistedQueries<T> {
|
||||||
|
Self(cache_storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: CacheStorage> ExtensionFactory for ApolloPersistedQueries<T> {
|
||||||
|
fn create(&self) -> Box<dyn Extension> {
|
||||||
|
Box::new(ApolloPersistedQueriesExtension {
|
||||||
|
storage: self.0.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ApolloPersistedQueriesExtension<T> {
|
||||||
|
storage: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<T: CacheStorage> Extension for ApolloPersistedQueriesExtension<T> {
|
||||||
|
async fn prepare_request(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &ExtensionContext<'_>,
|
||||||
|
mut request: Request,
|
||||||
|
) -> Result<Request> {
|
||||||
|
if let Some(value) = request.extensions.remove("persistedQuery") {
|
||||||
|
let persisted_query: PersistedQuery = serde_json::from_value(value).map_err(|_| {
|
||||||
|
Error::Other("Invalid \"PersistedQuery\" extension configuration.".to_string())
|
||||||
|
})?;
|
||||||
|
if persisted_query.version != 1 {
|
||||||
|
return Err(Error::Other (
|
||||||
|
format!("Only the \"PersistedQuery\" extension of version \"1\" is supported, and the current version is \"{}\".", persisted_query.version),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.query.is_empty() {
|
||||||
|
if let Some(query) = self.storage.get(persisted_query.sha256_hash).await {
|
||||||
|
Ok(Request { query, ..request })
|
||||||
|
} else {
|
||||||
|
Err(Error::Other("PersistedQueryNotFound".to_string()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.storage
|
||||||
|
.set(persisted_query.sha256_hash, request.query.clone())
|
||||||
|
.await;
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test() {
|
||||||
|
use super::*;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object(internal)]
|
||||||
|
impl Query {
|
||||||
|
async fn value(&self) -> i32 {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||||
|
.extension(ApolloPersistedQueries::new(LruCacheStorage::new(256)))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let mut request = Request::new("{ value }");
|
||||||
|
request.extensions.insert(
|
||||||
|
"persistedQuery".to_string(),
|
||||||
|
serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"sha256Hash": "abc",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(request).await.into_result().unwrap().data,
|
||||||
|
serde_json::json!({
|
||||||
|
"value": 100
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut request = Request::new("");
|
||||||
|
request.extensions.insert(
|
||||||
|
"persistedQuery".to_string(),
|
||||||
|
serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"sha256Hash": "abc",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(request).await.into_result().unwrap().data,
|
||||||
|
serde_json::json!({
|
||||||
|
"value": 100
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut request = Request::new("");
|
||||||
|
request.extensions.insert(
|
||||||
|
"persistedQuery".to_string(),
|
||||||
|
serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"sha256Hash": "def",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(request).await.into_result().unwrap_err(),
|
||||||
|
Error::Other("PersistedQueryNotFound".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,6 +101,12 @@ impl Extension for LoggerExtension {
|
||||||
write!(f, "variables: {}", self.log.variables)?;
|
write!(f, "variables: {}", self.log.variables)?;
|
||||||
write!(f, "{}", self.e.message)
|
write!(f, "{}", self.e.message)
|
||||||
}
|
}
|
||||||
|
Error::Other(err) => error!(
|
||||||
|
target: "async-graphql", "[OtherError] query: \"{}\", variables: {}, {}",
|
||||||
|
self.query,
|
||||||
|
self.variables,
|
||||||
|
err
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
error!(
|
error!(
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Extensions for schema
|
//! Extensions for schema
|
||||||
|
|
||||||
|
#[cfg(feature = "apollo_persisted_queries")]
|
||||||
|
pub mod apollo_persisted_queries;
|
||||||
#[cfg(feature = "apollo_tracing")]
|
#[cfg(feature = "apollo_tracing")]
|
||||||
mod apollo_tracing;
|
mod apollo_tracing;
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
|
@ -8,7 +10,7 @@ mod logger;
|
||||||
mod tracing;
|
mod tracing;
|
||||||
|
|
||||||
use crate::context::{QueryPathNode, ResolveId};
|
use crate::context::{QueryPathNode, ResolveId};
|
||||||
use crate::{Data, Result, ServerError, ServerResult, Variables};
|
use crate::{Data, Request, Result, ServerError, ServerResult, Variables};
|
||||||
|
|
||||||
#[cfg(feature = "apollo_tracing")]
|
#[cfg(feature = "apollo_tracing")]
|
||||||
pub use self::apollo_tracing::ApolloTracing;
|
pub use self::apollo_tracing::ApolloTracing;
|
||||||
|
@ -88,6 +90,7 @@ pub struct ResolveInfo<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a GraphQL extension
|
/// Represents a GraphQL extension
|
||||||
|
#[async_trait::async_trait]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Extension: Sync + Send + 'static {
|
pub trait Extension: Sync + Send + 'static {
|
||||||
/// If this extension needs to output data to query results, you need to specify a name.
|
/// If this extension needs to output data to query results, you need to specify a name.
|
||||||
|
@ -95,6 +98,15 @@ pub trait Extension: Sync + Send + 'static {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called at the prepare request
|
||||||
|
async fn prepare_request(
|
||||||
|
&mut self,
|
||||||
|
ctx: &ExtensionContext<'_>,
|
||||||
|
request: Request,
|
||||||
|
) -> ServerResult<Request> {
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
|
||||||
/// Called at the begin of the parse.
|
/// Called at the begin of the parse.
|
||||||
fn parse_start(
|
fn parse_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -158,7 +170,20 @@ impl<T> ErrorLogger for Result<T, Vec<ServerError>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl Extension for Extensions {
|
impl Extension for Extensions {
|
||||||
|
async fn prepare_request(
|
||||||
|
&mut self,
|
||||||
|
ctx: &ExtensionContext<'_>,
|
||||||
|
request: Request,
|
||||||
|
) -> ServerResult<Request> {
|
||||||
|
let mut request = request;
|
||||||
|
for e in self.0.iter_mut() {
|
||||||
|
request = e.prepare_request(ctx, request).await?;
|
||||||
|
}
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_start(
|
fn parse_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &ExtensionContext<'_>,
|
ctx: &ExtensionContext<'_>,
|
||||||
|
|
|
@ -48,12 +48,14 @@
|
||||||
//! * Error Extensions
|
//! * Error Extensions
|
||||||
//! * Apollo Federation
|
//! * Apollo Federation
|
||||||
//! * Batch Queries
|
//! * Batch Queries
|
||||||
|
//! * Apollo Persisted Queries
|
||||||
//!
|
//!
|
||||||
//! # Crate features
|
//! # Crate features
|
||||||
//!
|
//!
|
||||||
//! This crate offers the following features, all of which are activated by default:
|
//! This crate offers the following features, all of which are activated by default:
|
||||||
//!
|
//!
|
||||||
//! - `apollo_tracing`: Enable the [Apollo tracing extension](extensions/struct.ApolloTracing.html).
|
//! - `apollo_tracing`: Enable the [Apollo tracing extension](extensions/struct.ApolloTracing.html).
|
||||||
|
//! - `apollo_persisted_queries`: Enable the [Apollo persisted queries extension](extensions/apollo_persisted_queries/struct.ApolloPersistedQueries.html).
|
||||||
//! - `log`: Enable the [logger extension](extensions/struct.Logger.html).
|
//! - `log`: Enable the [logger extension](extensions/struct.Logger.html).
|
||||||
//! - `tracing`: Enable the [tracing extension](extensions/struct.Tracing.html).
|
//! - `tracing`: Enable the [tracing extension](extensions/struct.Tracing.html).
|
||||||
//! - `multipart`: Support [sending files over HTTP multipart](http/fn.receive_body.html).
|
//! - `multipart`: Support [sending files over HTTP multipart](http/fn.receive_body.html).
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::parser::types::UploadValue;
|
||||||
use crate::{Data, ParseRequestError, Value, Variables};
|
use crate::{Data, ParseRequestError, Value, Variables};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
|
@ -29,6 +30,10 @@ pub struct Request {
|
||||||
/// **This data is only valid for this request**
|
/// **This data is only valid for this request**
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub data: Data,
|
pub data: Data,
|
||||||
|
|
||||||
|
/// The extensions config of the request.
|
||||||
|
#[serde(default)]
|
||||||
|
pub extensions: HashMap<String, serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_variables<'de, D: Deserializer<'de>>(
|
fn deserialize_variables<'de, D: Deserializer<'de>>(
|
||||||
|
@ -45,6 +50,7 @@ impl Request {
|
||||||
operation_name: None,
|
operation_name: None,
|
||||||
variables: Variables::default(),
|
variables: Variables::default(),
|
||||||
data: Data::default(),
|
data: Data::default(),
|
||||||
|
extensions: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::pin::Pin;
|
||||||
|
|
||||||
/// A GraphQL container.
|
/// A GraphQL container.
|
||||||
///
|
///
|
||||||
/// This helper trait allows the type to call `resolve_object` on itself in its
|
/// This helper trait allows the type to call `resolve_container` on itself in its
|
||||||
/// `OutputValueType::resolve` implementation.
|
/// `OutputValueType::resolve` implementation.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ContainerType: OutputValueType {
|
pub trait ContainerType: OutputValueType {
|
||||||
|
@ -114,11 +114,11 @@ pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync>(
|
||||||
type BoxFieldFuture<'a> =
|
type BoxFieldFuture<'a> =
|
||||||
Pin<Box<dyn Future<Output = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
|
Pin<Box<dyn Future<Output = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
|
||||||
|
|
||||||
/// A set of fields on an object that are being selected.
|
/// A set of fields on an container that are being selected.
|
||||||
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
||||||
|
|
||||||
impl<'a> Fields<'a> {
|
impl<'a> Fields<'a> {
|
||||||
/// Add another set of fields to this set of fields using the given object.
|
/// Add another set of fields to this set of fields using the given container.
|
||||||
pub fn add_set<T: ContainerType + Send + Sync>(
|
pub fn add_set<T: ContainerType + Send + Sync>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &ContextSelectionSet<'a>,
|
ctx: &ContextSelectionSet<'a>,
|
||||||
|
|
|
@ -319,7 +319,7 @@ where
|
||||||
Self::create_registry().export_sdl(false)
|
Self::create_registry().export_sdl(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_request(
|
async fn prepare_request(
|
||||||
&self,
|
&self,
|
||||||
request: Request,
|
request: Request,
|
||||||
) -> Result<(QueryEnvInner, CacheControl), Vec<ServerError>> {
|
) -> Result<(QueryEnvInner, CacheControl), Vec<ServerError>> {
|
||||||
|
@ -331,6 +331,18 @@ where
|
||||||
.map(|factory| factory.create())
|
.map(|factory| factory.create())
|
||||||
.collect_vec(),
|
.collect_vec(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let request = extensions
|
||||||
|
.lock()
|
||||||
|
.prepare_request(
|
||||||
|
&ExtensionContext {
|
||||||
|
schema_data: &self.env.data,
|
||||||
|
query_data: &Default::default(),
|
||||||
|
},
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let ctx_extension = ExtensionContext {
|
let ctx_extension = ExtensionContext {
|
||||||
schema_data: &self.env.data,
|
schema_data: &self.env.data,
|
||||||
query_data: &request.data,
|
query_data: &request.data,
|
||||||
|
@ -454,7 +466,7 @@ where
|
||||||
/// Execute an GraphQL query.
|
/// Execute an GraphQL query.
|
||||||
pub async fn execute(&self, request: impl Into<Request>) -> Response {
|
pub async fn execute(&self, request: impl Into<Request>) -> Response {
|
||||||
let request = request.into();
|
let request = request.into();
|
||||||
match self.prepare_request(request) {
|
match self.prepare_request(request).await {
|
||||||
Ok((env, cache_control)) => self
|
Ok((env, cache_control)) => self
|
||||||
.execute_once(QueryEnv::new(env))
|
.execute_once(QueryEnv::new(env))
|
||||||
.await
|
.await
|
||||||
|
@ -485,7 +497,7 @@ where
|
||||||
|
|
||||||
async_stream::stream! {
|
async_stream::stream! {
|
||||||
let request = request.into();
|
let request = request.into();
|
||||||
let (mut env, cache_control) = match schema.prepare_request(request) {
|
let (mut env, cache_control) = match schema.prepare_request(request).await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(errors) => {
|
Err(errors) => {
|
||||||
yield Response::from_errors(errors);
|
yield Response::from_errors(errors);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user