click/decorators.rs
1//! Decorator-like convenience helpers.
2//!
3//! Python Click exposes a decorator factory `make_pass_decorator()` that creates
4//! decorators to pass a context object (stored on `Context.obj`) into callbacks.
5//!
6//! Rust doesn't have function decorators, so click-rs provides a small adapter
7//! that converts a function `Fn(&T, &Context)` into a standard `CommandCallback`
8//! (`Fn(&Context)`), looking up `T` from the context.
9
10use std::marker::PhantomData;
11
12use crate::command::CommandCallback;
13use crate::context::Context;
14use crate::error::{ClickError, Result};
15
16/// A decorator factory for passing a context object of type `T` to a callback.
17///
18/// Create one with [`make_pass_decorator`], then call [`PassDecorator::decorate`]
19/// to wrap a function that expects `&T`.
20///
21/// This matches the most common Click usage (ensure = false). Unlike Python,
22/// click-rs callbacks receive `&Context` (not `&mut Context`), so this helper
23/// does not support "ensure" semantics that would create and store an object
24/// if it is missing.
25pub struct PassDecorator<T> {
26 _marker: PhantomData<T>,
27}
28
29/// Create a pass-decorator for `T`.
30///
31/// # Example
32///
33/// ```rust
34/// use click::{make_pass_decorator, ContextBuilder};
35///
36/// #[derive(Debug)]
37/// struct AppState {
38/// value: i32,
39/// }
40///
41/// let cb = make_pass_decorator::<AppState>().decorate(|state, _ctx| {
42/// assert_eq!(state.value, 123);
43/// Ok(())
44/// });
45///
46/// let ctx = ContextBuilder::new().obj(AppState { value: 123 }).build();
47/// cb(&ctx).unwrap();
48/// ```
49pub fn make_pass_decorator<T: 'static>() -> PassDecorator<T> {
50 PassDecorator {
51 _marker: PhantomData,
52 }
53}
54
55impl<T: 'static> PassDecorator<T> {
56 /// Wrap a function that expects `&T` into a click-rs [`CommandCallback`].
57 pub fn decorate<F>(self, f: F) -> CommandCallback
58 where
59 F: Fn(&T, &Context) -> Result<()> + Send + Sync + 'static,
60 {
61 Box::new(move |ctx: &Context| {
62 let obj = ctx.find_obj::<T>().ok_or_else(|| {
63 ClickError::usage("Missing context object for make_pass_decorator().")
64 })?;
65 f(obj, ctx)
66 })
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::context::ContextBuilder;
74 use std::sync::Arc;
75
76 #[derive(Debug)]
77 struct AppState {
78 value: i32,
79 }
80
81 #[test]
82 fn test_make_pass_decorator_passes_obj() {
83 let cb = make_pass_decorator::<AppState>().decorate(|state, _ctx| {
84 assert_eq!(state.value, 7);
85 Ok(())
86 });
87
88 let ctx = ContextBuilder::new().obj(AppState { value: 7 }).build();
89 cb(&ctx).unwrap();
90 }
91
92 #[test]
93 fn test_make_pass_decorator_finds_obj_from_parent() {
94 let cb = make_pass_decorator::<AppState>().decorate(|state, _ctx| {
95 assert_eq!(state.value, 9);
96 Ok(())
97 });
98
99 let parent = Arc::new(ContextBuilder::new().obj(AppState { value: 9 }).build());
100 let child = ContextBuilder::new().parent(parent).build();
101 cb(&child).unwrap();
102 }
103}
104