1
0
mirror of https://github.com/actix/examples synced 2025-06-26 17:17:42 +02:00

update async-graphql deps

This commit is contained in:
Rob Ede
2022-07-09 23:35:09 +01:00
parent a46a459476
commit 0a201cd860
7 changed files with 212 additions and 130 deletions

View File

@ -4,14 +4,14 @@ version = "1.0.0"
edition = "2021"
[dependencies]
actix = "0.12"
actix = "0.13"
actix-web = "4"
actix-web-lab = "0.16"
actix-cors = "0.6"
async-graphql = "3"
async-graphql-actix-web = "3"
async-graphql = "4"
async-graphql-actix-web = "4"
env_logger = "0.9"
log = "0.4"
slab = "0.4.2"
slab = "0.4"

View File

@ -1,17 +1,18 @@
use std::collections::HashMap;
use async_graphql::{EmptyMutation, EmptySubscription, Schema};
use model::Episode;
use slab::Slab;
pub type StarWarsSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
mod model;
pub use self::model::QueryRoot;
use model::Episode;
pub use model::QueryRoot;
pub type StarWarsSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
pub struct StarWarsChar {
id: &'static str,
name: &'static str,
is_human: bool,
friends: Vec<usize>,
appears_in: Vec<Episode>,
home_planet: Option<&'static str>,
@ -22,8 +23,7 @@ pub struct StarWars {
luke: usize,
artoo: usize,
chars: Slab<StarWarsChar>,
human_data: HashMap<&'static str, usize>,
droid_data: HashMap<&'static str, usize>,
chars_by_id: HashMap<&'static str, usize>,
}
impl StarWars {
@ -34,6 +34,7 @@ impl StarWars {
let luke = chars.insert(StarWarsChar {
id: "1000",
name: "Luke Skywalker",
is_human: true,
friends: vec![],
appears_in: vec![],
home_planet: Some("Tatooine"),
@ -42,7 +43,8 @@ impl StarWars {
let vader = chars.insert(StarWarsChar {
id: "1001",
name: "Luke Skywalker",
name: "Anakin Skywalker",
is_human: true,
friends: vec![],
appears_in: vec![],
home_planet: Some("Tatooine"),
@ -52,6 +54,7 @@ impl StarWars {
let han = chars.insert(StarWarsChar {
id: "1002",
name: "Han Solo",
is_human: true,
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
@ -61,6 +64,7 @@ impl StarWars {
let leia = chars.insert(StarWarsChar {
id: "1003",
name: "Leia Organa",
is_human: true,
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: Some("Alderaa"),
@ -70,6 +74,7 @@ impl StarWars {
let tarkin = chars.insert(StarWarsChar {
id: "1004",
name: "Wilhuff Tarkin",
is_human: true,
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
@ -79,6 +84,7 @@ impl StarWars {
let threepio = chars.insert(StarWarsChar {
id: "2000",
name: "C-3PO",
is_human: false,
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
@ -88,6 +94,7 @@ impl StarWars {
let artoo = chars.insert(StarWarsChar {
id: "2001",
name: "R2-D2",
is_human: false,
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
@ -102,39 +109,52 @@ impl StarWars {
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);
let chars_by_id = chars.iter().map(|(idx, ch)| (ch.id, idx)).collect();
Self {
luke,
artoo,
chars,
human_data,
droid_data,
chars_by_id,
}
}
pub fn human(&self, id: &str) -> Option<usize> {
self.human_data.get(id).cloned()
pub fn human(&self, id: &str) -> Option<&StarWarsChar> {
self.chars_by_id
.get(id)
.copied()
.map(|idx| self.chars.get(idx).unwrap())
.filter(|ch| ch.is_human)
}
pub fn droid(&self, id: &str) -> Option<usize> {
self.droid_data.get(id).cloned()
pub fn droid(&self, id: &str) -> Option<&StarWarsChar> {
self.chars_by_id
.get(id)
.copied()
.map(|idx| self.chars.get(idx).unwrap())
.filter(|ch| !ch.is_human)
}
pub fn humans(&self) -> Vec<usize> {
self.human_data.values().cloned().collect()
pub fn humans(&self) -> Vec<&StarWarsChar> {
self.chars
.iter()
.filter(|(_, ch)| ch.is_human)
.map(|(_, ch)| ch)
.collect()
}
pub fn droids(&self) -> Vec<usize> {
self.droid_data.values().cloned().collect()
pub fn droids(&self) -> Vec<&StarWarsChar> {
self.chars
.iter()
.filter(|(_, ch)| !ch.is_human)
.map(|(_, ch)| ch)
.collect()
}
pub fn friends(&self, ch: &StarWarsChar) -> Vec<&StarWarsChar> {
ch.friends
.iter()
.copied()
.filter_map(|id| self.chars.get(id))
.collect()
}
}

View File

@ -1,10 +1,12 @@
use async_graphql::connection::{query, Connection, Edge, EmptyFields};
use async_graphql::{Context, Enum, FieldResult, Interface, Object};
use async_graphql::{
connection::{query, Connection, Edge},
Context, Enum, Error, Interface, Object, OutputType, Result,
};
use super::StarWars;
use super::{StarWars, StarWarsChar};
/// One of the films in the Star Wars Trilogy
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, PartialEq, Eq, Enum)]
pub enum Episode {
/// Released in 1977.
NewHope,
@ -16,73 +18,85 @@ pub enum Episode {
Jedi,
}
pub struct Human(usize);
pub struct Human<'a>(&'a StarWarsChar);
/// A humanoid creature in the Star Wars universe.
#[Object]
impl Human {
impl<'a> Human<'a> {
/// The id of the human.
async fn id(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].id
async fn id(&self) -> &str {
self.0.id
}
/// The name of the human.
async fn name(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].name
async fn name(&self) -> &str {
self.0.name
}
/// The friends of the human, or an empty list if they have none.
async fn friends(&self, ctx: &Context<'_>) -> Vec<Character> {
ctx.data_unchecked::<StarWars>().chars[self.0]
.friends
.iter()
.map(|id| Human(*id).into())
async fn friends<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Character<'ctx>> {
ctx.data_unchecked::<StarWars>()
.friends(self.0)
.into_iter()
.map(|ch| {
if ch.is_human {
Human(ch).into()
} else {
Droid(ch).into()
}
})
.collect()
}
/// Which movies they appear in.
async fn appears_in<'a>(&self, ctx: &'a Context<'_>) -> &'a [Episode] {
&ctx.data_unchecked::<StarWars>().chars[self.0].appears_in
async fn appears_in(&self) -> &[Episode] {
&self.0.appears_in
}
/// The home planet of the human, or null if unknown.
async fn home_planet<'a>(&self, ctx: &'a Context<'_>) -> &'a Option<&'a str> {
&ctx.data_unchecked::<StarWars>().chars[self.0].home_planet
async fn home_planet(&self) -> &Option<&str> {
&self.0.home_planet
}
}
pub struct Droid(usize);
pub struct Droid<'a>(&'a StarWarsChar);
/// A mechanical creature in the Star Wars universe.
#[Object]
impl Droid {
impl<'a> Droid<'a> {
/// The id of the droid.
async fn id(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].id
async fn id(&self) -> &str {
self.0.id
}
/// The name of the droid.
async fn name(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].name
async fn name(&self) -> &str {
self.0.name
}
/// The friends of the droid, or an empty list if they have none.
async fn friends(&self, ctx: &Context<'_>) -> Vec<Character> {
ctx.data_unchecked::<StarWars>().chars[self.0]
.friends
.iter()
.map(|id| Droid(*id).into())
async fn friends<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Character<'ctx>> {
ctx.data_unchecked::<StarWars>()
.friends(self.0)
.into_iter()
.map(|ch| {
if ch.is_human {
Human(ch).into()
} else {
Droid(ch).into()
}
})
.collect()
}
/// Which movies they appear in.
async fn appears_in<'a>(&self, ctx: &'a Context<'_>) -> &'a [Episode] {
&ctx.data_unchecked::<StarWars>().chars[self.0].appears_in
async fn appears_in(&self) -> &[Episode] {
&self.0.appears_in
}
/// The primary function of the droid.
async fn primary_function<'a>(&self, ctx: &'a Context<'_>) -> &'a Option<&'a str> {
&ctx.data_unchecked::<StarWars>().chars[self.0].primary_function
async fn primary_function(&self) -> &Option<&str> {
&self.0.primary_function
}
}
@ -90,65 +104,67 @@ pub struct QueryRoot;
#[Object]
impl QueryRoot {
async fn hero(
async fn hero<'a>(
&self,
ctx: &Context<'_>,
ctx: &Context<'a>,
#[graphql(
desc = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode."
)]
episode: Episode,
) -> Character {
if episode == Episode::Empire {
Human(ctx.data_unchecked::<StarWars>().luke).into()
} else {
Droid(ctx.data_unchecked::<StarWars>().artoo).into()
episode: Option<Episode>,
) -> Character<'a> {
let star_wars = ctx.data_unchecked::<StarWars>();
match episode {
Some(episode_name) => {
if episode_name == Episode::Empire {
Human(star_wars.chars.get(star_wars.luke).unwrap()).into()
} else {
Droid(star_wars.chars.get(star_wars.artoo).unwrap()).into()
}
}
None => Human(star_wars.chars.get(star_wars.luke).unwrap()).into(),
}
}
async fn human(
async fn human<'a>(
&self,
ctx: &Context<'_>,
ctx: &Context<'a>,
#[graphql(desc = "id of the human")] id: String,
) -> Option<Human> {
) -> Option<Human<'a>> {
ctx.data_unchecked::<StarWars>().human(&id).map(Human)
}
async fn humans(
async fn humans<'a>(
&self,
ctx: &Context<'_>,
ctx: &Context<'a>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<usize, Human, EmptyFields, EmptyFields>> {
) -> Result<Connection<usize, Human<'a>>> {
let humans = ctx.data_unchecked::<StarWars>().humans().to_vec();
query_characters(after, before, first, last, &humans)
.await
.map(|conn| conn.map_node(Human))
query_characters(after, before, first, last, &humans, Human).await
}
async fn droid(
async fn droid<'a>(
&self,
ctx: &Context<'_>,
ctx: &Context<'a>,
#[graphql(desc = "id of the droid")] id: String,
) -> Option<Droid> {
) -> Option<Droid<'a>> {
ctx.data_unchecked::<StarWars>().droid(&id).map(Droid)
}
async fn droids(
async fn droids<'a>(
&self,
ctx: &Context<'_>,
ctx: &Context<'a>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<usize, Droid, EmptyFields, EmptyFields>> {
) -> Result<Connection<usize, Droid<'a>>> {
let droids = ctx.data_unchecked::<StarWars>().droids().to_vec();
query_characters(after, before, first, last, &droids)
.await
.map(|conn| conn.map_node(Droid))
query_characters(after, before, first, last, &droids, Droid).await
}
}
@ -156,21 +172,26 @@ impl QueryRoot {
#[graphql(
field(name = "id", type = "&str"),
field(name = "name", type = "&str"),
field(name = "friends", type = "Vec<Character>"),
field(name = "appears_in", type = "&'ctx [Episode]")
field(name = "friends", type = "Vec<Character<'ctx>>"),
field(name = "appears_in", type = "&[Episode]")
)]
pub enum Character {
Human(Human),
Droid(Droid),
pub enum Character<'a> {
Human(Human<'a>),
Droid(Droid<'a>),
}
async fn query_characters(
async fn query_characters<'a, F, T>(
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
characters: &[usize],
) -> FieldResult<Connection<usize, usize, EmptyFields, EmptyFields>> {
characters: &[&'a StarWarsChar],
map_to: F,
) -> Result<Connection<usize, T>>
where
F: Fn(&'a StarWarsChar) -> T,
T: OutputType,
{
query(
after,
before,
@ -205,14 +226,15 @@ async fn query_characters(
}
let mut connection = Connection::new(start > 0, end < characters.len());
connection.append(
connection.edges.extend(
slice
.iter()
.enumerate()
.map(|(idx, item)| Edge::new(start + idx, *item)),
.map(|(idx, item)| Edge::new(start + idx, (map_to)(*item))),
);
FieldResult::Ok(connection)
Ok::<_, Error>(connection)
},
)
.await