fix some bug

add starwars example
This commit is contained in:
sunli 2020-03-05 21:34:31 +08:00
parent b3340680ba
commit 2351e75f2c
14 changed files with 356 additions and 86 deletions

View File

@ -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 = [

View File

@ -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(_, _) => {

View File

@ -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(

View File

@ -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))
}
}

135
examples/starwars/mod.rs Normal file
View File

@ -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()
}
}

132
examples/starwars/model.rs Normal file
View File

@ -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,
})
}
}

View File

@ -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)
}
}
}
}

View File

@ -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.")]

View File

@ -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(&registry.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(&registry.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
}

View File

@ -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);
}

View File

@ -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.";

View File

@ -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."),

View File

@ -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;

View File

@ -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;