c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

manifest_assertion.rs (8586B)


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