1use haematite::tree::Hash;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use super::protocol::{Change, Version};
7use super::storage::Failure;
8
9pub mod attribution;
10pub mod envelopes;
11
12mod receipt;
13pub use receipt::Receipt;
14
15pub(super) const FINGERPRINT_FORMAT: u32 = 1;
16const FINGERPRINT_DOMAIN: &[u8] = b"haem.mutation.fingerprint\0";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(try_from = "CallerWire")]
21pub struct Caller {
22 issuer: String,
23 subject: String,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 responsible: Option<String>,
26}
27
28#[derive(Deserialize)]
29#[serde(deny_unknown_fields)]
30struct CallerWire {
31 issuer: String,
32 subject: String,
33 responsible: Option<String>,
34}
35
36impl Caller {
37 pub fn new(
42 issuer: impl Into<String>,
43 subject: impl Into<String>,
44 responsible: Option<String>,
45 ) -> Result<Self, Failure> {
46 let caller = Self {
47 issuer: issuer.into(),
48 subject: subject.into(),
49 responsible,
50 };
51 identifier(&caller.issuer)?;
52 identifier(&caller.subject)?;
53 if let Some(person) = &caller.responsible {
54 identifier(person)?;
55 }
56 Ok(caller)
57 }
58
59 pub fn local(subject: impl Into<String>) -> Result<Self, Failure> {
64 Self::new("local", subject, None)
65 }
66
67 #[must_use]
68 pub fn issuer(&self) -> &str {
69 &self.issuer
70 }
71
72 #[must_use]
73 pub fn subject(&self) -> &str {
74 &self.subject
75 }
76
77 #[must_use]
78 pub fn responsible(&self) -> Option<&str> {
79 self.responsible.as_deref()
80 }
81}
82
83impl TryFrom<CallerWire> for Caller {
84 type Error = Failure;
85
86 fn try_from(wire: CallerWire) -> Result<Self, Self::Error> {
87 Self::new(wire.issuer, wire.subject, wire.responsible)
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
94pub enum ReadDependency {
95 Document {
96 collection: String,
97 document_id: String,
98 },
99 Collection {
100 collection: String,
101 },
102 SourceInterval {
105 collection: String,
106 lower: Option<String>,
107 upper: Option<String>,
108 },
109}
110
111impl ReadDependency {
112 fn validate(&self) -> Result<(), Failure> {
113 match self {
114 Self::Document {
115 collection,
116 document_id,
117 } => {
118 identifier(collection)?;
119 identifier(document_id)
120 }
121 Self::Collection { collection } => identifier(collection),
122 Self::SourceInterval {
123 collection,
124 lower,
125 upper,
126 } => {
127 identifier(collection)?;
128 for bound in [lower, upper].into_iter().flatten() {
129 if bound.len() > 512 || bound.contains('\0') {
130 return Err(Failure::new(
131 "invalid_dependency",
132 "interval bounds must contain at most 512 UTF-8 bytes and no NUL",
133 ));
134 }
135 }
136 if matches!((lower, upper), (Some(lower), Some(upper)) if lower > upper) {
137 return Err(Failure::new(
138 "invalid_dependency",
139 "interval lower bound exceeds its exclusive upper bound",
140 ));
141 }
142 Ok(())
143 }
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
151pub enum Contract {
152 ExactVersion {
153 expected: Version,
154 },
155 Dependencies {
156 origin: Version,
157 reads: Vec<ReadDependency>,
158 },
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case")]
163pub enum ContractKind {
164 ExactVersion,
165 Dependencies,
166}
167
168impl Contract {
169 #[must_use]
170 pub const fn origin(&self) -> Version {
171 match self {
172 Self::ExactVersion { expected } => *expected,
173 Self::Dependencies { origin, .. } => *origin,
174 }
175 }
176
177 #[must_use]
178 pub const fn kind(&self) -> ContractKind {
179 match self {
180 Self::ExactVersion { .. } => ContractKind::ExactVersion,
181 Self::Dependencies { .. } => ContractKind::Dependencies,
182 }
183 }
184
185 fn validate(&self) -> Result<(), Failure> {
186 if let Self::Dependencies { reads, .. } = self {
187 for read in reads {
188 read.validate()?;
189 }
190 }
191 Ok(())
192 }
193}
194
195#[derive(Debug, Serialize, Deserialize)]
197#[serde(try_from = "MutationWire")]
198pub struct MutationRequest {
199 request_id: String,
200 branch: String,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 caller: Option<Caller>,
203 contract: Contract,
204 change: Change,
205}
206
207#[derive(Deserialize)]
208#[serde(deny_unknown_fields)]
209struct MutationWire {
210 request_id: String,
211 branch: String,
212 #[serde(default, deserialize_with = "present_caller")]
213 caller: Option<Caller>,
214 contract: Contract,
215 change: Change,
216}
217
218fn present_caller<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Caller>, D::Error> {
219 Caller::deserialize(deserializer).map(Some)
220}
221
222impl TryFrom<MutationWire> for MutationRequest {
223 type Error = Failure;
224
225 fn try_from(wire: MutationWire) -> Result<Self, Self::Error> {
226 identifier(&wire.request_id)?;
227 identifier(&wire.branch)?;
228 wire.contract.validate()?;
229 if let Change::Fork(fork) = &wire.change {
230 identifier(&fork.name)?;
231 placeable(&fork.name)?;
232 }
233 Ok(Self {
234 request_id: wire.request_id,
235 branch: wire.branch,
236 caller: wire.caller,
237 contract: wire.contract,
238 change: wire.change,
239 })
240 }
241}
242
243impl MutationRequest {
244 pub fn decode(bytes: &[u8]) -> Result<Self, Failure> {
249 serde_json::from_slice(bytes)
250 .map_err(|error| Failure::new("invalid_mutation", error.to_string()))
251 }
252
253 pub fn resolve(self, socket_owner_subject: &str) -> Result<ResolvedMutation, Failure> {
258 let caller = match self.caller {
259 Some(caller) => caller,
260 None => Caller::local(socket_owner_subject)?,
261 };
262 Ok(ResolvedMutation {
263 request_id: self.request_id,
264 branch: self.branch,
265 caller,
266 contract: self.contract,
267 change: self.change,
268 })
269 }
270}
271
272#[derive(Debug, Serialize)]
274pub struct ResolvedMutation {
275 request_id: String,
276 branch: String,
277 caller: Caller,
278 contract: Contract,
279 change: Change,
280}
281
282impl ResolvedMutation {
283 #[must_use]
284 pub fn request_id(&self) -> &str {
285 &self.request_id
286 }
287
288 #[must_use]
289 pub fn branch(&self) -> &str {
290 &self.branch
291 }
292
293 #[must_use]
294 pub const fn caller(&self) -> &Caller {
295 &self.caller
296 }
297
298 #[must_use]
299 pub const fn contract(&self) -> &Contract {
300 &self.contract
301 }
302
303 #[must_use]
304 pub const fn change(&self) -> &Change {
305 &self.change
306 }
307
308 #[must_use]
309 pub const fn origin(&self) -> Version {
310 self.contract.origin()
311 }
312
313 pub fn fingerprint(&self) -> Result<String, Failure> {
318 let mut bytes = FINGERPRINT_DOMAIN.to_vec();
319 bytes.extend_from_slice(&FINGERPRINT_FORMAT.to_be_bytes());
320 serde_json::to_writer(&mut bytes, self).map_err(Failure::storage)?;
321 Ok(Hash::of(&bytes).to_string())
322 }
323
324 pub(super) fn destination(&self) -> &str {
325 match &self.change {
326 Change::Fork(fork) => &fork.name,
327 _ => &self.branch,
328 }
329 }
330}
331
332pub(super) fn placeable(name: &str) -> Result<(), Failure> {
337 if name
338 .bytes()
339 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
340 {
341 return Ok(());
342 }
343 Err(Failure::new(
344 "branch_name_unplaceable",
345 "a new branch name may hold only ASCII letters, digits, `_`, `-` and `.`, so Lys can place it",
346 ))
347}
348
349pub(super) fn identifier(value: &str) -> Result<(), Failure> {
350 if value.is_empty() || value.len() > 512 || value.contains('\0') {
351 return Err(Failure::new(
352 "invalid_identifier",
353 "names must contain 1 to 512 UTF-8 bytes and no NUL",
354 ));
355 }
356 Ok(())
357}
358
359#[cfg(test)]
360#[path = "contracts/placeable_tests.rs"]
361mod placeable_tests;