gha_expression_proof/
model.rs1use 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}