#![deny(missing_docs)]
use log::debug;
#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(feature = "v1_api")]
use crate::status_tracker::StatusTracker;
#[cfg(feature = "v1_api")]
use crate::store::Store;
pub use crate::validation_results::validation_codes::*;
use crate::{
error::Error,
jumbf,
status_tracker::{LogItem, LogKind},
};
#[derive(Clone, Debug, Deserialize, Serialize, Eq)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub struct ValidationStatus {
code: String,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
explanation: Option<String>,
#[serde(skip_serializing)]
#[allow(dead_code)]
success: Option<bool>,
#[serde(skip)]
#[serde(default = "default_log_kind")]
kind: LogKind,
#[serde(skip)]
ingredient_uri: Option<String>,
}
fn default_log_kind() -> LogKind {
LogKind::Success
}
impl ValidationStatus {
pub(crate) fn new<S: Into<String>>(code: S) -> Self {
Self {
code: code.into(),
url: None,
explanation: None,
success: None,
ingredient_uri: None,
kind: LogKind::Success,
}
}
pub(crate) fn new_failure<S: Into<String>>(code: S) -> Self {
Self::new(code).set_kind(LogKind::Failure)
}
pub fn code(&self) -> &str {
&self.code
}
pub fn url(&self) -> Option<&str> {
self.url.as_deref()
}
pub fn explanation(&self) -> Option<&str> {
self.explanation.as_deref()
}
pub fn ingredient_uri(&self) -> Option<&str> {
self.ingredient_uri.as_deref()
}
pub fn set_url<S: Into<String>>(mut self, url: S) -> Self {
self.url = Some(url.into());
self
}
pub fn set_kind(mut self, kind: LogKind) -> Self {
self.kind = kind;
self
}
pub fn set_ingredient_uri<S: Into<String>>(mut self, uri: S) -> Self {
self.ingredient_uri = Some(uri.into());
self
}
pub(crate) fn set_explanation(mut self, explanation: String) -> Self {
self.explanation = Some(explanation);
self
}
pub fn passed(&self) -> bool {
self.kind != LogKind::Failure
}
pub fn kind(&self) -> &LogKind {
&self.kind
}
fn code_from_error_str(error: &str) -> &str {
match error {
"ClaimMissing" => CLAIM_MISSING,
e if e.starts_with("AssertionMissing") => ASSERTION_MISSING,
e if e.starts_with("AssertionDecoding") => ASSERTION_REQUIRED_MISSING,
e if e.starts_with("HashMismatch") => ASSERTION_DATAHASH_MATCH,
e if e.starts_with("RemoteManifestFetch") => MANIFEST_INACCESSIBLE,
e if e.starts_with("PrereleaseError") => STATUS_PRERELEASE,
_ => GENERAL_ERROR,
}
}
fn code_from_error(error: &Error) -> &str {
match error {
Error::ClaimMissing { .. } => CLAIM_MISSING,
Error::AssertionMissing { .. } => ASSERTION_MISSING,
Error::AssertionDecoding(_code) => ASSERTION_REQUIRED_MISSING,
Error::HashMismatch(_) => ASSERTION_DATAHASH_MATCH,
Error::RemoteManifestFetch(_) => MANIFEST_INACCESSIBLE,
Error::PrereleaseError => STATUS_PRERELEASE,
_ => GENERAL_ERROR,
}
}
pub(crate) fn from_error(error: &Error) -> Self {
let code = Self::code_from_error(error);
debug!("ValidationStatus {code} from error {error:#?}");
Self::new_failure(code.to_string()).set_explanation(error.to_string())
}
pub(crate) fn from_log_item(item: &LogItem) -> Option<Self> {
match item.validation_status.as_ref() {
Some(status) => Some({
let mut vi = Self::new(status.to_string())
.set_url(item.label.to_string())
.set_kind(item.kind.clone())
.set_explanation(item.description.to_string());
if let Some(ingredient_uri) = &item.ingredient_uri {
vi = vi.set_ingredient_uri(ingredient_uri.to_string());
}
vi
}),
None => item.err_val.as_ref().map(|e| {
let code = Self::code_from_error_str(e);
Self::new_failure(code.to_string())
.set_url(item.label.to_string())
.set_explanation(format!("{}: {}", item.description, e))
}),
}
}
pub(crate) fn make_absolute(&mut self, manifest_label: &str) {
if let Some(url) = &self.url {
if url.starts_with("self#jumbf") {
self.url = Some(jumbf::labels::to_absolute_uri(manifest_label, url));
}
}
}
}
impl PartialEq for ValidationStatus {
fn eq(&self, other: &Self) -> bool {
self.code == other.code && self.url == other.url && self.kind == other.kind
}
}
#[cfg(feature = "v1_api")]
pub fn status_for_store(store: &Store, validation_log: &StatusTracker) -> Vec<ValidationStatus> {
let validation_results =
crate::validation_results::ValidationResults::from_store(store, validation_log);
validation_results.validation_errors().unwrap_or_default()
}
pub(crate) const STATUS_PRERELEASE: &str = "com.adobe.prerelease";