Skip to main content

cratestack_axum/transport/
http_transport.rs

1use axum::http::StatusCode;
2use axum::response::Response;
3use cratestack_core::{CoolCodec, CoolError, CoolErrorResponse};
4use serde::{Deserialize, Serialize};
5
6use crate::codec::encode_codec_response;
7
8use super::CBOR_SEQUENCE_CONTENT_TYPE;
9use super::internal::encode_cbor_sequence_response;
10use super::media_type::media_type_matches;
11
12pub trait HttpTransport: Clone + Send + Sync + 'static {
13    fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CoolError>
14    where
15        T: for<'de> Deserialize<'de>;
16
17    fn encode_response<T>(
18        &self,
19        content_type: &str,
20        status: StatusCode,
21        value: &T,
22    ) -> Result<Response, CoolError>
23    where
24        T: Serialize + ?Sized;
25
26    fn encode_sequence_response<T>(
27        &self,
28        content_type: &str,
29        status: StatusCode,
30        values: &[T],
31    ) -> Result<Response, CoolError>
32    where
33        T: Serialize;
34
35    fn encode_sequence_error_response(
36        &self,
37        content_type: &str,
38        status: StatusCode,
39        value: &CoolErrorResponse,
40    ) -> Result<Response, CoolError>;
41}
42
43impl<C> HttpTransport for C
44where
45    C: CoolCodec,
46{
47    fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CoolError>
48    where
49        T: for<'de> Deserialize<'de>,
50    {
51        if media_type_matches(content_type, C::CONTENT_TYPE) {
52            crate::codec::decode_codec_request(self, body)
53        } else {
54            Err(CoolError::UnsupportedMediaType(format!(
55                "unsupported request Content-Type {content_type}"
56            )))
57        }
58    }
59
60    fn encode_response<T>(
61        &self,
62        content_type: &str,
63        status: StatusCode,
64        value: &T,
65    ) -> Result<Response, CoolError>
66    where
67        T: Serialize + ?Sized,
68    {
69        if media_type_matches(content_type, C::CONTENT_TYPE) {
70            encode_codec_response(self, status, value)
71        } else {
72            Err(CoolError::NotAcceptable(format!(
73                "no encoder configured for response Content-Type {content_type}"
74            )))
75        }
76    }
77
78    fn encode_sequence_response<T>(
79        &self,
80        content_type: &str,
81        status: StatusCode,
82        values: &[T],
83    ) -> Result<Response, CoolError>
84    where
85        T: Serialize,
86    {
87        if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
88            encode_cbor_sequence_response(self, status, values)
89        } else {
90            self.encode_response(content_type, status, values)
91        }
92    }
93
94    fn encode_sequence_error_response(
95        &self,
96        content_type: &str,
97        status: StatusCode,
98        value: &CoolErrorResponse,
99    ) -> Result<Response, CoolError> {
100        if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
101            encode_cbor_sequence_response(self, status, std::slice::from_ref(value))
102        } else {
103            self.encode_response(content_type, status, value)
104        }
105    }
106}
107
108pub(crate) struct CborCodecMarker;
109
110impl CborCodecMarker {
111    pub(crate) const CONTENT_TYPE: &'static str = "application/cbor";
112}