use std::{
any::{Any, type_name},
cell::{Ref, RefMut},
};
use super::Hooks;
use crate::context::ContextLookup;
mod private {
pub trait Sealed {}
impl Sealed for crate::hooks::Hooks<'_, '_> {}
}
pub trait UseContext<'a>: private::Sealed {
fn use_context<T: Any>(&self) -> Ref<'a, T>;
fn use_context_mut<T: Any>(&self) -> RefMut<'a, T>;
fn try_use_context<T: Any>(&self) -> Option<Ref<'a, T>>;
fn try_use_context_mut<T: Any>(&self) -> Option<RefMut<'a, T>>;
}
impl<'a> UseContext<'a> for Hooks<'a, '_> {
fn use_context<T: Any>(&self) -> Ref<'a, T> {
let stack = self.context.expect("context not available");
match stack.get_context::<T>() {
ContextLookup::Found(res) => res,
ContextLookup::AlreadyBorrowed => {
let ty = type_name::<T>();
panic!(
"context `{ty}` is already borrowed: a guard of this type is still alive in \
the current scope. Drop the existing context guard before borrowing it again."
)
}
ContextLookup::NotFound => {
let ty = type_name::<T>();
panic!(
"context `{ty}` not found: `use_context` only searches ancestor \
`ContextProvider`s, so a context provided by a sibling or descendant \
component is not visible here. Render this component inside the matching \
`ContextProvider`'s subtree, or use `try_use_context` to get an `Option` \
instead of panicking."
)
}
}
}
fn use_context_mut<T: Any>(&self) -> RefMut<'a, T> {
let stack = self.context.expect("context not available");
match stack.get_context_mut::<T>() {
ContextLookup::Found(res) => res,
ContextLookup::AlreadyBorrowed => {
let ty = type_name::<T>();
panic!(
"context `{ty}` is already borrowed: a guard of this type is still alive in \
the current scope. Drop the existing context guard before borrowing it again."
)
}
ContextLookup::NotFound => {
let ty = type_name::<T>();
panic!(
"context `{ty}` not found: `use_context_mut` only searches ancestor \
`ContextProvider`s, so a context provided by a sibling or descendant \
component is not visible here. Render this component inside the matching \
`ContextProvider`'s subtree, or use `try_use_context_mut` to get an `Option` \
instead of panicking."
)
}
}
}
fn try_use_context<T: Any>(&self) -> Option<Ref<'a, T>> {
match self.context?.get_context::<T>() {
ContextLookup::Found(res) => Some(res),
_ => None,
}
}
fn try_use_context_mut<T: Any>(&self) -> Option<RefMut<'a, T>> {
match self.context?.get_context_mut::<T>() {
ContextLookup::Found(res) => Some(res),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::ContextStack;
#[test]
fn use_context_returns_value_from_ancestor() {
let mut hooks_vec = Vec::new();
let mut hooks = Hooks::new(&mut hooks_vec, true);
let mut root: i32 = 7;
let stack = ContextStack::root(&mut root);
let hooks = hooks.with_context_stack(&stack);
assert_eq!(*hooks.use_context::<i32>(), 7);
}
#[test]
fn try_use_context_returns_none_when_absent() {
let mut hooks_vec = Vec::new();
let mut hooks = Hooks::new(&mut hooks_vec, true);
let mut root: () = ();
let stack = ContextStack::root(&mut root);
let hooks = hooks.with_context_stack(&stack);
assert!(hooks.try_use_context::<i32>().is_none());
}
#[test]
#[should_panic(expected = "only searches ancestor")]
fn use_context_not_found_panics_with_helpful_message() {
let mut hooks_vec = Vec::new();
let mut hooks = Hooks::new(&mut hooks_vec, true);
let mut root: () = ();
let stack = ContextStack::root(&mut root);
let hooks = hooks.with_context_stack(&stack);
let _ = hooks.use_context::<i32>();
}
}