1use std::collections::BTreeMap;
4use std::fmt;
5
6use olai_stack_topology::EnvManifest;
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10macro_rules! id {
11 ($(#[$doc:meta])* $name:ident) => {
12 $(#[$doc])*
13 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14 #[serde(transparent)]
15 pub struct $name(pub String);
16
17 impl $name {
18 pub(crate) fn generate() -> Self {
19 $name(uuid::Uuid::new_v4().to_string())
20 }
21 }
22
23 impl fmt::Display for $name {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.write_str(&self.0)
26 }
27 }
28 };
29}
30
31id!(
32 WorkspaceId
34);
35id!(
36 EnvironmentId
38);
39id!(
40 InstanceId
42);
43
44#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
47pub struct SecretRef {
48 pub provider: String,
50 pub key: String,
52}
53
54#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum VarValue {
58 Literal(String),
60 Secret(SecretRef),
62}
63
64impl fmt::Debug for VarValue {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 VarValue::Literal(_) => f.write_str("Literal(<redacted>)"),
69 VarValue::Secret(r) => f.debug_tuple("Secret").field(r).finish(),
70 }
71 }
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(transparent)]
77pub struct VarSet(pub BTreeMap<String, VarValue>);
78
79impl VarSet {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn set(&mut self, name: impl Into<String>, value: VarValue) -> &mut Self {
87 self.0.insert(name.into(), value);
88 self
89 }
90
91 pub fn get(&self, name: &str) -> Option<&VarValue> {
93 self.0.get(name)
94 }
95
96 pub fn remove(&mut self, name: &str) -> Option<VarValue> {
98 self.0.remove(name)
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.0.is_empty()
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
109pub enum VarScope {
110 Workspace(WorkspaceId),
112 Environment(EnvironmentId),
114 Draft(EnvironmentId),
116}
117
118#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
122pub struct SourceBinding {
123 pub name: String,
125 #[serde(default)]
127 pub config: serde_json::Value,
128}
129
130impl SourceBinding {
131 pub fn new(name: impl Into<String>) -> Self {
133 SourceBinding {
134 name: name.into(),
135 config: serde_json::Value::Null,
136 }
137 }
138
139 pub fn with_config(mut self, config: serde_json::Value) -> Self {
141 self.config = config;
142 self
143 }
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct Workspace {
150 pub id: WorkspaceId,
152 pub name: String,
154 pub mesh: bool,
156 pub vars: VarSet,
158 pub sources: BTreeMap<String, serde_json::Value>,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct Draft {
168 pub manifest: EnvManifest,
170 pub modules: BTreeMap<String, String>,
173 pub vars: VarSet,
175}
176
177impl Draft {
178 pub fn new(manifest: EnvManifest) -> Self {
180 Draft {
181 manifest,
182 modules: BTreeMap::new(),
183 vars: VarSet::new(),
184 }
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, Eq)]
191pub struct Environment {
192 pub id: EnvironmentId,
194 pub workspace: WorkspaceId,
196 pub name: String,
198 pub draft: Draft,
200 pub vars: VarSet,
202 pub head: Option<u32>,
204}
205
206#[derive(Clone, Debug, PartialEq, Eq)]
210pub struct Revision {
211 pub(crate) environment: EnvironmentId,
212 pub(crate) number: u32,
213 pub(crate) manifest: EnvManifest,
214 pub(crate) manifest_toml: String,
215 pub(crate) modules: BTreeMap<String, String>,
216 pub(crate) vars: VarSet,
217 pub(crate) digest: String,
218}
219
220impl Revision {
221 pub fn environment(&self) -> &EnvironmentId {
223 &self.environment
224 }
225
226 pub fn number(&self) -> u32 {
228 self.number
229 }
230
231 pub fn manifest(&self) -> &EnvManifest {
233 &self.manifest
234 }
235
236 pub fn manifest_toml(&self) -> &str {
238 &self.manifest_toml
239 }
240
241 pub fn modules(&self) -> &BTreeMap<String, String> {
243 &self.modules
244 }
245
246 pub fn vars(&self) -> &VarSet {
248 &self.vars
249 }
250
251 pub fn digest(&self) -> &str {
253 &self.digest
254 }
255}
256
257pub(crate) fn digest(
259 manifest_toml: &str,
260 modules: &BTreeMap<String, String>,
261 vars: &VarSet,
262) -> Result<String, serde_json::Error> {
263 #[derive(Serialize)]
264 struct Content<'a> {
265 manifest: &'a str,
266 modules: &'a BTreeMap<String, String>,
267 vars: &'a VarSet,
268 }
269 let bytes = serde_json::to_vec(&Content {
270 manifest: manifest_toml,
271 modules,
272 vars,
273 })?;
274 Ok(hex::encode(Sha256::digest(bytes)))
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
279#[serde(rename_all = "snake_case")]
280pub enum Desired {
281 Running,
283 Stopped,
285}
286
287impl Desired {
288 pub(crate) fn as_str(self) -> &'static str {
289 match self {
290 Desired::Running => "running",
291 Desired::Stopped => "stopped",
292 }
293 }
294
295 pub(crate) fn parse(s: &str) -> Self {
296 if s == "running" {
297 Desired::Running
298 } else {
299 Desired::Stopped
300 }
301 }
302}
303
304#[derive(Clone, Debug, PartialEq, Eq)]
308pub struct Instance {
309 pub id: InstanceId,
311 pub environment: EnvironmentId,
313 pub revision: u32,
315 pub desired: Desired,
317 pub overrides: VarSet,
319 pub sources: Vec<SourceBinding>,
321 pub slot: Option<u16>,
323}
324
325#[derive(Clone, Debug, PartialEq, Eq)]
328pub struct SlotLease {
329 pub slot: u16,
331 pub instance: InstanceId,
333}