commit 665c5a359b761e3e48ada9c23a57d38dd681ce7c
parent 48df00c7ac5d81557c8f4c8bcc2a281555daf2ab
Author: Gavin Peacock <gpeacock@adobe.com>
Date: Fri, 10 Mar 2023 11:20:59 -0800
(MINOR) Resource format and is_parent / relationship changes (#202)
change resource.content_type to format
change ingredient.is_parent to relationship.
Diffstat:
5 files changed, 46 insertions(+), 53 deletions(-)
diff --git a/sdk/src/assertions/ingredient.rs b/sdk/src/assertions/ingredient.rs
@@ -24,7 +24,7 @@ use crate::{
const ASSERTION_CREATION_VERSION: usize = 1;
// Used to differentiate a parent from a component
-#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub enum Relationship {
#[serde(rename = "parentOf")]
ParentOf,
diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs
@@ -65,11 +65,13 @@ pub struct Ingredient {
#[serde(skip_serializing_if = "Option::is_none")]
hash: Option<String>,
- /// Set to `true` if this is the parent ingredient.
+ /// Set to `ParentOf` if this is the parent ingredient.
///
/// There can only be one parent ingredient in the ingredients.
- #[serde(skip_serializing_if = "Option::is_none")]
- is_parent: Option<bool>,
+ // #[serde(skip_serializing_if = "Option::is_none")]
+ // is_parent: Option<bool>,
+ #[serde(default = "default_relationship")]
+ relationship: Relationship,
/// The active manifest label (if one exists).
///
@@ -110,6 +112,10 @@ fn default_format() -> String {
"application/octet-stream".to_owned()
}
+fn default_relationship() -> Relationship {
+ Relationship::default()
+}
+
impl Ingredient {
/// Constructs a new `Ingredient`.
///
@@ -167,12 +173,12 @@ impl Ingredient {
self.thumbnail.as_ref()
}
- /// Returns thumbnail tuple Some((content_type, bytes)) or None
+ /// Returns thumbnail tuple Some((format, bytes)) or None
///
pub fn thumbnail(&self) -> Option<(&str, Cow<Vec<u8>>)> {
self.thumbnail
.as_ref()
- .and_then(|t| Some(t.content_type.as_str()).zip(self.resources.get(&t.identifier).ok()))
+ .and_then(|t| Some(t.format.as_str()).zip(self.resources.get(&t.identifier).ok()))
}
/// Returns a Cow of thumbnail bytes or Err(Error::NotFound)`.
@@ -191,7 +197,7 @@ impl Ingredient {
/// Returns `true` if this is labeled as the parent ingredient.
pub fn is_parent(&self) -> bool {
- self.is_parent.unwrap_or(false)
+ self.relationship == Relationship::ParentOf
}
/// Returns a reference to the [`ValidationStatus`]s if they exist.
@@ -261,7 +267,7 @@ impl Ingredient {
/// Only one ingredient should be flagged as a parent.
/// Use Manifest.set_parent to ensure this is the only parent ingredient
pub fn set_is_parent(&mut self) -> &mut Self {
- self.is_parent = Some(true);
+ self.relationship = Relationship::ParentOf;
self
}
@@ -275,17 +281,14 @@ impl Ingredient {
Ok(self)
}
- /// Sets the thumbnail content_type and image data.
+ /// Sets the thumbnail format and image data.
pub fn set_thumbnail<S: Into<String>, B: Into<Vec<u8>>>(
&mut self,
- content_type: S,
+ format: S,
bytes: B,
) -> Result<&mut Self> {
let base_id = self.instance_id().to_string();
- self.thumbnail = Some(
- self.resources
- .add_with(&base_id, &content_type.into(), bytes)?,
- );
+ self.thumbnail = Some(self.resources.add_with(&base_id, &format.into(), bytes)?);
Ok(self)
}
@@ -609,11 +612,6 @@ impl Ingredient {
None => Vec::new(),
};
- let is_parent = match ingredient_assertion.relationship {
- Relationship::ParentOf => Some(true),
- Relationship::ComponentOf => None,
- };
-
let active_manifest = ingredient_assertion
.c2pa_manifest
.and_then(|hash_url| jumbf::labels::manifest_label_from_uri(&hash_url.url()));
@@ -659,7 +657,7 @@ impl Ingredient {
ingredient.set_thumbnail(format, image)?;
}
- ingredient.is_parent = is_parent;
+ ingredient.relationship = ingredient_assertion.relationship;
ingredient.active_manifest = active_manifest;
if !validation_status.is_empty() {
ingredient.validation_status = Some(validation_status)
@@ -752,12 +750,6 @@ impl Ingredient {
None => None,
};
- let relationship = if self.is_parent() {
- Relationship::ParentOf
- } else {
- Relationship::ComponentOf
- };
-
// add ingredient thumbnail assertion if one is given and we don't already have one from the parent claim
if thumbnail.is_none() {
if let Some((format, data)) = self.thumbnail() {
@@ -778,7 +770,7 @@ impl Ingredient {
);
ingredient_assertion.c2pa_manifest = c2pa_manifest;
- ingredient_assertion.relationship = relationship;
+ ingredient_assertion.relationship = self.relationship.clone();
ingredient_assertion.thumbnail = thumbnail;
ingredient_assertion.metadata = self.metadata.clone();
ingredient_assertion.validation_status = self.validation_status.clone();
@@ -1086,7 +1078,8 @@ mod tests_file_io {
let ap = fixture_path("CIE-sig-CA.jpg");
let ingredient = Ingredient::from_file(ap).expect("from_file");
println!("ingredient = {ingredient}");
- assert_eq!(ingredient.validation_status(), None);
+ assert!(ingredient.validation_status().is_none());
+ assert!(ingredient.manifest_data().is_some());
}
#[test]
diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs
@@ -151,12 +151,12 @@ impl Manifest {
self.title.as_deref()
}
- /// Returns thumbnail tuple with Some((content_type, bytes)) or None
+ /// Returns thumbnail tuple with Some((format, bytes)) or None
///
pub fn thumbnail(&self) -> Option<(&str, Cow<Vec<u8>>)> {
self.thumbnail
.as_ref()
- .and_then(|t| Some(t.content_type.as_str()).zip(self.resources.get(&t.identifier).ok()))
+ .and_then(|t| Some(t.format.as_str()).zip(self.resources.get(&t.identifier).ok()))
}
/// Returns a thumbnail ResourceRef or `None`.
@@ -664,9 +664,9 @@ impl Manifest {
}
claim.format = self.format().to_owned();
claim.instance_id = self.instance_id().to_owned();
- if let Some((content_type, data)) = self.thumbnail() {
+ if let Some((format, data)) = self.thumbnail() {
claim.add_assertion(&Thumbnail::new(
- &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, content_type),
+ &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, format),
data.to_vec(),
))?;
}
@@ -1496,8 +1496,8 @@ pub(crate) mod tests {
manifest.embed(&output, &output, &signer).expect("embed");
let manifest_store = crate::ManifestStore::from_file(&output).expect("from_file");
let active_manifest = manifest_store.get_active().unwrap();
- let (content_type, image) = active_manifest.thumbnail().unwrap();
- assert_eq!(content_type, "image/jpeg");
+ let (format, image) = active_manifest.thumbnail().unwrap();
+ assert_eq!(format, "image/jpeg");
assert_eq!(image.into_owned(), thumb_data);
}
@@ -1506,16 +1506,16 @@ pub(crate) mod tests {
"claim_generator": "test",
"format" : "image/jpeg",
"thumbnail": {
- "content_type": "image/jpeg",
+ "format": "image/jpeg",
"identifier": "IMG_0003.jpg"
},
"ingredients": [{
"title": "A.jpg",
"format": "image/jpeg",
"document_id": "xmp.did:813ee422-9736-4cdc-9be6-4e35ed8e41cb",
- "is_parent": true,
+ "relationship": "parentOf",
"thumbnail": {
- "content_type": "image/png",
+ "format": "image/png",
"identifier": "exp-test1.png"
}
}]
@@ -1556,8 +1556,8 @@ pub(crate) mod tests {
let m = manifest_store.get_active().unwrap();
assert!(m.thumbnail().is_some());
- let (content_type, image) = m.thumbnail().unwrap();
- assert_eq!(content_type, "image/jpeg");
+ let (format, image) = m.thumbnail().unwrap();
+ assert_eq!(format, "image/jpeg");
assert_eq!(image.to_vec(), b"my value");
// println!("{manifest_store}");
}
@@ -1602,8 +1602,8 @@ pub(crate) mod tests {
let manifest_store = crate::ManifestStore::from_file(&output).expect("from_file");
println!("{manifest_store}");
let active_manifest = manifest_store.get_active().unwrap();
- let (content_type, _) = active_manifest.thumbnail().unwrap();
- assert_eq!(content_type, "image/jpeg");
+ let (format, _) = active_manifest.thumbnail().unwrap();
+ assert_eq!(format, "image/jpeg");
}
#[test]
diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs
@@ -29,14 +29,14 @@ pub(crate) fn skip_serializing_resources(_: &ResourceStore) -> bool {
/// A reference to a resource to be used in JSON serialization
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct ResourceRef {
- pub content_type: String,
+ pub format: String,
pub identifier: String,
}
impl ResourceRef {
- pub fn new<S: Into<String>, I: Into<String>>(content_type: S, identifier: I) -> Self {
+ pub fn new<S: Into<String>, I: Into<String>>(format: S, identifier: I) -> Self {
Self {
- content_type: content_type.into(),
+ format: format.into(),
identifier: identifier.into(),
}
}
@@ -99,7 +99,7 @@ impl ResourceStore {
R: Into<Vec<u8>>,
{
let id = self.id_from(key, format);
- self.add(id.clone(), value)?;
+ self.add(&id, value)?;
Ok(ResourceRef::new(format, id))
}
@@ -114,7 +114,7 @@ impl ResourceStore {
let path = base.join(id.into());
std::fs::create_dir_all(path.parent().unwrap_or(Path::new("")))?;
#[allow(clippy::expect_used)]
- std::fs::write(path, value.into()).expect("write failed");
+ std::fs::write(path, value.into())?;
return Ok(());
}
self.resources.insert(id.into(), value.into());
@@ -199,7 +199,7 @@ mod tests {
"instance_id": "12345",
"assertions": [],
"thumbnail": {
- "content_type": "image/jpeg",
+ "format": "image/jpeg",
"identifier": "abc123"
},
"ingredients": [{
@@ -207,9 +207,9 @@ mod tests {
"format": "image/jpeg",
"document_id": "xmp.did:813ee422-9736-4cdc-9be6-4e35ed8e41cb",
"instance_id": "xmp.iid:813ee422-9736-4cdc-9be6-4e35ed8e41cb",
- "is_parent": true,
+ "relationship": "parentOf",
"thumbnail": {
- "content_type": "image/jpeg",
+ "format": "image/jpeg",
"identifier": "cba321"
}
}]
diff --git a/sdk/src/utils/thumbnail.rs b/sdk/src/utils/thumbnail.rs
@@ -35,7 +35,7 @@ pub fn make_thumbnail(path: &std::path::Path) -> Result<(String, Vec<u8>)> {
}
// for png files, use png thumbnails if there is an alpha channel
// for other supported types try a jpeg thumbnail
- let (output_format, content_type) = match format {
+ let (output_format, format) = match format {
ImageFormat::Png if img.color().has_alpha() => (image::ImageOutputFormat::Png, "image/png"),
_ => (
image::ImageOutputFormat::Jpeg(THUMBNAIL_JPEG_QUALITY),
@@ -46,7 +46,7 @@ pub fn make_thumbnail(path: &std::path::Path) -> Result<(String, Vec<u8>)> {
let mut cursor = std::io::Cursor::new(thumbnail_bits);
img.write_to(&mut cursor, output_format)?;
- let format = content_type.to_owned();
+ let format = format.to_owned();
Ok((format, cursor.into_inner()))
}
@@ -73,7 +73,7 @@ pub fn make_thumbnail_from_stream(
// for png files, use png thumbnails for transparency
// for other supported types try a jpeg thumbnail
- let (output_format, content_type) = match format {
+ let (output_format, format) = match format {
ImageFormat::Png => (image::ImageOutputFormat::Png, "image/png"),
_ => (
image::ImageOutputFormat::Jpeg(THUMBNAIL_JPEG_QUALITY),
@@ -84,6 +84,6 @@ pub fn make_thumbnail_from_stream(
let mut cursor = std::io::Cursor::new(thumbnail_bits);
img.write_to(&mut cursor, output_format)?;
- let format = content_type.to_owned();
+ let format = format.to_owned();
Ok((format, cursor.into_inner()))
}