Skip to main content

ratatui_kit/hooks/
use_context.rs

1use std::{
2    any::{Any, type_name},
3    cell::{Ref, RefMut},
4};
5
6use super::Hooks;
7use crate::context::ContextLookup;
8
9mod private {
10    pub trait Sealed {}
11
12    impl Sealed for crate::hooks::Hooks<'_, '_> {}
13}
14
15pub trait UseContext<'a>: private::Sealed {
16    // 获取全局/局部上下文,实现依赖注入。适合主题、配置、全局状态等场景。
17    fn use_context<T: Any>(&self) -> Ref<'a, T>;
18    // 获取可变上下文。
19    fn use_context_mut<T: Any>(&self) -> RefMut<'a, T>;
20    // 尝试获取只读上下文,返回 Option。
21    fn try_use_context<T: Any>(&self) -> Option<Ref<'a, T>>;
22    // 尝试获取可变上下文,返回 Option。
23    fn try_use_context_mut<T: Any>(&self) -> Option<RefMut<'a, T>>;
24}
25
26impl<'a> UseContext<'a> for Hooks<'a, '_> {
27    fn use_context<T: Any>(&self) -> Ref<'a, T> {
28        let stack = self.context.expect("context not available");
29        match stack.get_context::<T>() {
30            ContextLookup::Found(res) => res,
31            ContextLookup::AlreadyBorrowed => {
32                let ty = type_name::<T>();
33                panic!(
34                    "context `{ty}` is already borrowed: a guard of this type is still alive in \
35                     the current scope. Drop the existing context guard before borrowing it again."
36                )
37            }
38            ContextLookup::NotFound => {
39                let ty = type_name::<T>();
40                panic!(
41                    "context `{ty}` not found: `use_context` only searches ancestor \
42                     `ContextProvider`s, so a context provided by a sibling or descendant \
43                     component is not visible here. Render this component inside the matching \
44                     `ContextProvider`'s subtree, or use `try_use_context` to get an `Option` \
45                     instead of panicking."
46                )
47            }
48        }
49    }
50
51    fn use_context_mut<T: Any>(&self) -> RefMut<'a, T> {
52        let stack = self.context.expect("context not available");
53        match stack.get_context_mut::<T>() {
54            ContextLookup::Found(res) => res,
55            ContextLookup::AlreadyBorrowed => {
56                let ty = type_name::<T>();
57                panic!(
58                    "context `{ty}` is already borrowed: a guard of this type is still alive in \
59                     the current scope. Drop the existing context guard before borrowing it again."
60                )
61            }
62            ContextLookup::NotFound => {
63                let ty = type_name::<T>();
64                panic!(
65                    "context `{ty}` not found: `use_context_mut` only searches ancestor \
66                     `ContextProvider`s, so a context provided by a sibling or descendant \
67                     component is not visible here. Render this component inside the matching \
68                     `ContextProvider`'s subtree, or use `try_use_context_mut` to get an `Option` \
69                     instead of panicking."
70                )
71            }
72        }
73    }
74
75    fn try_use_context<T: Any>(&self) -> Option<Ref<'a, T>> {
76        match self.context?.get_context::<T>() {
77            ContextLookup::Found(res) => Some(res),
78            _ => None,
79        }
80    }
81
82    fn try_use_context_mut<T: Any>(&self) -> Option<RefMut<'a, T>> {
83        match self.context?.get_context_mut::<T>() {
84            ContextLookup::Found(res) => Some(res),
85            _ => None,
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::context::ContextStack;
94
95    // 断言型 `use_context` 命中祖先注入的 context。
96    #[test]
97    fn use_context_returns_value_from_ancestor() {
98        let mut hooks_vec = Vec::new();
99        let mut hooks = Hooks::new(&mut hooks_vec, true);
100        let mut root: i32 = 7;
101        let stack = ContextStack::root(&mut root);
102        let hooks = hooks.with_context_stack(&stack);
103        assert_eq!(*hooks.use_context::<i32>(), 7);
104    }
105
106    // `try_use_context` 在缺失时安全降级为 `None`,绝不 panic。
107    #[test]
108    fn try_use_context_returns_none_when_absent() {
109        let mut hooks_vec = Vec::new();
110        let mut hooks = Hooks::new(&mut hooks_vec, true);
111        let mut root: () = ();
112        let stack = ContextStack::root(&mut root);
113        let hooks = hooks.with_context_stack(&stack);
114        assert!(hooks.try_use_context::<i32>().is_none());
115    }
116
117    // 断言型 `use_context` 缺失时 panic,且文案点明「只查祖先链」并指向 `try_use_context`。
118    #[test]
119    #[should_panic(expected = "only searches ancestor")]
120    fn use_context_not_found_panics_with_helpful_message() {
121        let mut hooks_vec = Vec::new();
122        let mut hooks = Hooks::new(&mut hooks_vec, true);
123        let mut root: () = ();
124        let stack = ContextStack::root(&mut root);
125        let hooks = hooks.with_context_stack(&stack);
126        let _ = hooks.use_context::<i32>();
127    }
128}