Skip to main content

ecat_graphql/
lib.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2use axum::Router;
3use axum::http::StatusCode;
4use axum::response::{IntoResponse, Response};
5use axum::routing::post;
6use serde::Deserialize;
7use serde_json::{Map, Value};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11mod parser;
12mod validation;
13
14pub use parser::{FieldNode, Operation, SelectionSet};
15pub use validation::QueryLimits;
16
17type Resolver = Arc<
18    dyn Fn(
19            serde_json::Value,
20        ) -> std::pin::Pin<
21            Box<dyn std::future::Future<Output = Result<serde_json::Value, String>> + Send>,
22        > + Send
23        + Sync,
24>;
25
26/// 富 resolver 请求上下文:字段参数、原始 variables 与嵌套 selection 树。
27#[derive(Debug, Clone)]
28pub struct FieldRequest {
29    pub args: Map<String, Value>,
30    pub variables: Value,
31    pub selection: Option<SelectionSet>,
32}
33
34/// 富 resolver trait:可访问字段参数与嵌套 selection(经
35/// [`GraphQLSchema::query_field`] / [`GraphQLSchema::mutation_field`] 注册)。
36/// async fn in trait 非 dyn-compatible,故用 #[async_trait] 换取对象安全
37/// (可存入 `FieldHandler::Rich` 的 `Arc<dyn GraphQLField>`)。
38#[async_trait::async_trait]
39pub trait GraphQLField: Send + Sync {
40    async fn resolve(&self, req: FieldRequest) -> Result<Value, String>;
41}
42
43enum FieldHandler {
44    Legacy(Resolver),
45    Rich(Arc<dyn GraphQLField>),
46}
47
48pub struct GraphQLSchema {
49    query_resolvers: HashMap<String, FieldHandler>,
50    mutation_resolvers: HashMap<String, FieldHandler>,
51    limits: QueryLimits,
52}
53
54impl GraphQLSchema {
55    pub fn new() -> Self {
56        Self {
57            query_resolvers: HashMap::new(),
58            mutation_resolvers: HashMap::new(),
59            limits: QueryLimits::default(),
60        }
61    }
62
63    /// 配置执行前查询预算(防查询放大 DoS),默认已启用。
64    pub fn with_limits(mut self, limits: QueryLimits) -> Self {
65        self.limits = limits;
66        self
67    }
68
69    pub fn query(mut self, name: impl Into<String>, r: Resolver) -> Self {
70        self.query_resolvers
71            .insert(name.into(), FieldHandler::Legacy(r));
72        self
73    }
74
75    pub fn mutation(mut self, name: impl Into<String>, r: Resolver) -> Self {
76        self.mutation_resolvers
77            .insert(name.into(), FieldHandler::Legacy(r));
78        self
79    }
80
81    pub fn query_fn(
82        self,
83        name: impl Into<String>,
84        f: impl Fn(
85            serde_json::Value,
86        ) -> std::pin::Pin<
87            Box<dyn std::future::Future<Output = Result<serde_json::Value, String>> + Send>,
88        > + Send
89        + Sync
90        + 'static,
91    ) -> Self {
92        self.query(name, Arc::new(f))
93    }
94
95    /// 注册富 query resolver:接收 `FieldRequest`(参数 + 嵌套 selection)。
96    pub fn query_field(mut self, name: impl Into<String>, f: impl GraphQLField + 'static) -> Self {
97        self.query_resolvers
98            .insert(name.into(), FieldHandler::Rich(Arc::new(f)));
99        self
100    }
101
102    /// 注册富 mutation resolver:接收 `FieldRequest`(参数 + 嵌套 selection)。
103    pub fn mutation_field(
104        mut self,
105        name: impl Into<String>,
106        f: impl GraphQLField + 'static,
107    ) -> Self {
108        self.mutation_resolvers
109            .insert(name.into(), FieldHandler::Rich(Arc::new(f)));
110        self
111    }
112}
113
114impl Default for GraphQLSchema {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120#[derive(Debug, Deserialize)]
121struct GqlReq {
122    query: String,
123    #[serde(default)]
124    variables: serde_json::Value,
125}
126
127pub fn graphql_router(schema: GraphQLSchema) -> Router {
128    let schema = Arc::new(schema);
129
130    async fn handler(axum::Json(req): axum::Json<GqlReq>, schema: Arc<GraphQLSchema>) -> Response {
131        match execute(&schema, &req.query, &req.variables).await {
132            Ok(data) => axum::Json(serde_json::json!({"data": data})).into_response(),
133            Err(errors) => (
134                StatusCode::BAD_REQUEST,
135                axum::Json(serde_json::json!({"errors": errors})),
136            )
137                .into_response(),
138        }
139    }
140
141    let s = Arc::clone(&schema);
142    Router::new().route("/graphql", post(move |body| handler(body, Arc::clone(&s))))
143}
144
145async fn execute(
146    schema: &GraphQLSchema,
147    query: &str,
148    variables: &serde_json::Value,
149) -> Result<serde_json::Value, Vec<String>> {
150    let trimmed = query.trim();
151    let mut errors = Vec::new();
152
153    let field = match parser::parse_query(trimmed, variables) {
154        Ok(f) => f,
155        Err(e) => {
156            errors.push(e);
157            return Err(errors);
158        }
159    };
160
161    if let Err(e) = validation::validate(&field, &schema.limits) {
162        errors.push(e);
163        return Err(errors);
164    }
165
166    let (resolvers, field_name) = if field.operation == Operation::Mutation {
167        (&schema.mutation_resolvers, &field.name)
168    } else {
169        (&schema.query_resolvers, &field.name)
170    };
171
172    match resolvers.get(field_name) {
173        Some(FieldHandler::Legacy(resolver)) => {
174            let vars = match merge_args(variables, &field.args) {
175                Ok(v) => v,
176                Err(e) => {
177                    errors.push(e);
178                    return Err(errors);
179                }
180            };
181            match resolver(vars).await {
182                Ok(data) => {
183                    let mut result = serde_json::Map::new();
184                    result.insert(field.name.clone(), data);
185                    return Ok(serde_json::Value::Object(result));
186                }
187                Err(e) => errors.push(e),
188            }
189        }
190        Some(FieldHandler::Rich(r)) => {
191            let req = FieldRequest {
192                args: field.args,
193                variables: variables.clone(),
194                selection: field.selection,
195            };
196            match r.resolve(req).await {
197                Ok(data) => {
198                    let mut result = serde_json::Map::new();
199                    result.insert(field.name.clone(), data);
200                    return Ok(serde_json::Value::Object(result));
201                }
202                Err(e) => errors.push(e),
203            }
204        }
205        None => errors.push(format!("unknown field: {field_name}")),
206    }
207
208    Err(errors)
209}
210
211/// Legacy resolver 的参数合并:字段参数并入 variables(同名时参数胜出)。
212/// 无参数时与旧行为逐字节一致(直接克隆 variables)。
213fn merge_args(variables: &Value, args: &Map<String, Value>) -> Result<Value, String> {
214    if args.is_empty() {
215        return Ok(variables.clone());
216    }
217    match variables {
218        Value::Object(m) => {
219            let mut merged = m.clone();
220            for (k, v) in args {
221                merged.insert(k.clone(), v.clone());
222            }
223            Ok(Value::Object(merged))
224        }
225        Value::Null => Ok(Value::Object(args.clone())),
226        _ => Err("variables must be a JSON object when field arguments are present".into()),
227    }
228}
229
230#[cfg(test)]
231mod tests;