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

schema_org.rs (8914B)


      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::collections::HashMap;
     15 
     16 use serde::{de::DeserializeOwned, Deserialize, Serialize};
     17 use serde_json::{json, Value};
     18 
     19 use crate::{
     20     assertion::{Assertion, AssertionBase, AssertionJson},
     21     assertions::labels,
     22     error::{Error, Result},
     23     hashed_uri::HashedUri,
     24 };
     25 
     26 const ASSERTION_CREATION_VERSION: usize = 1;
     27 
     28 #[derive(Serialize, Deserialize, Clone, Debug)]
     29 pub struct SchemaDotOrg {
     30     #[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
     31     object_context: Option<Value>,
     32     #[serde(rename = "@type", default = "default_type")]
     33     object_type: String,
     34     #[serde(flatten)]
     35     value: HashMap<String, Value>,
     36 }
     37 
     38 // used to set the default @type if it is missing
     39 fn default_type() -> String {
     40     "Thing".to_string()
     41 }
     42 
     43 impl SchemaDotOrg {
     44     /// constructs an empty Schema.org object of the specified @type with @context
     45     pub fn new(object_type: String) -> Self {
     46         Self {
     47             object_context: None,
     48             object_type,
     49             value: HashMap::new(),
     50         }
     51     }
     52 
     53     /// sets the @context field for Schema dot org.
     54     pub fn set_default_context(mut self) -> Self {
     55         self.object_context = Some(json!("https://schema.org"));
     56         self
     57     }
     58 
     59     /// sets the @context field for Schema dot org.
     60     pub fn set_context(mut self, context: Value) -> Self {
     61         self.object_context = Some(context);
     62         self
     63     }
     64 
     65     /// return the @type value from the object
     66     pub fn object_type(&self) -> &str {
     67         self.object_type.as_str()
     68     }
     69 
     70     /// get values by key as an instance of type `T`.
     71     /// This return T is owned, not a reference
     72     /// # Errors
     73     ///
     74     /// This conversion can fail if the structure of the field at key does not match the
     75     /// structure expected by `T`
     76     pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
     77         self.value
     78             .get(key)
     79             .and_then(|v| serde_json::from_value(v.clone()).ok())
     80     }
     81 
     82     /// insert key / value pair of instance of type `T`
     83     /// # Errors
     84     ///
     85     /// This conversion can fail if `T`'s implementation of `Serialize` decides to
     86     /// fail, or if `T` contains a map with non-string keys.
     87     pub fn insert<T: Serialize>(mut self, key: String, value: T) -> Result<Self> {
     88         self.value.insert(key, serde_json::to_value(value)?);
     89         Ok(self)
     90     }
     91 
     92     // add a value to a Vec stored at key
     93     pub fn insert_push<T: Serialize + DeserializeOwned>(
     94         self,
     95         key: String,
     96         value: T,
     97     ) -> Result<Self> {
     98         Ok(match self.get(&key) as Option<Vec<T>> {
     99             Some(mut v) => {
    100                 v.push(value);
    101                 self
    102             }
    103             None => self.insert(key, Vec::from([value]))?,
    104         })
    105     }
    106 
    107     /// creates the struct from a correctly formatted JSON string
    108     pub fn from_json_str(json: &str) -> Result<Self> {
    109         serde_json::from_slice(json.as_bytes()).map_err(Error::JsonError)
    110     }
    111 }
    112 
    113 impl Default for SchemaDotOrg {
    114     fn default() -> Self {
    115         Self::new(default_type())
    116     }
    117 }
    118 
    119 impl AssertionJson for SchemaDotOrg {}
    120 
    121 impl AssertionBase for SchemaDotOrg {
    122     const LABEL: &'static str = labels::SCHEMA_ORG;
    123     const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
    124 
    125     fn to_assertion(&self) -> Result<Assertion> {
    126         Self::to_json_assertion(self)
    127     }
    128 
    129     fn from_assertion(assertion: &Assertion) -> Result<Self> {
    130         Self::from_json_assertion(assertion)
    131     }
    132 }
    133 #[derive(Serialize, Deserialize, Clone, Debug)]
    134 pub struct SchemaDotOrgPerson(SchemaDotOrg);
    135 
    136 impl SchemaDotOrgPerson {
    137     pub const CREDENTIAL: &'static str = "credential";
    138     pub const IDENTIFIER: &'static str = "identifier";
    139     pub const NAME: &'static str = "name";
    140     pub const PERSON: &'static str = "Person";
    141 
    142     pub fn new() -> Self {
    143         Self(SchemaDotOrg::new(Self::PERSON.to_owned()))
    144     }
    145 
    146     pub fn new_person<S: Into<String>>(name: S, identifier: S) -> Result<Self> {
    147         Self(SchemaDotOrg::new(Self::PERSON.to_owned()))
    148             .set_name(name)?
    149             .set_identifier(identifier)
    150     }
    151 
    152     /// get values by key
    153     pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
    154         self.0.get(key)
    155     }
    156 
    157     /// insert key / value pair
    158     pub fn insert<S: Into<String>, T: Serialize>(self, key: S, value: T) -> Result<Self> {
    159         self.0.insert(key.into(), value).map(Self)
    160     }
    161 
    162     // add a value to a Vec stored at key
    163     pub fn insert_push<S: Into<String>, T>(self, key: S, value: T) -> Result<Self>
    164     where
    165         T: Serialize + DeserializeOwned,
    166     {
    167         self.0.insert_push(key.into(), value).map(Self)
    168     }
    169 
    170     // get name field if it exists
    171     pub fn name(&self) -> Option<String> {
    172         self.get(Self::NAME)
    173     }
    174 
    175     pub fn set_name<S: Into<String>>(self, author: S) -> Result<Self> {
    176         self.insert(Self::NAME.to_string(), author.into())
    177     }
    178 
    179     // get identifier field if it exists
    180     pub fn identifier(&self) -> Option<String> {
    181         self.get(Self::IDENTIFIER)
    182     }
    183 
    184     pub fn set_identifier<S: Into<String>>(self, identifier: S) -> Result<Self> {
    185         self.insert(Self::IDENTIFIER.to_owned(), identifier.into())
    186     }
    187 
    188     pub fn add_credential(self, credential: HashedUri) -> Result<Self> {
    189         self.insert_push(Self::CREDENTIAL.to_owned(), credential)
    190     }
    191 }
    192 
    193 impl Default for SchemaDotOrgPerson {
    194     fn default() -> Self {
    195         Self::new()
    196     }
    197 }
    198 
    199 impl std::ops::Deref for SchemaDotOrgPerson {
    200     type Target = SchemaDotOrg;
    201 
    202     fn deref(&self) -> &Self::Target {
    203         &self.0
    204     }
    205 }
    206 #[cfg(test)]
    207 pub mod tests {
    208     #![allow(clippy::expect_used)]
    209     #![allow(clippy::unwrap_used)]
    210 
    211     use super::*;
    212 
    213     const USER: &str = "Joe Bloggs";
    214     const USER_ID: &str = "1234567890";
    215     const IDENTITY_URI: &str = "https://some_identity/service/";
    216 
    217     // example review rating from
    218     // https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_claim_review
    219     const RATING: &str = r#"{
    220         "@context": "http://schema.org",
    221         "@type": "ClaimReview",
    222         "claimReviewed": "The world is flat",
    223         "reviewRating": {
    224           "@type": "Rating",
    225           "ratingValue": "1",
    226           "bestRating": "5",
    227           "worstRating": "1",
    228           "ratingExplanation": "The world is not flat",
    229           "alternateName": "False"
    230         },
    231         "itemReviewed": {
    232           "@type": "CreativeWork",
    233           "author": {
    234             "@type": "Person",
    235             "name": "A N Other"
    236           },
    237           "headline": "Earth: Flat."
    238         }
    239       }"#;
    240 
    241     #[test]
    242     fn assertion_creative_work() {
    243         let uri = HashedUri::new(USER_ID.to_string(), None, b"abcde");
    244         let original_person = SchemaDotOrgPerson::new()
    245             .set_name(USER.to_owned())
    246             .unwrap()
    247             .set_identifier(IDENTITY_URI.to_owned())
    248             .unwrap()
    249             .add_credential(uri)
    250             .unwrap();
    251         let original = SchemaDotOrg::new("CreativeWork".to_owned())
    252             .insert("author".to_owned(), original_person.clone())
    253             .expect("insert");
    254         let assertion = original.to_assertion().expect("build_assertion");
    255         assert_eq!(assertion.mime_type(), "application/json");
    256         assert_eq!(assertion.label(), SchemaDotOrg::LABEL);
    257         let result = SchemaDotOrg::from_assertion(&assertion).expect("extract_assertion");
    258         assert_eq!(original.object_type(), result.object_type());
    259         let result_person = result.get::<SchemaDotOrgPerson>("author").unwrap();
    260         assert_eq!(original_person.name(), result_person.name());
    261     }
    262 
    263     #[test]
    264     fn from_rating() {
    265         let original = SchemaDotOrg::from_json_str(RATING).expect("from_json");
    266         let original_claim_reviewed: String = original.get("claimReviewed").unwrap();
    267         let assertion = original.to_assertion().expect("build_assertion");
    268         assert_eq!(assertion.mime_type(), "application/json");
    269         assert_eq!(assertion.label(), SchemaDotOrg::LABEL);
    270         let result = SchemaDotOrg::from_assertion(&assertion).expect("extract_assertion");
    271         assert_eq!(original.object_type(), result.object_type());
    272         let result_claim_reviewed: String = result.get("claimReviewed").unwrap();
    273         assert_eq!(original_claim_reviewed, result_claim_reviewed);
    274     }
    275 }