示例说明
GraphQL 的响应形状由查询文档决定——graphql-client 在编译期解析查询与 schema,生成与选择集精确对应的 Rust 结构体:选了 id、name、lifetimeValue,响应类型就只有这三个字段,拼错字段名直接编译失败。变量结构体同样生成,region 与 first 的类型与 schema 参数定义一致。
技术要点
- 查询文档编译期生成响应类型
- 变量结构体与 schema 参数对齐
- GraphQL 层错误与 HTTP 层错误分流
代码解读
TopCustomers 结构体的 derive 属性声明查询与 schema 的路径;build_query 把变量序列化为请求体,reqwest 发送后反序列化为生成的 Response 泛型——data 与 errors 两个字段都是 Option,查询级错误逐条打印不静默。edges 展平为节点集合,空连接回退空集。
使用建议
生成代码由构建脚本驱动,查询文档变更时重新生成即暴露差异。认证头等公共配置建议封装客户端工厂。分页遵循连接规范时可写通用的游标推进器。
代码示例
use graphql_client::{GraphQLQuery, Response};
// queries/top_customers.graphql:
// query TopCustomers($region: Region!, $first: Int!) {
// customers(region: $region, first: $first) {
// edges { node { id name lifetimeValue } }
// }
// }
#[derive(GraphQLQuery)]
#[graphql(
query_path = "queries/top_customers.graphql",
schema_path = "schemas/customer.graphql",
response_derives = "Debug"
)]
pub struct TopCustomers;
type Customer = top_customers::TopCustomersCustomersEdgesNode;
async fn fetch_top_customers(
region: &str,
first: i64,
) -> Result<Vec<Customer>, Box<dyn std::error::Error>> {
let variables = top_customers::Variables {
region: region.into(),
first,
};
let request_body = TopCustomers::build_query(variables);
let client = reqwest::Client::new();
let res = client
.post("https://api.example.com/graphql")
.json(&request_body)
.send()
.await?
.error_for_status()?;
let response_body: Response<top_customers::ResponseData> = res.json().await?;
let data = response_body
.data
.ok_or("query returned no data")?;
if let Some(errors) = response_body.errors {
for err in &errors {
eprintln!("graphql error: {:?}", err.message);
}
}
let customers: Vec<Customer> = data
.customers
.map(|conn| {
conn.edges
.into_iter()
.filter_map(|edge| edge.map(|e| e.node))
.collect()
})
.unwrap_or_default();
Ok(customers)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let customers = fetch_top_customers("eu", 5).await?;
for customer in customers {
println!("{:?} value={}", customer, customer.lifetime_value);
}
Ok(())
}