Skip to main content

use_oci_manifest/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7use use_oci_annotation::Annotation;
8use use_oci_descriptor::OciDescriptor;
9
10/// Errors returned when manifest metadata is invalid.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum ManifestError {
13    UnsupportedSchemaVersion,
14}
15
16impl fmt::Display for ManifestError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::UnsupportedSchemaVersion => {
20                formatter.write_str("unsupported OCI manifest schema version")
21            },
22        }
23    }
24}
25
26impl Error for ManifestError {}
27
28/// OCI manifest schema version.
29#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
30pub struct SchemaVersion(u8);
31
32impl SchemaVersion {
33    /// Creates a schema version. OCI image manifests use schema version 2.
34    pub const fn new(value: u8) -> Result<Self, ManifestError> {
35        if value == 2 {
36            Ok(Self(value))
37        } else {
38            Err(ManifestError::UnsupportedSchemaVersion)
39        }
40    }
41
42    /// Returns the version number.
43    #[must_use]
44    pub const fn as_u8(self) -> u8 {
45        self.0
46    }
47}
48
49impl Default for SchemaVersion {
50    fn default() -> Self {
51        Self(2)
52    }
53}
54
55/// OCI image manifest primitives.
56#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct OciManifest {
58    schema_version: SchemaVersion,
59    config: OciDescriptor,
60    layers: Vec<OciDescriptor>,
61    subject: Option<OciDescriptor>,
62    annotations: Vec<Annotation>,
63}
64
65impl OciManifest {
66    /// Creates a manifest with a config descriptor.
67    #[must_use]
68    pub fn new(config: OciDescriptor) -> Self {
69        Self {
70            schema_version: SchemaVersion::default(),
71            config,
72            layers: Vec::new(),
73            subject: None,
74            annotations: Vec::new(),
75        }
76    }
77
78    /// Adds a layer descriptor.
79    #[must_use]
80    pub fn with_layer(mut self, layer: OciDescriptor) -> Self {
81        self.layers.push(layer);
82        self
83    }
84
85    /// Adds a subject descriptor.
86    #[must_use]
87    pub fn with_subject(mut self, subject: OciDescriptor) -> Self {
88        self.subject = Some(subject);
89        self
90    }
91
92    /// Adds an annotation.
93    #[must_use]
94    pub fn with_annotation(mut self, annotation: Annotation) -> Self {
95        self.annotations.push(annotation);
96        self
97    }
98
99    /// Returns the schema version.
100    #[must_use]
101    pub const fn schema_version(&self) -> SchemaVersion {
102        self.schema_version
103    }
104
105    /// Returns the config descriptor.
106    #[must_use]
107    pub const fn config(&self) -> &OciDescriptor {
108        &self.config
109    }
110
111    /// Returns layer descriptors.
112    #[must_use]
113    pub fn layers(&self) -> &[OciDescriptor] {
114        &self.layers
115    }
116
117    /// Returns the optional subject descriptor.
118    #[must_use]
119    pub const fn subject(&self) -> Option<&OciDescriptor> {
120        self.subject.as_ref()
121    }
122
123    /// Returns manifest annotations.
124    #[must_use]
125    pub fn annotations(&self) -> &[Annotation] {
126        &self.annotations
127    }
128}
129
130/// A named wrapper for manifest annotations.
131#[derive(Clone, Debug, Default, Eq, PartialEq)]
132pub struct ManifestAnnotations(Vec<Annotation>);
133
134impl ManifestAnnotations {
135    /// Creates an empty annotation list.
136    #[must_use]
137    pub const fn new() -> Self {
138        Self(Vec::new())
139    }
140
141    /// Adds an annotation.
142    #[must_use]
143    pub fn with_annotation(mut self, annotation: Annotation) -> Self {
144        self.0.push(annotation);
145        self
146    }
147
148    /// Returns annotations.
149    #[must_use]
150    pub fn as_slice(&self) -> &[Annotation] {
151        &self.0
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::{ManifestAnnotations, ManifestError, OciManifest, SchemaVersion};
158    use use_oci_annotation::Annotation;
159    use use_oci_descriptor::{DescriptorSize, OciDescriptor};
160    use use_oci_digest::OciDigest;
161    use use_oci_media_type::OciMediaType;
162
163    const SHA: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
164
165    fn descriptor() -> Result<OciDescriptor, Box<dyn std::error::Error>> {
166        let digest: OciDigest = format!("sha256:{SHA}").parse()?;
167        Ok(OciDescriptor::new(
168            OciMediaType::image_config(),
169            digest,
170            DescriptorSize::new(1),
171        ))
172    }
173
174    #[test]
175    fn models_manifest_layer_lists() -> Result<(), Box<dyn std::error::Error>> {
176        let manifest = OciManifest::new(descriptor()?)
177            .with_layer(descriptor()?)
178            .with_annotation(Annotation::title("Example")?);
179        let annotations = ManifestAnnotations::new().with_annotation(Annotation::title("Example")?);
180
181        assert_eq!(manifest.schema_version().as_u8(), 2);
182        assert_eq!(manifest.layers().len(), 1);
183        assert_eq!(manifest.annotations().len(), 1);
184        assert_eq!(annotations.as_slice().len(), 1);
185        assert_eq!(
186            SchemaVersion::new(1),
187            Err(ManifestError::UnsupportedSchemaVersion)
188        );
189        Ok(())
190    }
191}