s2_api/
mime.rs

1use mime::Mime;
2
3#[derive(Debug, Clone, Copy)]
4pub enum JsonOrProto {
5    Json,
6    Proto,
7}
8
9impl JsonOrProto {
10    pub fn from_mime(mime: &Mime) -> Option<Self> {
11        if is_json(mime) {
12            Some(JsonOrProto::Json)
13        } else if is_protobuf(mime) {
14            Some(JsonOrProto::Proto)
15        } else {
16            None
17        }
18    }
19}
20
21/// MIME type parsed from `Content-Type` header.
22pub fn content_type(headers: &http::HeaderMap) -> Option<Mime> {
23    headers.get(http::header::CONTENT_TYPE).and_then(parse)
24}
25
26/// First MIME type present in the `Accept` header.
27pub fn accept(headers: &http::HeaderMap) -> Option<Mime> {
28    headers.get(http::header::ACCEPT).and_then(parse)
29}
30
31/// Parse the **first** MIME type from a header value.
32pub fn parse(header: &http::HeaderValue) -> Option<Mime> {
33    header.to_str().ok()?.split(',').next()?.trim().parse().ok()
34}
35
36pub fn is_json(mime: &mime::Mime) -> bool {
37    mime.type_() == mime::APPLICATION
38        && (mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON))
39}
40
41pub fn is_protobuf(mime: &mime::Mime) -> bool {
42    mime.type_() == mime::APPLICATION && {
43        let s = mime.subtype().as_str();
44        s.eq_ignore_ascii_case("protobuf") || s.eq_ignore_ascii_case("x-protobuf")
45    }
46}
47
48pub fn is_s2s_proto(mime: &Mime) -> bool {
49    mime.type_().as_str().eq_ignore_ascii_case("s2s")
50        && mime.subtype().as_str().eq_ignore_ascii_case("proto")
51}
52
53pub fn is_event_stream(mime: &mime::Mime) -> bool {
54    mime.type_() == mime::TEXT && mime.subtype() == mime::EVENT_STREAM
55}
56
57#[cfg(test)]
58mod tests {
59    use mime::Mime;
60    use rstest::rstest;
61
62    #[rstest]
63    #[case("application/json", Some("application/json"))]
64    #[case("  application/json  , application/protobuf", Some("application/json"))]
65    #[case(
66        "application/json; charset=utf-8",
67        Some("application/json;charset=utf-8")
68    )]
69    #[case("", None)]
70    #[case("not/a/mime, application/json", None)]
71    fn parse(#[case] header: &'static str, #[case] expected: Option<&'static str>) {
72        let header = http::HeaderValue::from_static(header);
73        let expected = expected.map(|s| s.parse::<Mime>().unwrap());
74        assert_eq!(super::parse(&header), expected);
75    }
76
77    #[test]
78    fn parse_returns_none_for_non_utf8_header_values() {
79        let header = http::HeaderValue::from_bytes(b"\xFF\xFF").unwrap();
80        assert_eq!(super::parse(&header), None);
81    }
82}