1use crate::{NodeId, ObjectId, TransactionId, WriterId};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::{fmt, sync::Arc};
5
6pub(crate) const MAX_STRING_BYTES: usize = 1024 * 1024;
7pub(crate) const MAX_TRANSACTION_BYTES: usize = 16 * 1024 * 1024;
8pub(crate) const MAX_OBJECT_BYTES: usize = 64 * 1024 * 1024;
9pub(crate) const MAX_ITEMS: usize = 1_000_000;
10
11pub type Result<T> = std::result::Result<T, Error>;
12
13#[derive(Debug)]
14pub enum Error {
15 Io(std::io::Error),
16 Busy(String),
17 Corrupt(String),
18 InvalidConfig(String),
19 InvalidInput(String),
20 InvalidTransaction(String),
21 NotFound(String),
22}
23
24impl Error {
25 pub(crate) fn corrupt(message: impl Into<String>) -> Self {
26 Self::Corrupt(message.into())
27 }
28
29 pub(crate) fn invalid_config(message: impl Into<String>) -> Self {
30 Self::InvalidConfig(message.into())
31 }
32
33 pub(crate) fn invalid_input(message: impl Into<String>) -> Self {
34 Self::InvalidInput(message.into())
35 }
36
37 pub(crate) fn invalid_transaction(message: impl Into<String>) -> Self {
38 Self::InvalidTransaction(message.into())
39 }
40
41 pub(crate) fn not_found(message: impl Into<String>) -> Self {
42 Self::NotFound(message.into())
43 }
44}
45
46impl fmt::Display for Error {
47 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::Io(error) => write!(formatter, "I/O error: {error}"),
50 Self::Busy(message) => write!(formatter, "database is busy: {message}"),
51 Self::Corrupt(message) => write!(formatter, "database corruption: {message}"),
52 Self::InvalidConfig(message) => write!(formatter, "invalid configuration: {message}"),
53 Self::InvalidInput(message) => write!(formatter, "invalid input: {message}"),
54 Self::InvalidTransaction(message) => {
55 write!(formatter, "invalid transaction: {message}")
56 }
57 Self::NotFound(message) => write!(formatter, "not found: {message}"),
58 }
59 }
60}
61
62impl std::error::Error for Error {
63 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
64 match self {
65 Self::Io(error) => Some(error),
66 _ => None,
67 }
68 }
69}
70
71impl From<std::io::Error> for Error {
72 fn from(error: std::io::Error) -> Self {
73 Self::Io(error)
74 }
75}
76
77pub trait Gossip: Send + Sync + 'static {
78 fn announce(&self, package: TransactionPackage);
79}
80
81#[derive(Clone, Copy, Debug, Default)]
82pub struct NoopGossip;
83
84impl Gossip for NoopGossip {
85 fn announce(&self, _package: TransactionPackage) {}
86}
87
88#[derive(Clone)]
89pub struct Config {
90 pub signing_key: [u8; 32],
91 pub writers_by_priority: Vec<WriterId>,
92 pub gossip: Arc<dyn Gossip>,
93}
94
95impl fmt::Debug for Config {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter
98 .debug_struct("Config")
99 .field("signing_key", &"[redacted]")
100 .field("writers_by_priority", &self.writers_by_priority)
101 .field("gossip", &"dyn Gossip")
102 .finish()
103 }
104}
105
106#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
107pub struct Provenance {
108 pub author: String,
109 pub source: String,
110 pub source_created_at: DateTime<Utc>,
111 pub data: String,
112}
113
114impl Provenance {
115 pub(crate) fn validate(&self) -> Result<()> {
116 if self.author.trim().is_empty() || self.author.len() > MAX_STRING_BYTES {
117 return Err(Error::invalid_input(
118 "provenance author is empty or too large",
119 ));
120 }
121 if self.source.trim().is_empty() || self.source.len() > MAX_STRING_BYTES {
122 return Err(Error::invalid_input(
123 "provenance source is empty or too large",
124 ));
125 }
126 if self.data.len() > MAX_STRING_BYTES {
127 return Err(Error::invalid_input("provenance data exceeds 1 MiB"));
128 }
129 Ok(())
130 }
131}
132
133#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
134pub enum Owner {
135 Unowned,
136 SelfNode,
137 Node(NodeId),
138}
139
140#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
141pub struct NodeData {
142 pub short_name: String,
143 pub short_description: String,
144 pub long_description: String,
145 pub owner: Owner,
146 pub fixed_connections: [Option<NodeId>; 3],
147 pub objects: Vec<ObjectId>,
148}
149
150impl NodeData {
151 pub(crate) fn validate(&self) -> Result<()> {
152 let characters = self.short_name.chars().count();
153 if !(4..=50).contains(&characters) {
154 return Err(Error::invalid_input(
155 "short_name must contain between 4 and 50 characters",
156 ));
157 }
158 if self.short_description.chars().count() > 200 {
159 return Err(Error::invalid_input(
160 "short_description exceeds 200 characters",
161 ));
162 }
163 if self.long_description.split_whitespace().count() > 1_000 {
164 return Err(Error::invalid_input("long_description exceeds 1,000 words"));
165 }
166 if self.short_name.len() > MAX_STRING_BYTES
167 || self.short_description.len() > MAX_STRING_BYTES
168 || self.long_description.len() > MAX_STRING_BYTES
169 {
170 return Err(Error::invalid_input("node text exceeds 1 MiB"));
171 }
172 let fixed = self
173 .fixed_connections
174 .iter()
175 .flatten()
176 .copied()
177 .collect::<std::collections::BTreeSet<_>>();
178 if fixed.len() != self.fixed_connections.iter().flatten().count() {
179 return Err(Error::invalid_input("fixed connections must be unique"));
180 }
181 let objects = self
182 .objects
183 .iter()
184 .copied()
185 .collect::<std::collections::BTreeSet<_>>();
186 if objects.len() != self.objects.len() {
187 return Err(Error::invalid_input("object references must be unique"));
188 }
189 Ok(())
190 }
191}
192
193#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
194pub struct Node {
195 pub id: NodeId,
196 pub data: NodeData,
197 pub connections: Vec<NodeId>,
198 pub last_author: String,
199 pub committed_at: DateTime<Utc>,
200}
201
202#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
203pub struct MergePair {
204 pub first: TransactionId,
205 pub second: TransactionId,
206}
207
208impl MergePair {
209 pub fn new(first: TransactionId, second: TransactionId) -> Result<Self> {
210 if first == second {
211 return Err(Error::invalid_input(
212 "a merge pair must name two different transactions",
213 ));
214 }
215 let (first, second) = if first < second {
216 (first, second)
217 } else {
218 (second, first)
219 };
220 Ok(Self { first, second })
221 }
222}
223
224#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
225pub struct HistoryEntry {
226 pub transaction_id: TransactionId,
227 pub writer: WriterId,
228 pub committed_at: DateTime<Utc>,
229 pub provenance: Provenance,
230 pub active: bool,
231 pub created: bool,
232 pub updated: bool,
233 pub connections: Vec<(NodeId, NodeId)>,
234 pub merge_pairs: Vec<MergePair>,
235}
236
237#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
238pub struct NodeHistory {
239 pub node_id: NodeId,
240 pub frontier: Vec<TransactionId>,
241 pub visible: Option<TransactionId>,
242 pub entries: Vec<HistoryEntry>,
243}
244
245#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
246pub struct ObjectPayload {
247 pub id: ObjectId,
248 pub bytes: Vec<u8>,
249}
250
251#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
252pub struct TransactionPackage {
253 pub transaction: Vec<u8>,
254 pub objects: Vec<ObjectPayload>,
255}