1use alembic_core::{key_string, JsonMap, Key, Object, Schema, TypeName, Uid};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum BackendId {
13 Int(u64),
14 String(String),
15}
16
17impl fmt::Display for BackendId {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 BackendId::Int(id) => write!(f, "{}", id),
21 BackendId::String(id) => write!(f, "{}", id),
22 }
23 }
24}
25
26impl From<u64> for BackendId {
27 fn from(id: u64) -> Self {
28 BackendId::Int(id)
29 }
30}
31
32impl From<String> for BackendId {
33 fn from(id: String) -> Self {
34 BackendId::String(id)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct FieldChange {
41 pub field: String,
43 pub from: serde_json::Value,
45 pub to: serde_json::Value,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51#[serde(tag = "op", rename_all = "snake_case")]
52pub enum Op {
53 Create {
55 uid: Uid,
56 type_name: TypeName,
57 desired: Object,
58 },
59 Update {
61 uid: Uid,
62 type_name: TypeName,
63 desired: Object,
64 changes: Vec<FieldChange>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 backend_id: Option<BackendId>,
67 },
68 Delete {
70 uid: Uid,
71 type_name: TypeName,
72 key: Key,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 backend_id: Option<BackendId>,
75 },
76}
77
78impl Op {
79 pub fn uid(&self) -> Uid {
81 match self {
82 Op::Create { uid, .. } => *uid,
83 Op::Update { uid, .. } => *uid,
84 Op::Delete { uid, .. } => *uid,
85 }
86 }
87
88 pub fn type_name(&self) -> &TypeName {
90 match self {
91 Op::Create { type_name, .. } => type_name,
92 Op::Update { type_name, .. } => type_name,
93 Op::Delete { type_name, .. } => type_name,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct Plan {
101 pub schema: Schema,
103 pub ops: Vec<Op>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub summary: Option<PlanSummary>,
108}
109
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
112pub struct PlanSummary {
113 pub create: usize,
115 pub update: usize,
117 pub delete: usize,
119}
120
121impl Plan {
122 pub fn summary(&self) -> PlanSummary {
124 let mut summary = PlanSummary::default();
125 for op in &self.ops {
126 match op {
127 Op::Create { .. } => summary.create += 1,
128 Op::Update { .. } => summary.update += 1,
129 Op::Delete { .. } => summary.delete += 1,
130 }
131 }
132 summary
133 }
134}
135
136#[derive(Debug, Clone)]
138pub struct ObservedObject {
139 pub type_name: TypeName,
141 pub key: Key,
143 pub attrs: JsonMap,
145 pub backend_id: Option<BackendId>,
147}
148
149#[derive(Debug, Default, Clone)]
151pub struct ObservedState {
152 pub by_backend_id: BTreeMap<(TypeName, BackendId), ObservedObject>,
154 pub by_key: BTreeMap<(TypeName, String), ObservedObject>,
156}
157
158impl ObservedState {
159 pub fn insert(&mut self, object: ObservedObject) {
161 if let Some(id) = &object.backend_id {
162 self.by_backend_id
163 .insert((object.type_name.clone(), id.clone()), object.clone());
164 }
165 self.by_key
166 .insert((object.type_name.clone(), key_string(&object.key)), object);
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct AppliedOp {
173 pub uid: Uid,
175 pub type_name: TypeName,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub backend_id: Option<BackendId>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct ApplyReport {
185 pub applied: Vec<AppliedOp>,
187}
188
189#[derive(Debug, Clone, Default, Serialize, Deserialize)]
191pub struct ProvisionReport {
192 pub created_fields: Vec<String>,
194 pub created_tags: Vec<String>,
196 #[serde(default, skip_serializing_if = "Vec::is_empty")]
198 pub created_object_types: Vec<String>,
199 #[serde(default, skip_serializing_if = "Vec::is_empty")]
201 pub created_object_fields: Vec<String>,
202 #[serde(default, skip_serializing_if = "Vec::is_empty")]
204 pub deprecated_object_types: Vec<String>,
205 #[serde(default, skip_serializing_if = "Vec::is_empty")]
207 pub deprecated_object_fields: Vec<String>,
208 #[serde(default, skip_serializing_if = "Vec::is_empty")]
210 pub deleted_object_types: Vec<String>,
211 #[serde(default, skip_serializing_if = "Vec::is_empty")]
213 pub deleted_object_fields: Vec<String>,
214}
215
216#[async_trait]
218pub trait Adapter: Send + Sync {
219 async fn read(
220 &self,
221 schema: &Schema,
222 types: &[TypeName],
223 state: &crate::state::StateStore,
224 ) -> anyhow::Result<ObservedState>;
225 async fn write(
226 &self,
227 schema: &Schema,
228 ops: &[Op],
229 state: &crate::state::StateStore,
230 ) -> anyhow::Result<ApplyReport>;
231 async fn ensure_schema(&self, _schema: &Schema) -> anyhow::Result<ProvisionReport> {
232 Ok(ProvisionReport::default())
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use alembic_core::{Key, TypeName, Uid};
240
241 #[test]
242 fn backend_id_serialization() {
243 let int_id = BackendId::Int(123);
244 let json = serde_json::to_string(&int_id).unwrap();
245 assert_eq!(json, "123");
246 let back: BackendId = serde_json::from_str(&json).unwrap();
247 assert_eq!(back, int_id);
248
249 let str_id = BackendId::String("uuid".to_string());
250 let json = serde_json::to_string(&str_id).unwrap();
251 assert_eq!(json, "\"uuid\"");
252 let back: BackendId = serde_json::from_str(&json).unwrap();
253 assert_eq!(back, str_id);
254 }
255
256 #[test]
257 fn op_helpers() {
258 let uid = Uid::from_u128(1);
259 let type_name = TypeName::new("test.type");
260 let op = Op::Delete {
261 uid,
262 type_name: type_name.clone(),
263 key: Key::default(),
264 backend_id: None,
265 };
266 assert_eq!(op.uid(), uid);
267 assert_eq!(op.type_name(), &type_name);
268 }
269}