1pub use axum;
2
3use axum::body::Body;
4use axum::http::{HeaderMap, HeaderValue, StatusCode, header};
5use axum::response::Response;
6use cratestack_core::{CoolCodec, CoolError, CoolErrorResponse, RouteTransportCapabilities};
7use serde::{Deserialize, Serialize};
8use url::form_urlencoded;
9
10pub const CBOR_SEQUENCE_CONTENT_TYPE: &str = "application/cbor-seq";
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum QueryExpr {
14 Predicate { key: String, value: String },
15 All(Vec<QueryExpr>),
16 Any(Vec<QueryExpr>),
17 Not(Box<QueryExpr>),
18}
19
20pub trait HttpTransport: Clone + Send + Sync + 'static {
21 fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CoolError>
22 where
23 T: for<'de> Deserialize<'de>;
24
25 fn encode_response<T>(
26 &self,
27 content_type: &str,
28 status: StatusCode,
29 value: &T,
30 ) -> Result<Response, CoolError>
31 where
32 T: Serialize + ?Sized;
33
34 fn encode_sequence_response<T>(
35 &self,
36 content_type: &str,
37 status: StatusCode,
38 values: &[T],
39 ) -> Result<Response, CoolError>
40 where
41 T: Serialize;
42
43 fn encode_sequence_error_response(
44 &self,
45 content_type: &str,
46 status: StatusCode,
47 value: &CoolErrorResponse,
48 ) -> Result<Response, CoolError>;
49}
50
51impl<C> HttpTransport for C
52where
53 C: CoolCodec,
54{
55 fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CoolError>
56 where
57 T: for<'de> Deserialize<'de>,
58 {
59 if media_type_matches(content_type, C::CONTENT_TYPE) {
60 decode_codec_request(self, body)
61 } else {
62 Err(CoolError::UnsupportedMediaType(format!(
63 "unsupported request Content-Type {content_type}"
64 )))
65 }
66 }
67
68 fn encode_response<T>(
69 &self,
70 content_type: &str,
71 status: StatusCode,
72 value: &T,
73 ) -> Result<Response, CoolError>
74 where
75 T: Serialize + ?Sized,
76 {
77 if media_type_matches(content_type, C::CONTENT_TYPE) {
78 encode_codec_response(self, status, value)
79 } else {
80 Err(CoolError::NotAcceptable(format!(
81 "no encoder configured for response Content-Type {content_type}"
82 )))
83 }
84 }
85
86 fn encode_sequence_response<T>(
87 &self,
88 content_type: &str,
89 status: StatusCode,
90 values: &[T],
91 ) -> Result<Response, CoolError>
92 where
93 T: Serialize,
94 {
95 if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
96 encode_cbor_sequence_response(self, status, values)
97 } else {
98 self.encode_response(content_type, status, values)
99 }
100 }
101
102 fn encode_sequence_error_response(
103 &self,
104 content_type: &str,
105 status: StatusCode,
106 value: &CoolErrorResponse,
107 ) -> Result<Response, CoolError> {
108 if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
109 encode_cbor_sequence_response(self, status, std::slice::from_ref(value))
110 } else {
111 self.encode_response(content_type, status, value)
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
117pub struct CodecSet<Primary, Secondary> {
118 primary: Primary,
119 secondary: Secondary,
120}
121
122impl<Primary, Secondary> CodecSet<Primary, Secondary> {
123 pub fn new(primary: Primary, secondary: Secondary) -> Self {
124 Self { primary, secondary }
125 }
126}
127
128impl<Primary, Secondary> HttpTransport for CodecSet<Primary, Secondary>
129where
130 Primary: CoolCodec,
131 Secondary: CoolCodec,
132{
133 fn decode_request<T>(&self, content_type: &str, body: &[u8]) -> Result<T, CoolError>
134 where
135 T: for<'de> Deserialize<'de>,
136 {
137 if content_type == Primary::CONTENT_TYPE {
138 self.primary.decode(body)
139 } else if content_type == Secondary::CONTENT_TYPE {
140 self.secondary.decode(body)
141 } else {
142 Err(CoolError::UnsupportedMediaType(format!(
143 "unsupported request Content-Type {content_type}"
144 )))
145 }
146 }
147
148 fn encode_response<T>(
149 &self,
150 content_type: &str,
151 status: StatusCode,
152 value: &T,
153 ) -> Result<Response, CoolError>
154 where
155 T: Serialize + ?Sized,
156 {
157 if content_type == Primary::CONTENT_TYPE {
158 encode_codec_response(&self.primary, status, value)
159 } else if content_type == Secondary::CONTENT_TYPE {
160 encode_codec_response(&self.secondary, status, value)
161 } else {
162 Err(CoolError::NotAcceptable(format!(
163 "no encoder configured for response Content-Type {content_type}"
164 )))
165 }
166 }
167
168 fn encode_sequence_response<T>(
169 &self,
170 content_type: &str,
171 status: StatusCode,
172 values: &[T],
173 ) -> Result<Response, CoolError>
174 where
175 T: Serialize,
176 {
177 if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
178 if Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
179 encode_cbor_sequence_response(&self.primary, status, values)
180 } else if Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
181 encode_cbor_sequence_response(&self.secondary, status, values)
182 } else {
183 Err(CoolError::NotAcceptable(
184 "router does not have a CBOR codec for cbor-seq responses".to_owned(),
185 ))
186 }
187 } else if content_type == Primary::CONTENT_TYPE {
188 self.encode_response(content_type, status, values)
189 } else if content_type == Secondary::CONTENT_TYPE {
190 self.encode_response(content_type, status, values)
191 } else {
192 Err(CoolError::NotAcceptable(format!(
193 "no encoder configured for response Content-Type {content_type}"
194 )))
195 }
196 }
197
198 fn encode_sequence_error_response(
199 &self,
200 content_type: &str,
201 status: StatusCode,
202 value: &CoolErrorResponse,
203 ) -> Result<Response, CoolError> {
204 if content_type == CBOR_SEQUENCE_CONTENT_TYPE {
205 if Primary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
206 encode_cbor_sequence_response(&self.primary, status, std::slice::from_ref(value))
207 } else if Secondary::CONTENT_TYPE == CborCodecMarker::CONTENT_TYPE {
208 encode_cbor_sequence_response(&self.secondary, status, std::slice::from_ref(value))
209 } else {
210 Err(CoolError::NotAcceptable(
211 "router does not have a CBOR codec for cbor-seq responses".to_owned(),
212 ))
213 }
214 } else if content_type == Primary::CONTENT_TYPE {
215 self.encode_response(content_type, status, value)
216 } else if content_type == Secondary::CONTENT_TYPE {
217 self.encode_response(content_type, status, value)
218 } else {
219 Err(CoolError::NotAcceptable(format!(
220 "no encoder configured for response Content-Type {content_type}"
221 )))
222 }
223 }
224}
225
226struct CborCodecMarker;
227
228impl CborCodecMarker {
229 const CONTENT_TYPE: &'static str = "application/cbor";
230}
231
232pub fn validate_codec_response_headers<C>(headers: &HeaderMap) -> Result<(), CoolError>
233where
234 C: CoolCodec,
235{
236 validate_accept_header::<C>(headers)
237}
238
239pub fn validate_transport_request_headers<T>(
240 transport: &T,
241 headers: &HeaderMap,
242) -> Result<(), CoolError>
243where
244 T: HttpTransport,
245{
246 validate_transport_request_headers_for(
247 transport,
248 headers,
249 &RouteTransportCapabilities {
250 request_types: &[],
251 response_types: &[],
252 default_response_type: "",
253 supports_sequence_response: false,
254 },
255 )
256}
257
258pub fn validate_transport_response_headers<T>(
259 transport: &T,
260 headers: &HeaderMap,
261) -> Result<(), CoolError>
262where
263 T: HttpTransport,
264{
265 validate_transport_response_headers_for(
266 transport,
267 headers,
268 &RouteTransportCapabilities {
269 request_types: &[],
270 response_types: &[],
271 default_response_type: "",
272 supports_sequence_response: false,
273 },
274 )
275}
276
277pub fn validate_transport_request_headers_for<T>(
278 _transport: &T,
279 headers: &HeaderMap,
280 capabilities: &RouteTransportCapabilities,
281) -> Result<(), CoolError>
282where
283 T: HttpTransport,
284{
285 validate_transport_accept_header(headers, capabilities.response_types)?;
286 if capabilities.request_types.is_empty() {
287 Ok(())
288 } else {
289 validate_transport_content_type_header(headers, capabilities.request_types)
290 }
291}
292
293pub fn validate_transport_response_headers_for<T>(
294 _transport: &T,
295 headers: &HeaderMap,
296 capabilities: &RouteTransportCapabilities,
297) -> Result<(), CoolError>
298where
299 T: HttpTransport,
300{
301 validate_transport_accept_header(headers, capabilities.response_types)
302}
303
304pub fn decode_transport_request_for<TTransport, TValue>(
305 transport: &TTransport,
306 headers: &HeaderMap,
307 capabilities: &RouteTransportCapabilities,
308 body: &[u8],
309) -> Result<TValue, CoolError>
310where
311 TTransport: HttpTransport,
312 TValue: for<'de> Deserialize<'de>,
313{
314 let content_type = request_content_type(headers, capabilities.request_types)?;
315 transport.decode_request(content_type, body)
316}
317
318pub fn parse_query_pairs(raw_query: Option<&str>) -> Result<Vec<(String, String)>, CoolError> {
319 let Some(raw_query) = raw_query else {
320 return Ok(Vec::new());
321 };
322
323 let mut pairs = Vec::new();
324 for (key, value) in form_urlencoded::parse(raw_query.as_bytes()) {
325 pairs.push((key.into_owned(), value.into_owned()));
326 }
327 Ok(pairs)
328}
329
330pub fn parse_filter_expression(input: &str) -> Result<QueryExpr, CoolError> {
331 let mut parser = FilterExpressionParser::new(input);
332 let expr = parser.parse_expr()?;
333 parser.skip_whitespace();
334 if !parser.is_eof() {
335 return Err(CoolError::BadRequest(format!(
336 "unexpected trailing filter expression content near '{}'",
337 parser.remaining(),
338 )));
339 }
340 Ok(expr)
341}
342
343struct FilterExpressionParser<'a> {
344 input: &'a str,
345 cursor: usize,
346}
347
348impl<'a> FilterExpressionParser<'a> {
349 fn new(input: &'a str) -> Self {
350 Self { input, cursor: 0 }
351 }
352
353 fn parse_expr(&mut self) -> Result<QueryExpr, CoolError> {
354 self.parse_or()
355 }
356
357 fn parse_or(&mut self) -> Result<QueryExpr, CoolError> {
358 let mut nodes = vec![self.parse_and()?];
359 loop {
360 self.skip_whitespace();
361 if !self.consume('|') {
362 break;
363 }
364 nodes.push(self.parse_and()?);
365 }
366 Ok(if nodes.len() == 1 {
367 nodes.pop().expect("single node should exist")
368 } else {
369 QueryExpr::Any(nodes)
370 })
371 }
372
373 fn parse_and(&mut self) -> Result<QueryExpr, CoolError> {
374 let mut nodes = vec![self.parse_factor()?];
375 loop {
376 self.skip_whitespace();
377 if !self.consume(',') {
378 break;
379 }
380 nodes.push(self.parse_factor()?);
381 }
382 Ok(if nodes.len() == 1 {
383 nodes.pop().expect("single node should exist")
384 } else {
385 QueryExpr::All(nodes)
386 })
387 }
388
389 fn parse_factor(&mut self) -> Result<QueryExpr, CoolError> {
390 self.skip_whitespace();
391 if self.consume_keyword("not") {
392 self.skip_whitespace();
393 if !self.consume('(') {
394 return Err(CoolError::BadRequest(
395 "negated filter expression must use not(...)".to_owned(),
396 ));
397 }
398 let expr = self.parse_expr()?;
399 self.skip_whitespace();
400 if !self.consume(')') {
401 return Err(CoolError::BadRequest(
402 "unterminated negated filter expression".to_owned(),
403 ));
404 }
405 return Ok(QueryExpr::Not(Box::new(expr)));
406 }
407 if self.consume('(') {
408 let expr = self.parse_expr()?;
409 self.skip_whitespace();
410 if !self.consume(')') {
411 return Err(CoolError::BadRequest(
412 "unterminated grouped filter expression".to_owned(),
413 ));
414 }
415 return Ok(expr);
416 }
417
418 self.parse_predicate()
419 }
420
421 fn parse_predicate(&mut self) -> Result<QueryExpr, CoolError> {
422 let start = self.cursor;
423 while let Some(ch) = self.peek() {
424 if matches!(ch, ',' | '|' | ')') {
425 break;
426 }
427 self.cursor += ch.len_utf8();
428 }
429 let raw = self.input[start..self.cursor].trim();
430 let (key, value) = raw.split_once('=').ok_or_else(|| {
431 CoolError::BadRequest(format!(
432 "invalid grouped filter '{}': expected key=value",
433 raw,
434 ))
435 })?;
436 if key.trim().is_empty() || value.trim().is_empty() {
437 return Err(CoolError::BadRequest(format!(
438 "invalid grouped filter '{}': expected non-empty key and value",
439 raw,
440 )));
441 }
442 Ok(QueryExpr::Predicate {
443 key: key.trim().to_owned(),
444 value: value.trim().to_owned(),
445 })
446 }
447
448 fn consume(&mut self, expected: char) -> bool {
449 match self.peek() {
450 Some(ch) if ch == expected => {
451 self.cursor += ch.len_utf8();
452 true
453 }
454 _ => false,
455 }
456 }
457
458 fn consume_keyword(&mut self, expected: &str) -> bool {
459 let remaining = &self.input[self.cursor..];
460 if !remaining.starts_with(expected) {
461 return false;
462 }
463 let boundary = remaining[expected.len()..].chars().next();
464 if boundary.is_some_and(|ch| ch.is_ascii_alphanumeric() || ch == '_') {
465 return false;
466 }
467 self.cursor += expected.len();
468 true
469 }
470
471 fn peek(&self) -> Option<char> {
472 self.input[self.cursor..].chars().next()
473 }
474
475 fn skip_whitespace(&mut self) {
476 while let Some(ch) = self.peek() {
477 if !ch.is_whitespace() {
478 break;
479 }
480 self.cursor += ch.len_utf8();
481 }
482 }
483
484 fn remaining(&self) -> &str {
485 &self.input[self.cursor..]
486 }
487
488 fn is_eof(&self) -> bool {
489 self.cursor >= self.input.len()
490 }
491}
492
493pub fn validate_codec_request_headers<C>(headers: &HeaderMap) -> Result<(), CoolError>
494where
495 C: CoolCodec,
496{
497 validate_accept_header::<C>(headers)?;
498 validate_content_type_header::<C>(headers)
499}
500
501fn validate_accept_header<C>(headers: &HeaderMap) -> Result<(), CoolError>
502where
503 C: CoolCodec,
504{
505 validate_transport_accept_header(headers, &[C::CONTENT_TYPE])
506}
507
508fn validate_content_type_header<C>(headers: &HeaderMap) -> Result<(), CoolError>
509where
510 C: CoolCodec,
511{
512 validate_transport_content_type_header(headers, &[C::CONTENT_TYPE])
513}
514
515fn validate_transport_accept_header(
516 headers: &HeaderMap,
517 supported: &[&'static str],
518) -> Result<(), CoolError> {
519 let Some(accept) = headers.get(header::ACCEPT) else {
520 return Ok(());
521 };
522 let accept = accept
523 .to_str()
524 .map_err(|error| CoolError::BadRequest(format!("invalid Accept header: {error}")))?;
525
526 if supported
527 .iter()
528 .any(|content_type| accepts_content_type(accept, content_type))
529 {
530 Ok(())
531 } else {
532 Err(CoolError::NotAcceptable(format!(
533 "router only serves {} responses",
534 supported.join(", "),
535 )))
536 }
537}
538
539fn validate_transport_content_type_header(
540 headers: &HeaderMap,
541 supported: &[&'static str],
542) -> Result<(), CoolError> {
543 request_content_type(headers, supported).map(|_| ())
544}
545
546fn request_content_type(
547 headers: &HeaderMap,
548 supported: &[&'static str],
549) -> Result<&'static str, CoolError> {
550 let Some(content_type) = headers.get(header::CONTENT_TYPE) else {
551 return Err(CoolError::UnsupportedMediaType(format!(
552 "expected Content-Type one of {}",
553 supported.join(", "),
554 )));
555 };
556 let content_type = content_type
557 .to_str()
558 .map_err(|error| CoolError::BadRequest(format!("invalid Content-Type header: {error}")))?;
559
560 supported
561 .iter()
562 .copied()
563 .find(|expected| media_type_matches(content_type, expected))
564 .ok_or_else(|| {
565 CoolError::UnsupportedMediaType(format!(
566 "expected Content-Type one of {}, got {}",
567 supported.join(", "),
568 content_type,
569 ))
570 })
571}
572
573fn select_response_content_type(
574 headers: &HeaderMap,
575 supported: &[&'static str],
576 default: &'static str,
577) -> Result<&'static str, CoolError> {
578 let Some(accept) = headers.get(header::ACCEPT) else {
579 return Ok(default);
580 };
581 let accept = accept
582 .to_str()
583 .map_err(|error| CoolError::BadRequest(format!("invalid Accept header: {error}")))?;
584
585 supported
586 .iter()
587 .copied()
588 .find(|content_type| accepts_content_type(accept, content_type))
589 .ok_or_else(|| {
590 CoolError::NotAcceptable(format!(
591 "router only serves {} responses",
592 supported.join(", "),
593 ))
594 })
595}
596
597fn accepts_content_type(accept: &str, expected: &str) -> bool {
598 accept.split(',').map(str::trim).any(|value| {
599 if value == "*/*" {
600 return true;
601 }
602 let media_type = strip_media_type_params(value);
603 media_type == expected
604 || media_type == wildcard_media_type(expected)
605 || media_type == "application/*"
606 })
607}
608
609fn media_type_matches(candidate: &str, expected: &str) -> bool {
610 strip_media_type_params(candidate) == expected
611}
612
613fn strip_media_type_params(value: &str) -> &str {
614 value.split(';').next().unwrap_or(value).trim()
615}
616
617fn wildcard_media_type(content_type: &str) -> &str {
618 content_type
619 .split_once('/')
620 .map(|(prefix, _)| {
621 if prefix == "application" {
622 "application/*"
623 } else {
624 "*/*"
625 }
626 })
627 .unwrap_or("*/*")
628}
629
630pub fn decode_codec_request<C, T>(codec: &C, body: &[u8]) -> Result<T, CoolError>
631where
632 C: CoolCodec,
633 T: for<'de> Deserialize<'de>,
634{
635 codec.decode(body)
636}
637
638pub fn encode_codec_response<C, T>(
639 codec: &C,
640 status: StatusCode,
641 value: &T,
642) -> Result<Response, CoolError>
643where
644 C: CoolCodec,
645 T: Serialize + ?Sized,
646{
647 let bytes = codec.encode(value)?;
648 let mut response = Response::new(Body::from(bytes));
649 *response.status_mut() = status;
650 response.headers_mut().insert(
651 header::CONTENT_TYPE,
652 HeaderValue::from_static(C::CONTENT_TYPE),
653 );
654 Ok(response)
655}
656
657pub fn encode_codec_result<C, T>(codec: &C, result: Result<T, CoolError>) -> Response
658where
659 C: CoolCodec,
660 T: Serialize,
661{
662 encode_codec_result_with_status(codec, StatusCode::OK, result)
663}
664
665pub fn encode_transport_result<TTransport, TValue>(
666 transport: &TTransport,
667 headers: &HeaderMap,
668 result: Result<TValue, CoolError>,
669) -> Response
670where
671 TTransport: HttpTransport,
672 TValue: Serialize,
673{
674 encode_transport_result_with_status_for(
675 transport,
676 headers,
677 &RouteTransportCapabilities {
678 request_types: &[],
679 response_types: &[],
680 default_response_type: "",
681 supports_sequence_response: false,
682 },
683 StatusCode::OK,
684 result,
685 )
686}
687
688pub fn encode_transport_result_with_status<TTransport, TValue>(
689 transport: &TTransport,
690 headers: &HeaderMap,
691 success_status: StatusCode,
692 result: Result<TValue, CoolError>,
693) -> Response
694where
695 TTransport: HttpTransport,
696 TValue: Serialize,
697{
698 encode_transport_result_with_status_for(
699 transport,
700 headers,
701 &RouteTransportCapabilities {
702 request_types: &[],
703 response_types: &[],
704 default_response_type: "",
705 supports_sequence_response: false,
706 },
707 success_status,
708 result,
709 )
710}
711
712pub fn encode_transport_result_with_status_for<TTransport, TValue>(
713 transport: &TTransport,
714 headers: &HeaderMap,
715 capabilities: &RouteTransportCapabilities,
716 success_status: StatusCode,
717 result: Result<TValue, CoolError>,
718) -> Response
719where
720 TTransport: HttpTransport,
721 TValue: Serialize,
722{
723 let content_type = match select_response_content_type(
724 headers,
725 capabilities.response_types,
726 capabilities.default_response_type,
727 ) {
728 Ok(content_type) => content_type,
729 Err(error) => return fallback_error_response(error),
730 };
731 match result {
732 Ok(value) => transport
733 .encode_response(content_type, success_status, &value)
734 .unwrap_or_else(fallback_error_response),
735 Err(error) => {
736 let status = error.status_code();
737 let body = error.into_response();
738 transport
739 .encode_response(content_type, status, &body)
740 .unwrap_or_else(fallback_error_response)
741 }
742 }
743}
744
745pub fn encode_transport_sequence_result<TTransport, TValue>(
746 transport: &TTransport,
747 headers: &HeaderMap,
748 result: Result<Vec<TValue>, CoolError>,
749) -> Response
750where
751 TTransport: HttpTransport,
752 TValue: Serialize,
753{
754 encode_transport_sequence_result_with_status_for(
755 transport,
756 headers,
757 &RouteTransportCapabilities {
758 request_types: &[],
759 response_types: &[],
760 default_response_type: "",
761 supports_sequence_response: false,
762 },
763 StatusCode::OK,
764 result,
765 )
766}
767
768pub fn encode_transport_sequence_result_with_status<TTransport, TValue>(
769 transport: &TTransport,
770 headers: &HeaderMap,
771 success_status: StatusCode,
772 result: Result<Vec<TValue>, CoolError>,
773) -> Response
774where
775 TTransport: HttpTransport,
776 TValue: Serialize,
777{
778 encode_transport_sequence_result_with_status_for(
779 transport,
780 headers,
781 &RouteTransportCapabilities {
782 request_types: &[],
783 response_types: &[],
784 default_response_type: "",
785 supports_sequence_response: false,
786 },
787 success_status,
788 result,
789 )
790}
791
792pub fn encode_transport_sequence_result_with_status_for<TTransport, TValue>(
793 transport: &TTransport,
794 headers: &HeaderMap,
795 capabilities: &RouteTransportCapabilities,
796 success_status: StatusCode,
797 result: Result<Vec<TValue>, CoolError>,
798) -> Response
799where
800 TTransport: HttpTransport,
801 TValue: Serialize,
802{
803 if !capabilities.supports_sequence_response {
804 return fallback_error_response(CoolError::Internal(
805 "sequence response encoding requested for a route without sequence capability"
806 .to_owned(),
807 ));
808 }
809 let content_type = match select_response_content_type(
810 headers,
811 capabilities.response_types,
812 capabilities.default_response_type,
813 ) {
814 Ok(content_type) => content_type,
815 Err(error) => return fallback_error_response(error),
816 };
817 match result {
818 Ok(values) => transport
819 .encode_sequence_response(content_type, success_status, &values)
820 .unwrap_or_else(fallback_error_response),
821 Err(error) => {
822 let status = error.status_code();
823 let body = error.into_response();
824 transport
825 .encode_sequence_error_response(content_type, status, &body)
826 .unwrap_or_else(fallback_error_response)
827 }
828 }
829}
830
831fn encode_cbor_sequence_response<C, T>(
832 codec: &C,
833 status: StatusCode,
834 values: &[T],
835) -> Result<Response, CoolError>
836where
837 C: CoolCodec,
838 T: Serialize,
839{
840 if C::CONTENT_TYPE != CborCodecMarker::CONTENT_TYPE {
841 return Err(CoolError::NotAcceptable(
842 "cbor-seq requires a CBOR codec".to_owned(),
843 ));
844 }
845
846 let mut bytes = Vec::new();
847 for value in values {
848 bytes.extend(codec.encode(value)?);
849 }
850 encode_bytes_response(status, CBOR_SEQUENCE_CONTENT_TYPE, bytes)
851}
852
853fn encode_bytes_response(
854 status: StatusCode,
855 content_type: &'static str,
856 bytes: Vec<u8>,
857) -> Result<Response, CoolError> {
858 let mut response = Response::new(Body::from(bytes));
859 *response.status_mut() = status;
860 response
861 .headers_mut()
862 .insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
863 Ok(response)
864}
865
866pub fn encode_codec_result_with_status<C, T>(
867 codec: &C,
868 success_status: StatusCode,
869 result: Result<T, CoolError>,
870) -> Response
871where
872 C: CoolCodec,
873 T: Serialize,
874{
875 match result {
876 Ok(value) => encode_codec_response(codec, success_status, &value)
877 .unwrap_or_else(fallback_error_response),
878 Err(error) => {
879 let status = error.status_code();
880 let body = error.into_response();
881 encode_codec_response(codec, status, &body).unwrap_or_else(fallback_error_response)
882 }
883 }
884}
885
886fn fallback_error_response(error: CoolError) -> Response {
887 let mut response = Response::new(Body::from(error.to_string()));
888 *response.status_mut() = error.status_code();
889 response.headers_mut().insert(
890 header::CONTENT_TYPE,
891 HeaderValue::from_static("text/plain; charset=utf-8"),
892 );
893 response
894}