use chrono::{SecondsFormat, Utc};
#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
assertion::{Assertion, AssertionBase, AssertionCbor},
assertions::{labels, region_of_interest::RegionOfInterest},
error::Result,
hashed_uri::HashedUri,
utils::cbor_types::DateT,
};
const ASSERTION_CREATION_VERSION: usize = 1;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub struct AssertionMetadata {
#[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<DateT>,
#[serde(skip_serializing_if = "Option::is_none")]
reference: Option<HashedUri>,
#[serde(rename = "dataSource", skip_serializing_if = "Option::is_none")]
data_source: Option<DataSource>,
#[serde(rename = "regionOfInterest", skip_serializing_if = "Option::is_none")]
region_of_interest: Option<RegionOfInterest>,
}
impl AssertionMetadata {
pub const LABEL: &'static str = labels::ASSERTION_METADATA;
pub fn new() -> Self {
Self {
reviews: None,
date_time: Some(DateT(
Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true),
)),
reference: None,
data_source: None,
region_of_interest: None,
}
}
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 region_of_interest(&self) -> Option<&RegionOfInterest> {
self.region_of_interest.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) -> Self {
self.date_time = Some(DateT(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 set_region_of_interest(mut self, region_of_interest: RegionOfInterest) -> Self {
self.region_of_interest = Some(region_of_interest);
self
}
}
impl Default for AssertionMetadata {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for AssertionMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string_pretty(self).unwrap_or_default())
}
}
impl AssertionCbor for AssertionMetadata {}
impl AssertionBase for AssertionMetadata {
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, Eq)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
#[non_exhaustive]
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, Eq)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
#[non_exhaustive]
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, Eq)]
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, Eq, Clone)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
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,
}
}
}
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub struct AssetType {
#[serde(rename = "type")]
pub asset_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
impl AssetType {
pub fn new<S: Into<String>>(asset_type: S, version: Option<String>) -> Self {
AssetType {
asset_type: asset_type.into(),
version,
}
}
}
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
pub struct DataBox {
#[serde(rename = "dc:format")]
pub format: String,
#[serde(with = "serde_bytes")]
pub data: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_types: Option<Vec<AssetType>>,
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use super::*;
use crate::assertions::region_of_interest::{Range, RangeType, Time, TimeType};
#[test]
fn assertion_metadata() {
let review = ReviewRating::new("foo", Some("bar".to_owned()), 3);
let original = AssertionMetadata::new()
.add_review(review)
.set_region_of_interest(RegionOfInterest {
region: vec![Range {
range_type: RangeType::Temporal,
time: Some(Time {
time_type: TimeType::Npt,
start: None,
end: None,
}),
..Default::default()
}],
..Default::default()
});
println!("{:}", &original);
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.mime_type(), "application/cbor");
assert_eq!(assertion.label(), AssertionMetadata::LABEL);
let result = AssertionMetadata::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.region_of_interest.as_ref(),
result.region_of_interest()
)
}
}