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

creative_work.rs (7889B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::ops::Deref;
     15 
     16 use serde::{de::DeserializeOwned, Deserialize, Serialize};
     17 use serde_json::json;
     18 
     19 use crate::{
     20     assertion::{Assertion, AssertionBase, AssertionJson},
     21     assertions::{labels, SchemaDotOrg, SchemaDotOrgPerson},
     22     error::Result,
     23 };
     24 
     25 const ASSERTION_CREATION_VERSION: usize = 1;
     26 const CW_AUTHOR: &str = "author";
     27 
     28 #[derive(Serialize, Deserialize, Debug)]
     29 pub struct CreativeWork(SchemaDotOrg);
     30 
     31 impl CreativeWork {
     32     /// Label prefix for a creative work assertion.
     33     ///
     34     /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_creative_work>.
     35     pub const LABEL: &'static str = labels::CREATIVE_WORK;
     36 
     37     pub fn new() -> CreativeWork {
     38         Self(
     39             SchemaDotOrg::new("CreativeWork".to_owned()).set_context(json!("http://schema.org/")),
     40             // todo: this should reflect the c2pa extensions in some way to be correct
     41             //.set_context(json!(["http://schema.org/",{"credential": {"@id": "c2pa:Credential"},"alg": {"@id": "c2pa:Alg"},"hash": {"@id": "c2pa:hash"}}]))
     42         )
     43     }
     44 
     45     /// get values by key
     46     pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
     47         self.0.get(key)
     48     }
     49 
     50     /// insert key / value pair
     51     pub fn insert<S: Into<String>, T: Serialize>(self, key: S, value: T) -> Result<Self> {
     52         self.0.insert(key.into(), value).map(Self)
     53     }
     54 
     55     /// get creative work from json string
     56     pub fn from_json_str(json: &str) -> Result<Self> {
     57         SchemaDotOrg::from_json_str(json).map(Self)
     58     }
     59 
     60     // get author field if it exists
     61     pub fn author(&self) -> Option<Vec<SchemaDotOrgPerson>> {
     62         self.get(CW_AUTHOR)
     63     }
     64 
     65     pub fn set_author(self, author: &[SchemaDotOrgPerson]) -> Result<Self> {
     66         self.insert(CW_AUTHOR.to_owned(), author)
     67     }
     68 
     69     pub fn add_author(self, author: SchemaDotOrgPerson) -> Result<Self> {
     70         let mut v = self.author().unwrap_or_default();
     71         v.push(author);
     72         self.insert(CW_AUTHOR.to_owned(), &v)
     73     }
     74 }
     75 
     76 impl Default for CreativeWork {
     77     fn default() -> Self {
     78         Self::new()
     79     }
     80 }
     81 
     82 impl Deref for CreativeWork {
     83     type Target = SchemaDotOrg;
     84 
     85     fn deref(&self) -> &Self::Target {
     86         &self.0
     87     }
     88 }
     89 
     90 impl AssertionJson for CreativeWork {}
     91 
     92 impl AssertionBase for CreativeWork {
     93     const LABEL: &'static str = Self::LABEL;
     94     const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
     95 
     96     fn to_assertion(&self) -> Result<Assertion> {
     97         Self::to_json_assertion(self)
     98     }
     99 
    100     fn from_assertion(assertion: &Assertion) -> Result<Self> {
    101         Self::from_json_assertion(assertion)
    102     }
    103 }
    104 
    105 #[cfg(test)]
    106 pub mod tests {
    107     #![allow(clippy::expect_used)]
    108     #![allow(clippy::unwrap_used)]
    109 
    110     use super::*;
    111     use crate::hashed_uri::HashedUri;
    112 
    113     const USER: &str = "Joe Bloggs";
    114     const USER_ID: &str = "1234567890";
    115     const IDENTITY_URI: &str = "https://some_identity/service/";
    116 
    117     // example CreativeWork from
    118     // https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_claim_review
    119     const SAMPLE_CREATIVE_WORK: &str = r#"{
    120         "@context": [
    121           "http://schema.org/",
    122           {
    123             "credential": null
    124           }
    125         ],
    126         "@type": "CreativeWork",
    127         "datePublished": "2021-05-20T23:02:36+00:00",
    128         "publisher": {
    129           "name": "BBC News",
    130           "publishingPrinciples": "https://www.bbc.co.uk/news/help-41670342",
    131           "logo": "https://m.files.bbci.co.uk/modules/bbc-morph-news-waf-page-meta/5.1.0/bbc_news_logo.png",
    132           "parentOrganization": {
    133             "name": "BBC",
    134             "legalName": "British Broadcasting Corporation"
    135           }
    136         },
    137         "url": "https://www.bbc.co.uk/news/av/world-europe-57194011",
    138         "identifier": "p09j7vzv",
    139         "producer": {
    140           "identifier": "https://en.wikipedia.org/wiki/Joe_Bloggs",
    141           "name": "Joe Bloggs",
    142           "credential": [
    143             {
    144               "url": "self#jumbf=c2pa/urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.credentials/Joe_Bloggs",
    145               "alg": "sha256",
    146               "hash": "Auxjtmax46cC2N3Y9aFmBO9Jfay8LEwJWzBUtZ0sUM8gA"
    147             }
    148           ]
    149         },
    150         "copyrightHolder": {
    151           "name": "BBC",
    152           "legalName": "British Broadcasting Corporation"
    153         },
    154         "copyrightYear": 2021,
    155         "copyrightNotice": "Copyright © 2021 BBC."
    156       }"#;
    157 
    158     const STOCK_CREATIVE_WORK: &str = r#"{"@type":"CreativeWork","@context":"https://schema.org","url":"https://stock.adobe.com/295991044"}"#;
    159 
    160     #[test]
    161     fn assertion_creative_work() {
    162         let uri = HashedUri::new(USER_ID.to_string(), None, b"abcde");
    163         let cw_person = SchemaDotOrgPerson::new()
    164             .set_name(USER.to_owned())
    165             .unwrap()
    166             .set_identifier(IDENTITY_URI.to_owned())
    167             .unwrap()
    168             .insert(
    169                 "@id".to_owned(),
    170                 ["https://www.twitter.com/joebloggs".to_owned()].to_vec(),
    171             )
    172             .unwrap()
    173             .add_credential(uri)
    174             .unwrap();
    175         let original = CreativeWork::new()
    176             .add_author(cw_person.clone())
    177             .expect("add_author")
    178             // example of adding a different kind of person field
    179             .insert("creator".to_owned(), cw_person)
    180             .expect("insert");
    181         let assertion = original.to_assertion().expect("build_assertion");
    182         assert_eq!(assertion.mime_type(), "application/json");
    183         assert_eq!(assertion.label(), CreativeWork::LABEL);
    184         let result = CreativeWork::from_assertion(&assertion).expect("extract_assertion");
    185         assert_eq!(
    186             original.author().unwrap()[0].name(),
    187             result.author().unwrap()[0].name()
    188         );
    189     }
    190 
    191     #[test]
    192     fn from_creative_work_sample() {
    193         let original = CreativeWork::from_json_str(SAMPLE_CREATIVE_WORK).expect("from_json_str");
    194         let original_publisher: SchemaDotOrgPerson = original.get("publisher").unwrap();
    195         let assertion = original.to_assertion().expect("build_assertion");
    196         assert_eq!(assertion.mime_type(), "application/json");
    197         assert_eq!(assertion.label(), CreativeWork::LABEL);
    198         let result = CreativeWork::from_assertion(&assertion).expect("extract_assertion");
    199         assert_eq!(original.object_type(), result.object_type());
    200         let result_publisher: SchemaDotOrgPerson = result.get("publisher").unwrap();
    201         assert_eq!(result_publisher.name().unwrap(), "BBC News");
    202         assert_eq!(original_publisher.name(), result_publisher.name());
    203     }
    204 
    205     #[test]
    206     fn from_creative_work_stock() {
    207         let original = CreativeWork::from_json_str(STOCK_CREATIVE_WORK).expect("from_json_str");
    208         let original_url: String = original.get("url").unwrap();
    209         let assertion = original.to_assertion().expect("build_assertion");
    210         assert_eq!(assertion.mime_type(), "application/json");
    211         assert_eq!(assertion.label(), CreativeWork::LABEL);
    212         let result = CreativeWork::from_assertion(&assertion).expect("extract_assertion");
    213         assert_eq!(original.object_type(), result.object_type());
    214         let result_url: String = result.get("url").unwrap();
    215         assert_eq!(original_url, result_url);
    216     }
    217 }