Skip to main content

haematite_cli/serve/storage/
branches.rs

1//! Fork origins are recorded at the branch's immutable roots.
2
3use haematite::branch::ForkWrite;
4use serde::{Deserialize, Serialize};
5
6use super::{Captured, Failure, Storage, identifier, reads};
7use crate::serve::protocol::Version;
8
9const PROVENANCE_KEY: &[u8] = b"b\0fork";
10
11#[derive(Debug, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct ForkProvenance {
14    pub branch: String,
15    pub version: Version,
16}
17
18/// The provenance row as stored: an ordinary branch's bytes are the same as
19/// before lines, and a line's add `"line": true` (STOR-050 R2). Each fork
20/// writes its own, so a fork of a line is a line only when it asks to be.
21#[derive(Debug, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23struct Stored {
24    branch: String,
25    version: Version,
26    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
27    line: bool,
28}
29
30pub(super) fn provenance_write(
31    writer: &haematite::db::branches::BranchDatabase,
32    branch: &str,
33    at: Version,
34    line: bool,
35) -> Result<ForkWrite, Failure> {
36    Ok(ForkWrite {
37        shard_id: writer.shard_for(PROVENANCE_KEY),
38        key: PROVENANCE_KEY.to_vec(),
39        value: Some(
40            serde_json::to_vec(&Stored {
41                branch: branch.to_owned(),
42                version: at,
43                line,
44            })
45            .map_err(Failure::storage)?,
46        ),
47    })
48}
49
50/// A provenance row's origin and whether it made a line.
51///
52/// # Errors
53/// A row that does not decode, or names no branch.
54pub(in crate::serve) fn decode(bytes: &[u8]) -> Result<(ForkProvenance, bool), Failure> {
55    let stored: Stored = serde_json::from_slice(bytes).map_err(Failure::storage)?;
56    identifier(&stored.branch)?;
57    Ok((
58        ForkProvenance {
59            branch: stored.branch,
60            version: stored.version,
61        },
62        stored.line,
63    ))
64}
65
66/// The fork origin recorded at `at`'s roots and whether it made a line, or
67/// `None` for a branch that was not forked.
68///
69/// # Errors
70/// As [`decode`]; storage failures.
71pub(super) fn origin(
72    reader: &haematite::db::ReadOnlyDatabase,
73    at: &Captured,
74) -> Result<Option<(ForkProvenance, bool)>, Failure> {
75    reads::get_bytes(reader, at, PROVENANCE_KEY)?
76        .map(|bytes| decode(&bytes))
77        .transpose()
78}
79
80pub(super) fn read_provenance(
81    reader: &haematite::db::ReadOnlyDatabase,
82    at: &Captured,
83) -> Result<ForkProvenance, Failure> {
84    provenance(reader, at)?.ok_or_else(|| {
85        Failure::new(
86            "fork_provenance_missing",
87            "branch has no recorded fork origin",
88        )
89    })
90}
91
92/// The fork origin recorded at `at`'s roots, or `None` for a branch that was
93/// not forked (STOR-003 HR1: a release reads it for every live branch).
94pub(super) fn provenance(
95    reader: &haematite::db::ReadOnlyDatabase,
96    at: &Captured,
97) -> Result<Option<ForkProvenance>, Failure> {
98    Ok(origin(reader, at)?.map(|(provenance, _line)| provenance))
99}
100
101impl Storage {
102    /// Read the explicit fork origin at the selected immutable roots.
103    ///
104    /// # Errors
105    /// Refuses missing or invalid provenance and propagates storage failures.
106    pub fn fork_provenance(&self, at: &Captured) -> Result<ForkProvenance, Failure> {
107        read_provenance(&self.reader, at)
108    }
109}