typeway_server/
typed_bind.rs1use serde::de::DeserializeOwned;
7
8use crate::handler::{into_boxed_handler, BoxedHandler, Handler};
9use crate::handler_for::{BindableEndpoint, BoundHandler};
10use crate::response::IntoResponse;
11use crate::typed::*;
12
13pub fn bind_validated<V, E, H, Args>(handler: H) -> BoundHandler<Validated<V, E>>
22where
23 V: Validate<E::Req>,
24 E: BindableEndpoint + HasReqType,
25 E::Req: DeserializeOwned + Send + 'static,
26 H: Handler<Args>,
27 Args: 'static,
28{
29 let method = E::method();
30 let pattern = E::pattern();
31 let match_fn = E::match_fn();
32
33 let inner = into_boxed_handler(handler);
35 let boxed: BoxedHandler = std::sync::Arc::new(move |parts, body| {
36 let validation_result: Result<(), String> = serde_json::from_slice::<E::Req>(&body)
38 .map_err(|e| format!("invalid request body: {e}"))
39 .and_then(|parsed| V::validate(&parsed));
40
41 match validation_result {
42 Ok(()) => inner(parts, body),
43 Err(msg) => {
44 let error = crate::error::JsonError::unprocessable(msg);
45 Box::pin(async move { error.into_response() })
46 }
47 }
48 });
49
50 BoundHandler::new(method, pattern, match_fn, boxed)
51}
52
53pub trait HasReqType {
55 type Req;
56}
57
58impl<M: typeway_core::HttpMethod, P: typeway_core::PathSpec, Req, Res, Q, Err> HasReqType
59 for typeway_core::Endpoint<M, P, Req, Res, Q, Err>
60{
61 type Req = Req;
62}
63
64pub fn bind_content_type<C, E, H, Args>(handler: H) -> BoundHandler<ContentType<C, E>>
73where
74 C: ContentTypeMarker,
75 E: BindableEndpoint,
76 H: Handler<Args>,
77 Args: 'static,
78{
79 let method = E::method();
80 let pattern = E::pattern();
81 let match_fn = E::match_fn();
82
83 let inner = into_boxed_handler(handler);
84 let expected = C::CONTENT_TYPE;
85 let boxed: BoxedHandler =
86 std::sync::Arc::new(move |parts: http::request::Parts, body: bytes::Bytes| {
87 let ct = parts
88 .headers
89 .get(http::header::CONTENT_TYPE)
90 .and_then(|v| v.to_str().ok())
91 .unwrap_or("");
92
93 if !ct.starts_with(expected) {
94 let error = crate::error::JsonError::new(
95 http::StatusCode::UNSUPPORTED_MEDIA_TYPE,
96 format!("expected Content-Type: {expected}, got: {ct}"),
97 );
98 return Box::pin(async move { error.into_response() });
99 }
100
101 inner(parts, body)
102 });
103
104 BoundHandler::new(method, pattern, match_fn, boxed)
105}
106
107#[macro_export]
113macro_rules! bind_validated {
114 ($handler:expr) => {
115 $crate::typed_bind::bind_validated::<_, _, _, _>($handler)
116 };
117}
118
119#[macro_export]
121macro_rules! bind_content_type {
122 ($handler:expr) => {
123 $crate::typed_bind::bind_content_type::<_, _, _, _>($handler)
124 };
125}