1use strum::{Display, EnumProperty, EnumString};
4
5use crate::errors::Result;
6
7#[derive(EnumProperty, EnumString, Display, Debug, Hash, PartialEq, Eq, Clone)]
12pub enum MediaTypes {
13 #[strum(serialize = "application/vnd.docker.distribution.manifest.v1+json")]
16 #[strum(props(Sub = "vnd.docker.distribution.manifest.v1+json"))]
17 ManifestV2S1,
18 #[strum(serialize = "application/vnd.docker.distribution.manifest.v1+prettyjws")]
20 #[strum(props(Sub = "vnd.docker.distribution.manifest.v1+prettyjws"))]
21 ManifestV2S1Signed,
22 #[strum(serialize = "application/vnd.docker.distribution.manifest.v2+json")]
24 #[strum(props(Sub = "vnd.docker.distribution.manifest.v2+json"))]
25 ManifestV2S2,
26 #[strum(serialize = "application/vnd.docker.distribution.manifest.list.v2+json")]
28 #[strum(props(Sub = "vnd.docker.distribution.manifest.list.v2+json"))]
29 ManifestList,
30 #[strum(serialize = "application/vnd.docker.image.rootfs.diff.tar.gzip")]
32 #[strum(props(Sub = "vnd.docker.image.rootfs.diff.tar.gzip"))]
33 ImageLayerTgz,
34 #[strum(serialize = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip")]
36 #[strum(props(Sub = "vnd.docker.image.rootfs.foreign.diff.tar.gzip"))]
37 ImageLayerForeignTgz,
38 #[strum(serialize = "application/vnd.docker.container.image.v1+json")]
40 #[strum(props(Sub = "vnd.docker.container.image.v1+json"))]
41 ContainerConfigV1,
42
43 #[strum(serialize = "application/vnd.oci.image.manifest.v1+json")]
46 #[strum(props(Sub = "vnd.oci.image.manifest.v1+json"))]
47 OciImageManifest,
48 #[strum(serialize = "application/vnd.oci.image.index.v1+json")]
50 #[strum(props(Sub = "vnd.oci.image.index.v1+json"))]
51 OciImageIndexV1,
52 #[strum(serialize = "application/vnd.oci.image.config.v1+json")]
54 #[strum(props(Sub = "vnd.oci.image.config.v1+json"))]
55 OciImageConfig,
56 #[strum(serialize = "application/vnd.oci.image.layer.v1.tar")]
58 #[strum(props(Sub = "vnd.oci.image.layer.v1.tar"))]
59 OciImageLayerTar,
60 #[strum(serialize = "application/vnd.oci.image.layer.v1.tar+gzip")]
62 #[strum(props(Sub = "vnd.oci.image.layer.v1.tar+gzip"))]
63 OciImageLayerTgz,
64 #[strum(serialize = "application/vnd.oci.image.layer.v1.tar+zstd")]
66 #[strum(props(Sub = "vnd.oci.image.layer.v1.tar+zstd"))]
67 OciImageLayerZstd,
68 #[strum(serialize = "application/vnd.oci.empty.v1+json")]
70 #[strum(props(Sub = "vnd.oci.empty.v1+json"))]
71 OciEmptyV1,
72
73 #[strum(serialize = "application/json")]
76 #[strum(props(Sub = "json"))]
77 ApplicationJson,
78}
79
80impl MediaTypes {
81 pub fn from_mime(mtype: &mime::Mime) -> Result<Self> {
83 match (mtype.type_(), mtype.subtype(), mtype.suffix()) {
84 (mime::APPLICATION, mime::JSON, _) => Ok(MediaTypes::ApplicationJson),
85 (mime::APPLICATION, subt, None) if subt == "vnd.docker.image.rootfs.diff.tar.gzip" => {
86 Ok(MediaTypes::ImageLayerTgz)
87 }
88 (mime::APPLICATION, subt, None) if subt == "vnd.docker.image.rootfs.foreign.diff.tar.gzip" => {
89 Ok(MediaTypes::ImageLayerForeignTgz)
90 }
91 (mime::APPLICATION, subt, None) if subt == "vnd.oci.image.layer.v1.tar" => Ok(MediaTypes::OciImageLayerTar),
92 (mime::APPLICATION, subt, Some(suff)) => match (subt.to_string().as_str(), suff.to_string().as_str()) {
93 ("vnd.docker.distribution.manifest.v1", "json") => Ok(MediaTypes::ManifestV2S1),
95 ("vnd.docker.distribution.manifest.v1", "prettyjws") => Ok(MediaTypes::ManifestV2S1Signed),
96 ("vnd.docker.distribution.manifest.v2", "json") => Ok(MediaTypes::ManifestV2S2),
97 ("vnd.docker.distribution.manifest.list.v2", "json") => Ok(MediaTypes::ManifestList),
98 ("vnd.docker.image.rootfs.diff.tar.gzip", _) => Ok(MediaTypes::ImageLayerTgz),
99 ("vnd.docker.image.rootfs.foreign.diff.tar.gzip", _) => Ok(MediaTypes::ImageLayerForeignTgz),
100 ("vnd.docker.container.image.v1", "json") => Ok(MediaTypes::ContainerConfigV1),
101 ("vnd.oci.image.manifest.v1", "json") => Ok(MediaTypes::OciImageManifest),
103 ("vnd.oci.image.index.v1", "json") => Ok(MediaTypes::OciImageIndexV1),
104 ("vnd.oci.image.config.v1", "json") => Ok(MediaTypes::OciImageConfig),
105 ("vnd.oci.image.layer.v1.tar", "gzip") => Ok(MediaTypes::OciImageLayerTgz),
106 ("vnd.oci.image.layer.v1.tar", "zstd") => Ok(MediaTypes::OciImageLayerZstd),
107 ("vnd.oci.empty.v1", "json") => Ok(MediaTypes::OciEmptyV1),
108 _ => Err(crate::Error::UnknownMimeType(mtype.clone())),
109 },
110 _ => Err(crate::Error::UnknownMimeType(mtype.clone())),
111 }
112 }
113 pub fn to_mime(&self) -> mime::Mime {
114 match self {
115 &MediaTypes::ApplicationJson => Ok(mime::APPLICATION_JSON),
116 m => {
117 if let Some(s) = m.get_str("Sub") {
118 ("application/".to_string() + s).parse()
119 } else {
120 "application/star".parse()
121 }
122 }
123 }
124 .expect("to_mime should be always successful")
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use std::str::FromStr;
131
132 use super::*;
133
134 #[test]
135 fn test_roundtrip_to_mime_from_mime() {
136 let types = [
137 MediaTypes::ManifestV2S1,
138 MediaTypes::ManifestV2S1Signed,
139 MediaTypes::ManifestV2S2,
140 MediaTypes::ManifestList,
141 MediaTypes::ImageLayerTgz,
142 MediaTypes::ImageLayerForeignTgz,
143 MediaTypes::ContainerConfigV1,
144 MediaTypes::OciImageManifest,
145 MediaTypes::OciImageIndexV1,
146 MediaTypes::OciImageConfig,
147 MediaTypes::OciImageLayerTar,
148 MediaTypes::OciImageLayerTgz,
149 MediaTypes::OciImageLayerZstd,
150 MediaTypes::OciEmptyV1,
151 MediaTypes::ApplicationJson,
152 ];
153 for mt in &types {
154 let mime = mt.to_mime();
155 let back = MediaTypes::from_mime(&mime).unwrap();
156 assert_eq!(&back, mt, "roundtrip failed for {mt:?}");
157 }
158 }
159
160 #[test]
161 fn test_from_str_roundtrip() {
162 let types = [
163 "application/vnd.docker.distribution.manifest.v2+json",
164 "application/vnd.docker.distribution.manifest.v1+prettyjws",
165 "application/vnd.docker.distribution.manifest.list.v2+json",
166 "application/vnd.oci.image.manifest.v1+json",
167 "application/vnd.oci.image.index.v1+json",
168 "application/json",
169 ];
170 for s in &types {
171 let mt = MediaTypes::from_str(s).unwrap();
172 assert_eq!(&mt.to_string(), s, "roundtrip failed for {s}");
173 }
174 }
175
176 #[test]
177 fn test_from_mime_unknown_type() {
178 let mime: mime::Mime = "text/plain".parse().unwrap();
179 let result = MediaTypes::from_mime(&mime);
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_from_mime_unknown_application_subtype() {
185 let mime: mime::Mime = "application/vnd.unknown.type.v1+json".parse().unwrap();
186 let result = MediaTypes::from_mime(&mime);
187 assert!(result.is_err());
188 }
189
190 #[test]
191 fn test_from_str_invalid() {
192 let result = MediaTypes::from_str("not/a/valid/media/type");
193 assert!(result.is_err());
194 }
195}