1use crate::{
7 ChangeSeq, CommitId, ContentRef, DisplayName, InodeId, InodeKind, NamespaceId, RevisionNo,
8};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
19pub struct CommitResponse {
20 pub namespace_id: NamespaceId,
22 pub commit_id: CommitId,
25 pub committed_seq: ChangeSeq,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
37#[serde(tag = "kind", rename_all = "snake_case")]
38pub enum FilesystemChange {
39 #[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeCreated"))]
41 Created {
42 inode_id: InodeId,
44 inode_kind: InodeKind,
46 parent_inode_id: InodeId,
48 name: DisplayName,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 revision_no: Option<RevisionNo>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 content_ref: Option<ContentRef>,
56 },
57 #[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeContentChanged"))]
60 ContentChanged {
61 inode_id: InodeId,
63 revision_no: RevisionNo,
65 content_ref: ContentRef,
67 },
68 #[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeMoved"))]
70 Moved {
71 inode_id: InodeId,
73 from_parent_inode_id: InodeId,
75 from_name: DisplayName,
77 to_parent_inode_id: InodeId,
79 to_name: DisplayName,
81 },
82 #[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeDeleted"))]
86 Deleted {
87 inode_id: InodeId,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
92 parent_inode_id: Option<InodeId>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 name: Option<DisplayName>,
96 },
97 #[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeUndeleted"))]
99 Undeleted {
100 inode_id: InodeId,
102 parent_inode_id: InodeId,
104 name: DisplayName,
106 },
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
112pub struct CommittedChange {
113 pub seq: ChangeSeq,
115 pub commit_id: CommitId,
117 pub committed_at_ms: u64,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub message: Option<String>,
123 pub events: Vec<FilesystemChange>,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
130#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
131pub struct ChangesResponse {
132 pub namespace_id: NamespaceId,
134 pub after_seq: ChangeSeq,
136 pub through_seq: ChangeSeq,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub next_after_seq: Option<ChangeSeq>,
141 pub changes: Vec<CommittedChange>,
143}
144
145#[cfg(test)]
146mod tests {
147 use super::FilesystemChange;
148 use crate::{InodeId, InodeKind};
149
150 #[test]
151 fn filesystem_change_events_use_snake_case_kind_tags() {
152 let sample_content_ref = crate::ContentRef::blob_v1(
153 crate::ContentId::parse("con_0123456789abcdef0123456789abcdef")
154 .expect("valid content id"),
155 b"hello",
156 );
157 let sample_content_ref_json = r#"{"kind":"blob_v1","content_id":"con_0123456789abcdef0123456789abcdef","size_bytes":5,"storage_checksum":{"algorithm":"sha256","value":"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"},"whole_file_sha256":"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}"#;
158
159 let created = FilesystemChange::Created {
160 inode_id: InodeId(2),
161 inode_kind: InodeKind::Directory,
162 parent_inode_id: InodeId(1),
163 name: crate::DisplayName::parse("Docs").expect("valid display name"),
164 revision_no: None,
165 content_ref: None,
166 };
167 assert_eq!(
168 serde_json::to_string(&created).expect("serialize created event"),
169 r#"{"kind":"created","inode_id":2,"inode_kind":"dir","parent_inode_id":1,"name":"Docs"}"#
170 );
171
172 let created_file = FilesystemChange::Created {
173 inode_id: InodeId(2),
174 inode_kind: InodeKind::File,
175 parent_inode_id: InodeId(1),
176 name: crate::DisplayName::parse("a.txt").expect("valid display name"),
177 revision_no: Some(crate::RevisionNo(1)),
178 content_ref: Some(sample_content_ref.clone()),
179 };
180 assert_eq!(
181 serde_json::to_string(&created_file).expect("serialize created file event"),
182 format!(
183 r#"{{"kind":"created","inode_id":2,"inode_kind":"file","parent_inode_id":1,"name":"a.txt","revision_no":1,"content_ref":{sample_content_ref_json}}}"#
184 )
185 );
186
187 let content_changed = FilesystemChange::ContentChanged {
188 inode_id: InodeId(2),
189 revision_no: crate::RevisionNo(3),
190 content_ref: sample_content_ref,
191 };
192 assert_eq!(
193 serde_json::to_string(&content_changed).expect("serialize content changed event"),
194 format!(
195 r#"{{"kind":"content_changed","inode_id":2,"revision_no":3,"content_ref":{sample_content_ref_json}}}"#
196 )
197 );
198
199 let moved = FilesystemChange::Moved {
200 inode_id: InodeId(2),
201 from_parent_inode_id: InodeId(1),
202 from_name: crate::DisplayName::parse("a.txt").expect("valid display name"),
203 to_parent_inode_id: InodeId(3),
204 to_name: crate::DisplayName::parse("b.txt").expect("valid display name"),
205 };
206 assert_eq!(
207 serde_json::to_string(&moved).expect("serialize moved event"),
208 r#"{"kind":"moved","inode_id":2,"from_parent_inode_id":1,"from_name":"a.txt","to_parent_inode_id":3,"to_name":"b.txt"}"#
209 );
210
211 let deleted = FilesystemChange::Deleted {
212 inode_id: InodeId(2),
213 parent_inode_id: Some(InodeId(1)),
214 name: Some(crate::DisplayName::parse("a.txt").expect("valid display name")),
215 };
216 assert_eq!(
217 serde_json::to_string(&deleted).expect("serialize deleted event"),
218 r#"{"kind":"deleted","inode_id":2,"parent_inode_id":1,"name":"a.txt"}"#
219 );
220
221 let undeleted = FilesystemChange::Undeleted {
222 inode_id: InodeId(2),
223 parent_inode_id: InodeId(1),
224 name: crate::DisplayName::parse("a.txt").expect("valid display name"),
225 };
226 assert_eq!(
227 serde_json::to_string(&undeleted).expect("serialize undeleted event"),
228 r#"{"kind":"undeleted","inode_id":2,"parent_inode_id":1,"name":"a.txt"}"#
229 );
230 }
231}