use std::{collections::HashMap, env::consts};
use serde::{Deserialize, Serialize};
use crate::{
assertions::{region_of_interest::RegionOfInterest, Action, ActionTemplate, SoftwareAgent},
cbor_types::DateT,
resource_store::UriOrResource,
settings::SettingsValidate,
ClaimGeneratorInfo, Error, ResourceRef, Result,
};
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum ThumbnailFormat {
Png,
Jpeg,
Gif,
WebP,
Tiff,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum ThumbnailQuality {
Low,
Medium,
High,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ThumbnailSettings {
pub enabled: bool,
pub ignore_errors: bool,
pub long_edge: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<ThumbnailFormat>,
pub prefer_smallest_format: bool,
pub quality: ThumbnailQuality,
}
impl Default for ThumbnailSettings {
fn default() -> Self {
ThumbnailSettings {
enabled: true,
ignore_errors: true,
long_edge: 1024,
format: None,
prefer_smallest_format: true,
quality: ThumbnailQuality::Medium,
}
}
}
impl SettingsValidate for ThumbnailSettings {
fn validate(&self) -> Result<()> {
#[cfg(not(feature = "add_thumbnails"))]
if self.enabled {
log::warn!("c2pa-rs feature `add_thumbnails` must be enabled to generate thumbnails!");
}
Ok(())
}
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct AutoActionSettings {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_type: Option<String>,
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ClaimGeneratorInfoOSSettings {
pub infer: bool,
pub name: Option<String>,
}
impl Default for ClaimGeneratorInfoOSSettings {
fn default() -> Self {
Self {
infer: true,
name: None,
}
}
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ClaimGeneratorInfoSettings {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<ResourceRef>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operating_system: Option<ClaimGeneratorInfoOSSettings>,
#[serde(flatten)]
pub other: HashMap<String, toml::Value>,
}
impl TryFrom<ClaimGeneratorInfoSettings> for ClaimGeneratorInfo {
type Error = Error;
fn try_from(value: ClaimGeneratorInfoSettings) -> Result<Self> {
Ok(ClaimGeneratorInfo {
name: value.name,
version: value.version,
icon: value.icon.map(UriOrResource::ResourceRef),
operating_system: {
let os = value.operating_system.unwrap_or_default();
match os.infer {
true => Some(consts::OS.to_owned()),
false => os.name,
}
},
other: value
.other
.into_iter()
.map(|(key, value)| {
serde_json::to_value(value)
.map(|value| (key, value))
.map_err(|err| err.into())
})
.collect::<Result<HashMap<String, serde_json::Value>>>()?,
})
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ActionTemplateSettings {
pub action: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub software_agent: Option<ClaimGeneratorInfoSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub software_agent_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<ResourceRef>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_parameters: Option<HashMap<String, toml::Value>>,
}
impl TryFrom<ActionTemplateSettings> for ActionTemplate {
type Error = Error;
fn try_from(value: ActionTemplateSettings) -> Result<Self> {
Ok(ActionTemplate {
action: value.action,
software_agent: value
.software_agent
.map(|software_agent| software_agent.try_into())
.transpose()?,
software_agent_index: value.software_agent_index,
source_type: value.source_type,
icon: value.icon.map(UriOrResource::ResourceRef),
description: value.description,
template_parameters: value
.template_parameters
.map(|template_parameters| {
template_parameters
.into_iter()
.map(|(key, value)| {
serde_cbor::value::to_value(value)
.map(|value| (key, value))
.map_err(|err| err.into())
})
.collect::<Result<HashMap<String, serde_cbor::Value>>>()
})
.transpose()?,
})
}
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ActionSettings {
pub action: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<DateT>,
#[serde(skip_serializing_if = "Option::is_none")]
pub software_agent: Option<ClaimGeneratorInfoSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub software_agent_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub changes: Option<Vec<RegionOfInterest>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<HashMap<String, toml::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related: Option<Vec<Action>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl TryFrom<ActionSettings> for Action {
type Error = Error;
fn try_from(value: ActionSettings) -> Result<Self> {
Ok(Action {
action: value.action,
when: value.when,
software_agent: value
.software_agent
.map(|software_agent| software_agent.try_into())
.transpose()?
.map(SoftwareAgent::ClaimGeneratorInfo),
software_agent_index: value.software_agent_index,
changes: value.changes,
parameters: value
.parameters
.map(|template_parameters| {
template_parameters
.into_iter()
.map(|(key, value)| {
serde_cbor::value::to_value(value)
.map(|value| (key, value))
.map_err(|err| err.into())
})
.collect::<Result<HashMap<String, serde_cbor::Value>>>()
})
.transpose()?,
source_type: value.source_type,
related: value.related,
reason: value.reason,
description: value.description,
..Default::default()
})
}
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct ActionsSettings {
pub all_actions_included: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Vec<ActionTemplateSettings>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<Vec<ActionSettings>>,
pub auto_created_action: AutoActionSettings,
pub auto_opened_action: AutoActionSettings,
pub auto_placed_action: AutoActionSettings,
}
impl Default for ActionsSettings {
fn default() -> Self {
ActionsSettings {
all_actions_included: true,
templates: None,
actions: None,
auto_created_action: AutoActionSettings {
enabled: true,
source_type: Some(crate::assertions::source_type::EMPTY.to_owned()),
},
auto_opened_action: AutoActionSettings {
enabled: true,
source_type: None,
},
auto_placed_action: AutoActionSettings {
enabled: true,
source_type: None,
},
}
}
}
impl SettingsValidate for ActionsSettings {
fn validate(&self) -> Result<()> {
match self.auto_created_action.enabled && self.auto_created_action.source_type.is_none() {
true => Err(Error::MissingAutoCreatedActionSourceType),
false => Ok(()),
}
}
}
#[allow(unused)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
pub(crate) struct BuilderSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub claim_generator_info: Option<ClaimGeneratorInfoSettings>,
pub thumbnail: ThumbnailSettings,
pub actions: ActionsSettings,
}
impl SettingsValidate for BuilderSettings {
fn validate(&self) -> Result<()> {
self.actions.validate()?;
self.thumbnail.validate()
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::assertions::source_type;
#[test]
fn test_auto_created_action_without_source_type() {
let actions_settings = ActionsSettings {
auto_created_action: AutoActionSettings {
enabled: true,
source_type: None,
},
..Default::default()
};
assert!(actions_settings.validate().is_err());
}
#[test]
fn test_auto_created_action_with_source_type() {
let actions_settings = ActionsSettings {
auto_created_action: AutoActionSettings {
enabled: true,
source_type: Some(source_type::EMPTY.to_owned()),
},
..Default::default()
};
assert!(actions_settings.validate().is_ok());
}
}