use ferro_blob_store::Digest;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Descriptor {
#[serde(rename = "mediaType")]
pub media_type: String,
pub digest: Digest,
pub size: u64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub urls: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<std::collections::BTreeMap<String, String>>,
#[serde(
rename = "artifactType",
default,
skip_serializing_if = "Option::is_none"
)]
pub artifact_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<Value>,
#[serde(flatten, default)]
pub extra: std::collections::BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ImageManifest {
#[serde(rename = "schemaVersion")]
pub schema_version: u32,
#[serde(rename = "mediaType", skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
#[serde(
rename = "artifactType",
default,
skip_serializing_if = "Option::is_none"
)]
pub artifact_type: Option<String>,
pub config: Descriptor,
pub layers: Vec<Descriptor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject: Option<Descriptor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<std::collections::BTreeMap<String, String>>,
#[serde(flatten, default)]
pub extra: std::collections::BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ImageIndex {
#[serde(rename = "schemaVersion")]
pub schema_version: u32,
#[serde(rename = "mediaType", skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
#[serde(
rename = "artifactType",
default,
skip_serializing_if = "Option::is_none"
)]
pub artifact_type: Option<String>,
pub manifests: Vec<Descriptor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject: Option<Descriptor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<std::collections::BTreeMap<String, String>>,
#[serde(flatten, default)]
pub extra: std::collections::BTreeMap<String, Value>,
}
#[must_use]
pub fn empty_image_index() -> ImageIndex {
ImageIndex {
schema_version: 2,
media_type: Some(crate::media_types::OCI_IMAGE_INDEX.to_owned()),
artifact_type: None,
manifests: Vec::new(),
subject: None,
annotations: None,
extra: std::collections::BTreeMap::new(),
}
}
#[cfg(test)]
mod tests {
use super::{Descriptor, ImageIndex, ImageManifest, empty_image_index};
use ferro_blob_store::Digest;
fn sample_digest() -> Digest {
Digest::sha256_of(b"sample")
}
#[test]
fn image_manifest_round_trips_via_json() {
let m = ImageManifest {
schema_version: 2,
media_type: Some("application/vnd.oci.image.manifest.v1+json".to_owned()),
artifact_type: None,
config: Descriptor {
media_type: "application/vnd.oci.image.config.v1+json".to_owned(),
digest: sample_digest(),
size: 512,
urls: Vec::new(),
annotations: None,
artifact_type: None,
platform: None,
extra: std::collections::BTreeMap::new(),
},
layers: vec![Descriptor {
media_type: "application/vnd.oci.image.layer.v1.tar+gzip".to_owned(),
digest: sample_digest(),
size: 1024,
urls: Vec::new(),
annotations: None,
artifact_type: None,
platform: None,
extra: std::collections::BTreeMap::new(),
}],
subject: None,
annotations: None,
extra: std::collections::BTreeMap::new(),
};
let json = serde_json::to_string(&m).expect("serialise");
let back: ImageManifest = serde_json::from_str(&json).expect("deserialise");
assert_eq!(back, m);
}
#[test]
fn empty_image_index_has_no_manifests() {
let idx = empty_image_index();
assert_eq!(idx.schema_version, 2);
assert!(idx.manifests.is_empty());
}
#[test]
fn image_index_with_two_platforms_round_trips() {
let idx = ImageIndex {
schema_version: 2,
media_type: Some("application/vnd.oci.image.index.v1+json".to_owned()),
artifact_type: None,
manifests: vec![
Descriptor {
media_type: "application/vnd.oci.image.manifest.v1+json".to_owned(),
digest: sample_digest(),
size: 256,
urls: Vec::new(),
annotations: None,
artifact_type: None,
platform: Some(serde_json::json!({ "architecture": "amd64", "os": "linux" })),
extra: std::collections::BTreeMap::new(),
},
Descriptor {
media_type: "application/vnd.oci.image.manifest.v1+json".to_owned(),
digest: sample_digest(),
size: 256,
urls: Vec::new(),
annotations: None,
artifact_type: None,
platform: Some(serde_json::json!({ "architecture": "arm64", "os": "linux" })),
extra: std::collections::BTreeMap::new(),
},
],
subject: None,
annotations: None,
extra: std::collections::BTreeMap::new(),
};
let json = serde_json::to_string(&idx).expect("serialise");
let back: ImageIndex = serde_json::from_str(&json).expect("deserialise");
assert_eq!(back, idx);
}
}