aven_core/api/
change_sets.rs1use super::{Error, ErrorCode, Store, TaskCapture, TaskEdit};
2use crate::ids::{TaskId, WorkspaceId};
3use crate::operations::{ChangeSetOperation, IndexedChangeSetError};
4
5pub use crate::operations::{ChangeSetOutcome, ChangeTarget, CreatedTaskRef};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ChangeOp {
10 Create {
11 local_id: String,
12 fields: TaskCapture,
13 },
14 Update {
15 target: ChangeTarget,
16 fields: TaskEdit,
17 },
18 AddNote {
19 target: ChangeTarget,
20 body: String,
21 },
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ChangeSetError {
26 pub operation_index: Option<usize>,
28 pub error: Error,
29}
30
31impl std::fmt::Display for ChangeSetError {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 if let Some(index) = self.operation_index {
34 write!(f, "operation {index}: ")?;
35 }
36 self.error.fmt(f)
37 }
38}
39
40impl std::error::Error for ChangeSetError {
41 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
42 Some(&self.error)
43 }
44}
45
46impl From<IndexedChangeSetError> for ChangeSetError {
47 fn from(value: IndexedChangeSetError) -> Self {
48 Self {
49 operation_index: value.operation_index,
50 error: Error::from_internal(value.error),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct NoteReceipt {
57 pub note_id: String,
58 pub undo_token: String,
59}
60
61impl Store {
62 pub async fn apply_change_set(
66 &self,
67 workspace_id: &WorkspaceId,
68 ops: Vec<ChangeOp>,
69 ) -> Result<ChangeSetOutcome, ChangeSetError> {
70 let ops = ops
71 .into_iter()
72 .enumerate()
73 .map(|(index, op)| {
74 let result = match op {
75 ChangeOp::Create { local_id, fields } => fields
76 .into_draft()
77 .map(|draft| ChangeSetOperation::Create { local_id, draft }),
78 ChangeOp::Update { target, fields } => {
79 fields.into_update().map(|(project_id, update)| {
80 ChangeSetOperation::Update {
81 target,
82 project_id,
83 update,
84 }
85 })
86 }
87 ChangeOp::AddNote { target, body } => {
88 Ok(ChangeSetOperation::AddNote { target, body })
89 }
90 };
91 result.map_err(|error| ChangeSetError {
92 operation_index: Some(index),
93 error,
94 })
95 })
96 .collect::<Result<Vec<_>, _>>()?;
97 self.database
98 .apply_consumer_change_set(workspace_id, ops)
99 .await
100 .map(|result| result.outcome)
101 .map_err(Into::into)
102 }
103
104 pub async fn undo_change_set(&self, undo_token: &str) -> Result<(), Error> {
108 let token = serde_json::from_str(undo_token).map_err(|_| {
109 Error::new(
110 ErrorCode::Validation,
111 "invalid change set undo token".into(),
112 )
113 })?;
114 self.database
115 .undo_consumer_change_set(token)
116 .await
117 .map_err(Error::from_internal)
118 }
119
120 pub async fn add_note(
122 &self,
123 workspace_id: &WorkspaceId,
124 task_id: &TaskId,
125 body: String,
126 ) -> Result<NoteReceipt, Error> {
127 let result = self
128 .database
129 .apply_consumer_change_set(
130 workspace_id,
131 vec![ChangeSetOperation::AddNote {
132 target: ChangeTarget::Existing(task_id.clone()),
133 body,
134 }],
135 )
136 .await
137 .map_err(|error| Error::from_internal(error.error))?;
138 Ok(NoteReceipt {
139 note_id: result
140 .note_ids
141 .into_iter()
142 .next()
143 .expect("one note operation"),
144 undo_token: result.outcome.undo_token,
145 })
146 }
147
148 pub async fn undo_note(&self, undo_token: &str) -> Result<(), Error> {
150 self.undo_change_set(undo_token).await
151 }
152}