1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5pub mod clock;
6pub mod errors;
7pub mod util;
8
9pub use clock::{Clock, MockClock, SystemClock};
10
11pub const VERSION: &str = env!("CARGO_PKG_VERSION");
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ExitCode {
19 Ok = 0,
20 Error = 1,
21 Usage = 64,
22 Unavailable = 69,
23}
24
25impl ExitCode {
26 pub const fn as_i32(self) -> i32 {
27 self as i32
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "lowercase")]
37pub enum Severity {
38 Error,
39 Warning,
40 Info,
41}
42
43impl fmt::Display for Severity {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Severity::Error => f.write_str("error"),
47 Severity::Warning => f.write_str("warning"),
48 Severity::Info => f.write_str("info"),
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct Span {
56 pub file: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub line: Option<u32>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub column: Option<u32>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct Diagnostic {
69 pub severity: Severity,
70 pub code: String,
71 pub message: String,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub span: Option<Span>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub hint: Option<String>,
76}
77
78impl fmt::Display for Diagnostic {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "[{}] {}: {}", self.severity, self.code, self.message)?;
81 if let Some(hint) = &self.hint {
82 write!(f, " (hint: {hint})")?;
83 }
84 Ok(())
85 }
86}
87
88pub const MANIFEST_VERSION: u32 = 1;
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub struct AppManifest {
96 pub manifest_version: u32,
97 pub name: String,
98 pub version: String,
99 pub entities: Vec<ManifestEntity>,
100 pub routes: Vec<ManifestRoute>,
101 #[serde(default)]
102 pub queries: Vec<ManifestQuery>,
103 #[serde(default)]
104 pub actions: Vec<ManifestAction>,
105 #[serde(default)]
106 pub policies: Vec<ManifestPolicy>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct ManifestEntity {
111 pub name: String,
112 pub fields: Vec<ManifestField>,
113 pub indexes: Vec<ManifestIndex>,
114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
115 pub relations: Vec<ManifestRelation>,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct ManifestRelation {
120 pub name: String,
121 pub target: String,
122 pub field: String,
123 #[serde(default)]
124 pub many: bool,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct ManifestField {
129 pub name: String,
130 #[serde(rename = "type")]
131 pub field_type: String,
132 pub optional: bool,
133 pub unique: bool,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ManifestIndex {
138 pub name: String,
139 pub fields: Vec<String>,
140 pub unique: bool,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct ManifestRoute {
145 pub path: String,
146 pub mode: String,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub query: Option<String>,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub auth: Option<String>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct ManifestQuery {
155 pub name: String,
156 #[serde(default, skip_serializing_if = "Vec::is_empty")]
157 pub input: Vec<ManifestField>,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct ManifestAction {
162 pub name: String,
163 #[serde(default, skip_serializing_if = "Vec::is_empty")]
164 pub input: Vec<ManifestField>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
176pub struct ManifestPolicy {
177 pub name: String,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub entity: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub action: Option<String>,
182 #[serde(default, skip_serializing_if = "String::is_empty")]
183 pub allow: String,
184 #[serde(default, rename = "allowRead", skip_serializing_if = "Option::is_none")]
186 pub allow_read: Option<String>,
187 #[serde(
190 default,
191 rename = "allowInsert",
192 skip_serializing_if = "Option::is_none"
193 )]
194 pub allow_insert: Option<String>,
195 #[serde(
197 default,
198 rename = "allowUpdate",
199 skip_serializing_if = "Option::is_none"
200 )]
201 pub allow_update: Option<String>,
202 #[serde(
204 default,
205 rename = "allowDelete",
206 skip_serializing_if = "Option::is_none"
207 )]
208 pub allow_delete: Option<String>,
209 #[serde(
212 default,
213 rename = "allowWrite",
214 skip_serializing_if = "Option::is_none"
215 )]
216 pub allow_write: Option<String>,
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn exit_code_values() {
225 assert_eq!(ExitCode::Ok.as_i32(), 0);
226 assert_eq!(ExitCode::Error.as_i32(), 1);
227 assert_eq!(ExitCode::Usage.as_i32(), 64);
228 assert_eq!(ExitCode::Unavailable.as_i32(), 69);
229 }
230
231 #[test]
232 fn severity_display() {
233 assert_eq!(format!("{}", Severity::Error), "error");
234 assert_eq!(format!("{}", Severity::Warning), "warning");
235 assert_eq!(format!("{}", Severity::Info), "info");
236 }
237
238 #[test]
239 fn diagnostic_display_without_hint() {
240 let d = Diagnostic {
241 severity: Severity::Error,
242 code: "TEST".into(),
243 message: "something failed".into(),
244 span: None,
245 hint: None,
246 };
247 assert_eq!(format!("{d}"), "[error] TEST: something failed");
248 }
249
250 #[test]
251 fn diagnostic_display_with_hint() {
252 let d = Diagnostic {
253 severity: Severity::Warning,
254 code: "WARN".into(),
255 message: "check this".into(),
256 span: None,
257 hint: Some("try again".into()),
258 };
259 assert_eq!(
260 format!("{d}"),
261 "[warning] WARN: check this (hint: try again)"
262 );
263 }
264
265 #[test]
266 fn manifest_version_constant() {
267 assert_eq!(MANIFEST_VERSION, 1);
268 }
269}