Skip to main content

use_oci_layer/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7use use_oci_digest::OciDigest;
8use use_oci_media_type::{KnownMediaType, OciMediaType};
9
10/// Errors returned when OCI layer metadata is invalid.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum LayerError {
13    InvalidMediaType,
14}
15
16impl fmt::Display for LayerError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::InvalidMediaType => {
20                formatter.write_str("media type is not an OCI layer media type")
21            },
22        }
23    }
24}
25
26impl Error for LayerError {}
27
28/// OCI layer compression labels.
29#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
30pub enum LayerCompression {
31    Uncompressed,
32    Gzip,
33    Zstd,
34}
35
36impl LayerCompression {
37    /// Returns the stable compression label.
38    #[must_use]
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::Uncompressed => "uncompressed",
42            Self::Gzip => "gzip",
43            Self::Zstd => "zstd",
44        }
45    }
46}
47
48impl fmt::Display for LayerCompression {
49    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50        formatter.write_str(self.as_str())
51    }
52}
53
54/// OCI layer kind labels.
55#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
56pub enum LayerKind {
57    Filesystem,
58    Foreign,
59    Nondistributable,
60    Unknown,
61}
62
63impl Default for LayerKind {
64    fn default() -> Self {
65        Self::Filesystem
66    }
67}
68
69/// Layer size in bytes.
70#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub struct LayerSize(u64);
72
73impl LayerSize {
74    /// Creates a layer size.
75    #[must_use]
76    pub const fn new(value: u64) -> Self {
77        Self(value)
78    }
79
80    /// Returns the size in bytes.
81    #[must_use]
82    pub const fn as_u64(self) -> u64 {
83        self.0
84    }
85}
86
87/// An OCI layer media type.
88#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct LayerMediaType(OciMediaType);
90
91impl LayerMediaType {
92    /// Creates a layer media type after checking that it is layer-shaped.
93    pub fn new(media_type: OciMediaType) -> Result<Self, LayerError> {
94        if media_type.is_layer() {
95            Ok(Self(media_type))
96        } else {
97            Err(LayerError::InvalidMediaType)
98        }
99    }
100
101    /// Returns an uncompressed tar layer media type.
102    #[must_use]
103    pub const fn tar() -> Self {
104        Self(OciMediaType::Known(KnownMediaType::LayerTar))
105    }
106
107    /// Returns a gzip-compressed tar layer media type.
108    #[must_use]
109    pub const fn gzip_tar() -> Self {
110        Self(OciMediaType::Known(KnownMediaType::LayerTarGzip))
111    }
112
113    /// Returns a zstd-compressed tar layer media type.
114    #[must_use]
115    pub const fn zstd_tar() -> Self {
116        Self(OciMediaType::Known(KnownMediaType::LayerTarZstd))
117    }
118
119    /// Returns the underlying media type.
120    #[must_use]
121    pub const fn media_type(&self) -> &OciMediaType {
122        &self.0
123    }
124
125    /// Returns the compression implied by the media type.
126    #[must_use]
127    pub const fn compression(&self) -> LayerCompression {
128        match self.0 {
129            OciMediaType::Known(KnownMediaType::LayerTarGzip) => LayerCompression::Gzip,
130            OciMediaType::Known(KnownMediaType::LayerTarZstd) => LayerCompression::Zstd,
131            _ => LayerCompression::Uncompressed,
132        }
133    }
134}
135
136impl fmt::Display for LayerMediaType {
137    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
138        self.0.fmt(formatter)
139    }
140}
141
142/// A layer diff ID digest marker.
143#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
144pub struct DiffId(OciDigest);
145
146impl DiffId {
147    /// Creates a diff ID from a digest.
148    #[must_use]
149    pub const fn new(digest: OciDigest) -> Self {
150        Self(digest)
151    }
152
153    /// Returns the digest.
154    #[must_use]
155    pub const fn digest(&self) -> &OciDigest {
156        &self.0
157    }
158}
159
160/// OCI layer metadata. This type does not extract or decompress layers.
161#[derive(Clone, Debug, Eq, PartialEq)]
162pub struct OciLayer {
163    media_type: LayerMediaType,
164    digest: OciDigest,
165    size: LayerSize,
166    diff_id: Option<DiffId>,
167    kind: LayerKind,
168}
169
170impl OciLayer {
171    /// Creates layer metadata.
172    #[must_use]
173    pub fn new(media_type: LayerMediaType, digest: OciDigest, size: LayerSize) -> Self {
174        Self {
175            media_type,
176            digest,
177            size,
178            diff_id: None,
179            kind: LayerKind::Filesystem,
180        }
181    }
182
183    /// Adds a diff ID.
184    #[must_use]
185    pub fn with_diff_id(mut self, diff_id: DiffId) -> Self {
186        self.diff_id = Some(diff_id);
187        self
188    }
189
190    /// Adds a layer kind.
191    #[must_use]
192    pub const fn with_kind(mut self, kind: LayerKind) -> Self {
193        self.kind = kind;
194        self
195    }
196
197    /// Returns the layer media type.
198    #[must_use]
199    pub const fn media_type(&self) -> &LayerMediaType {
200        &self.media_type
201    }
202
203    /// Returns the digest.
204    #[must_use]
205    pub const fn digest(&self) -> &OciDigest {
206        &self.digest
207    }
208
209    /// Returns the size.
210    #[must_use]
211    pub const fn size(&self) -> LayerSize {
212        self.size
213    }
214
215    /// Returns the compression marker.
216    #[must_use]
217    pub const fn compression(&self) -> LayerCompression {
218        self.media_type.compression()
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::{LayerCompression, LayerMediaType, LayerSize, OciLayer};
225    use use_oci_digest::OciDigest;
226    use use_oci_media_type::OciMediaType;
227
228    const SHA: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
229
230    #[test]
231    fn models_layer_metadata_without_extraction() -> Result<(), Box<dyn std::error::Error>> {
232        let digest: OciDigest = format!("sha256:{SHA}").parse()?;
233        let layer = OciLayer::new(LayerMediaType::gzip_tar(), digest, LayerSize::new(42));
234
235        assert_eq!(layer.compression(), LayerCompression::Gzip);
236        assert_eq!(layer.size().as_u64(), 42);
237        assert!(LayerMediaType::new(OciMediaType::image_config()).is_err());
238        Ok(())
239    }
240}