1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; //, Deserializer, Serializer};
use serde_json::Value;
use crate::{
assertion::{AssertionBase, AssertionDecodeError},
error::{Error, Result},
};
/// Assertions in C2PA can be stored in several formats
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum ManifestAssertionKind {
Cbor,
Json,
Binary,
Uri,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
#[serde(untagged)]
enum ManifestData {
Json(Value), // { label: String, instance: usize, data: Value },
Binary(Vec<u8>), // ) { label: String, instance: usize, data: Value },
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
/// A labeled container for an Assertion value in a Manifest
pub struct ManifestAssertion {
/// An assertion label in reverse domain format
label: String,
/// The data of the assertion as Value
data: ManifestData,
/// There can be more than one assertion for any label
#[serde(skip_serializing_if = "Option::is_none")]
instance: Option<usize>,
/// The [ManifestAssertionKind] for this assertion (as stored in c2pa content)
#[serde(skip_serializing_if = "Option::is_none")]
kind: Option<ManifestAssertionKind>,
}
impl ManifestAssertion {
/// Create with label and value
pub fn new(label: String, data: Value) -> Self {
Self {
label,
data: ManifestData::Json(data),
instance: None,
kind: None,
}
}
/// An assertion label in reverse domain format
pub fn label(&self) -> &str {
&self.label
}
/// An assertion label in reverse domain format with appended instance number
/// The instance number follows two underscores and is only added when the instance is > 1
/// This is a c2pa spec internal standard format
pub fn label_with_instance(&self) -> String {
match self.instance {
Some(i) if i > 1 => format!("{}__{}", self.label, i),
_ => self.label.to_owned(),
}
}
/// The data of the assertion as a serde_Json::Value
/// This will return UnsupportedType if the assertion data is binary
pub fn value(&self) -> Result<&Value> {
match &self.data {
ManifestData::Json(d) => Ok(d),
ManifestData::Binary(_) => Err(Error::UnsupportedType),
}
}
/// The data of the assertion as u8 binary vector
/// This will return UnsupportedType if the assertion data is Json/String
pub fn binary(&self) -> Result<&[u8]> {
match &self.data {
ManifestData::Json(_) => Err(Error::UnsupportedType),
ManifestData::Binary(b) => Ok(b),
}
}
/// The instance number of this assertion
/// If the same label is used for multiple assertions, incremental instances are added
/// The first instance is always 1 and increased by 1 per duplicated label
pub fn instance(&self) -> usize {
self.instance.unwrap_or(1)
}
/// The ManifestAssertionKind for this assertion
/// This refers to how the format of the assertion inside a C2PA manifest
/// The default is ManifestAssertionKind::Cbor
pub fn kind(&self) -> &ManifestAssertionKind {
match self.kind.as_ref() {
Some(kind) => kind,
None => &ManifestAssertionKind::Cbor,
}
}
/// This can be used to set an instance number, but generally should not be used
/// Instance numbers will be assigned automatically when the assertions are embedded
pub(crate) fn set_instance(mut self, instance: usize) -> Self {
self.instance = if instance > 0 { Some(instance) } else { None };
self
}
/// Allows overriding the default [ManifestAssertionKind] to Json
/// For assertions like Schema.org that require being stored in Json format
pub fn set_kind(mut self, kind: ManifestAssertionKind) -> Self {
self.kind = Some(kind);
self
}
/// Creates a ManifestAssertion with the given label and any serde serializable object
///
/// # Example: Creating a custom assertion from a serde_json object.
///
///```
/// # use c2pa::Result;
/// use c2pa::ManifestAssertion;
/// use serde_json::json;
/// # fn main() -> Result<()> {
/// let value = json!({"my_tag": "Anything I want"});
/// let _ma = ManifestAssertion::from_labeled_assertion("org.contentauth.foo", &value)?;
/// # Ok(())
/// # }
/// ```
pub fn from_labeled_assertion<S: Into<String>, T: Serialize>(
label: S,
data: &T,
) -> Result<Self> {
Ok(Self::new(
label.into(),
serde_json::to_value(data).map_err(|_err| Error::AssertionEncoding)?,
))
}
/// Creates a ManifestAssertion from an AssertionBase object
///
/// # Example: Creating a custom assertion an Action assertion
///
///```
/// # use c2pa::Result;
/// use c2pa::{
/// assertions::{c2pa_action, Action, Actions},
/// ManifestAssertion,
/// };
/// # fn main() -> Result<()> {
/// let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
/// let _ma = ManifestAssertion::from_labeled_assertion(Actions::LABEL, &actions)?;
/// # Ok(())
/// # }
/// ```
pub fn from_assertion<T: Serialize + AssertionBase>(data: &T) -> Result<Self> {
Ok(Self::new(
data.label().to_owned(),
serde_json::to_value(data).map_err(|_err| Error::AssertionEncoding)?,
))
}
/// Creates an Assertion object from a ManifestAssertion
///
/// # Example: extracting an Actions Assertion
/// ```
/// # use c2pa::Result;
/// use c2pa::{
/// assertions::{c2pa_action, Action, Actions},
/// ManifestAssertion,
/// };
/// # fn main() -> Result<()> {
/// let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
/// let manifest_assertion = ManifestAssertion::from_labeled_assertion(Actions::LABEL, &actions)?;
///
/// let actions: Actions = manifest_assertion.to_assertion()?;
/// for action in actions.actions {
/// println!("{}", action.action());
/// }
/// # Ok(())
/// # }
/// ```
pub fn to_assertion<T: DeserializeOwned>(&self) -> Result<T> {
serde_json::from_value(self.value()?.to_owned()).map_err(|e| {
Error::AssertionDecoding(AssertionDecodeError::from_json_err(
self.label.to_owned(),
None,
"application/json".to_owned(),
e,
))
})
}
}
#[cfg(test)]
pub(crate) mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use super::*;
use crate::assertions::{c2pa_action, Action, Actions};
#[test]
fn test_from_labeled() {
let data = serde_json::json!({"mytag": "mydata"});
let ma = ManifestAssertion::from_labeled_assertion("org.contentauth.foo", &data)
.expect("from_labeled_assertion");
assert_eq!(ma.label(), "org.contentauth.foo");
assert!(ma.value().is_ok());
}
#[test]
fn test_manifest_assertion() {
let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
let value = serde_json::to_value(actions).unwrap();
let mut ma = ManifestAssertion::new(Actions::LABEL.to_owned(), value);
assert_eq!(ma.label(), Actions::LABEL);
ma = ma.set_instance(0);
assert_eq!(ma.instance, None);
ma = ma.set_instance(1);
assert_eq!(ma.instance(), 1);
ma = ma.set_instance(2);
assert_eq!(ma.instance(), 2);
assert_eq!(ma.kind(), &ManifestAssertionKind::Cbor);
ma = ma.set_kind(ManifestAssertionKind::Json);
assert_eq!(ma.kind(), &ManifestAssertionKind::Json);
let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
let ma2 = ManifestAssertion::from_assertion(&actions).expect("from_assertion");
let actions2: Actions = ma2.to_assertion().expect("to_assertion");
let actions3 = ManifestAssertion::from_labeled_assertion("foo".to_owned(), &actions2)
.expect("from_labeled_assertion");
assert_eq!(actions3.label(), "foo");
}
}