cat_dev/net/server/requestable/
state.rs

1//! Handles fetching app wide state that can be attached to a request.
2//!
3//! If you need dynamic request-local state, prefer [`crate::net::requestable::Extension`].
4
5use crate::{
6	errors::CatBridgeError,
7	net::{
8		models::{FromRef, FromRequest, FromRequestParts, Request},
9		server::models::{FromResponseStreamEvent, ResponseStreamEvent},
10	},
11};
12use std::{
13	fmt::{Debug, Formatter, Result as FmtResult},
14	ops::{Deref, DerefMut},
15};
16use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
17
18/// A type that has been cloned and extracted out of app state.
19pub struct State<Ty: Clone + Send + Sync + 'static>(pub Ty);
20
21impl<
22	OuterState: Clone + Send + Sync + 'static,
23	InnerState: FromRef<OuterState> + Clone + Send + Sync + 'static,
24> FromRequestParts<OuterState> for State<InnerState>
25{
26	async fn from_request_parts(req: &mut Request<OuterState>) -> Result<Self, CatBridgeError> {
27		Ok(Self(InnerState::from_ref(req.state())))
28	}
29}
30
31impl<
32	OuterState: Clone + Send + Sync + 'static,
33	InnerState: FromRef<OuterState> + Clone + Send + Sync + 'static,
34> FromRequest<OuterState> for State<InnerState>
35{
36	async fn from_request(req: Request<OuterState>) -> Result<Self, CatBridgeError> {
37		Ok(Self(InnerState::from_ref(req.state())))
38	}
39}
40
41impl<
42	OuterState: Clone + Send + Sync + 'static,
43	InnerState: FromRef<OuterState> + Clone + Send + Sync + 'static,
44> FromResponseStreamEvent<OuterState> for State<InnerState>
45{
46	async fn from_stream_event(
47		evt: &mut ResponseStreamEvent<OuterState>,
48	) -> Result<Self, CatBridgeError> {
49		Ok(Self(InnerState::from_ref(evt.state())))
50	}
51}
52
53impl<Ty: Clone + Send + Sync + 'static> Deref for State<Ty> {
54	type Target = Ty;
55
56	fn deref(&self) -> &Self::Target {
57		&self.0
58	}
59}
60
61impl<Ty: Clone + Send + Sync + 'static> DerefMut for State<Ty> {
62	fn deref_mut(&mut self) -> &mut Self::Target {
63		&mut self.0
64	}
65}
66
67impl<Ty: Clone + Send + Sync + 'static> Debug for State<Ty>
68where
69	Ty: Debug,
70{
71	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
72		fmt.debug_struct("State").field("inner", &self.0).finish()
73	}
74}
75
76const STATE_FIELDS: &[NamedField<'static>] = &[NamedField::new("inner")];
77
78impl<Ty: Clone + Send + Sync + 'static> Structable for State<Ty>
79where
80	Ty: Valuable,
81{
82	fn definition(&self) -> StructDef<'_> {
83		StructDef::new_static("State", Fields::Named(STATE_FIELDS))
84	}
85}
86
87impl<Ty: Clone + Send + Sync + 'static> Valuable for State<Ty>
88where
89	Ty: Valuable,
90{
91	fn as_value(&self) -> Value<'_> {
92		Value::Structable(self)
93	}
94
95	fn visit(&self, visitor: &mut dyn Visit) {
96		visitor.visit_named_fields(&NamedValues::new(STATE_FIELDS, &[self.0.as_value()]));
97	}
98}
99
100#[cfg(test)]
101mod unit_tests {
102	use super::*;
103	use crate::net::server::router::{Router, test_helpers::router_body_no_close_with_state};
104
105	#[tokio::test]
106	pub async fn test_state() {
107		async fn echo_state(State(data): State<String>) -> String {
108			data
109		}
110
111		let mut router = Router::<String>::new();
112		router
113			.add_route(&[0x1], echo_state)
114			.expect("Failed to add route!");
115		assert_eq!(
116			router_body_no_close_with_state(
117				&mut router,
118				&[0x1, 0x2, 0x3, 0x4],
119				"Hey from state!".to_owned(),
120			)
121			.await,
122			b"Hey from state!",
123		);
124	}
125}