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 }