parent
b3340680ba
commit
2351e75f2c
|
@ -33,6 +33,7 @@ uuid = { version = "0.8.1", optional = true }
|
|||
async-std = { version = "1.5.0", features = ["attributes"] }
|
||||
actix-web = "2.0.0"
|
||||
actix-rt = "1.0.0"
|
||||
slab = "0.4.2"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
|
|
@ -9,7 +9,6 @@ use syn::{
|
|||
|
||||
enum OutputType<'a> {
|
||||
Value(&'a Type),
|
||||
ValueRef(&'a TypeReference),
|
||||
Result(&'a Type, &'a Type),
|
||||
}
|
||||
|
||||
|
@ -94,8 +93,8 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
} else {
|
||||
OutputType::Value(ty)
|
||||
}
|
||||
} else if let Type::Reference(ty) = ty.as_ref() {
|
||||
OutputType::ValueRef(ty)
|
||||
} else if let Type::Reference(_) = ty.as_ref() {
|
||||
OutputType::Value(ty)
|
||||
} else {
|
||||
return Err(Error::new_spanned(&method.sig.output, "Invalid type"));
|
||||
}
|
||||
|
@ -190,9 +189,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
let default = match &default {
|
||||
Some(default) => {
|
||||
let repr = build_value_repr(&crate_name, &default);
|
||||
quote! {Some(|| #repr) }
|
||||
quote! {|| #repr }
|
||||
}
|
||||
None => quote! { None },
|
||||
None => quote! { || #crate_name::Value::Null },
|
||||
};
|
||||
get_params.push(quote! {
|
||||
let #ident: #ty = ctx_field.param_value(#name, #default)?;
|
||||
|
@ -202,7 +201,6 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
let schema_ty = match &ty {
|
||||
OutputType::Result(_, value_ty) => value_ty,
|
||||
OutputType::Value(value_ty) => value_ty,
|
||||
OutputType::ValueRef(r) => r.elem.as_ref(),
|
||||
};
|
||||
schema_fields.push(quote! {
|
||||
#crate_name::registry::Field {
|
||||
|
@ -221,7 +219,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
|
||||
let field_ident = &method.sig.ident;
|
||||
let resolve_obj = match &ty {
|
||||
OutputType::Value(_) | OutputType::ValueRef(_) => quote! {
|
||||
OutputType::Value(_) => quote! {
|
||||
self.#field_ident(#ctx_field #(#use_params),*).await
|
||||
},
|
||||
OutputType::Result(_, _) => {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
mod schema;
|
||||
mod starwars;
|
||||
|
||||
use crate::schema::MyObj;
|
||||
use actix_web::{guard, web, App, HttpResponse, HttpServer};
|
||||
use async_graphql::http::{graphiql_source, playground_source, GQLRequest, GQLResponse};
|
||||
use async_graphql::{GQLEmptyMutation, Schema};
|
||||
|
||||
type MySchema = Schema<MyObj, GQLEmptyMutation>;
|
||||
type StarWarsSchema = Schema<starwars::QueryRoot, GQLEmptyMutation>;
|
||||
|
||||
async fn index(s: web::Data<MySchema>, req: web::Json<GQLRequest>) -> web::Json<GQLResponse> {
|
||||
async fn index(s: web::Data<StarWarsSchema>, req: web::Json<GQLRequest>) -> web::Json<GQLResponse> {
|
||||
web::Json(req.into_inner().execute(&s).await)
|
||||
}
|
||||
|
||||
|
@ -25,9 +24,12 @@ async fn gql_graphiql() -> HttpResponse {
|
|||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(Schema::new(MyObj { value: 10 }, GQLEmptyMutation))
|
||||
.data(Schema::new(
|
||||
starwars::QueryRoot(starwars::StarWars::new()),
|
||||
GQLEmptyMutation,
|
||||
))
|
||||
.service(web::resource("/").guard(guard::Post()).to(index))
|
||||
.service(web::resource("/").guard(guard::Get()).to(gql_playgound))
|
||||
.service(
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
#[async_graphql::Enum]
|
||||
pub enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[async_graphql::InputObject]
|
||||
pub struct MyInputObj {
|
||||
#[field(default = "\"hehe\"")]
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
pub struct MyObj {
|
||||
pub value: i32,
|
||||
}
|
||||
|
||||
#[async_graphql::Object(
|
||||
field(name = "a", type = "i32"),
|
||||
field(
|
||||
owned,
|
||||
name = "b",
|
||||
type = "i32",
|
||||
arg(name = "v", type = "i32", default = "123")
|
||||
),
|
||||
field(owned, name = "c", type = "Option<String>")
|
||||
)]
|
||||
impl MyObj {
|
||||
#[field]
|
||||
async fn a(&self) -> &i32 {
|
||||
&self.value
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn b(&self, #[arg(default = "123")] v: i32) -> i32 {
|
||||
v
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn c(&self) -> Option<String> {
|
||||
Some(format!("**{}**", self.value))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
mod model;
|
||||
|
||||
use model::Episode;
|
||||
pub use model::*;
|
||||
use slab::Slab;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct StarWarsChar {
|
||||
id: &'static str,
|
||||
name: &'static str,
|
||||
friends: Vec<usize>,
|
||||
appears_in: Vec<Episode>,
|
||||
home_planet: Option<&'static str>,
|
||||
primary_function: Option<&'static str>,
|
||||
}
|
||||
|
||||
pub struct StarWars {
|
||||
luke: usize,
|
||||
artoo: usize,
|
||||
chars: Slab<StarWarsChar>,
|
||||
human_data: HashMap<&'static str, usize>,
|
||||
droid_data: HashMap<&'static str, usize>,
|
||||
}
|
||||
|
||||
impl StarWars {
|
||||
pub fn new() -> Self {
|
||||
let mut chars = Slab::new();
|
||||
|
||||
let luke = chars.insert(StarWarsChar {
|
||||
id: "1000",
|
||||
name: "Luke Skywalker",
|
||||
friends: vec![],
|
||||
appears_in: vec![],
|
||||
home_planet: Some("Tatooine"),
|
||||
primary_function: None,
|
||||
});
|
||||
|
||||
let vader = chars.insert(StarWarsChar {
|
||||
id: "1001",
|
||||
name: "Luke Skywalker",
|
||||
friends: vec![],
|
||||
appears_in: vec![],
|
||||
home_planet: Some("Tatooine"),
|
||||
primary_function: None,
|
||||
});
|
||||
|
||||
let han = chars.insert(StarWarsChar {
|
||||
id: "1002",
|
||||
name: "Han Solo",
|
||||
friends: vec![],
|
||||
appears_in: vec![Episode::EMPIRE, Episode::NEWHOPE, Episode::JEDI],
|
||||
home_planet: None,
|
||||
primary_function: None,
|
||||
});
|
||||
|
||||
let leia = chars.insert(StarWarsChar {
|
||||
id: "1003",
|
||||
name: "Leia Organa",
|
||||
friends: vec![],
|
||||
appears_in: vec![Episode::EMPIRE, Episode::NEWHOPE, Episode::JEDI],
|
||||
home_planet: Some("Alderaa"),
|
||||
primary_function: None,
|
||||
});
|
||||
|
||||
let tarkin = chars.insert(StarWarsChar {
|
||||
id: "1004",
|
||||
name: "Wilhuff Tarkin",
|
||||
friends: vec![],
|
||||
appears_in: vec![Episode::EMPIRE, Episode::NEWHOPE, Episode::JEDI],
|
||||
home_planet: None,
|
||||
primary_function: None,
|
||||
});
|
||||
|
||||
let threepio = chars.insert(StarWarsChar {
|
||||
id: "2000",
|
||||
name: "C-3PO",
|
||||
friends: vec![],
|
||||
appears_in: vec![Episode::EMPIRE, Episode::NEWHOPE, Episode::JEDI],
|
||||
home_planet: None,
|
||||
primary_function: Some("Protocol"),
|
||||
});
|
||||
|
||||
let artoo = chars.insert(StarWarsChar {
|
||||
id: "2001",
|
||||
name: "R2-D2",
|
||||
friends: vec![],
|
||||
appears_in: vec![Episode::EMPIRE, Episode::NEWHOPE, Episode::JEDI],
|
||||
home_planet: None,
|
||||
primary_function: Some("Astromech"),
|
||||
});
|
||||
|
||||
chars[luke].friends = vec![han, leia, threepio, artoo];
|
||||
chars[vader].friends = vec![tarkin];
|
||||
chars[han].friends = vec![luke, leia, artoo];
|
||||
chars[leia].friends = vec![luke, han, threepio, artoo];
|
||||
chars[tarkin].friends = vec![vader];
|
||||
chars[threepio].friends = vec![luke, han, leia, artoo];
|
||||
chars[artoo].friends = vec![luke, han, leia];
|
||||
|
||||
let mut human_data = HashMap::new();
|
||||
human_data.insert("1000", luke);
|
||||
human_data.insert("1001", vader);
|
||||
human_data.insert("1002", han);
|
||||
human_data.insert("1003", leia);
|
||||
human_data.insert("1004", tarkin);
|
||||
|
||||
let mut droid_data = HashMap::new();
|
||||
droid_data.insert("2000", threepio);
|
||||
droid_data.insert("2001", artoo);
|
||||
|
||||
Self {
|
||||
luke,
|
||||
artoo,
|
||||
chars,
|
||||
human_data,
|
||||
droid_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hero(&self, episode: Episode) -> usize {
|
||||
if episode == Episode::EMPIRE {
|
||||
self.luke
|
||||
} else {
|
||||
self.artoo
|
||||
}
|
||||
}
|
||||
|
||||
pub fn human(&self, id: &str) -> Option<usize> {
|
||||
self.human_data.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn droid(&self, id: &str) -> Option<usize> {
|
||||
self.droid_data.get(id).cloned()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
use super::StarWars;
|
||||
|
||||
#[async_graphql::Enum(desc = "One of the films in the Star Wars Trilogy")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Episode {
|
||||
#[item(desc = "Released in 1977.")]
|
||||
NEWHOPE,
|
||||
|
||||
#[item(desc = "Released in 1980.")]
|
||||
EMPIRE,
|
||||
|
||||
#[item(desc = "Released in 1983.")]
|
||||
JEDI,
|
||||
}
|
||||
|
||||
pub struct Human<'a> {
|
||||
starwars: &'a StarWars,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
#[async_graphql::Object(desc = "A humanoid creature in the Star Wars universe.")]
|
||||
impl<'a> Human<'a> {
|
||||
#[field(desc = "The id of the human.")]
|
||||
async fn id(&self) -> &str {
|
||||
self.starwars.chars[self.id].id
|
||||
}
|
||||
|
||||
#[field(desc = "The name of the human.")]
|
||||
async fn name(&self) -> &str {
|
||||
self.starwars.chars[self.id].name
|
||||
}
|
||||
|
||||
#[field(desc = "The friends of the human, or an empty list if they have none.")]
|
||||
async fn friends(&self) -> Vec<Human<'a>> {
|
||||
self.starwars.chars[self.id]
|
||||
.friends
|
||||
.iter()
|
||||
.map(|id| Human {
|
||||
id: *id,
|
||||
starwars: self.starwars,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[field(name = "appearsIn", desc = "Which movies they appear in.")]
|
||||
async fn appears_in(&self) -> &[Episode] {
|
||||
&self.starwars.chars[self.id].appears_in
|
||||
}
|
||||
|
||||
#[field(
|
||||
name = "homePlanet",
|
||||
desc = "The home planet of the human, or null if unknown."
|
||||
)]
|
||||
async fn home_planet(&self) -> &Option<&str> {
|
||||
&self.starwars.chars[self.id].home_planet
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Droid<'a> {
|
||||
starwars: &'a StarWars,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
#[async_graphql::Object(desc = "A mechanical creature in the Star Wars universe.")]
|
||||
impl<'a> Droid<'a> {
|
||||
#[field(desc = "The id of the droid.")]
|
||||
async fn id(&self) -> &str {
|
||||
self.starwars.chars[self.id].id
|
||||
}
|
||||
|
||||
#[field(desc = "The name of the droid.")]
|
||||
async fn name(&self) -> &str {
|
||||
self.starwars.chars[self.id].name
|
||||
}
|
||||
|
||||
#[field(desc = "The friends of the droid, or an empty list if they have none.")]
|
||||
async fn friends(&self) -> Vec<Droid<'a>> {
|
||||
self.starwars.chars[self.id]
|
||||
.friends
|
||||
.iter()
|
||||
.map(|id| Droid {
|
||||
id: *id,
|
||||
starwars: self.starwars,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[field(name = "appearsIn", desc = "Which movies they appear in.")]
|
||||
async fn appears_in(&self) -> &[Episode] {
|
||||
&self.starwars.chars[self.id].appears_in
|
||||
}
|
||||
|
||||
#[field(name = "primaryFunction", desc = "The primary function of the droid.")]
|
||||
async fn primary_function(&self) -> &Option<&str> {
|
||||
&self.starwars.chars[self.id].primary_function
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryRoot(pub StarWars);
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl QueryRoot {
|
||||
#[field]
|
||||
async fn hero(
|
||||
&self,
|
||||
#[arg(
|
||||
desc = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode."
|
||||
)]
|
||||
episode: Episode,
|
||||
) -> Human<'_> {
|
||||
Human {
|
||||
id: self.0.hero(episode),
|
||||
starwars: &self.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn human(&self, #[arg(desc = "id of the human")] id: String) -> Option<Human<'_>> {
|
||||
self.0.human(&id).map(|id| Human {
|
||||
id,
|
||||
starwars: &self.0,
|
||||
})
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn droid(&self, #[arg(desc = "id of the droid")] id: String) -> Option<Droid<'_>> {
|
||||
self.0.droid(&id).map(|id| Droid {
|
||||
id,
|
||||
starwars: &self.0,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -254,7 +254,7 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
pub fn param_value<T: GQLInputValue, F: FnOnce() -> Value>(
|
||||
&self,
|
||||
name: &str,
|
||||
default: Option<F>,
|
||||
default: F,
|
||||
) -> Result<T> {
|
||||
match self
|
||||
.arguments
|
||||
|
@ -274,8 +274,7 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
None if default.is_some() => {
|
||||
let default = default.unwrap();
|
||||
None => {
|
||||
let value = default();
|
||||
let res = GQLInputValue::parse(&value).ok_or_else(|| {
|
||||
QueryError::ExpectedType {
|
||||
|
@ -286,16 +285,6 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
None => {
|
||||
let res = GQLInputValue::parse(&Value::Null).ok_or_else(|| {
|
||||
QueryError::ExpectedType {
|
||||
expect: T::qualified_type_name(),
|
||||
actual: Value::Null,
|
||||
}
|
||||
.with_position(self.item.position)
|
||||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ pub enum __DirectiveLocation {
|
|||
#[item(desc = "Location adjacent to a variable definition.")]
|
||||
VARIABLE_DEFINITION,
|
||||
|
||||
#[item(desc = "Location adjacent to a schema definition.")]
|
||||
#[item(desc = "Location adjacent to a starwars definition.")]
|
||||
SCHEMA,
|
||||
|
||||
#[item(desc = "Location adjacent to a scalar definition.")]
|
||||
|
|
|
@ -5,8 +5,8 @@ use async_graphql_derive::Object;
|
|||
|
||||
enum TypeDetail<'a> {
|
||||
Simple(&'a registry::Type),
|
||||
NonNull(&'a registry::Type),
|
||||
List(&'a registry::Type),
|
||||
NonNull(String),
|
||||
List(String),
|
||||
}
|
||||
|
||||
pub struct __Type<'a> {
|
||||
|
@ -26,12 +26,12 @@ impl<'a> __Type<'a> {
|
|||
if let Some(type_name) = parse_non_null(type_name) {
|
||||
__Type {
|
||||
registry,
|
||||
detail: TypeDetail::NonNull(®istry.types[type_name]),
|
||||
detail: TypeDetail::NonNull(type_name.to_string()),
|
||||
}
|
||||
} else if let Some(type_name) = parse_list(type_name) {
|
||||
__Type {
|
||||
registry,
|
||||
detail: TypeDetail::List(®istry.types[type_name]),
|
||||
detail: TypeDetail::List(type_name.to_string()),
|
||||
}
|
||||
} else {
|
||||
__Type {
|
||||
|
@ -189,15 +189,9 @@ impl<'a> __Type<'a> {
|
|||
#[field(name = "ofType")]
|
||||
async fn of_type(&self) -> Option<__Type<'a>> {
|
||||
if let TypeDetail::List(ty) = &self.detail {
|
||||
Some(__Type {
|
||||
registry: self.registry,
|
||||
detail: TypeDetail::Simple(ty),
|
||||
})
|
||||
Some(__Type::new(self.registry, &ty))
|
||||
} else if let TypeDetail::NonNull(ty) = &self.detail {
|
||||
Some(__Type {
|
||||
registry: self.registry,
|
||||
detail: TypeDetail::Simple(ty),
|
||||
})
|
||||
Some(__Type::new(self.registry, &ty))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -72,6 +72,13 @@ impl Registry {
|
|||
pub fn create_type<T: GQLType, F: FnMut(&mut Registry) -> Type>(&mut self, mut f: F) -> String {
|
||||
let name = T::type_name();
|
||||
if !self.types.contains_key(name.as_ref()) {
|
||||
self.types.insert(
|
||||
name.to_string(),
|
||||
Type::Scalar {
|
||||
name: String::new(),
|
||||
description: None,
|
||||
},
|
||||
);
|
||||
let ty = f(self);
|
||||
self.types.insert(name.to_string(), ty);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::registry;
|
||||
use crate::{ContextSelectionSet, GQLOutputValue, GQLType, Result, GQLScalar, Value};
|
||||
use crate::{ContextSelectionSet, GQLOutputValue, GQLScalar, GQLType, Result, Value};
|
||||
use std::borrow::Cow;
|
||||
|
||||
const STRING_DESC:&'static str = "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.";
|
||||
|
|
|
@ -24,7 +24,11 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
|||
registry.add_directive(Directive {
|
||||
name: "include",
|
||||
description: Some("Directs the executor to include this field or fragment only when the `if` argument is true."),
|
||||
locations: vec![__DirectiveLocation::FIELD],
|
||||
locations: vec![
|
||||
__DirectiveLocation::FIELD,
|
||||
__DirectiveLocation::FRAGMENT_SPREAD,
|
||||
__DirectiveLocation::INLINE_FRAGMENT
|
||||
],
|
||||
args: vec![InputValue{
|
||||
name: "if",
|
||||
description: Some("Included when true."),
|
||||
|
@ -36,7 +40,11 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
|||
registry.add_directive(Directive {
|
||||
name: "skip",
|
||||
description: Some("Directs the executor to skip this field or fragment when the `if` argument is true."),
|
||||
locations: vec![__DirectiveLocation::FIELD],
|
||||
locations: vec![
|
||||
__DirectiveLocation::FIELD,
|
||||
__DirectiveLocation::FRAGMENT_SPREAD,
|
||||
__DirectiveLocation::INLINE_FRAGMENT
|
||||
],
|
||||
args: vec![InputValue{
|
||||
name: "if",
|
||||
description: Some("Skipped when true."),
|
||||
|
|
|
@ -11,7 +11,8 @@ impl<T: GQLType> GQLType for Vec<T> {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry)
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +63,27 @@ impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &[T] {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: GQLType> GQLType for &Vec<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::type_name()))
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &Vec<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
let mut res = Vec::new();
|
||||
for item in self.iter() {
|
||||
res.push(item.resolve(&ctx).await?);
|
||||
}
|
||||
Ok(res.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::GQLType;
|
||||
|
|
|
@ -35,6 +35,31 @@ impl<T: GQLOutputValue + Sync> GQLOutputValue for Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: GQLType> GQLType for &Option<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
T::type_name()
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
T::type_name().to_string()
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: GQLOutputValue + Sync> GQLOutputValue for &Option<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where {
|
||||
if let Some(inner) = self {
|
||||
inner.resolve(ctx).await
|
||||
} else {
|
||||
Ok(serde_json::Value::Null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::GQLType;
|
||||
|
|
Loading…
Reference in New Issue