Skip to main content

cratestack_axum/
lib.rs

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}