v1.3.2
This commit is contained in:
parent
ab0f0239d0
commit
62b4908ffe
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2018"
|
||||
description = "The GraphQL server library implemented by rust"
|
||||
|
@ -14,10 +14,10 @@ categories = ["network-programming", "asynchronous"]
|
|||
readme = "README.md"
|
||||
|
||||
[features]
|
||||
default = ["chrono", "uuid"]
|
||||
default = ["chrono", "uuid", "url"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql-derive = { path = "async-graphql-derive", version = "1.3.1" }
|
||||
async-graphql-derive = { path = "async-graphql-derive", version = "1.3.2" }
|
||||
graphql-parser = "0.2.3"
|
||||
anyhow = "1.0.26"
|
||||
thiserror = "1.0.11"
|
||||
|
@ -32,6 +32,7 @@ base64 = "0.12.0"
|
|||
byteorder = "1.3.4"
|
||||
chrono = { version = "0.4.10", optional = true }
|
||||
uuid = { version = "0.8.1", optional = true }
|
||||
url = { version = "2.1.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = { version = "1.5.0", features = ["attributes"] }
|
||||
|
|
|
@ -47,7 +47,8 @@ Open `http://localhost:8000` in browser
|
|||
* Minimal overhead
|
||||
* Easy integration (hyper, actix_web, tide ...)
|
||||
* Upload files (Multipart request)
|
||||
* Subscription (WebSocket transport
|
||||
* Subscription (WebSocket transport)
|
||||
|
||||
## Integrations
|
||||
|
||||
* Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web)
|
||||
|
@ -63,6 +64,7 @@ Open `http://localhost:8000` in browser
|
|||
- [X] ID
|
||||
- [X] DateTime
|
||||
- [X] UUID
|
||||
- [X] Url
|
||||
- [X] Containers
|
||||
- [X] List
|
||||
- [X] Non-Null
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-actix-web"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2018"
|
||||
description = "async-graphql for actix-web"
|
||||
|
@ -13,7 +13,7 @@ keywords = ["futures", "async", "graphql"]
|
|||
categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql = { path = "..", version = "1.3.1" }
|
||||
async-graphql = { path = "..", version = "1.3.2" }
|
||||
actix-web = "2.0.0"
|
||||
actix-multipart = "0.2.0"
|
||||
actix-web-actors = "2.0.0"
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! Integrate `async-graphql` to `actix-web`
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-derive"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2018"
|
||||
description = "Macros for async-graphql"
|
||||
|
|
8
scripts/publish-all.sh
Executable file
8
scripts/publish-all.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd async-graphql-derive && cargo publish && cd ..
|
||||
sleep 5
|
||||
cd async-graphql-actix-web && cargo publish && cd ..
|
||||
sleep 5
|
||||
cargo publish
|
||||
sleep 5
|
28
src/base.rs
28
src/base.rs
|
@ -1,9 +1,11 @@
|
|||
use crate::registry::Registry;
|
||||
use crate::{registry, Context, ContextSelectionSet, Result};
|
||||
use crate::{registry, Context, ContextSelectionSet, QueryError, Result, ID};
|
||||
use graphql_parser::query::{Field, Value};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Represents a GraphQL type
|
||||
///
|
||||
/// All GraphQL types implement this trait, such as `Scalar`, `Object`, `Union` ...
|
||||
pub trait Type {
|
||||
/// Type the name.
|
||||
fn type_name() -> Cow<'static, str>;
|
||||
|
@ -15,16 +17,39 @@ pub trait Type {
|
|||
|
||||
/// Create type information in the registry and return qualified typename.
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String;
|
||||
|
||||
/// Returns a `GlobalID` that is unique among all types.
|
||||
fn global_id(id: ID) -> ID {
|
||||
base64::encode(format!("{}:{}", Self::type_name(), id)).into()
|
||||
}
|
||||
|
||||
/// Parse `GlobalID`.
|
||||
fn from_global_id(id: ID) -> Result<ID> {
|
||||
let v: Vec<&str> = id.splitn(2, ":").collect();
|
||||
if v.len() != 2 {
|
||||
return Err(QueryError::InvalidGlobalID.into());
|
||||
}
|
||||
if v[0] != Self::type_name() {
|
||||
return Err(QueryError::InvalidGlobalIDType {
|
||||
expect: Self::type_name().to_string(),
|
||||
actual: v[0].to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Ok(v[1].to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a GraphQL input value
|
||||
pub trait InputValueType: Type + Sized {
|
||||
/// Parse from `Value`
|
||||
fn parse(value: &Value) -> Option<Self>;
|
||||
}
|
||||
|
||||
/// Represents a GraphQL output value
|
||||
#[async_trait::async_trait]
|
||||
pub trait OutputValueType: Type {
|
||||
/// Resolve an output value to `serde_json::Value`.
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
|
@ -142,6 +167,7 @@ macro_rules! impl_scalar_internal {
|
|||
};
|
||||
}
|
||||
|
||||
/// After implementing the `Scalar` trait, you must call this macro to implement some additional traits.
|
||||
#[macro_export]
|
||||
macro_rules! impl_scalar {
|
||||
($ty:ty) => {
|
||||
|
|
|
@ -43,6 +43,7 @@ impl DerefMut for Variables {
|
|||
}
|
||||
|
||||
impl Variables {
|
||||
/// Parse variables from JSON object.
|
||||
pub fn parse_from_json(value: serde_json::Value) -> Result<Self> {
|
||||
let gql_value = json_value_to_gql_value(value);
|
||||
if let Value::Object(_) = gql_value {
|
||||
|
@ -140,11 +141,13 @@ impl Data {
|
|||
}
|
||||
}
|
||||
|
||||
/// Context for `SelectionSet`
|
||||
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>;
|
||||
|
||||
/// Context object for resolve field.
|
||||
/// Context object for resolve field
|
||||
pub type Context<'a> = ContextBase<'a, &'a Field>;
|
||||
|
||||
/// Query context
|
||||
pub struct ContextBase<'a, T> {
|
||||
pub(crate) item: T,
|
||||
pub(crate) variables: &'a Variables,
|
||||
|
|
104
src/error.rs
104
src/error.rs
|
@ -3,35 +3,59 @@ use graphql_parser::query::Value;
|
|||
use graphql_parser::Pos;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
/// Error for query parser
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{0}")]
|
||||
pub struct QueryParseError(pub(crate) String);
|
||||
|
||||
/// Error for query
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum QueryError {
|
||||
#[error("Not supported.")]
|
||||
NotSupported,
|
||||
|
||||
#[error("Expected type \"{expect}\", found {actual}.")]
|
||||
ExpectedType { expect: String, actual: Value },
|
||||
ExpectedType {
|
||||
/// Expect input type
|
||||
expect: String,
|
||||
|
||||
/// Actual input type
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
#[error("Expected type \"{expect}\", found {actual}.")]
|
||||
ExpectedJsonType {
|
||||
/// Expect input JSON type
|
||||
expect: String,
|
||||
|
||||
/// Actual input JSON type
|
||||
actual: serde_json::Value,
|
||||
},
|
||||
|
||||
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
|
||||
FieldNotFound { field_name: String, object: String },
|
||||
FieldNotFound {
|
||||
/// Field name
|
||||
field_name: String,
|
||||
|
||||
/// Object name
|
||||
object: String,
|
||||
},
|
||||
|
||||
#[error("Missing operation")]
|
||||
MissingOperation,
|
||||
|
||||
#[error("Unknown operation named \"{name}\"")]
|
||||
UnknownOperationNamed { name: String },
|
||||
UnknownOperationNamed {
|
||||
/// Operation name for query
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("Type \"{object}\" must have a selection of subfields.")]
|
||||
MustHaveSubFields { object: String },
|
||||
MustHaveSubFields {
|
||||
/// Object name
|
||||
object: String,
|
||||
},
|
||||
|
||||
#[error("Schema is not configured for mutations.")]
|
||||
NotConfiguredMutations,
|
||||
|
@ -40,44 +64,98 @@ pub enum QueryError {
|
|||
NotConfiguredSubscriptions,
|
||||
|
||||
#[error("Invalid value for enum \"{ty}\".")]
|
||||
InvalidEnumValue { ty: String, value: String },
|
||||
InvalidEnumValue {
|
||||
/// Enum type name
|
||||
ty: String,
|
||||
|
||||
/// Enum value
|
||||
value: String,
|
||||
},
|
||||
|
||||
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
|
||||
RequiredField {
|
||||
/// field name
|
||||
field_name: String,
|
||||
|
||||
/// object name
|
||||
object: &'static str,
|
||||
},
|
||||
|
||||
#[error("Variable \"${var_name}\" is not defined")]
|
||||
VarNotDefined { var_name: String },
|
||||
VarNotDefined {
|
||||
/// Variable name
|
||||
var_name: String,
|
||||
},
|
||||
|
||||
#[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,
|
||||
},
|
||||
|
||||
#[error("Unknown directive \"{name}\".")]
|
||||
UnknownDirective { name: String },
|
||||
UnknownDirective {
|
||||
/// Directive name
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("Unknown fragment \"{name}\".")]
|
||||
UnknownFragment { name: String },
|
||||
UnknownFragment {
|
||||
// Fragment name
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("Object \"{object}\" does not implement interface \"{interface}\"")]
|
||||
NotImplementedInterface { object: String, interface: String },
|
||||
NotImplementedInterface {
|
||||
/// Object name
|
||||
object: String,
|
||||
|
||||
/// Interface name
|
||||
interface: String,
|
||||
},
|
||||
|
||||
#[error("Unrecognized inline fragment \"{name}\" on type \"{object}\"")]
|
||||
UnrecognizedInlineFragment { object: String, name: String },
|
||||
UnrecognizedInlineFragment {
|
||||
/// Object name
|
||||
object: String,
|
||||
|
||||
/// Inline fragment name
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("Argument \"{field_name}\" must be a non-negative integer")]
|
||||
ArgumentMustBeNonNegative { field_name: String },
|
||||
ArgumentMustBeNonNegative {
|
||||
/// Field name
|
||||
field_name: String,
|
||||
},
|
||||
|
||||
#[error("Invalid global id")]
|
||||
InvalidGlobalID,
|
||||
|
||||
#[error("Invalid global id, expected type \"{expect}\", found {actual}.")]
|
||||
InvalidGlobalIDType {
|
||||
/// Expect type
|
||||
expect: String,
|
||||
|
||||
/// Actual type
|
||||
actual: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
/// Creates a wrapper with an error location
|
||||
#[allow(missing_docs)]
|
||||
pub trait ErrorWithPosition {
|
||||
type Result;
|
||||
|
||||
fn with_position(self, position: Pos) -> PositionError;
|
||||
}
|
||||
|
||||
|
@ -92,6 +170,8 @@ impl<T: Into<Error>> ErrorWithPosition for T {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper with the wrong location
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Error)]
|
||||
pub struct PositionError {
|
||||
pub position: Pos,
|
||||
|
@ -99,10 +179,12 @@ pub struct PositionError {
|
|||
}
|
||||
|
||||
impl PositionError {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(position: Pos, inner: Error) -> Self {
|
||||
Self { position, inner }
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn into_inner(self) -> Error {
|
||||
self.inner
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// Generate the page for GraphIQL
|
||||
pub fn graphiql_source(graphql_endpoint_url: &str) -> String {
|
||||
let stylesheet_source = r#"
|
||||
<style>
|
||||
|
|
|
@ -12,15 +12,22 @@ use serde::ser::{SerializeMap, SerializeSeq};
|
|||
use serde::{Serialize, Serializer};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// GraphQL Request object
|
||||
#[derive(Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct GQLRequest {
|
||||
/// Query source
|
||||
pub query: String,
|
||||
|
||||
/// Operation name for this query
|
||||
#[serde(rename = "operationName")]
|
||||
pub operation_name: Option<String>,
|
||||
|
||||
/// Variables for this query
|
||||
pub variables: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl GQLRequest {
|
||||
/// Execute the query and return the `GQLResponse`.
|
||||
pub async fn execute<Query, Mutation, Subscription>(
|
||||
mut self,
|
||||
schema: &Schema<Query, Mutation, Subscription>,
|
||||
|
@ -36,6 +43,7 @@ impl GQLRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prepare a query and return a `PreparedQuery` object that gets some information about the query.
|
||||
pub fn prepare<'a, Query, Mutation, Subscription>(
|
||||
&'a mut self,
|
||||
schema: &'a Schema<Query, Mutation, Subscription>,
|
||||
|
@ -65,6 +73,7 @@ impl GQLRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serializable query result type
|
||||
pub struct GQLResponse(pub Result<serde_json::Value>);
|
||||
|
||||
impl Serialize for GQLResponse {
|
||||
|
@ -86,6 +95,7 @@ impl Serialize for GQLResponse {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serializable error type
|
||||
pub struct GQLError<'a>(pub &'a anyhow::Error);
|
||||
|
||||
impl<'a> Deref for GQLError<'a> {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// Generate the page for GraphQL Playground
|
||||
pub fn playground_source(
|
||||
graphql_endpoint_url: &str,
|
||||
subscription_endpoint: Option<&str>,
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -53,6 +53,11 @@
|
|||
//! ## References
|
||||
//!
|
||||
//! * [GraphQL](https://graphql.org)
|
||||
//! * [GraphQL Multipart Request](https://github.com/jaydenseric/graphql-multipart-request-spec)
|
||||
//! * [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm)
|
||||
//! * [GraphQL over WebSocket Protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md)
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
@ -80,9 +85,10 @@ pub use graphql_parser;
|
|||
#[doc(hidden)]
|
||||
pub use serde_json;
|
||||
|
||||
/// A helper module that supports HTTP
|
||||
pub mod http;
|
||||
|
||||
pub use base::Scalar;
|
||||
pub use base::{Scalar, Type};
|
||||
pub use context::{Context, Variables};
|
||||
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
|
||||
pub use graphql_parser::query::Value;
|
||||
|
@ -95,7 +101,10 @@ pub use types::{
|
|||
Upload,
|
||||
};
|
||||
|
||||
/// Result type, are actually `anyhow::Result<T>`
|
||||
pub type Result<T> = anyhow::Result<T>;
|
||||
|
||||
/// Error type, are actually `anyhow::Error`
|
||||
pub type Error = anyhow::Error;
|
||||
|
||||
// internal types
|
||||
|
@ -104,7 +113,7 @@ pub use context::ContextSelectionSet;
|
|||
#[doc(hidden)]
|
||||
pub mod registry;
|
||||
#[doc(hidden)]
|
||||
pub use base::{InputObjectType, InputValueType, ObjectType, OutputValueType, Type};
|
||||
pub use base::{InputObjectType, InputValueType, ObjectType, OutputValueType};
|
||||
#[doc(hidden)]
|
||||
pub use context::ContextBase;
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -119,6 +119,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prepared query object
|
||||
pub struct PreparedQuery<'a, Query, Mutation> {
|
||||
root: Root<'a, Query, Mutation>,
|
||||
registry: &'a Registry,
|
||||
|
|
|
@ -83,6 +83,7 @@ impl<'a, T: ObjectType + Send + Sync> Resolver<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub async fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
||||
ctx: &'a ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
|
@ -98,6 +99,7 @@ pub async fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
|||
Ok(result.into())
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub async fn do_resolve_values<'a, T: ObjectType + Send + Sync>(
|
||||
ctx: &'a ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use crate::{impl_scalar_internal, Result, Scalar, Value};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
||||
/// Implement the DateTime<Utc> scalar
|
||||
///
|
||||
/// The input/output is a string in RFC3339 format.
|
||||
impl Scalar for DateTime<Utc> {
|
||||
fn type_name() -> &'static str {
|
||||
"DateTime"
|
||||
|
|
|
@ -7,6 +7,12 @@ use std::ops::{Deref, DerefMut};
|
|||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct ID(String);
|
||||
|
||||
impl std::fmt::Display for ID {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ID {
|
||||
type Target = String;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ mod floats;
|
|||
mod id;
|
||||
mod integers;
|
||||
mod string;
|
||||
mod url;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod datetime;
|
||||
|
|
21
src/scalars/url.rs
Normal file
21
src/scalars/url.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use crate::{impl_scalar_internal, Result, Scalar, Value};
|
||||
use url::Url;
|
||||
|
||||
impl Scalar for Url {
|
||||
fn type_name() -> &'static str {
|
||||
"Url"
|
||||
}
|
||||
|
||||
fn parse(value: &Value) -> Option<Self> {
|
||||
match value {
|
||||
Value::String(s) => Some(Url::parse(s).ok()?),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<serde_json::Value> {
|
||||
Ok(self.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl_scalar_internal!(Url);
|
|
@ -19,7 +19,7 @@ pub struct Schema<Query, Mutation, Subscription> {
|
|||
impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
|
||||
Schema<Query, Mutation, Subscription>
|
||||
{
|
||||
/// Create a schema.
|
||||
/// Create a schema
|
||||
///
|
||||
/// The root object for the query and Mutation needs to be specified.
|
||||
/// If there is no mutation, you can use `EmptyMutation`.
|
||||
|
@ -124,6 +124,7 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
|
|||
}
|
||||
}
|
||||
|
||||
/// Start a subscribe and return `SubscribeBuilder`.
|
||||
pub fn subscribe<'a>(&'a self, source: &'a str) -> SubscribeBuilder<'a, Subscription> {
|
||||
SubscribeBuilder {
|
||||
subscription: &self.subscription,
|
||||
|
|
|
@ -11,6 +11,10 @@ use graphql_parser::query::{
|
|||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Subscribe stub
|
||||
///
|
||||
/// When a new push message is generated, a JSON object that needs to be pushed can be obtained by
|
||||
/// `Subscribe::resolve`, and if None is returned, the Subscribe is not subscribed to a message of this type.
|
||||
pub struct Subscribe {
|
||||
types: HashMap<TypeId, Field>,
|
||||
variables: Variables,
|
||||
|
@ -19,6 +23,7 @@ pub struct Subscribe {
|
|||
}
|
||||
|
||||
impl Subscribe {
|
||||
#[allow(missing_docs)]
|
||||
pub async fn resolve<Query, Mutation, Subscription>(
|
||||
&self,
|
||||
schema: &Schema<Query, Mutation, Subscription>,
|
||||
|
@ -40,6 +45,7 @@ impl Subscribe {
|
|||
}
|
||||
|
||||
/// Represents a GraphQL subscription object
|
||||
#[allow(missing_docs)]
|
||||
#[async_trait::async_trait]
|
||||
pub trait SubscriptionType: Type {
|
||||
/// This function returns true of type `EmptySubscription` only
|
||||
|
@ -159,6 +165,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Perform a subscription operation and return `Subscribe`.
|
||||
pub fn execute(self) -> Result<Subscribe> {
|
||||
let document = parse_query(self.source).map_err(|err| QueryParseError(err.to_string()))?;
|
||||
check_rules(self.registry, &document)?;
|
||||
|
|
|
@ -9,7 +9,7 @@ use inflector::Inflector;
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Connection type.
|
||||
/// Connection type
|
||||
///
|
||||
/// Connection is the result of a query for `DataSource`,
|
||||
/// If the `T` type is `OutputValueType`, you can return the value as a field function directly,
|
||||
|
@ -22,6 +22,7 @@ pub struct Connection<T, E: ObjectType + Sync + Send> {
|
|||
}
|
||||
|
||||
impl<T, E: ObjectType + Sync + Send> Connection<T, E> {
|
||||
/// Create a connection object.
|
||||
pub fn new(
|
||||
total_count: Option<usize>,
|
||||
has_previous_page: bool,
|
||||
|
|
|
@ -7,25 +7,33 @@ use crate::{Context, ObjectType, QueryError, Result};
|
|||
|
||||
pub use connection::Connection;
|
||||
|
||||
/// Connection query operation.
|
||||
/// Connection query operation
|
||||
pub enum QueryOperation<'a> {
|
||||
/// Forward query
|
||||
Forward {
|
||||
/// After this cursor
|
||||
after: Option<&'a str>,
|
||||
|
||||
/// How many records did this query return
|
||||
limit: usize,
|
||||
},
|
||||
/// Backward query
|
||||
Backward {
|
||||
/// Before this cursor
|
||||
before: Option<&'a str>,
|
||||
|
||||
/// How many records did this query return
|
||||
limit: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Empty edge extension object.
|
||||
/// Empty edge extension object
|
||||
pub struct EmptyEdgeFields;
|
||||
|
||||
#[async_graphql_derive::Object(internal)]
|
||||
impl EmptyEdgeFields {}
|
||||
|
||||
/// Data source of GraphQL Cursor Connections type.
|
||||
/// Data source of GraphQL Cursor Connections type
|
||||
///
|
||||
/// `Edge` is an extension object type that extends the edge fields, If you don't need it, you can use `EmptyEdgeFields`.
|
||||
///
|
||||
|
@ -55,9 +63,9 @@ impl EmptyEdgeFields {}
|
|||
/// #[async_trait::async_trait]
|
||||
/// impl DataSource for Numbers {
|
||||
/// type Element = i32;
|
||||
/// type Edge = DiffFields;
|
||||
/// type EdgeFieldsObj = DiffFields;
|
||||
///
|
||||
/// async fn query_operation(&self, operation: &QueryOperation<'_>) -> Result<Connection<Self::Element, Self::Edge>> {
|
||||
/// async fn query_operation(&self, operation: &QueryOperation<'_>) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
/// let (start, end) = match operation {
|
||||
/// QueryOperation::Forward {after, limit} => {
|
||||
/// let start = after.and_then(|after| base64::decode(after).ok())
|
||||
|
@ -119,9 +127,15 @@ impl EmptyEdgeFields {}
|
|||
/// ```
|
||||
#[async_trait::async_trait]
|
||||
pub trait DataSource: Sync + Send {
|
||||
/// Record type
|
||||
type Element;
|
||||
type Edge: ObjectType + Send + Sync;
|
||||
|
||||
/// Fields for Edge
|
||||
///
|
||||
/// Is a type that implements `ObjectType` and can be defined by the procedure macro `#[Object]`.
|
||||
type EdgeFieldsObj: ObjectType + Send + Sync;
|
||||
|
||||
/// Execute the query.
|
||||
async fn query(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
@ -129,7 +143,7 @@ pub trait DataSource: Sync + Send {
|
|||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
) -> Result<Connection<Self::Element, Self::Edge>> {
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
let operation = if let Some(after) = &after {
|
||||
QueryOperation::Forward {
|
||||
after: Some(after),
|
||||
|
@ -198,8 +212,9 @@ pub trait DataSource: Sync + Send {
|
|||
self.query_operation(&operation).await
|
||||
}
|
||||
|
||||
/// Parses the parameters and executes the query,Usually you just need to implement this method.
|
||||
async fn query_operation(
|
||||
&self,
|
||||
operation: &QueryOperation<'_>,
|
||||
) -> Result<Connection<Self::Element, Self::Edge>>;
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>>;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ use byteorder::{ReadBytesExt, BE};
|
|||
#[async_trait::async_trait]
|
||||
impl<'a, T: Sync> DataSource for &'a [T] {
|
||||
type Element = &'a T;
|
||||
type Edge = EmptyEdgeFields;
|
||||
type EdgeFieldsObj = EmptyEdgeFields;
|
||||
|
||||
async fn query_operation(
|
||||
&self,
|
||||
operation: &QueryOperation<'_>,
|
||||
) -> Result<Connection<Self::Element, Self::Edge>> {
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
let (start, end) = match operation {
|
||||
QueryOperation::Forward { after, limit } => {
|
||||
let start = after
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use crate::{Result, Type};
|
||||
use graphql_parser::query::Value;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct EnumItem<T> {
|
||||
pub name: &'static str,
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[async_trait::async_trait]
|
||||
pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static {
|
||||
fn items() -> &'static [EnumItem<Self>];
|
||||
|
|
|
@ -5,8 +5,13 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// Reference: https://github.com/jaydenseric/graphql-multipart-request-spec
|
||||
pub struct Upload {
|
||||
/// Filename
|
||||
pub filename: String,
|
||||
|
||||
/// Content type, such as `application/json`, `image/jpg` ...
|
||||
pub content_type: Option<String>,
|
||||
|
||||
/// File content
|
||||
pub content: Vec<u8>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user