use crate::{
assertion::{Assertion, AssertionBase, AssertionCbor},
assertions::labels,
error::Result,
hashed_uri::HashedUri,
};
use chrono::{SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
const ASSERTION_CREATION_VERSION: usize = 1;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Metadata {
#[serde(rename = "reviewRatings", skip_serializing_if = "Option::is_none")]
reviews: Option<Vec<ReviewRating>>,
#[serde(rename = "dateTime", skip_serializing_if = "Option::is_none")]
date_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
reference: Option<HashedUri>,
#[serde(skip_serializing_if = "Option::is_none")]
data_source: Option<DataSource>,
#[serde(flatten)]
other: HashMap<String, Value>,
}
impl Metadata {
pub const LABEL: &'static str = labels::ASSERTION_METADATA;
pub fn new() -> Self {
Self {
reviews: None,
date_time: Some(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)),
reference: None,
data_source: None,
other: HashMap::new(),
}
}
pub fn reviews(&self) -> Option<&[ReviewRating]> {
self.reviews.as_deref()
}
pub fn date_time(&self) -> Option<&str> {
self.date_time.as_deref()
}
pub fn data_source(&self) -> Option<&DataSource> {
self.data_source.as_ref()
}
pub fn add_review(mut self, review: ReviewRating) -> Self {
match &mut self.reviews {
None => self.reviews = Some(vec![review]),
Some(reviews) => reviews.push(review),
}
self
}
pub fn set_reviews(mut self, reviews: Vec<ReviewRating>) -> Self {
self.reviews = Some(reviews);
self
}
pub fn set_date_time(&mut self, date_time: String) -> &mut Self {
self.date_time = Some(date_time);
self
}
#[cfg(test)] pub(crate) fn set_reference(mut self, reference: HashedUri) -> Self {
self.reference = Some(reference);
self
}
pub fn set_data_source(mut self, data_source: DataSource) -> Self {
self.data_source = Some(data_source);
self
}
pub fn insert(&mut self, key: &str, value: Value) -> &mut Self {
self.other.insert(key.to_string(), value);
self
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.other.get(key)
}
}
impl Default for Metadata {
fn default() -> Self {
Self::new()
}
}
impl AssertionCbor for Metadata {}
impl AssertionBase for Metadata {
const LABEL: &'static str = Self::LABEL;
const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
fn to_assertion(&self) -> Result<Assertion> {
Self::to_cbor_assertion(self)
}
fn from_assertion(assertion: &Assertion) -> Result<Self> {
Self::from_cbor_assertion(assertion)
}
}
pub mod c2pa_source {
pub const SIGNER: &str = "signer";
pub const GENERATOR_REE: &str = "claimGenerator.REE";
pub const GENERATOR_TEE: &str = "claimGenerator.TEE";
pub const LOCAL_REE: &str = "localProvider.REE";
pub const LOCAL_TEE: &str = "localProvider.TEE";
pub const REMOTE_REE: &str = "remoteProvider.1stParty";
pub const REMOTE_TEE: &str = "remoteProvider.3rdParty";
pub const HUMAN_ANONYMOUS: &str = "humanEntry.anonymous";
pub const HUMAN_IDENTIFIED: &str = "humanEntry.identified";
}
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct DataSource {
#[serde(rename = "type")]
pub source_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actors: Option<Vec<Actor>>,
}
impl DataSource {
pub fn new(source_type: &str) -> Self {
Self {
source_type: source_type.to_owned(),
details: None,
actors: None,
}
}
pub fn set_details(mut self, details: String) -> Self {
self.details = Some(details);
self
}
pub fn set_actors(mut self, actors: Option<Vec<Actor>>) -> Self {
self.actors = actors;
self
}
}
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Actor {
#[serde(skip_serializing_if = "Option::is_none")]
pub identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub credentials: Option<Vec<HashedUri>>,
}
impl Actor {
pub fn new(identifier: Option<&str>, credentials: Option<&Vec<HashedUri>>) -> Self {
Self {
identifier: identifier.map(|id| id.to_owned()),
credentials: credentials.cloned(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum ReviewCode {
#[serde(rename(serialize = "actions.unknownActionsPerformed"))]
ActionsUnknown,
#[serde(rename(serialize = "actions.missing"))]
ActionsMissing,
#[serde(rename(serialize = "actions.possiblyMissing"))]
ActionsPossiblyMissing,
#[serde(rename(serialize = "depthMap.sceneMismatch"))]
DepthMapSceneMismatch,
#[serde(rename(serialize = "ingredient.modified"))]
IngredientModified,
#[serde(rename(serialize = "ingredient.possiblyModified"))]
IngredientPossiblyModified,
#[serde(rename(serialize = "thumbnail.primaryMismatch"))]
ThumbnailPrimaryMismatch,
#[serde(rename(serialize = "stds.iptc.location.inaccurate"))]
IptcLocationInaccurate,
#[serde(rename(serialize = "stds.schema-org.CreativeWork.misattributed"))]
CreativeWorkMisAttributed,
#[serde(rename(serialize = "stds.schema-org.CreativeWork.missingAttribution"))]
CreativeWorkMissingAttribution,
Other(String),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ReviewRating {
pub explanation: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub value: u8,
}
impl ReviewRating {
pub fn new(explanation: &str, code: Option<String>, value: u8) -> Self {
Self {
explanation: explanation.to_owned(),
value, code,
}
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn assertion_metadata() {
let review = ReviewRating::new("foo", Some("bar".to_owned()), 3);
let test_value = Value::from("test");
let mut original = Metadata::new().add_review(review);
original.insert("foo", test_value);
println!("{:?}", &original);
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.mime_type(), "application/cbor");
assert_eq!(assertion.label(), Metadata::LABEL);
let result = Metadata::from_assertion(&assertion).expect("extract_assertion");
println!("{:?}", serde_json::to_string(&result));
assert_eq!(original.date_time, result.date_time);
assert_eq!(original.reviews, result.reviews);
assert_eq!(original.get("foo").unwrap(), "test");
}
}