Skip to main content

atelier_sdk/
landing.rs

1use std::fmt;
2use std::str::FromStr;
3
4use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
5
6use crate::config::Actor;
7use crate::error::Error;
8use crate::session::SessionId;
9
10/// A landing request's identity: `r` plus its row in the workspace store.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct RequestId(pub(crate) i64);
13
14impl fmt::Display for RequestId {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "r{}", self.0)
17    }
18}
19
20impl FromStr for RequestId {
21    type Err = Error;
22
23    fn from_str(text: &str) -> Result<Self, Self::Err> {
24        let not_found = || Error::RequestNotFound(text.to_owned());
25        let digits = text.strip_prefix('r').ok_or_else(not_found)?;
26        let row: i64 = digits.parse().map_err(|_| not_found())?;
27        Ok(Self(row))
28    }
29}
30
31/// A change's application to land on the shared line (ADR-0007): its
32/// requester and the approvals its gate has gathered, open until it lands,
33/// parks, or closes.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct LandingRequest {
36    /// The request's identity.
37    pub id: RequestId,
38    /// The session whose change the request would land.
39    pub session_id: SessionId,
40    /// Who opened the request.
41    pub requester: Actor,
42    /// Where the request stands in its gate.
43    pub state: RequestState,
44    /// The approvals counting toward the gate; dismissed ones are gone.
45    pub approvals: Vec<Approval>,
46    /// When the request opened, in unix milliseconds.
47    pub created_at_ms: i64,
48}
49
50/// Where a landing request stands in its gate.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RequestState {
53    /// Open, gathering approvals.
54    Open,
55    /// The gate is satisfied; the apply has not run.
56    Approved,
57    /// The change landed on the shared line.
58    Landed,
59    /// The apply hit a conflict; a new snapshot re-opens the gate.
60    Parked,
61    /// An approver rejected the request.
62    Rejected,
63    /// The session closed without landing.
64    Abandoned,
65}
66
67impl RequestState {
68    /// The state's canonical lowercase name, as stored and rendered.
69    #[must_use]
70    pub fn as_str(self) -> &'static str {
71        match self {
72            Self::Open => "open",
73            Self::Approved => "approved",
74            Self::Landed => "landed",
75            Self::Parked => "parked",
76            Self::Rejected => "rejected",
77            Self::Abandoned => "abandoned",
78        }
79    }
80}
81
82impl fmt::Display for RequestState {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.write_str(self.as_str())
85    }
86}
87
88impl FromStr for RequestState {
89    type Err = Error;
90
91    fn from_str(text: &str) -> Result<Self, Self::Err> {
92        match text {
93            "open" => Ok(Self::Open),
94            "approved" => Ok(Self::Approved),
95            "landed" => Ok(Self::Landed),
96            "parked" => Ok(Self::Parked),
97            "rejected" => Ok(Self::Rejected),
98            "abandoned" => Ok(Self::Abandoned),
99            other => Err(Error::Engine(format!("unknown request state: {other}"))),
100        }
101    }
102}
103
104impl ToSql for RequestState {
105    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
106        Ok(ToSqlOutput::from(self.as_str()))
107    }
108}
109
110impl FromSql for RequestState {
111    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
112        let text = value.as_str()?;
113        Self::from_str(text).map_err(|error| FromSqlError::Other(error.to_string().into()))
114    }
115}
116
117/// A recorded grant by an actor toward a request's gate, tied to the
118/// snapshot of the change it covered.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Approval {
121    /// Who approved.
122    pub actor: Actor,
123    /// The snapshot of the change the approval covered.
124    pub snapshot: String,
125    /// When the approval was granted, in unix milliseconds.
126    pub at_ms: i64,
127}
128
129/// One line a landed request stepped back off (ADR-0011): the source
130/// and the head the line returned to.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct Restore {
133    /// The mount the line belongs to; `None` for the root.
134    pub source: Option<String>,
135    /// The snapshot the line's head returned to.
136    pub head: String,
137}
138
139/// One source's landing under a request: the root's when `source` is
140/// `None`; the source's shared line's new head.
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct Landing {
143    /// The mount the landing belongs to; `None` for the root.
144    pub source: Option<String>,
145    /// The landed snapshot, the source's shared line's new head.
146    pub snapshot: String,
147}
148
149/// What a landing attempt produced. The apply fans out per source
150/// (ADR-0009): every landing that happened is recorded and stands,
151/// whatever the sources after it did.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub enum GateOutcome {
154    /// Every touched source landed.
155    Landed {
156        /// The landings, one per touched source.
157        landings: Vec<Landing>,
158    },
159    /// The gate wants more approvals before the apply runs.
160    Pending {
161        /// The request as it stands, approvals included.
162        request: LandingRequest,
163        /// How many approvals the gate needs in total.
164        required: u32,
165    },
166    /// At least one source's apply hit a conflict: the request parked,
167    /// that line did not move — and the sources in `landings` landed
168    /// before or despite it (ADR-0007, per line).
169    Parked {
170        /// The request, now parked.
171        request: LandingRequest,
172        /// The landings that happened before or despite the conflict.
173        landings: Vec<Landing>,
174        /// The sources whose applies conflicted; `None` names the root.
175        parked: Vec<Option<String>>,
176    },
177}