use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub type SchemaVersion = u32;
pub const SCHEMA_VERSION: SchemaVersion = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CheckStatus {
Pass,
Warn,
Fail,
Skip,
}
impl CheckStatus {
pub fn as_str(self) -> &'static str {
match self {
Self::Pass => "pass",
Self::Warn => "warn",
Self::Fail => "fail",
Self::Skip => "skip",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Check {
pub id: String,
pub status: CheckStatus,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
impl Check {
pub fn pass(id: impl Into<String>, message: impl Into<String>) -> Self {
Self {
id: id.into(),
status: CheckStatus::Pass,
message: message.into(),
detail: None,
}
}
pub fn fail(id: impl Into<String>, message: impl Into<String>) -> Self {
Self {
id: id.into(),
status: CheckStatus::Fail,
message: message.into(),
detail: None,
}
}
pub fn skip(id: impl Into<String>, message: impl Into<String>) -> Self {
Self {
id: id.into(),
status: CheckStatus::Skip,
message: message.into(),
detail: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReceiptSummary {
pub passed: usize,
pub warnings: usize,
pub failed: usize,
pub skipped: usize,
}
impl ReceiptSummary {
pub fn from_checks(checks: &[Check]) -> Self {
let mut summary = Self::default();
for check in checks {
match check.status {
CheckStatus::Pass => summary.passed += 1,
CheckStatus::Warn => summary.warnings += 1,
CheckStatus::Fail => summary.failed += 1,
CheckStatus::Skip => summary.skipped += 1,
}
}
summary
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvaluationReceipt {
pub schema_version: SchemaVersion,
pub tool: ToolInfo,
pub checked_at: DateTime<Utc>,
pub mode: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expression: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rendered: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_string: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truthy: Option<bool>,
pub contexts: Vec<String>,
pub functions: Vec<String>,
pub references: Vec<String>,
pub summary: ReceiptSummary,
pub checks: Vec<Check>,
}
impl EvaluationReceipt {
pub fn has_failures(&self) -> bool {
self.summary.failed > 0
}
}
pub fn value_type(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}