Rust graphql 入门之星球大战示例1
2021-07-24 本文已影响0人
子十一刻
星球大战 GraphQL 项目示例,基于 async-graphql + actix-web。项目结构略有修改
1.创建项目
cargo new startwars
2.目录结构

3. 添加依赖包

4.main.rs 添加测试代码
bin/main.rs
use actix_web::{guard, web, App, HttpResponse, HttpServer, Result};
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
async fn index() -> HttpResponse {
HttpResponse::Ok().body("index test")
}
async fn index_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(playground_source(
GraphQLPlaygroundConfig::new("/").subscription_endpoint("/"),
))
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Playgroud: http://localhost:8000");
HttpServer::new(move || {
App::new()
.service(
web::resource("/").guard(guard::Post()).to(index)
)
.service(
web::resource("/")
.guard(guard::Get()).to(index_playground)
)
})
.bind("0.0.0.0:8000")?
.run()
.await
}
运行项目 cargo run
访问 http://localhost:8000

lib.rs
///
/// 星球大战
///
// 声名模块
pub mod common;
pub mod human;
pub mod droid;
pub mod query_root;
pub mod star_wars;
// 模块重新导出
pub use common::*;
pub use human::*;
pub use droid::*;
pub use query_root::*;
pub use star_wars::*;
common.rs
use crate::{Human, Droid, QueryRoot};
use async_graphql::{Interface, Enum, Schema, EmptyMutation, EmptySubscription};
pub type StarWarsSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
///
/// 星战系列
/// 正传三部曲
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum Episode {
// 新希望
NewHope,
// 帝国反击战
Empire,
// 绝地归来
Jedi
}
/// 角色类型
#[derive(Interface)]
#[graphql(
field(name = "id", type = "&str"),
field(name = "name", type = "&str"),
field(name = "friends", type = "Vec<Character>"),
field(name = "appears_in", type = "&'ctx [Episode]"),
)]
pub enum Character {
// 人类
Human(Human),
// 机器人
Droid(Droid),
}
/// 角色
pub struct StarWarsChar {
pub id: &'static str,
// 姓名
pub name: &'static str,
// 英文姓名
pub en_name: &'static str,
// 朋友
pub friends: Vec<usize>,
// 参与的电影系列
pub appears_in: Vec<Episode>,
// 籍贯行星
pub home_planet: Option<&'static str>,
// 主要功能
pub primary_function: Option<&'static str>,
}
droid.rs
use super::{Character, Episode, StarWars};
use async_graphql::{Context, Object};
/// 机器人
pub struct Droid(pub usize);
#[Object]
impl Droid {
pub async fn id(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].id
}
pub async fn name(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].name
}
pub async fn friends(&self, ctx: &Context<'_>) -> Vec<Character> {
ctx.data_unchecked::<StarWars>().chars[self.0]
.friends
.iter()
.map(|id| Droid(*id).into())
.collect()
}
pub async fn appears_in<'a>(&self, ctx: &'a Context<'_>) -> &'a [Episode] {
&ctx.data_unchecked::<StarWars>().chars[self.0].appears_in
}
pub async fn primary_function<'a>(&self, ctx: &'a Context<'_>) -> &'a Option<&'a str> {
&ctx.data_unchecked::<StarWars>().chars[self.0].primary_function
}
}
human.rs
use super::{Character, Episode, StarWars};
use async_graphql::{Context, Object};
/// 人类
pub struct Human(pub usize);
#[Object]
impl Human {
pub async fn id(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].id
}
pub async fn name(&self, ctx: &Context<'_>) -> &str {
ctx.data_unchecked::<StarWars>().chars[self.0].name
}
pub async fn friends(&self, ctx: &Context<'_>) -> Vec<Character> {
ctx.data_unchecked::<StarWars>().chars[self.0]
.friends
.iter()
.map(|id| Human(*id).into())
.collect()
}
pub async fn appears_in<'a>(&self, ctx: &'a Context<'_>) -> &'a [Episode] {
&ctx.data_unchecked::<StarWars>().chars[self.0].appears_in
}
pub async fn home_planet<'a>(&self, ctx: &'a Context<'_>) -> &'a Option<&'a str> {
&ctx.data_unchecked::<StarWars>().chars[self.0].home_planet
}
}
star_wars.rs
use std::collections::HashMap;
use slab::Slab;
use crate::{StarWarsChar, Episode};
///
/// 星球大战所有数据初始化
///
pub struct StarWars {
// 卢克·天行者
pub luke: usize,
// R2-D2
pub artoo: usize,
// 所有角色
pub chars: Slab<StarWarsChar>,
// 人类数据
pub human_data: HashMap<&'static str, usize>,
// 机器人数据
pub droid_data: HashMap<&'static str, usize>,
}
impl StarWars {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let mut chars = Slab::new();
let luke = chars.insert(StarWarsChar {
id: "1000",
name: "卢克·天行者",
en_name: "Luke Skywalker",
friends: vec![],
appears_in: vec![],
home_planet: Some("塔图因星(Tatooine)"),
primary_function: None,
});
// 卢克
let vader = chars.insert(StarWarsChar {
id: "1001",
name: "卢克·天行者",
en_name: "Luke Skywalker",
friends: vec![],
appears_in: vec![],
home_planet: Some("塔图因星(Tatooine)"),
primary_function: None,
});
// 汉
let han = chars.insert(StarWarsChar {
id: "1002",
name: "汉·索罗",
en_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: "莉亚·欧嘉纳",
en_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: "威尔赫夫·塔金",
en_name: "Wilhuff Tarkin",
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
primary_function: None,
});
// 3po
let threepio = chars.insert(StarWarsChar {
id: "2000",
name: "C-3PO",
en_name: "C-3PO",
friends: vec![],
appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi],
home_planet: None,
primary_function: Some("礼仪机器人(Protocol)"),
});
// r2
let artoo = chars.insert(StarWarsChar {
id: "2001",
name: "R2-D2",
en_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 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()
}
pub fn humans(&self) -> Vec<usize> {
self.human_data.values().cloned().collect()
}
pub fn droids(&self) -> Vec<usize> {
self.droid_data.values().cloned().collect()
}
}
query_root.rs
use async_graphql::{Object, Context, FieldResult};
use async_graphql::connection::{query, Connection, Edge, EmptyFields};
use super::{Episode, Human, Droid, StarWars, Character};
pub struct QueryRoot;
#[Object]
impl QueryRoot {
async fn hero(
&self,
ctx: &Context<'_>,
#[graphql(
desc = "无值返回所有系列的角色,有值返回指定的系列角色"
)]
episode: Episode,
) -> Character {
if episode == Episode::Empire {
Human(ctx.data_unchecked::<StarWars>().luke).into()
} else {
Droid(ctx.data_unchecked::<StarWars>().artoo).into()
}
}
async fn human(
&self,
ctx: &Context<'_>,
#[graphql(desc = "人类ID")]
id: String,
) -> Option<Human> {
ctx.data_unchecked::<StarWars>().human(&id).map(Human)
}
async fn humans(
&self,
ctx: &Context<'_>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<usize, Human, EmptyFields, EmptyFields>> {
let humans = ctx
.data_unchecked::<StarWars>()
.humans()
.iter()
.copied()
.collect::<Vec<_>>();
query_characters(after, before, first, last, &humans)
.await
.map(|conn| conn.map_node(Human))
}
}
async fn query_characters(
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
characters: &[usize],
) -> FieldResult<Connection<usize, usize, EmptyFields, EmptyFields>> {
query(
after,
before,
first,
last,
|after, before, first, last| async move {
let mut start = 0usize;
let mut end = characters.len();
// 如果有 after 参数 更新 start
if let Some(after) = after {
if after >= characters.len() {
return Ok(Connection::new(false, false));
}
start = after + 1;
}
// 如果有 before 参数 更新 end
if let Some(before) = before {
if before == 0 {
return Ok(Connection::new(false, false));
}
end = before;
}
let mut slice = &characters[start..end];
// 如果有 first 参数
if let Some(first) = first {
// 更新切片为 first 参数指定大小的切片 最大是整个切片 从前向后取
slice = &slice[..first.min(slice.len())];
end -= first.min(slice.len());
} else if let Some(last) = last {
// 如果指定了 last 参数
// 更新切片 从后向前取 last 个数据
slice = &slice[slice.len() - last.min(slice.len())..];
start = end - last.min(slice.len());
}
let mut connection = Connection::new(
start > 0,
end < characters.len()
);
connection.append(
slice
.iter()
.enumerate()
.map(|(idx, item)| Edge::new(start + idx, *item)),
);
Ok(connection)
},
)
.await
}
5. 测试运行并更新入口文件 main.rs
先测试无报错再修改 main.rs
use actix_web::{guard, web, App, HttpResponse, HttpServer, Result};
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
use async_graphql::{EmptyMutation, EmptySubscription, Schema};
use async_graphql_actix_web::{Request, Response};
use startwars1::{QueryRoot, StarWars, StarWarsSchema};
async fn index(schema: web::Data<StarWarsSchema>, req: Request) -> Response {
schema.execute(req.into_inner()).await.into()
}
async fn index_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(playground_source(
GraphQLPlaygroundConfig::new("/").subscription_endpoint("/"),
))
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let schema = Schema::build(
QueryRoot,
EmptyMutation,
EmptySubscription,
)
.data(StarWars::new())
.finish();
println!("Playgroud: http://localhost:8000");
HttpServer::new(move || {
App::new()
.data(schema.clone())
.service(
web::resource("/").guard(guard::Post()).to(index)
)
.service(
web::resource("/")
.guard(guard::Get()).to(index_playground)
)
})
.bind("0.0.0.0:8000")?
.run()
.await
}
请求示例:


