ratatui_kit/hooks/
use_context.rs1use 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 fn use_context<T: Any>(&self) -> Ref<'a, T>;
18 fn use_context_mut<T: Any>(&self) -> RefMut<'a, T>;
20 fn try_use_context<T: Any>(&self) -> Option<Ref<'a, T>>;
22 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 #[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 #[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 #[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}