cratestack_client_rust/
codec.rs1use cratestack_codec_cbor::CborCodec;
2#[cfg(feature = "codec-json")]
3use cratestack_codec_json::JsonCodec;
4use cratestack_core::{CratestackCodec, CratestackError};
5use serde::de::DeserializeOwned;
6
7pub(crate) const CBOR_SEQUENCE_CONTENT_TYPE: &str = "application/cbor-seq";
8
9pub trait HttpClientCodec: CratestackCodec {
10 fn accept_header_value(&self) -> &'static str;
11
12 fn sequence_accept_header_value(&self) -> &'static str;
13
14 fn payload_accept(&self) -> &'static str {
23 Self::CONTENT_TYPE
24 }
25
26 fn decode_response<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CratestackError>
27 where
28 T: DeserializeOwned;
29
30 fn decode_sequence_response<T>(
31 &self,
32 content_type: &str,
33 body: &[u8],
34 ) -> Result<Vec<T>, CratestackError>
35 where
36 T: DeserializeOwned;
37}
38
39impl HttpClientCodec for CborCodec {
40 fn accept_header_value(&self) -> &'static str {
41 #[cfg(feature = "codec-json")]
47 {
48 "application/cbor, application/json"
49 }
50 #[cfg(not(feature = "codec-json"))]
51 {
52 "application/cbor"
53 }
54 }
55
56 fn sequence_accept_header_value(&self) -> &'static str {
57 #[cfg(feature = "codec-json")]
58 {
59 "application/cbor-seq, application/cbor, application/json"
60 }
61 #[cfg(not(feature = "codec-json"))]
62 {
63 "application/cbor-seq, application/cbor"
64 }
65 }
66
67 fn decode_response<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CratestackError>
68 where
69 T: DeserializeOwned,
70 {
71 if media_type_matches(content_type, CborCodec::CONTENT_TYPE) {
72 self.decode(body)
73 } else {
74 #[cfg(feature = "codec-json")]
75 if media_type_matches(content_type, JsonCodec::CONTENT_TYPE) {
76 return JsonCodec.decode(body);
77 }
78 Err(CratestackError::Codec(format!(
79 "unsupported response Content-Type {content_type}"
80 )))
81 }
82 }
83
84 fn decode_sequence_response<T>(
85 &self,
86 content_type: &str,
87 body: &[u8],
88 ) -> Result<Vec<T>, CratestackError>
89 where
90 T: DeserializeOwned,
91 {
92 if media_type_matches(content_type, CBOR_SEQUENCE_CONTENT_TYPE) {
93 decode_cbor_sequence(body)
94 } else if media_type_matches(content_type, CborCodec::CONTENT_TYPE) {
95 self.decode(body)
96 } else {
97 #[cfg(feature = "codec-json")]
98 if media_type_matches(content_type, JsonCodec::CONTENT_TYPE) {
99 return JsonCodec.decode(body);
100 }
101 Err(CratestackError::Codec(format!(
102 "unsupported response Content-Type {content_type}"
103 )))
104 }
105 }
106}
107
108#[cfg(feature = "codec-json")]
109impl HttpClientCodec for JsonCodec {
110 fn accept_header_value(&self) -> &'static str {
111 "application/json, application/cbor"
112 }
113
114 fn sequence_accept_header_value(&self) -> &'static str {
115 "application/cbor-seq, application/json, application/cbor"
116 }
117
118 fn decode_response<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CratestackError>
119 where
120 T: DeserializeOwned,
121 {
122 if media_type_matches(content_type, JsonCodec::CONTENT_TYPE) {
123 self.decode(body)
124 } else if media_type_matches(content_type, CborCodec::CONTENT_TYPE) {
125 CborCodec.decode(body)
126 } else {
127 Err(CratestackError::Codec(format!(
128 "unsupported response Content-Type {content_type}"
129 )))
130 }
131 }
132
133 fn decode_sequence_response<T>(
134 &self,
135 content_type: &str,
136 body: &[u8],
137 ) -> Result<Vec<T>, CratestackError>
138 where
139 T: DeserializeOwned,
140 {
141 if media_type_matches(content_type, CBOR_SEQUENCE_CONTENT_TYPE) {
142 decode_cbor_sequence(body)
143 } else if media_type_matches(content_type, JsonCodec::CONTENT_TYPE) {
144 self.decode(body)
145 } else if media_type_matches(content_type, CborCodec::CONTENT_TYPE) {
146 CborCodec.decode(body)
147 } else {
148 Err(CratestackError::Codec(format!(
149 "unsupported response Content-Type {content_type}"
150 )))
151 }
152 }
153}
154
155pub(crate) fn media_type_matches(candidate: &str, expected: &str) -> bool {
156 candidate.split(';').next().unwrap_or(candidate).trim() == expected
157}
158
159pub(crate) fn decode_cbor_sequence<T>(bytes: &[u8]) -> Result<Vec<T>, CratestackError>
160where
161 T: DeserializeOwned,
162{
163 let mut values = Vec::new();
164 let mut offset = 0usize;
165 while offset < bytes.len() {
166 cratestack_codec_cbor::check_depth(
169 &bytes[offset..],
170 cratestack_codec_cbor::DEFAULT_MAX_DECODE_DEPTH,
171 )?;
172 let mut deserializer = minicbor_serde::Deserializer::new(&bytes[offset..]);
173 values.push(T::deserialize(&mut deserializer).map_err(|error| {
174 CratestackError::Codec(format!("failed to decode CBOR sequence body: {error}"))
175 })?);
176 let consumed = deserializer.decoder().position();
177 if consumed == 0 {
178 return Err(CratestackError::Codec(
179 "failed to decode CBOR sequence body: decoder made no progress".to_owned(),
180 ));
181 }
182 offset += consumed;
183 }
184 Ok(values)
185}