oci_unpack/reference/
mediatype.rs

1use std::{fmt, str::FromStr};
2
3/// Generate the `MediaType` enum, its `FromStr` and `Display`
4/// implementations, and the associated constant `ALL` with all
5/// the valid values.
6macro_rules! media_types {
7    ($($variant:ident = $mediatype:expr,)*) => {
8        /// Known media types.
9        #[non_exhaustive]
10        #[derive(Copy, Clone, PartialEq, Debug)]
11        pub enum MediaType {
12            $(
13                #[doc = concat!("Variant for `", $mediatype, "`.")]
14                $variant,
15            )*
16        }
17
18        impl MediaType {
19            /// List with all known media types.
20            pub(crate) const ALL: &[&str] = &[ $($mediatype),* ];
21
22            pub fn as_str(&self) -> &'static str {
23                match self {
24                    $(MediaType::$variant => $mediatype,)*
25                }
26            }
27        }
28
29        impl FromStr for MediaType {
30            type Err = InvalidMediaType;
31
32            fn from_str(s: &str) -> Result<Self, Self::Err> {
33                match s {
34                    $($mediatype => Ok(MediaType::$variant),)*
35                    _ => Err(InvalidMediaType),
36                }
37            }
38        }
39
40        impl fmt::Display for MediaType {
41            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42                f.write_str(self.as_str())
43            }
44        }
45    }
46}
47
48media_types!(
49    DockerFsTarGzip = "application/vnd.docker.image.rootfs.diff.tar.gzip",
50    DockerImageV1 = "application/vnd.docker.container.image.v1+json",
51    DockerManifestList = "application/vnd.docker.distribution.manifest.list.v2+json",
52    DockerManifestV2 = "application/vnd.docker.distribution.manifest.v2+json",
53    OciConfig = "application/vnd.oci.image.config.v1+json",
54    OciFsTar = "application/vnd.oci.image.layer.v1.tar",
55    OciFsTarGzip = "application/vnd.oci.image.layer.v1.tar+gzip",
56    OciFsTarZstd = "application/vnd.oci.image.layer.v1.tar+zstd",
57    OciImageIndex = "application/vnd.oci.image.index.v1+json",
58    OciManifestV1 = "application/vnd.oci.image.manifest.v1+json",
59);
60
61pub struct InvalidMediaType;
62
63struct MediaTypeVisitor;
64
65impl serde::de::Visitor<'_> for MediaTypeVisitor {
66    type Value = MediaType;
67
68    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
69        formatter.write_str("Media type for OCI/Docker objects.")
70    }
71
72    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
73    where
74        E: serde::de::Error,
75    {
76        MediaType::from_str(v).map_err(|_| E::custom(format!("Unknown type: {v}")))
77    }
78}
79
80impl<'de> serde::Deserialize<'de> for MediaType {
81    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82    where
83        D: serde::Deserializer<'de>,
84    {
85        deserializer.deserialize_str(MediaTypeVisitor)
86    }
87}
88
89#[test]
90fn media_type_in_json() {
91    #[derive(serde::Deserialize, Debug)]
92    struct Example {
93        mt: MediaType,
94    }
95
96    assert!(matches!(
97        serde_json::from_str(r#"{"mt": "application/vnd.oci.image.index.v1+json"}"#),
98        Ok(Example {
99            mt: MediaType::OciImageIndex
100        })
101    ));
102}