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