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#[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#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
30pub enum LayerCompression {
31 Uncompressed,
32 Gzip,
33 Zstd,
34}
35
36impl LayerCompression {
37 #[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#[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#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub struct LayerSize(u64);
72
73impl LayerSize {
74 #[must_use]
76 pub const fn new(value: u64) -> Self {
77 Self(value)
78 }
79
80 #[must_use]
82 pub const fn as_u64(self) -> u64 {
83 self.0
84 }
85}
86
87#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct LayerMediaType(OciMediaType);
90
91impl LayerMediaType {
92 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 #[must_use]
103 pub const fn tar() -> Self {
104 Self(OciMediaType::Known(KnownMediaType::LayerTar))
105 }
106
107 #[must_use]
109 pub const fn gzip_tar() -> Self {
110 Self(OciMediaType::Known(KnownMediaType::LayerTarGzip))
111 }
112
113 #[must_use]
115 pub const fn zstd_tar() -> Self {
116 Self(OciMediaType::Known(KnownMediaType::LayerTarZstd))
117 }
118
119 #[must_use]
121 pub const fn media_type(&self) -> &OciMediaType {
122 &self.0
123 }
124
125 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
144pub struct DiffId(OciDigest);
145
146impl DiffId {
147 #[must_use]
149 pub const fn new(digest: OciDigest) -> Self {
150 Self(digest)
151 }
152
153 #[must_use]
155 pub const fn digest(&self) -> &OciDigest {
156 &self.0
157 }
158}
159
160#[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 #[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 #[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 #[must_use]
192 pub const fn with_kind(mut self, kind: LayerKind) -> Self {
193 self.kind = kind;
194 self
195 }
196
197 #[must_use]
199 pub const fn media_type(&self) -> &LayerMediaType {
200 &self.media_type
201 }
202
203 #[must_use]
205 pub const fn digest(&self) -> &OciDigest {
206 &self.digest
207 }
208
209 #[must_use]
211 pub const fn size(&self) -> LayerSize {
212 self.size
213 }
214
215 #[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}