use crate::{
ChangeSeq, CommitId, ContentRef, DisplayName, InodeId, InodeKind, NamespaceId, RevisionNo,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CommitResponse {
pub namespace_id: NamespaceId,
pub commit_id: CommitId,
pub committed_seq: ChangeSeq,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum FilesystemChange {
#[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeCreated"))]
Created {
inode_id: InodeId,
inode_kind: InodeKind,
parent_inode_id: InodeId,
name: DisplayName,
#[serde(default, skip_serializing_if = "Option::is_none")]
revision_no: Option<RevisionNo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
content_ref: Option<ContentRef>,
},
#[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeContentChanged"))]
ContentChanged {
inode_id: InodeId,
revision_no: RevisionNo,
content_ref: ContentRef,
},
#[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeMoved"))]
Moved {
inode_id: InodeId,
from_parent_inode_id: InodeId,
from_name: DisplayName,
to_parent_inode_id: InodeId,
to_name: DisplayName,
},
#[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeDeleted"))]
Deleted {
inode_id: InodeId,
#[serde(default, skip_serializing_if = "Option::is_none")]
parent_inode_id: Option<InodeId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<DisplayName>,
},
#[cfg_attr(feature = "openapi", schema(title = "FilesystemChangeUndeleted"))]
Undeleted {
inode_id: InodeId,
parent_inode_id: InodeId,
name: DisplayName,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CommittedChange {
pub seq: ChangeSeq,
pub commit_id: CommitId,
pub committed_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
pub events: Vec<FilesystemChange>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ChangesResponse {
pub namespace_id: NamespaceId,
pub after_seq: ChangeSeq,
pub through_seq: ChangeSeq,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_after_seq: Option<ChangeSeq>,
pub changes: Vec<CommittedChange>,
}
#[cfg(test)]
mod tests {
use super::FilesystemChange;
use crate::{InodeId, InodeKind};
#[test]
fn filesystem_change_events_use_snake_case_kind_tags() {
let sample_content_ref = crate::ContentRef::blob_v1(
crate::ContentId::parse("con_0123456789abcdef0123456789abcdef")
.expect("valid content id"),
b"hello",
);
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"}"#;
let created = FilesystemChange::Created {
inode_id: InodeId(2),
inode_kind: InodeKind::Directory,
parent_inode_id: InodeId(1),
name: crate::DisplayName::parse("Docs").expect("valid display name"),
revision_no: None,
content_ref: None,
};
assert_eq!(
serde_json::to_string(&created).expect("serialize created event"),
r#"{"kind":"created","inode_id":2,"inode_kind":"dir","parent_inode_id":1,"name":"Docs"}"#
);
let created_file = FilesystemChange::Created {
inode_id: InodeId(2),
inode_kind: InodeKind::File,
parent_inode_id: InodeId(1),
name: crate::DisplayName::parse("a.txt").expect("valid display name"),
revision_no: Some(crate::RevisionNo(1)),
content_ref: Some(sample_content_ref.clone()),
};
assert_eq!(
serde_json::to_string(&created_file).expect("serialize created file event"),
format!(
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}}}"#
)
);
let content_changed = FilesystemChange::ContentChanged {
inode_id: InodeId(2),
revision_no: crate::RevisionNo(3),
content_ref: sample_content_ref,
};
assert_eq!(
serde_json::to_string(&content_changed).expect("serialize content changed event"),
format!(
r#"{{"kind":"content_changed","inode_id":2,"revision_no":3,"content_ref":{sample_content_ref_json}}}"#
)
);
let moved = FilesystemChange::Moved {
inode_id: InodeId(2),
from_parent_inode_id: InodeId(1),
from_name: crate::DisplayName::parse("a.txt").expect("valid display name"),
to_parent_inode_id: InodeId(3),
to_name: crate::DisplayName::parse("b.txt").expect("valid display name"),
};
assert_eq!(
serde_json::to_string(&moved).expect("serialize moved event"),
r#"{"kind":"moved","inode_id":2,"from_parent_inode_id":1,"from_name":"a.txt","to_parent_inode_id":3,"to_name":"b.txt"}"#
);
let deleted = FilesystemChange::Deleted {
inode_id: InodeId(2),
parent_inode_id: Some(InodeId(1)),
name: Some(crate::DisplayName::parse("a.txt").expect("valid display name")),
};
assert_eq!(
serde_json::to_string(&deleted).expect("serialize deleted event"),
r#"{"kind":"deleted","inode_id":2,"parent_inode_id":1,"name":"a.txt"}"#
);
let undeleted = FilesystemChange::Undeleted {
inode_id: InodeId(2),
parent_inode_id: InodeId(1),
name: crate::DisplayName::parse("a.txt").expect("valid display name"),
};
assert_eq!(
serde_json::to_string(&undeleted).expect("serialize undeleted event"),
r#"{"kind":"undeleted","inode_id":2,"parent_inode_id":1,"name":"a.txt"}"#
);
}
}