commit 3aa77b991763b4bcc5d0271497f60a39c7149743
parent a181bde72fe37010544681bcca13055a1adbbf8c
Author: Gavin Peacock <gpeacock@adobe.com>
Date: Thu, 2 Jun 2022 16:02:19 -0700
Add documentation for the `Actions` and `Metadata` assertions (#30)
* Actions and metadata fields private
* Refine documentation for Actions and Metadata
* Remove HashedUri reference from doc
Co-authored-by: Eric Scouten <scouten@adobe.com>
Diffstat:
7 files changed, 225 insertions(+), 103 deletions(-)
diff --git a/sdk/examples/client/client.rs b/sdk/examples/client/client.rs
@@ -49,12 +49,7 @@ fn show_manifest(manifest_store: &ManifestStore, manifest_label: &str, level: us
labels::ACTIONS => {
let actions: Actions = assertion.to_assertion()?;
for action in actions.actions {
- println!(
- "{}{:?}, {:?}",
- indent,
- action.label,
- action.parameters.unwrap_or_default()
- );
+ println!("{}{}", indent, action.action());
}
}
labels::CREATIVE_WORK => {
diff --git a/sdk/src/assertions/actions.rs b/sdk/src/assertions/actions.rs
@@ -58,29 +58,55 @@ pub mod c2pa_action {
pub const UNKNOWN: &str = "c2pa.unknown";
}
-/// Defines an action taken on an image
+/// Defines a single action taken on an asset.
+///
+/// An [`Action`] describes what took place on the asset, when it took place,
+/// along with possible other information such as what software performed
+/// the action.
+///
+/// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_actions>.
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub struct Action {
- #[serde(rename = "action")]
- pub label: String,
+ /// The label associated with this action. See ([`c2pa_action`]).
+ action: String,
+
+ /// Timestamp of when the action occurred.
#[serde(skip_serializing_if = "Option::is_none")]
- pub when: Option<String>,
+ when: Option<String>,
+
+ /// The software agent that performed the action.
#[serde(rename = "softwareAgent", skip_serializing_if = "Option::is_none")]
- pub software_agent: Option<String>,
+ software_agent: Option<String>,
+
+ /// A semicolon-delimited list of the parts of the resource that were changed since the previous event history.
+ ///
+ /// If not present, presumed to be undefined.
+ /// When tracking changes and the scope of the changed components is unknown,
+ /// it should be assumed that anything might have changed.
#[serde(skip_serializing_if = "Option::is_none")]
- pub changed: Option<String>,
+ changed: Option<String>,
+
+ /// The value of the `xmpMM:InstanceID` property for the modified (output) resource.
#[serde(rename = "InstanceId", skip_serializing_if = "Option::is_none")]
- pub instance_id: Option<String>,
+ instance_id: Option<String>,
+
+ /// Additional parameters of the action. These vary by the type of action.
#[serde(skip_serializing_if = "Option::is_none")]
- pub parameters: Option<HashMap<String, Value>>,
+ parameters: Option<HashMap<String, Value>>,
+
+ /// An array of the creators that undertook this action.
#[serde(skip_serializing_if = "Option::is_none")]
- pub actors: Option<Vec<Actor>>,
+ actors: Option<Vec<Actor>>,
}
impl Action {
+ /// Create a new action with a specific action label.
+ ///
+ /// This label is often one of the labels defined in [`c2pa_action`],
+ /// but can also be a custom string in reverse-domain format.
pub fn new(label: &str) -> Self {
Self {
- label: label.to_owned(),
+ action: label.to_owned(),
when: None,
software_agent: None,
changed: None,
@@ -90,31 +116,83 @@ impl Action {
}
}
- /// set Timestamp of when the action occurred
+ /// Returns the label for this action.
+ ///
+ /// This label is often one of the labels defined in [`c2pa_action`],
+ /// but can also be a custom string in reverse-domain format.
+ pub fn action(&self) -> &str {
+ &self.action
+ }
+
+ /// Returns the timestamp of when the action occurred.
+ ///
+ /// This string, if present, will be in ISO-8601 date format.
+ pub fn when(&self) -> Option<&str> {
+ self.when.as_deref()
+ }
+
+ /// Returns the software agent that performed the action.
+ pub fn software_agent(&self) -> Option<&str> {
+ self.software_agent.as_deref()
+ }
+
+ /// Returns the value of the `xmpMM:InstanceID` property for the modified
+ /// (output) resource.
+ pub fn instance_id(&self) -> Option<&str> {
+ self.instance_id.as_deref()
+ }
+
+ /// Returns the additional parameters for this action.
+ ///
+ /// These vary by the type of action.
+ pub fn parameters(&self) -> Option<&HashMap<String, Value>> {
+ self.parameters.as_ref()
+ }
+
+ /// Returns an individual action parameter if it exists.
+ pub fn get_parameter(&self, key: &str) -> Option<&Value> {
+ match self.parameters.as_ref() {
+ Some(parameters) => parameters.get(key),
+ None => None,
+ }
+ }
+
+ /// An array of the [`Actor`]s that undertook this action.
+ pub fn actors(&self) -> Option<&[Actor]> {
+ self.actors.as_deref()
+ }
+
+ /// Sets the timestamp for when the action occurred.
+ ///
+ /// This timestamp must be in ISO-8601 date.
pub fn set_when(mut self, when: &str) -> Self {
self.when = Some(when.to_owned());
self
}
- /// Set the software agent that performed the action.
+ /// Sets the software agent that performed the action.
pub fn set_software_agent(mut self, software_agent: &str) -> Self {
self.software_agent = Some(software_agent.to_owned());
self
}
- /// Set a list of the parts of the resource that were changed since the previous event history.
+ /// Sets the list of the parts of the resource that were changed
+ /// since the previous event history.
pub fn set_changed(mut self, changed: Option<&Vec<&str>>) -> Self {
self.changed = changed.map(|v| v.join(";"));
self
}
- /// The value of the xmpMM:InstanceID property for the modified (output) resource
+ /// Sets the value of the `xmpMM:InstanceID` property for the
+ /// modified (output) resource.
pub fn set_instance_id(mut self, id: &str) -> Self {
self.instance_id = Some(id.to_owned());
self
}
- /// Set additional parameters of the action. These will often vary by the type of action
+ /// Sets the additional parameters for this action.
+ ///
+ /// These vary by the type of action.
pub fn set_parameter<T: Serialize>(mut self, key: String, value: T) -> Result<Self> {
let value = serde_json::to_value(value).map_err(|_| Error::AssertionEncoding)?;
self.parameters = Some(match self.parameters {
@@ -131,28 +209,38 @@ impl Action {
Ok(self)
}
- /// An array of the creators that undertook this action
+ /// Sets the array of [`Actor`]s that undertook this action.
pub fn set_actors(mut self, actors: Option<&Vec<Actor>>) -> Self {
self.actors = actors.cloned();
self
}
}
-/// A list of actions as an assertion
+/// An `Actions` assertion provides information on edits and other
+/// actions taken that affect the asset’s content.
+///
+/// This assertion contains a list of [`Action`]s, each one declaring
+/// what took place on the asset, when it took place, along with possible
+/// other information such as what software performed the action.
+///
+/// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_actions>.
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub struct Actions {
+ /// A list of [`Action`]s.
pub actions: Vec<Action>,
+
+ /// Additional information about the assertion.
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
}
impl Actions {
- /// Label prefix for an actions assertion.
+ /// Label prefix for an [`Actions`] assertion.
///
/// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_actions>.
pub const LABEL: &'static str = labels::ACTIONS;
- /// creates a new Actions object
+ /// Creates a new [`Actions`] assertion struct.
pub fn new() -> Self {
Self {
actions: Vec::new(),
@@ -160,19 +248,29 @@ impl Actions {
}
}
- /// Adds an action
+ /// Returns the list of [`Action`]s.
+ pub fn actions(&self) -> &[Action] {
+ &self.actions
+ }
+
+ /// Returns the assertion's [`Metadata`], if it exists.
+ pub fn metadata(&self) -> Option<&Metadata> {
+ self.metadata.as_ref()
+ }
+
+ /// Adds an [`Action`] to this assertion's list of actions.
pub fn add_action(&mut self, action: Action) -> &mut Self {
self.actions.push(action);
self
}
- /// Adds a metadata structure to the action
- pub fn add_metadata(&mut self, metadata: Metadata) -> &Self {
+ /// Sets [`Metadata`] for the action.
+ pub fn add_metadata(&mut self, metadata: Metadata) -> &mut Self {
self.metadata = Some(metadata);
self
}
- /// creates an actions assertion from a compatible JSON Value
+ /// Creates an [`Actions`] assertion from a compatible JSON value.
pub fn from_json_value(json: &serde_json::Value) -> Result<Self> {
let actions: Actions = serde_json::from_value(json.clone())?;
Ok(actions)
@@ -208,7 +306,7 @@ pub mod tests {
use super::*;
use crate::assertion::{Assertion, AssertionData};
- use crate::assertions::metadata::{DataSource, ReviewRating, C2PA_SOURCE_GENERATOR_REE};
+ use crate::assertions::metadata::{c2pa_source::GENERATOR_REE, DataSource, ReviewRating};
use crate::hashed_uri::HashedUri;
fn make_hashed_uri1() -> HashedUri {
@@ -261,8 +359,8 @@ pub mod tests {
.add_metadata(
Metadata::new()
.add_review(ReviewRating::new("foo", Some("bar".to_owned()), 3))
- .set_reference(Some(make_hashed_uri1()))
- .set_data_source(Some(DataSource::new(C2PA_SOURCE_GENERATOR_REE))),
+ .set_reference(make_hashed_uri1())
+ .set_data_source(DataSource::new(GENERATOR_REE)),
);
dbg!(&original);
@@ -273,20 +371,20 @@ pub mod tests {
let result = Actions::from_assertion(&assertion).expect("extract_assertion");
assert_eq!(result.actions.len(), 2);
- assert_eq!(result.actions[0].label, original.actions[0].label);
+ assert_eq!(result.actions[0].action(), original.actions[0].action());
assert_eq!(
- result.actions[0].parameters.as_ref().unwrap().get("name"),
- original.actions[0].parameters.as_ref().unwrap().get("name")
+ result.actions[0].parameters().unwrap().get("name"),
+ original.actions[0].parameters().unwrap().get("name")
);
- assert_eq!(result.actions[1].label, original.actions[1].label);
+ assert_eq!(result.actions[1].action(), original.actions[1].action());
assert_eq!(
result.actions[1].parameters.as_ref().unwrap().get("name"),
original.actions[1].parameters.as_ref().unwrap().get("name")
);
- assert_eq!(result.actions[1].when, original.actions[1].when);
+ assert_eq!(result.actions[1].when(), original.actions[1].when());
assert_eq!(
- result.metadata.unwrap().date_time,
- original.metadata.unwrap().date_time
+ result.metadata.unwrap().date_time(),
+ original.metadata.unwrap().date_time()
);
}
diff --git a/sdk/src/assertions/data_hash.rs b/sdk/src/assertions/data_hash.rs
@@ -193,6 +193,7 @@ impl DataHash {
}
/// Used to verify a DataHash against an asset.
+ #[allow(dead_code)] // used in tests
pub fn verify_hash(&self, asset_path: &Path) -> Result<()> {
let buf = fs::read(asset_path).map_err(wrap_io_err)?;
self.verify_in_memory_hash(&buf, self.alg.clone())
diff --git a/sdk/src/assertions/ingredient.rs b/sdk/src/assertions/ingredient.rs
@@ -102,22 +102,14 @@ impl Ingredient {
}
pub fn add_review(mut self, review: ReviewRating) -> Self {
- if self.metadata.is_none() {
- self.metadata = Some(Metadata::new())
- }
- if let Some(metadata) = &mut self.metadata {
- match &mut metadata.reviews {
- None => metadata.reviews = Some(vec![review]),
- Some(reviews) => reviews.push(review),
- }
- }
+ let metadata = self.metadata.unwrap_or_else(Metadata::new);
+ self.metadata = Some(metadata.add_review(review));
self
}
pub fn add_reviews(mut self, reviews: Option<Vec<ReviewRating>>) -> Self {
if let Some(reviews) = reviews {
- let mut metadata = Metadata::new();
- metadata.reviews = Some(reviews);
+ let metadata = Metadata::new().set_reviews(reviews);
self.metadata = Some(metadata);
};
self
@@ -263,13 +255,13 @@ pub mod tests {
assert!(restored.metadata.is_some());
let metadata = restored.metadata.unwrap();
- let date_time = metadata.date_time.unwrap();
- let date_time_parsed = chrono::DateTime::parse_from_rfc3339(&date_time);
+ let date_time = metadata.date_time().unwrap();
+ let date_time_parsed = chrono::DateTime::parse_from_rfc3339(date_time);
- assert!(metadata.reviews.is_some());
+ assert!(metadata.reviews().is_some());
assert!(date_time_parsed.is_ok());
- let reviews = metadata.reviews.unwrap();
+ let reviews = metadata.reviews().unwrap();
assert_eq!(reviews.len(), 1);
assert_eq!(
diff --git a/sdk/src/assertions/metadata.rs b/sdk/src/assertions/metadata.rs
@@ -29,13 +29,13 @@ const ASSERTION_CREATION_VERSION: usize = 1;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Metadata {
#[serde(rename = "reviewRatings", skip_serializing_if = "Option::is_none")]
- pub reviews: Option<Vec<ReviewRating>>,
+ reviews: Option<Vec<ReviewRating>>,
#[serde(rename = "dateTime", skip_serializing_if = "Option::is_none")]
- pub date_time: Option<String>,
+ date_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub reference: Option<HashedUri>,
+ reference: Option<HashedUri>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub data_source: Option<DataSource>,
+ data_source: Option<DataSource>,
#[serde(flatten)]
other: HashMap<String, Value>,
}
@@ -56,7 +56,22 @@ impl Metadata {
}
}
- /// add a review rating associated with the assertion
+ /// Returns the list of [`ReviewRating`] for this assertion if it exists.
+ pub fn reviews(&self) -> Option<&[ReviewRating]> {
+ self.reviews.as_deref()
+ }
+
+ /// Returns the ISO 8601 date-time string when the assertion was created/generated.
+ pub fn date_time(&self) -> Option<&str> {
+ self.date_time.as_deref()
+ }
+
+ /// Returns the [`DataSource`] for this assertion if it exists.
+ pub fn data_source(&self) -> Option<&DataSource> {
+ self.data_source.as_ref()
+ }
+
+ /// Adds a [`ReviewRating`] associated with the assertion.
pub fn add_review(mut self, review: ReviewRating) -> Self {
match &mut self.reviews {
None => self.reviews = Some(vec![review]),
@@ -65,33 +80,42 @@ impl Metadata {
self
}
- /// Set review ratings associated with the assertion
- pub fn set_reviews(mut self, reviews: Option<Vec<ReviewRating>>) -> Self {
- self.reviews = reviews;
+ /// Sets the list of [`ReviewRating`]s associated with the assertion.
+ ///
+ /// This replaces any previous list.
+ pub fn set_reviews(mut self, reviews: Vec<ReviewRating>) -> Self {
+ self.reviews = Some(reviews);
self
}
- /// Set hashed_uri reference to another assertion to which this metadata applies
- pub fn set_reference(mut self, reference: Option<HashedUri>) -> Self {
- self.reference = reference;
+ /// Sets the ISO 8601 date-time string when the assertion was created/generated.
+ pub fn set_date_time(&mut self, date_time: String) -> &mut Self {
+ self.date_time = Some(date_time);
self
}
- /// set a description of the source of the assertion data, selected from a predefined list
- pub fn set_data_source(mut self, data_source: Option<DataSource>) -> Self {
- self.data_source = data_source;
+ /// Sets a [`HashedUri`] reference to another assertion to which this metadata applies.
+ #[cfg(test)] // only referenced from test code
+ pub(crate) fn set_reference(mut self, reference: HashedUri) -> Self {
+ self.reference = Some(reference);
self
}
- /// add additional key / value pair
- pub fn insert(&mut self, key: &str, value: &Value) -> &mut Self {
- self.other.insert(key.to_string(), value.clone());
+ /// Sets a description of the source of the assertion data, selected from a predefined list.
+ pub fn set_data_source(mut self, data_source: DataSource) -> Self {
+ self.data_source = Some(data_source);
self
}
- /// get additional values by key
- pub fn get(self, key: &str) -> Option<Value> {
- self.other.get(key).cloned()
+ /// Adds an additional key / value pair.
+ pub fn insert(&mut self, key: &str, value: Value) -> &mut Self {
+ self.other.insert(key.to_string(), value);
+ self
+ }
+
+ /// Gets additional values by key.
+ pub fn get(&self, key: &str) -> Option<&Value> {
+ self.other.get(key)
}
}
@@ -117,25 +141,32 @@ impl AssertionBase for Metadata {
}
/// DATA_SOURCE Type values
-pub const C2PA_SOURCE_SIGNER: &str = "signer";
-pub const C2PA_SOURCE_GENERATOR_REE: &str = "claimGenerator.REE";
-pub const C2PA_SOURCE_GENERATOR_TEE: &str = "claimGenerator.TEE";
-pub const C2PA_SOURCE_LOCAL_REE: &str = "localProvider.REE";
-pub const C2PA_SOURCE_LOCAL_TEE: &str = "localProvider.TEE";
-pub const C2PA_SOURCE_REMOTE_REE: &str = "remoteProvider.1stParty";
-pub const C2PA_SOURCE_REMOTE_TEE: &str = "remoteProvider.3rdParty";
-pub const C2PA_SOURCE_HUMAN_ANONYMOUS: &str = "humanEntry.anonymous";
-pub const C2PA_SOURCE_HUMAN_IDENTIFIED: &str = "humanEntry.identified";
+pub mod c2pa_source {
+ pub const SIGNER: &str = "signer";
+ pub const GENERATOR_REE: &str = "claimGenerator.REE";
+ pub const GENERATOR_TEE: &str = "claimGenerator.TEE";
+ pub const LOCAL_REE: &str = "localProvider.REE";
+ pub const LOCAL_TEE: &str = "localProvider.TEE";
+ pub const REMOTE_REE: &str = "remoteProvider.1stParty";
+ pub const REMOTE_TEE: &str = "remoteProvider.3rdParty";
+ pub const HUMAN_ANONYMOUS: &str = "humanEntry.anonymous";
+ pub const HUMAN_IDENTIFIED: &str = "humanEntry.identified";
+}
/// A description of the source for assertion data
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct DataSource {
+ /// A value from among the enumerated list indicating the source of the assertion.
#[serde(rename = "type")]
- pub source_type: String, // A value from among the enumerated list indicating the source of the assertion
+ pub source_type: String,
+
+ /// A human-readable string giving details about the source of the assertion data.
#[serde(skip_serializing_if = "Option::is_none")]
- pub details: Option<String>, // A human readable string giving details about the source of the assertion data
+ pub details: Option<String>,
+
+ /// A list of [`Actor`]s associated with this source.
#[serde(skip_serializing_if = "Option::is_none")]
- pub actors: Option<Vec<Actor>>, // array of hashed_uri references to W3C Verifiable Credentials
+ pub actors: Option<Vec<Actor>>,
}
impl DataSource {
@@ -147,25 +178,29 @@ impl DataSource {
}
}
- /// Set a human readable string giving details about the source of the assertion data
- pub fn set_details(mut self, details: Option<&str>) -> Self {
- self.details = details.map(|s| s.to_owned());
+ /// Sets a human-readable string giving details about the source of the assertion data.
+ pub fn set_details(mut self, details: String) -> Self {
+ self.details = Some(details);
self
}
- /// Set list of actors associated with this source
- pub fn set_actors(mut self, actors: Option<&Vec<Actor>>) -> Self {
- self.actors = actors.cloned();
+ /// Sets a list of [`Actor`]s associated with this source.
+ pub fn set_actors(mut self, actors: Option<Vec<Actor>>) -> Self {
+ self.actors = actors;
self
}
}
-/// identifies a person responsible for an action
+
+/// Identifies a person responsible for an action.
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Actor {
+ /// An identifier for a human actor, used when the "type" is `humanEntry.identified`.
#[serde(skip_serializing_if = "Option::is_none")]
- pub identifier: Option<String>, // An identifier for a human actor, used when the "type" is humanEntry.identified
+ pub identifier: Option<String>,
+
+ /// List of references to W3C Verifiable Credentials.
#[serde(skip_serializing_if = "Option::is_none")]
- pub credentials: Option<Vec<HashedUri>>, // array of hashed_uri references to W3C Verifiable Credentials
+ pub credentials: Option<Vec<HashedUri>>,
}
impl Actor {
@@ -202,7 +237,9 @@ pub enum ReviewCode {
Other(String),
}
-/// A rating on an assertion
+/// A rating on an [`Assertion`].
+///
+/// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_claim_review>.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ReviewRating {
pub explanation: String,
@@ -233,7 +270,7 @@ pub mod tests {
let review = ReviewRating::new("foo", Some("bar".to_owned()), 3);
let test_value = Value::from("test");
let mut original = Metadata::new().add_review(review);
- original.insert("foo", &test_value);
+ original.insert("foo", test_value);
println!("{:?}", &original);
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.mime_type(), "application/cbor");
@@ -242,7 +279,7 @@ pub mod tests {
println!("{:?}", serde_json::to_string(&result));
assert_eq!(original.date_time, result.date_time);
assert_eq!(original.reviews, result.reviews);
- assert_eq!(original.get("foo").unwrap(), "test".to_string());
+ assert_eq!(original.get("foo").unwrap(), "test");
//assert_eq!(original.reviews.unwrap().len(), 1);
}
}
diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs
@@ -19,7 +19,7 @@
//! This library supports reading, creating and embedding C2PA data
//! with JPEG and PNG images.
//!
-//! # Example: Reading a `ManifestStore`
+//! # Example: Reading a ManifestStore
//!
//! ```
//! # use c2pa::Result;
@@ -32,14 +32,14 @@
//! if let Some(manifest) = manifest_store.get_active() {
//! let actions: Actions = manifest.find_assertion(Actions::LABEL)?;
//! for action in actions.actions {
-//! println!("{}\n", action.label);
+//! println!("{}\n", action.action());
//! }
//! }
//! # Ok(())
//! # }
//! ```
//!
-//! # Example: Adding a `Manifest` to a file
+//! # Example: Adding a Manifest to a file
//!
//! ```
//! # use c2pa::Result;
diff --git a/sdk/src/utils/hash_utils.rs b/sdk/src/utils/hash_utils.rs
@@ -37,6 +37,7 @@ impl Exclusion {
}
/// update the start value
+ #[allow(dead_code)]
pub fn set_start(&mut self, start: usize) {
self.start = start;
}
@@ -199,7 +200,6 @@ pub fn hash256(data: &[u8]) -> String {
/// Verify muiltihash against input data. True if match,
/// false if no match or unsupported. The hash value should be
/// be multibase encoded string.
-#[allow(dead_code)]
pub fn verify_hash(hash: &str, data: &[u8]) -> bool {
match decode(hash) {
Ok((_code, mh)) => {
@@ -223,7 +223,6 @@ pub fn verify_hash(hash: &str, data: &[u8]) -> bool {
}
/// Return the hash of data in the same hash format in_hash
-#[allow(dead_code)]
pub fn hash_as_source(in_hash: &str, data: &[u8]) -> Option<String> {
match decode(in_hash) {
Ok((code, mh)) => {