Skip to main content

cratestack_axum/codec/
set.rs

1use axum::http::StatusCode;
2use axum::response::Response;
3use cratestack_core::{CratestackCodec, CratestackError, CratestackErrorResponse};
4use futures_util::Stream;
5use serde::{Deserialize, Serialize};
6
7use crate::transport::{
8    CBOR_SEQUENCE_CONTENT_TYPE, CborCodecMarker, HttpTransport, encode_cbor_sequence_response,
9    encode_cbor_sequence_stream_response,
10};
11
12use super::encode::encode_codec_response;
13
14#[derive(Debug, Clone)]
15pub struct CodecSet<Primary, Secondary> {
16    pub(super) primary: Primary,
17    pub(super) secondary: Secondary,
18}
19
20impl<Primary, Secondary> CodecSet<Primary, Secondary> {
21    pub fn new(primary: Primary, secondary: Secondary) -> Self {
22        Self { primary, secondary }
23    }
24}
25
26impl<Primary, Secondary> HttpTransport for CodecSet<Primary, Secondary>
27where
28    Primary: CratestackCodec,
29    Secondary: CratestackCodec,
30{
31    fn can_encode(&self, content_type: &str) -> bool {
32        if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
33            Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE
34                || Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE
35        } else {
36            content_type == Primary::CONTENT_TYPE || content_type == Secondary::CONTENT_TYPE
37        }
38    }
39
40    fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CratestackError>
41    where
42        T: for<'de> Deserialize<'de>,
43    {
44        if content_type == Primary::CONTENT_TYPE {
45            self.primary.decode(body)
46        } else if content_type == Secondary::CONTENT_TYPE {
47            self.secondary.decode(body)
48        } else {
49            Err(CratestackError::UnsupportedMediaType(format!(
50                "unsupported request Content-Type {content_type}"
51            )))
52        }
53    }
54
55    fn encode_response<T>(
56        &self,
57        content_type: &str,
58        status: StatusCode,
59        value: &T,
60    ) -> Result<Response, CratestackError>
61    where
62        T: Serialize + ?Sized,
63    {
64        if content_type == Primary::CONTENT_TYPE {
65            encode_codec_response(&self.primary, status, value)
66        } else if content_type == Secondary::CONTENT_TYPE {
67            encode_codec_response(&self.secondary, status, value)
68        } else {
69            Err(CratestackError::NotAcceptable(format!(
70                "no encoder configured for response Content-Type {content_type}"
71            )))
72        }
73    }
74
75    fn encode_sequence_response<T>(
76        &self,
77        content_type: &str,
78        status: StatusCode,
79        values: &[T],
80    ) -> Result<Response, CratestackError>
81    where
82        T: Serialize,
83    {
84        if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
85            if Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
86                encode_cbor_sequence_response(&self.primary, status, values)
87            } else if Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
88                encode_cbor_sequence_response(&self.secondary, status, values)
89            } else {
90                Err(CratestackError::NotAcceptable(
91                    "router does not have a CBOR codec for cbor-seq responses".to_owned(),
92                ))
93            }
94        } else if content_type == Primary::CONTENT_TYPE || content_type == Secondary::CONTENT_TYPE {
95            self.encode_response(content_type, status, values)
96        } else {
97            Err(CratestackError::NotAcceptable(format!(
98                "no encoder configured for response Content-Type {content_type}"
99            )))
100        }
101    }
102
103    fn encode_sequence_error_response(
104        &self,
105        content_type: &str,
106        status: StatusCode,
107        value: &CratestackErrorResponse,
108    ) -> Result<Response, CratestackError> {
109        if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
110            if Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
111                encode_cbor_sequence_response(&self.primary, status, std::slice::from_ref(value))
112            } else if Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
113                encode_cbor_sequence_response(&self.secondary, status, std::slice::from_ref(value))
114            } else {
115                Err(CratestackError::NotAcceptable(
116                    "router does not have a CBOR codec for cbor-seq responses".to_owned(),
117                ))
118            }
119        } else if content_type == Primary::CONTENT_TYPE || content_type == Secondary::CONTENT_TYPE {
120            self.encode_response(content_type, status, value)
121        } else {
122            Err(CratestackError::NotAcceptable(format!(
123                "no encoder configured for response Content-Type {content_type}"
124            )))
125        }
126    }
127
128    fn encode_sequence_stream_response<T, S>(
129        &self,
130        content_type: &str,
131        status: StatusCode,
132        values: S,
133    ) -> Result<Response, CratestackError>
134    where
135        T: Serialize + Send + 'static,
136        S: Stream<Item = Result<T, CratestackError>> + Send + 'static,
137    {
138        if content_type != CBOR_SEQUENCE_CONTENT_TYPE {
139            return Err(CratestackError::NotAcceptable(format!(
140                "incremental sequence streaming requires {CBOR_SEQUENCE_CONTENT_TYPE}, got \
141                 response Content-Type {content_type}"
142            )));
143        }
144        if Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
145            encode_cbor_sequence_stream_response(self.primary.clone(), status, values)
146        } else if Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
147            encode_cbor_sequence_stream_response(self.secondary.clone(), status, values)
148        } else {
149            Err(CratestackError::NotAcceptable(
150                "router does not have a CBOR codec for cbor-seq responses".to_owned(),
151            ))
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests;