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 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub search: Option<ManifestSearchConfig>,
121 #[serde(default = "default_crdt_enabled")]
129 pub crdt: bool,
130}
131
132fn default_crdt_enabled() -> bool {
133 true
134}
135
136impl Default for ManifestEntity {
137 fn default() -> Self {
138 Self {
139 name: String::new(),
140 fields: Vec::new(),
141 indexes: Vec::new(),
142 relations: Vec::new(),
143 search: None,
144 crdt: true,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
157pub struct ManifestSearchConfig {
158 #[serde(default)]
159 pub text: Vec<String>,
160 #[serde(default)]
161 pub facets: Vec<String>,
162 #[serde(default)]
163 pub sortable: Vec<String>,
164}
165
166impl ManifestSearchConfig {
167 pub fn is_empty(&self) -> bool {
168 self.text.is_empty() && self.facets.is_empty() && self.sortable.is_empty()
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct ManifestRelation {
174 pub name: String,
175 pub target: String,
176 pub field: String,
177 #[serde(default)]
178 pub many: bool,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182pub struct ManifestField {
183 pub name: String,
184 #[serde(rename = "type")]
185 pub field_type: String,
186 pub optional: bool,
187 pub unique: bool,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub crdt: Option<CrdtAnnotation>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(rename_all = "kebab-case")]
212pub enum CrdtAnnotation {
213 Lww,
215 Text,
217 Counter,
221 List,
223 #[serde(rename = "movable-list")]
226 MovableList,
227 Tree,
230}
231
232impl CrdtAnnotation {
233 pub fn as_str(self) -> &'static str {
236 match self {
237 Self::Lww => "lww",
238 Self::Text => "text",
239 Self::Counter => "counter",
240 Self::List => "list",
241 Self::MovableList => "movable-list",
242 Self::Tree => "tree",
243 }
244 }
245}
246
247impl std::fmt::Display for CrdtAnnotation {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 f.write_str(self.as_str())
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub struct ManifestIndex {
255 pub name: String,
256 pub fields: Vec<String>,
257 pub unique: bool,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub struct ManifestRoute {
262 pub path: String,
263 pub mode: String,
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub query: Option<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub auth: Option<String>,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271pub struct ManifestQuery {
272 pub name: String,
273 #[serde(default, skip_serializing_if = "Vec::is_empty")]
274 pub input: Vec<ManifestField>,
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
278pub struct ManifestAction {
279 pub name: String,
280 #[serde(default, skip_serializing_if = "Vec::is_empty")]
281 pub input: Vec<ManifestField>,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
293pub struct ManifestPolicy {
294 pub name: String,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub entity: Option<String>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub action: Option<String>,
299 #[serde(default, skip_serializing_if = "String::is_empty")]
300 pub allow: String,
301 #[serde(default, rename = "allowRead", skip_serializing_if = "Option::is_none")]
303 pub allow_read: Option<String>,
304 #[serde(
307 default,
308 rename = "allowInsert",
309 skip_serializing_if = "Option::is_none"
310 )]
311 pub allow_insert: Option<String>,
312 #[serde(
314 default,
315 rename = "allowUpdate",
316 skip_serializing_if = "Option::is_none"
317 )]
318 pub allow_update: Option<String>,
319 #[serde(
321 default,
322 rename = "allowDelete",
323 skip_serializing_if = "Option::is_none"
324 )]
325 pub allow_delete: Option<String>,
326 #[serde(
329 default,
330 rename = "allowWrite",
331 skip_serializing_if = "Option::is_none"
332 )]
333 pub allow_write: Option<String>,
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn exit_code_values() {
342 assert_eq!(ExitCode::Ok.as_i32(), 0);
343 assert_eq!(ExitCode::Error.as_i32(), 1);
344 assert_eq!(ExitCode::Usage.as_i32(), 64);
345 assert_eq!(ExitCode::Unavailable.as_i32(), 69);
346 }
347
348 #[test]
349 fn severity_display() {
350 assert_eq!(format!("{}", Severity::Error), "error");
351 assert_eq!(format!("{}", Severity::Warning), "warning");
352 assert_eq!(format!("{}", Severity::Info), "info");
353 }
354
355 #[test]
356 fn diagnostic_display_without_hint() {
357 let d = Diagnostic {
358 severity: Severity::Error,
359 code: "TEST".into(),
360 message: "something failed".into(),
361 span: None,
362 hint: None,
363 };
364 assert_eq!(format!("{d}"), "[error] TEST: something failed");
365 }
366
367 #[test]
368 fn diagnostic_display_with_hint() {
369 let d = Diagnostic {
370 severity: Severity::Warning,
371 code: "WARN".into(),
372 message: "check this".into(),
373 span: None,
374 hint: Some("try again".into()),
375 };
376 assert_eq!(
377 format!("{d}"),
378 "[warning] WARN: check this (hint: try again)"
379 );
380 }
381
382 #[test]
383 fn manifest_version_constant() {
384 assert_eq!(MANIFEST_VERSION, 1);
385 }
386}