Skip to main content

gha_expression_proof/
model.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5pub type SchemaVersion = u32;
6pub const SCHEMA_VERSION: SchemaVersion = 1;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ToolInfo {
10    pub name: String,
11    pub version: String,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum CheckStatus {
17    Pass,
18    Warn,
19    Fail,
20    Skip,
21}
22
23impl CheckStatus {
24    pub fn as_str(self) -> &'static str {
25        match self {
26            Self::Pass => "pass",
27            Self::Warn => "warn",
28            Self::Fail => "fail",
29            Self::Skip => "skip",
30        }
31    }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Check {
36    pub id: String,
37    pub status: CheckStatus,
38    pub message: String,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub detail: Option<String>,
41}
42
43impl Check {
44    pub fn pass(id: impl Into<String>, message: impl Into<String>) -> Self {
45        Self {
46            id: id.into(),
47            status: CheckStatus::Pass,
48            message: message.into(),
49            detail: None,
50        }
51    }
52
53    pub fn fail(id: impl Into<String>, message: impl Into<String>) -> Self {
54        Self {
55            id: id.into(),
56            status: CheckStatus::Fail,
57            message: message.into(),
58            detail: None,
59        }
60    }
61
62    pub fn skip(id: impl Into<String>, message: impl Into<String>) -> Self {
63        Self {
64            id: id.into(),
65            status: CheckStatus::Skip,
66            message: message.into(),
67            detail: None,
68        }
69    }
70}
71
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73pub struct ReceiptSummary {
74    pub passed: usize,
75    pub warnings: usize,
76    pub failed: usize,
77    pub skipped: usize,
78}
79
80impl ReceiptSummary {
81    pub fn from_checks(checks: &[Check]) -> Self {
82        let mut summary = Self::default();
83        for check in checks {
84            match check.status {
85                CheckStatus::Pass => summary.passed += 1,
86                CheckStatus::Warn => summary.warnings += 1,
87                CheckStatus::Fail => summary.failed += 1,
88                CheckStatus::Skip => summary.skipped += 1,
89            }
90        }
91        summary
92    }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct EvaluationReceipt {
97    pub schema_version: SchemaVersion,
98    pub tool: ToolInfo,
99    pub checked_at: DateTime<Utc>,
100    pub mode: String,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub expression: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub template: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub rendered: Option<String>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub result: Option<Value>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub result_string: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub result_type: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub truthy: Option<bool>,
115    pub contexts: Vec<String>,
116    pub functions: Vec<String>,
117    pub references: Vec<String>,
118    pub summary: ReceiptSummary,
119    pub checks: Vec<Check>,
120}
121
122impl EvaluationReceipt {
123    pub fn has_failures(&self) -> bool {
124        self.summary.failed > 0
125    }
126}
127
128pub fn value_type(value: &Value) -> &'static str {
129    match value {
130        Value::Null => "null",
131        Value::Bool(_) => "boolean",
132        Value::Number(_) => "number",
133        Value::String(_) => "string",
134        Value::Array(_) => "array",
135        Value::Object(_) => "object",
136    }
137}