1pub mod adapter;
2pub mod config;
3pub mod embed;
4pub mod handlers;
5pub mod sessions;
6pub mod sql;
7pub mod substrate;
8pub mod transport;
9pub mod wire;
10
11pub const PROTOCOL_VERSION: u16 = 1;
12
13use std::time::Duration;
14
15use chrono::{DateTime, Utc};
16use serde_json::Value;
17
18pub trait Clock: Send + Sync {
19 fn now(&self) -> DateTime<Utc>;
20}
21
22#[derive(Debug, Clone, Copy, Default)]
23pub struct SystemClock;
24
25impl Clock for SystemClock {
26 fn now(&self) -> DateTime<Utc> {
27 Utc::now()
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct RetryPolicy {
33 pub attempts: u8,
34 pub initial_backoff: Duration,
35 pub max_backoff: Duration,
36 pub jitter: f64,
41}
42
43impl Default for RetryPolicy {
44 fn default() -> Self {
45 Self {
46 attempts: 3,
47 initial_backoff: Duration::from_millis(300),
48 max_backoff: Duration::from_secs(5),
49 jitter: 0.2,
50 }
51 }
52}
53
54pub mod output {
55 use std::{
56 io::{self, IsTerminal, Write},
57 sync::OnceLock,
58 };
59
60 use anstyle::{AnsiColor, Style};
61 use anyhow::Context;
62
63 fn use_color() -> bool {
68 static USE: OnceLock<bool> = OnceLock::new();
69 *USE.get_or_init(|| std::env::var_os("NO_COLOR").is_none() && io::stdout().is_terminal())
70 }
71
72 pub fn paint(text: &str, style: Style) -> String {
76 if use_color() {
77 format!("{}{text}{}", style.render(), style.render_reset())
78 } else {
79 text.to_owned()
80 }
81 }
82
83 pub fn bold() -> Style {
84 Style::new().bold()
85 }
86 pub fn dim() -> Style {
87 Style::new().dimmed()
88 }
89 pub fn green() -> Style {
90 Style::new().fg_color(Some(AnsiColor::Green.into()))
91 }
92 pub fn yellow() -> Style {
93 Style::new().fg_color(Some(AnsiColor::Yellow.into()))
94 }
95 pub fn red() -> Style {
96 Style::new().fg_color(Some(AnsiColor::Red.into()))
97 }
98 pub fn cyan() -> Style {
99 Style::new().fg_color(Some(AnsiColor::Cyan.into()))
100 }
101
102 #[allow(clippy::print_stdout)]
103 pub fn line(message: &str) -> anyhow::Result<()> {
104 let mut stdout = io::stdout().lock();
105 writeln!(stdout, "{message}").context("failed to write command output")
106 }
107
108 pub fn line_err(message: &str) -> anyhow::Result<()> {
113 let mut stderr = io::stderr().lock();
114 writeln!(stderr, "{message}").context("failed to write command meta")
115 }
116}
117
118#[derive(Debug, thiserror::Error)]
119pub enum Error {
120 #[error("validation failed: {message}")]
121 Validation {
122 message: String,
123 field: Option<String>,
124 value: Option<Value>,
125 expected: Option<String>,
126 },
127 #[error("not found: {message}")]
128 NotFound {
129 message: String,
130 kind: String,
131 pk: Value,
132 },
133 #[error("namespace unknown: {namespace}")]
134 NamespaceUnknown { namespace: String },
135 #[error("commit conflict after {attempts} attempt(s)")]
136 Conflict { attempts: u8 },
137 #[error("storage unavailable: {0}")]
138 Storage(#[from] anyhow::Error),
139 #[error("internal error: {0}")]
140 Internal(String),
141}
142
143impl Error {
144 pub fn validation(message: impl Into<String>) -> Self {
145 Self::Validation {
146 message: message.into(),
147 field: None,
148 value: None,
149 expected: None,
150 }
151 }
152
153 pub fn validation_field(
154 message: impl Into<String>,
155 field: impl Into<String>,
156 value: Option<Value>,
157 expected: Option<String>,
158 ) -> Self {
159 Self::Validation {
160 message: message.into(),
161 field: Some(field.into()),
162 value,
163 expected,
164 }
165 }
166
167 pub fn not_found(kind: impl Into<String>, pk: Value, message: impl Into<String>) -> Self {
168 Self::NotFound {
169 message: message.into(),
170 kind: kind.into(),
171 pk,
172 }
173 }
174
175 pub fn namespace_unknown(namespace: impl Into<String>) -> Self {
176 Self::NamespaceUnknown {
177 namespace: namespace.into(),
178 }
179 }
180
181 pub fn conflict(attempts: u8) -> Self {
182 Self::Conflict { attempts }
183 }
184
185 pub fn internal(message: impl Into<String>) -> Self {
186 Self::Internal(message.into())
187 }
188}