git_bug/entities/issue/issue_operation/
mod.rs1use std::{fmt::Display, str::FromStr};
14
15use gix::ObjectId;
16use operation_type::IssueOperationType;
17use serde::{Deserialize, Serialize};
18use simd_json::{base::ValueAsScalar, derived::ValueTryIntoObject, owned};
19
20use super::{
21 Issue,
22 data::{label::Label, status::Status},
23};
24use crate::replica::entity::{
25 id::{Id, entity_id::EntityId},
26 operation::operation_data::OperationData,
27};
28
29mod op;
30pub mod operation_type;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct File {
35 git_id: Vec<u8>,
36}
37impl TryFrom<&owned::Value> for File {
38 type Error = file_parse::Error;
39
40 fn try_from(value: &owned::Value) -> Result<Self, Self::Error> {
41 let s = value
42 .as_str()
43 .ok_or_else(|| file_parse::Error::ExpectedStr {
44 data: value.to_owned(),
45 })?;
46
47 let object_id = ObjectId::from_str(s)?;
48
49 Ok(Self {
50 git_id: object_id.as_slice().to_owned(),
51 })
52 }
53}
54
55#[allow(missing_docs)]
56pub mod file_parse {
57 use simd_json::owned;
58
59 #[derive(Debug, thiserror::Error)]
60 pub enum Error {
61 #[error("Expected the '{data}' data, to be a string, but was not.")]
62 ExpectedStr { data: owned::Value },
63
64 #[error("Failed to parse the object id from json data for file: {0}")]
65 ObjectIdParse(#[from] gix::hash::decode::Error),
66 }
67}
68
69impl Display for File {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 ObjectId::from_bytes_or_panic(self.git_id.as_slice()).fmt(f)
72 }
73}
74
75#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
77pub enum IssueOperationData {
78 AddComment {
80 message: String,
82 files: Vec<File>,
84 },
85 Create {
87 title: String,
89 message: String,
91 files: Vec<File>,
93 },
94 EditComment {
96 target: EntityId<Issue>,
98 message: String,
100 files: Vec<File>,
102 },
103 LabelChange {
105 added: Vec<Label>,
107 removed: Vec<Label>,
109 },
110 SetMetadata {
112 target: Id,
114 new_metadata: Vec<(String, String)>,
117 },
118 SetStatus {
120 status: Status,
122 },
123 SetTitle {
125 title: String,
127 was: String,
129 },
130
131 Noop {},
136}
137
138impl OperationData for IssueOperationData {
139 type DecodeError = decode::Error;
140
141 fn is_root(&self) -> bool {
142 matches!(self, IssueOperationData::Create { .. })
143 }
144
145 fn from_value(raw: owned::Value, predicted_type: u64) -> Result<Self, Self::DecodeError>
146 where
147 Self: Sized,
148 {
149 let r#type = IssueOperationType::try_from(predicted_type)?;
150
151 let object = raw
152 .try_into_object()
153 .map_err(|err| decode::Error::ObjectExpected { err })?;
154
155 match r#type {
156 IssueOperationType::AddComment => op::add_comment(object),
157 IssueOperationType::Create => op::create(object),
158 IssueOperationType::EditComment => op::edit_comment(object),
159 IssueOperationType::LabelChange => op::label_change(object),
160 IssueOperationType::NoOp => Ok(op::noop()),
161 IssueOperationType::SetMetadata => op::set_metadata(object),
162 IssueOperationType::SetStatus => op::set_status(object),
163 IssueOperationType::SetTitle => op::set_title(object),
164 }
165 }
166
167 fn as_value(&self) -> simd_json::borrowed::Object<'_> {
168 match self {
169 IssueOperationData::AddComment { message, files } => {
170 op::add_comment_value(message, files)
171 }
172 IssueOperationData::Create {
173 title,
174 message,
175 files,
176 } => op::create_value(title, message, files),
177 IssueOperationData::EditComment {
178 target,
179 message,
180 files,
181 } => op::edit_comment_value(target, message, files),
182 IssueOperationData::LabelChange { added, removed } => {
183 op::label_change_value(added, removed)
184 }
185 IssueOperationData::SetMetadata {
186 target,
187 new_metadata,
188 } => op::set_metadata_value(target, new_metadata),
189 IssueOperationData::SetStatus { status } => op::set_status_value(status),
190 IssueOperationData::SetTitle { title, was } => op::set_title_value(title, was),
191 IssueOperationData::Noop {} => op::noop_value(),
192 }
193 }
194
195 fn to_json_type(&self) -> u64 {
196 IssueOperationType::from(self).into()
197 }
198}
199
200#[allow(missing_docs)]
201pub mod decode {
202
203 use super::file_parse;
204 use crate::{
205 entities::issue::data::{label, status},
206 replica::entity::id,
207 };
208
209 #[derive(Debug, thiserror::Error)]
210 pub enum Error {
211 #[error("The json value did not contain the expected field '{field}'.")]
212 MissingJsonField { field: &'static str },
213
214 #[error("Expected to parse the '{field}' field in the json data: {err}")]
215 WrongJsonType {
216 err: simd_json::TryTypeError,
217 field: &'static str,
218 },
219
220 #[error("Failed to decode an object id (e.g., for a file blob): {0}")]
221 ObjectIdDecode(#[from] gix::hash::decode::Error),
222
223 #[error("Failed to decode an entity id (e.g., for a target field): {0}")]
224 IdDecode(#[from] id::decode::Error),
225
226 #[error(transparent)]
227 InvalidType(#[from] crate::entities::issue::issue_operation::operation_type::decode::Error),
228
229 #[error("Failed to parse the `files` field: {0}")]
230 FileParse(#[from] file_parse::Error),
231 #[error("Failed to parse the `added` or `removed` label fields: {0}")]
232 LabelParse(#[from] label::value_parse::Error),
233 #[error("Failed to parse the status fields: {0}")]
234 StatusParse(#[from] status::decode::Error),
235
236 #[error("Expected this operation json data to be a object, but was not: {err}")]
237 ObjectExpected { err: simd_json::TryTypeError },
238 }
239}