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 match writeln!(stdout, "{message}") {
106 Err(error) if error.kind() == io::ErrorKind::BrokenPipe => std::process::exit(0),
110 result => result.context("failed to write command output"),
111 }
112 }
113
114 pub fn line_err(message: &str) -> anyhow::Result<()> {
119 let mut stderr = io::stderr().lock();
120 match writeln!(stderr, "{message}") {
121 Err(error) if error.kind() == io::ErrorKind::BrokenPipe => std::process::exit(0),
122 result => result.context("failed to write command meta"),
123 }
124 }
125}
126
127#[derive(Debug, thiserror::Error)]
128pub enum Error {
129 #[error("validation failed: {message}")]
130 Validation {
131 message: String,
132 field: Option<String>,
133 value: Option<Value>,
134 expected: Option<String>,
135 },
136 #[error("not found: {message}")]
137 NotFound {
138 message: String,
139 kind: String,
140 pk: Value,
141 },
142 #[error("namespace unknown: {namespace}")]
143 NamespaceUnknown { namespace: String },
144 #[error("commit conflict after {attempts} attempt(s)")]
145 Conflict { attempts: u8 },
146 #[error("storage unavailable: {0}")]
147 Storage(#[from] anyhow::Error),
148 #[error("internal error: {0}")]
149 Internal(String),
150}
151
152impl Error {
153 pub fn validation(message: impl Into<String>) -> Self {
154 Self::Validation {
155 message: message.into(),
156 field: None,
157 value: None,
158 expected: None,
159 }
160 }
161
162 pub fn validation_field(
163 message: impl Into<String>,
164 field: impl Into<String>,
165 value: Option<Value>,
166 expected: Option<String>,
167 ) -> Self {
168 Self::Validation {
169 message: message.into(),
170 field: Some(field.into()),
171 value,
172 expected,
173 }
174 }
175
176 pub fn not_found(kind: impl Into<String>, pk: Value, message: impl Into<String>) -> Self {
177 Self::NotFound {
178 message: message.into(),
179 kind: kind.into(),
180 pk,
181 }
182 }
183
184 pub fn namespace_unknown(namespace: impl Into<String>) -> Self {
185 Self::NamespaceUnknown {
186 namespace: namespace.into(),
187 }
188 }
189
190 pub fn conflict(attempts: u8) -> Self {
191 Self::Conflict { attempts }
192 }
193
194 pub fn internal(message: impl Into<String>) -> Self {
195 Self::Internal(message.into())
196 }
197}