containerregistry_image/
media_type.rs1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::Error;
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum MediaType {
13 OciManifest,
16 OciIndex,
18 OciConfig,
20 OciLayerGzip,
22 OciLayer,
24 OciLayerZstd,
26 OciLayerNondistributable,
28 OciLayerNondistributableGzip,
30 OciLayerNondistributableZstd,
32 OciEmptyJson,
34
35 DockerManifest,
38 DockerManifestList,
40 DockerConfig,
42 DockerLayerGzip,
44 DockerForeignLayer,
46
47 Other(String),
49}
50
51impl MediaType {
52 pub fn as_str(&self) -> &str {
54 match self {
55 MediaType::OciManifest => "application/vnd.oci.image.manifest.v1+json",
56 MediaType::OciIndex => "application/vnd.oci.image.index.v1+json",
57 MediaType::OciConfig => "application/vnd.oci.image.config.v1+json",
58 MediaType::OciLayerGzip => "application/vnd.oci.image.layer.v1.tar+gzip",
59 MediaType::OciLayer => "application/vnd.oci.image.layer.v1.tar",
60 MediaType::OciLayerZstd => "application/vnd.oci.image.layer.v1.tar+zstd",
61 MediaType::OciLayerNondistributable => {
62 "application/vnd.oci.image.layer.nondistributable.v1.tar"
63 }
64 MediaType::OciLayerNondistributableGzip => {
65 "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
66 }
67 MediaType::OciLayerNondistributableZstd => {
68 "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
69 }
70 MediaType::OciEmptyJson => "application/vnd.oci.empty.v1+json",
71 MediaType::DockerManifest => "application/vnd.docker.distribution.manifest.v2+json",
72 MediaType::DockerManifestList => {
73 "application/vnd.docker.distribution.manifest.list.v2+json"
74 }
75 MediaType::DockerConfig => "application/vnd.docker.container.image.v1+json",
76 MediaType::DockerLayerGzip => "application/vnd.docker.image.rootfs.diff.tar.gzip",
77 MediaType::DockerForeignLayer => {
78 "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
79 }
80 MediaType::Other(s) => s,
81 }
82 }
83
84 pub fn is_manifest(&self) -> bool {
86 matches!(self, MediaType::OciManifest | MediaType::DockerManifest)
87 }
88
89 pub fn is_index(&self) -> bool {
91 matches!(self, MediaType::OciIndex | MediaType::DockerManifestList)
92 }
93
94 pub fn is_config(&self) -> bool {
96 matches!(
97 self,
98 MediaType::OciConfig | MediaType::DockerConfig | MediaType::OciEmptyJson
99 )
100 }
101
102 pub fn is_layer(&self) -> bool {
104 matches!(
105 self,
106 MediaType::OciLayerGzip
107 | MediaType::OciLayer
108 | MediaType::OciLayerZstd
109 | MediaType::OciLayerNondistributable
110 | MediaType::OciLayerNondistributableGzip
111 | MediaType::OciLayerNondistributableZstd
112 | MediaType::DockerLayerGzip
113 | MediaType::DockerForeignLayer
114 )
115 }
116
117 pub fn is_nondistributable(&self) -> bool {
119 matches!(
120 self,
121 MediaType::OciLayerNondistributable
122 | MediaType::OciLayerNondistributableGzip
123 | MediaType::OciLayerNondistributableZstd
124 | MediaType::DockerForeignLayer
125 )
126 }
127
128 pub fn to_oci(&self) -> MediaType {
130 match self {
131 MediaType::DockerManifest => MediaType::OciManifest,
132 MediaType::DockerManifestList => MediaType::OciIndex,
133 MediaType::DockerConfig => MediaType::OciConfig,
134 MediaType::DockerLayerGzip => MediaType::OciLayerGzip,
135 MediaType::DockerForeignLayer => MediaType::OciLayerNondistributableGzip,
136 other => other.clone(),
137 }
138 }
139
140 pub fn to_docker(&self) -> MediaType {
147 match self {
148 MediaType::OciManifest => MediaType::DockerManifest,
149 MediaType::OciIndex => MediaType::DockerManifestList,
150 MediaType::OciConfig | MediaType::OciEmptyJson => MediaType::DockerConfig,
151 MediaType::OciLayerGzip => MediaType::DockerLayerGzip,
152 MediaType::OciLayerNondistributableGzip => MediaType::DockerForeignLayer,
154 MediaType::OciLayer
156 | MediaType::OciLayerZstd
157 | MediaType::OciLayerNondistributable
158 | MediaType::OciLayerNondistributableZstd => self.clone(),
159 other => other.clone(),
160 }
161 }
162
163 pub fn is_docker_compatible(&self) -> bool {
167 !matches!(
168 self,
169 MediaType::OciLayer
170 | MediaType::OciLayerZstd
171 | MediaType::OciLayerNondistributable
172 | MediaType::OciLayerNondistributableZstd
173 )
174 }
175}
176
177impl fmt::Display for MediaType {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(f, "{}", self.as_str())
180 }
181}
182
183impl FromStr for MediaType {
184 type Err = Error;
185
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 Ok(match s {
188 "application/vnd.oci.image.manifest.v1+json" => MediaType::OciManifest,
189 "application/vnd.oci.image.index.v1+json" => MediaType::OciIndex,
190 "application/vnd.oci.image.config.v1+json" => MediaType::OciConfig,
191 "application/vnd.oci.image.layer.v1.tar+gzip" => MediaType::OciLayerGzip,
192 "application/vnd.oci.image.layer.v1.tar" => MediaType::OciLayer,
193 "application/vnd.oci.image.layer.v1.tar+zstd" => MediaType::OciLayerZstd,
194 "application/vnd.oci.image.layer.nondistributable.v1.tar" => {
195 MediaType::OciLayerNondistributable
196 }
197 "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" => {
198 MediaType::OciLayerNondistributableGzip
199 }
200 "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd" => {
201 MediaType::OciLayerNondistributableZstd
202 }
203 "application/vnd.oci.empty.v1+json" => MediaType::OciEmptyJson,
204 "application/vnd.docker.distribution.manifest.v2+json" => MediaType::DockerManifest,
205 "application/vnd.docker.distribution.manifest.list.v2+json" => {
206 MediaType::DockerManifestList
207 }
208 "application/vnd.docker.container.image.v1+json" => MediaType::DockerConfig,
209 "application/vnd.docker.image.rootfs.diff.tar.gzip" => MediaType::DockerLayerGzip,
210 "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip" => {
211 MediaType::DockerForeignLayer
212 }
213 other => MediaType::Other(other.to_string()),
214 })
215 }
216}
217
218impl Serialize for MediaType {
219 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220 where
221 S: serde::Serializer,
222 {
223 serializer.serialize_str(self.as_str())
224 }
225}
226
227impl<'de> Deserialize<'de> for MediaType {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: serde::Deserializer<'de>,
231 {
232 let s = String::deserialize(deserializer)?;
233 Ok(s.parse().unwrap())
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_media_type_parse() {
244 let mt: MediaType = "application/vnd.oci.image.manifest.v1+json"
245 .parse()
246 .unwrap();
247 assert_eq!(mt, MediaType::OciManifest);
248 }
249
250 #[test]
251 fn test_media_type_unknown() {
252 let mt: MediaType = "application/custom".parse().unwrap();
253 assert_eq!(mt, MediaType::Other("application/custom".to_string()));
254 }
255
256 #[test]
257 fn test_media_type_to_oci() {
258 assert_eq!(MediaType::DockerManifest.to_oci(), MediaType::OciManifest);
259 assert_eq!(MediaType::OciManifest.to_oci(), MediaType::OciManifest);
260 }
261
262 #[test]
263 fn test_media_type_to_docker_compatible() {
264 assert_eq!(
266 MediaType::OciLayerGzip.to_docker(),
267 MediaType::DockerLayerGzip
268 );
269 assert!(MediaType::OciLayerGzip.is_docker_compatible());
270 }
271
272 #[test]
273 fn test_media_type_to_docker_incompatible() {
274 assert_eq!(MediaType::OciLayerZstd.to_docker(), MediaType::OciLayerZstd);
276 assert_eq!(MediaType::OciLayer.to_docker(), MediaType::OciLayer);
277 assert!(!MediaType::OciLayerZstd.is_docker_compatible());
278 assert!(!MediaType::OciLayer.is_docker_compatible());
279 }
280
281 #[test]
282 fn test_media_type_nondistributable() {
283 assert!(MediaType::OciLayerNondistributableGzip.is_nondistributable());
284 assert!(MediaType::DockerForeignLayer.is_nondistributable());
285 assert!(!MediaType::OciLayerGzip.is_nondistributable());
286 }
287
288 #[test]
289 fn test_media_type_is_layer() {
290 assert!(MediaType::OciLayerGzip.is_layer());
291 assert!(MediaType::OciLayerNondistributableGzip.is_layer());
292 assert!(MediaType::DockerForeignLayer.is_layer());
293 assert!(!MediaType::OciManifest.is_layer());
294 }
295
296 #[test]
297 fn test_media_type_serde_roundtrip() {
298 let mt = MediaType::OciManifest;
299 let json = serde_json::to_string(&mt).unwrap();
300 let parsed: MediaType = serde_json::from_str(&json).unwrap();
301 assert_eq!(mt, parsed);
302 }
303
304 #[test]
305 fn test_media_type_nondistributable_roundtrip() {
306 let mt = MediaType::OciLayerNondistributableGzip;
307 let json = serde_json::to_string(&mt).unwrap();
308 let parsed: MediaType = serde_json::from_str(&json).unwrap();
309 assert_eq!(mt, parsed);
310 }
311}