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