Skip to main content

oxirs_gql/
field_resolver.rs

1/// Field-level resolver pipeline with middleware support for GraphQL.
2///
3/// Provides a composable pipeline where field handlers can be wrapped
4/// with middleware that intercepts calls for logging, auth, caching, etc.
5use std::collections::HashMap;
6use std::fmt;
7
8/// Result type returned by resolvers and middleware.
9pub type ResolverResult = Result<serde_json::Value, ResolverError>;
10
11/// Errors that can occur during field resolution.
12#[derive(Debug)]
13pub enum ResolverError {
14    /// The requested field has no registered handler.
15    FieldNotFound(String),
16    /// A middleware layer encountered an error.
17    MiddlewareError(String),
18    /// The field handler itself returned an error.
19    HandlerError(String),
20}
21
22impl fmt::Display for ResolverError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            ResolverError::FieldNotFound(name) => write!(f, "Field not found: {name}"),
26            ResolverError::MiddlewareError(msg) => write!(f, "Middleware error: {msg}"),
27            ResolverError::HandlerError(msg) => write!(f, "Handler error: {msg}"),
28        }
29    }
30}
31
32impl std::error::Error for ResolverError {}
33
34/// Context passed to every resolver and middleware call.
35#[derive(Debug, Clone)]
36pub struct ResolverContext {
37    /// The name of the field being resolved.
38    pub field_name: String,
39    /// The value of the parent object, if any.
40    pub parent_value: Option<serde_json::Value>,
41    /// Arguments passed to the field.
42    pub args: HashMap<String, serde_json::Value>,
43}
44
45impl ResolverContext {
46    /// Create a new resolver context.
47    pub fn new(field_name: impl Into<String>) -> Self {
48        Self {
49            field_name: field_name.into(),
50            parent_value: None,
51            args: HashMap::new(),
52        }
53    }
54
55    /// Create a context with a parent value.
56    pub fn with_parent(mut self, parent: serde_json::Value) -> Self {
57        self.parent_value = Some(parent);
58        self
59    }
60
61    /// Add an argument to the context.
62    pub fn with_arg(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
63        self.args.insert(key.into(), value);
64        self
65    }
66}
67
68/// Trait implemented by field middleware.
69///
70/// Middleware wraps field resolution allowing pre/post processing.
71pub trait FieldMiddleware: Send + Sync {
72    /// Return the name of this middleware (for diagnostics).
73    fn name(&self) -> &str;
74
75    /// Process the field resolution, optionally calling `next` to continue.
76    fn process(
77        &self,
78        ctx: &ResolverContext,
79        next: &dyn Fn(&ResolverContext) -> ResolverResult,
80    ) -> ResolverResult;
81}
82
83/// A handler function for a field.
84type HandlerFn = Box<dyn Fn(&ResolverContext) -> ResolverResult + Send + Sync>;
85
86/// Field resolver pipeline.
87///
88/// Executes registered middleware in order, then calls the field handler.
89pub struct FieldResolver {
90    middlewares: Vec<Box<dyn FieldMiddleware>>,
91    handlers: HashMap<String, HandlerFn>,
92}
93
94impl FieldResolver {
95    /// Create a new empty resolver.
96    pub fn new() -> Self {
97        Self {
98            middlewares: Vec::new(),
99            handlers: HashMap::new(),
100        }
101    }
102
103    /// Add a middleware to the pipeline.
104    ///
105    /// Middleware is applied in order of registration (outermost first).
106    pub fn add_middleware(&mut self, mw: Box<dyn FieldMiddleware>) {
107        self.middlewares.push(mw);
108    }
109
110    /// Register a handler for a field name.
111    pub fn register(
112        &mut self,
113        field: impl Into<String>,
114        handler: impl Fn(&ResolverContext) -> ResolverResult + Send + Sync + 'static,
115    ) {
116        self.handlers.insert(field.into(), Box::new(handler));
117    }
118
119    /// Resolve a field by running it through the middleware pipeline.
120    pub fn resolve(&self, ctx: ResolverContext) -> ResolverResult {
121        // Build the innermost "call handler" function.
122        let field_name = ctx.field_name.clone();
123        let handler = self
124            .handlers
125            .get(&field_name)
126            .ok_or_else(|| ResolverError::FieldNotFound(field_name.clone()))?;
127
128        // Wrap handler in a closure for the middleware chain.
129        let base: &dyn Fn(&ResolverContext) -> ResolverResult = handler.as_ref();
130
131        // Build the middleware chain from back to front.
132        // We need to compose dynamically since each closure captures the next.
133        // Use index-based recursion via a helper.
134        self.run_middleware(&ctx, 0, base)
135    }
136
137    fn run_middleware(
138        &self,
139        ctx: &ResolverContext,
140        idx: usize,
141        final_handler: &dyn Fn(&ResolverContext) -> ResolverResult,
142    ) -> ResolverResult {
143        if idx >= self.middlewares.len() {
144            return final_handler(ctx);
145        }
146
147        let mw = &self.middlewares[idx];
148        let next_idx = idx + 1;
149
150        // Build a "next" closure that invokes the remaining middleware.
151        let next = |next_ctx: &ResolverContext| -> ResolverResult {
152            self.run_middleware(next_ctx, next_idx, final_handler)
153        };
154
155        mw.process(ctx, &next)
156    }
157
158    /// Return the number of registered middleware layers.
159    pub fn middleware_count(&self) -> usize {
160        self.middlewares.len()
161    }
162
163    /// Return sorted list of registered field names.
164    pub fn registered_fields(&self) -> Vec<&str> {
165        let mut fields: Vec<&str> = self.handlers.keys().map(String::as_str).collect();
166        fields.sort_unstable();
167        fields
168    }
169
170    /// Return true if a handler is registered for the given field.
171    pub fn has_field(&self, field: &str) -> bool {
172        self.handlers.contains_key(field)
173    }
174}
175
176impl Default for FieldResolver {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182// ---- Built-in middleware examples ----
183
184/// Logging middleware that records field resolution attempts.
185pub struct LoggingMiddleware {
186    pub prefix: String,
187}
188
189impl LoggingMiddleware {
190    pub fn new(prefix: impl Into<String>) -> Self {
191        Self {
192            prefix: prefix.into(),
193        }
194    }
195}
196
197impl FieldMiddleware for LoggingMiddleware {
198    fn name(&self) -> &str {
199        "logging"
200    }
201
202    fn process(
203        &self,
204        ctx: &ResolverContext,
205        next: &dyn Fn(&ResolverContext) -> ResolverResult,
206    ) -> ResolverResult {
207        let _log = format!("{} resolving field: {}", self.prefix, ctx.field_name);
208        next(ctx)
209    }
210}
211
212/// Middleware that always returns an error (for testing error propagation).
213pub struct ErrorMiddleware {
214    message: String,
215}
216
217impl ErrorMiddleware {
218    pub fn new(message: impl Into<String>) -> Self {
219        Self {
220            message: message.into(),
221        }
222    }
223}
224
225impl FieldMiddleware for ErrorMiddleware {
226    fn name(&self) -> &str {
227        "error_injector"
228    }
229
230    fn process(
231        &self,
232        _ctx: &ResolverContext,
233        _next: &dyn Fn(&ResolverContext) -> ResolverResult,
234    ) -> ResolverResult {
235        Err(ResolverError::MiddlewareError(self.message.clone()))
236    }
237}
238
239/// Middleware that transforms the context field name to uppercase.
240pub struct UppercaseFieldMiddleware;
241
242impl FieldMiddleware for UppercaseFieldMiddleware {
243    fn name(&self) -> &str {
244        "uppercase_field"
245    }
246
247    fn process(
248        &self,
249        ctx: &ResolverContext,
250        next: &dyn Fn(&ResolverContext) -> ResolverResult,
251    ) -> ResolverResult {
252        let mut new_ctx = ctx.clone();
253        new_ctx.field_name = ctx.field_name.to_uppercase();
254        next(&new_ctx)
255    }
256}
257
258/// Middleware that passes through to the next layer (no-op).
259pub struct PassthroughMiddleware;
260
261impl FieldMiddleware for PassthroughMiddleware {
262    fn name(&self) -> &str {
263        "passthrough"
264    }
265
266    fn process(
267        &self,
268        ctx: &ResolverContext,
269        next: &dyn Fn(&ResolverContext) -> ResolverResult,
270    ) -> ResolverResult {
271        next(ctx)
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use serde_json::json;
279
280    fn make_resolver() -> FieldResolver {
281        let mut r = FieldResolver::new();
282        r.register("hello", |_ctx| Ok(json!("world")));
283        r.register("answer", |_ctx| Ok(json!(42)));
284        r.register("null_field", |_ctx| Ok(json!(null)));
285        r
286    }
287
288    // --- ResolverError ---
289
290    #[test]
291    fn test_resolver_error_display_field_not_found() {
292        let e = ResolverError::FieldNotFound("foo".to_string());
293        assert!(e.to_string().contains("foo"));
294    }
295
296    #[test]
297    fn test_resolver_error_display_middleware() {
298        let e = ResolverError::MiddlewareError("oops".to_string());
299        assert!(e.to_string().contains("oops"));
300    }
301
302    #[test]
303    fn test_resolver_error_display_handler() {
304        let e = ResolverError::HandlerError("bad".to_string());
305        assert!(e.to_string().contains("bad"));
306    }
307
308    #[test]
309    fn test_resolver_error_is_error_trait() {
310        let e: Box<dyn std::error::Error> = Box::new(ResolverError::FieldNotFound("x".to_string()));
311        assert!(!e.to_string().is_empty());
312    }
313
314    // --- ResolverContext ---
315
316    #[test]
317    fn test_context_new() {
318        let ctx = ResolverContext::new("myField");
319        assert_eq!(ctx.field_name, "myField");
320        assert!(ctx.parent_value.is_none());
321        assert!(ctx.args.is_empty());
322    }
323
324    #[test]
325    fn test_context_with_parent() {
326        let ctx = ResolverContext::new("f").with_parent(json!({"id": 1}));
327        assert!(ctx.parent_value.is_some());
328    }
329
330    #[test]
331    fn test_context_with_arg() {
332        let ctx = ResolverContext::new("f").with_arg("limit", json!(10));
333        assert_eq!(ctx.args.get("limit"), Some(&json!(10)));
334    }
335
336    #[test]
337    fn test_context_clone() {
338        let ctx = ResolverContext::new("f").with_arg("k", json!("v"));
339        let ctx2 = ctx.clone();
340        assert_eq!(ctx2.field_name, "f");
341    }
342
343    #[test]
344    fn test_context_multiple_args() {
345        let ctx = ResolverContext::new("f")
346            .with_arg("a", json!(1))
347            .with_arg("b", json!(2));
348        assert_eq!(ctx.args.len(), 2);
349    }
350
351    // --- FieldResolver construction ---
352
353    #[test]
354    fn test_new_resolver_empty() {
355        let r = FieldResolver::new();
356        assert_eq!(r.middleware_count(), 0);
357        assert!(r.registered_fields().is_empty());
358    }
359
360    #[test]
361    fn test_default_resolver() {
362        let r = FieldResolver::default();
363        assert_eq!(r.middleware_count(), 0);
364    }
365
366    #[test]
367    fn test_register_field() {
368        let mut r = FieldResolver::new();
369        r.register("myField", |_| Ok(json!("ok")));
370        assert!(r.has_field("myField"));
371    }
372
373    #[test]
374    fn test_registered_fields_sorted() {
375        let mut r = FieldResolver::new();
376        r.register("z_field", |_| Ok(json!(1)));
377        r.register("a_field", |_| Ok(json!(2)));
378        r.register("m_field", |_| Ok(json!(3)));
379        let fields = r.registered_fields();
380        assert_eq!(fields, vec!["a_field", "m_field", "z_field"]);
381    }
382
383    #[test]
384    fn test_middleware_count() {
385        let mut r = FieldResolver::new();
386        r.add_middleware(Box::new(PassthroughMiddleware));
387        r.add_middleware(Box::new(PassthroughMiddleware));
388        assert_eq!(r.middleware_count(), 2);
389    }
390
391    // --- resolve ---
392
393    #[test]
394    fn test_resolve_registered_field() {
395        let r = make_resolver();
396        let ctx = ResolverContext::new("hello");
397        let result = r.resolve(ctx).expect("should resolve");
398        assert_eq!(result, json!("world"));
399    }
400
401    #[test]
402    fn test_resolve_integer_field() {
403        let r = make_resolver();
404        let ctx = ResolverContext::new("answer");
405        let result = r.resolve(ctx).expect("should resolve");
406        assert_eq!(result, json!(42));
407    }
408
409    #[test]
410    fn test_resolve_null_field() {
411        let r = make_resolver();
412        let ctx = ResolverContext::new("null_field");
413        let result = r.resolve(ctx).expect("should resolve");
414        assert_eq!(result, json!(null));
415    }
416
417    #[test]
418    fn test_resolve_unknown_field_returns_error() {
419        let r = make_resolver();
420        let ctx = ResolverContext::new("nonexistent");
421        let err = r.resolve(ctx).expect_err("should fail");
422        assert!(matches!(err, ResolverError::FieldNotFound(_)));
423    }
424
425    #[test]
426    fn test_resolve_with_context_args() {
427        let mut r = FieldResolver::new();
428        r.register("greet", |ctx| {
429            let name = ctx
430                .args
431                .get("name")
432                .and_then(|v| v.as_str())
433                .unwrap_or("World");
434            Ok(json!(format!("Hello, {name}!")))
435        });
436        let ctx = ResolverContext::new("greet").with_arg("name", json!("Rust"));
437        let result = r.resolve(ctx).expect("ok");
438        assert_eq!(result, json!("Hello, Rust!"));
439    }
440
441    #[test]
442    fn test_resolve_handler_error_propagated() {
443        let mut r = FieldResolver::new();
444        r.register("bad", |_| Err(ResolverError::HandlerError("fail".into())));
445        let ctx = ResolverContext::new("bad");
446        let err = r.resolve(ctx).expect_err("should fail");
447        assert!(matches!(err, ResolverError::HandlerError(_)));
448    }
449
450    // --- Middleware ---
451
452    #[test]
453    fn test_passthrough_middleware_does_not_change_result() {
454        let mut r = make_resolver();
455        r.add_middleware(Box::new(PassthroughMiddleware));
456        let ctx = ResolverContext::new("hello");
457        let result = r.resolve(ctx).expect("ok");
458        assert_eq!(result, json!("world"));
459    }
460
461    #[test]
462    fn test_logging_middleware_transparent() {
463        let mut r = make_resolver();
464        r.add_middleware(Box::new(LoggingMiddleware::new("TEST")));
465        let ctx = ResolverContext::new("answer");
466        let result = r.resolve(ctx).expect("ok");
467        assert_eq!(result, json!(42));
468    }
469
470    #[test]
471    fn test_error_middleware_short_circuits() {
472        let mut r = make_resolver();
473        r.add_middleware(Box::new(ErrorMiddleware::new("blocked")));
474        let ctx = ResolverContext::new("hello");
475        let err = r.resolve(ctx).expect_err("should fail");
476        assert!(matches!(err, ResolverError::MiddlewareError(_)));
477    }
478
479    #[test]
480    fn test_middleware_chain_order() {
481        // Two passthrough middlewares → result still returned.
482        let mut r = make_resolver();
483        r.add_middleware(Box::new(PassthroughMiddleware));
484        r.add_middleware(Box::new(LoggingMiddleware::new("x")));
485        let ctx = ResolverContext::new("hello");
486        assert!(r.resolve(ctx).is_ok());
487    }
488
489    #[test]
490    fn test_middleware_name() {
491        let mw = LoggingMiddleware::new("test");
492        assert_eq!(mw.name(), "logging");
493
494        let emw = ErrorMiddleware::new("err");
495        assert_eq!(emw.name(), "error_injector");
496
497        let pmw = PassthroughMiddleware;
498        assert_eq!(pmw.name(), "passthrough");
499    }
500
501    #[test]
502    fn test_multiple_fields_resolved_correctly() {
503        let r = make_resolver();
504        let r1 = r.resolve(ResolverContext::new("hello")).expect("ok");
505        let r2 = r.resolve(ResolverContext::new("answer")).expect("ok");
506        assert_eq!(r1, json!("world"));
507        assert_eq!(r2, json!(42));
508    }
509
510    #[test]
511    fn test_resolve_with_parent_value() {
512        let mut r = FieldResolver::new();
513        r.register("id", |ctx| {
514            let id = ctx
515                .parent_value
516                .as_ref()
517                .and_then(|v| v.get("id"))
518                .cloned()
519                .unwrap_or(json!(0));
520            Ok(id)
521        });
522        let ctx = ResolverContext::new("id").with_parent(json!({"id": 99}));
523        let result = r.resolve(ctx).expect("ok");
524        assert_eq!(result, json!(99));
525    }
526
527    #[test]
528    fn test_error_before_middleware_not_reached() {
529        // When field is not found, middleware shouldn't even be called.
530        let mut r = FieldResolver::new();
531        r.add_middleware(Box::new(PassthroughMiddleware));
532        let ctx = ResolverContext::new("not_registered");
533        let err = r.resolve(ctx).expect_err("should fail");
534        assert!(matches!(err, ResolverError::FieldNotFound(_)));
535    }
536
537    #[test]
538    fn test_has_field_true() {
539        let r = make_resolver();
540        assert!(r.has_field("hello"));
541        assert!(r.has_field("answer"));
542    }
543
544    #[test]
545    fn test_has_field_false() {
546        let r = make_resolver();
547        assert!(!r.has_field("missing"));
548    }
549
550    #[test]
551    fn test_overwrite_handler() {
552        let mut r = FieldResolver::new();
553        r.register("f", |_| Ok(json!(1)));
554        r.register("f", |_| Ok(json!(2)));
555        let result = r.resolve(ResolverContext::new("f")).expect("ok");
556        // Second registration wins.
557        assert_eq!(result, json!(2));
558    }
559
560    #[test]
561    fn test_many_middlewares_passthrough() {
562        let mut r = make_resolver();
563        for _ in 0..10 {
564            r.add_middleware(Box::new(PassthroughMiddleware));
565        }
566        assert_eq!(r.middleware_count(), 10);
567        let result = r.resolve(ResolverContext::new("hello")).expect("ok");
568        assert_eq!(result, json!("world"));
569    }
570
571    #[test]
572    fn test_resolver_field_not_found_message() {
573        let r = FieldResolver::new();
574        let err = r.resolve(ResolverContext::new("ghost")).expect_err("fail");
575        if let ResolverError::FieldNotFound(name) = err {
576            assert_eq!(name, "ghost");
577        } else {
578            panic!("Expected FieldNotFound");
579        }
580    }
581
582    #[test]
583    fn test_null_json_value_handler() {
584        let mut r = FieldResolver::new();
585        r.register("nullable", |_| Ok(serde_json::Value::Null));
586        let result = r.resolve(ResolverContext::new("nullable")).expect("ok");
587        assert!(result.is_null());
588    }
589
590    #[test]
591    fn test_array_json_value_handler() {
592        let mut r = FieldResolver::new();
593        r.register("list", |_| Ok(json!([1, 2, 3])));
594        let result = r.resolve(ResolverContext::new("list")).expect("ok");
595        assert_eq!(result, json!([1, 2, 3]));
596    }
597
598    #[test]
599    fn test_object_json_value_handler() {
600        let mut r = FieldResolver::new();
601        r.register("obj", |_| Ok(json!({"a": 1, "b": "two"})));
602        let result = r.resolve(ResolverContext::new("obj")).expect("ok");
603        assert_eq!(result["a"], json!(1));
604    }
605}