use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct WriteOutput {
pub r#type: &'static str,
pub status: &'static str,
pub path: String,
pub bytes_written: u64,
pub checksum: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub backup_path: Option<String>,
pub elapsed_ms: u64,
pub platform: PlatformInfo,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct PlatformInfo {
pub fsync: &'static str,
pub dir_fsync: &'static str,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ReadOutput {
pub r#type: &'static str,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
pub lines: u64,
pub bytes: u64,
pub checksum: String,
pub permissions: String,
pub modified: String,
pub kind: String,
pub binary: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<LineRange>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verified: Option<bool>,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct LineRange {
pub start: usize,
pub end: usize,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchBegin {
pub r#type: &'static str,
pub path: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchMatch {
pub r#type: &'static str,
pub path: String,
pub line_number: u64,
pub lines: String,
pub byte_offset: u64,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub submatches: Vec<Submatch>,
}
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
pub struct Submatch {
pub r#match: String,
pub start: usize,
pub end: usize,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchContext {
pub r#type: &'static str,
pub path: String,
pub line_number: u64,
pub lines: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchEnd {
pub r#type: &'static str,
pub path: String,
pub stats: FileStats,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct FileStats {
pub matches: u64,
pub lines_searched: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchCount {
pub r#type: &'static str,
pub path: String,
pub count: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct SearchFile {
pub r#type: &'static str,
pub path: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct Summary {
pub r#type: &'static str,
pub files_visited: u64,
pub files_matched: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub files_modified: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub files_skipped: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_matches: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_replacements: Option<u64>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ReplaceResult {
pub r#type: &'static str,
pub path: String,
pub replacements: u64,
pub bytes_before: u64,
pub bytes_after: u64,
pub checksum_before: String,
pub checksum_after: String,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct EditOutput {
pub r#type: &'static str,
pub path: String,
pub edits: u64,
pub mode: String,
pub bytes_before: u64,
pub bytes_after: u64,
pub checksum_before: String,
pub checksum_after: String,
pub lines_before: u64,
pub lines_after: u64,
pub elapsed_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub fuzzy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strategy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strategies_tried: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub similarity: Option<f64>,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct DryRunPlan {
pub r#type: &'static str,
pub operation: String,
pub path: String,
pub would_modify: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ListEntry {
pub r#type: &'static str,
pub path: String,
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified: Option<String>,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ListSummary {
pub r#type: &'static str,
pub files: u64,
pub dirs: u64,
pub symlinks: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub by_extension: Option<std::collections::BTreeMap<String, u64>>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct CalcOutput {
pub r#type: &'static str,
pub expression: String,
pub result: String,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct RegexOutput {
pub r#type: &'static str,
pub regex: String,
pub examples: u64,
pub anchored: bool,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct TransformResult {
pub r#type: &'static str,
pub path: String,
pub language: String,
pub matches: u64,
pub replacements: u64,
pub bytes_before: u64,
pub bytes_after: u64,
pub checksum_before: String,
pub checksum_after: String,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ScopeResult {
pub r#type: &'static str,
pub path: String,
pub language: String,
pub query: String,
pub action: String,
pub scopes_matched: u64,
pub bytes_before: u64,
pub bytes_after: u64,
pub checksum_before: String,
pub checksum_after: String,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct BackupResult {
pub r#type: &'static str,
pub path: String,
pub backup_path: String,
pub checksum: String,
pub bytes: u64,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct RollbackResult {
pub r#type: &'static str,
pub path: String,
pub restored_from: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum_before: Option<String>,
pub checksum_after: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub verified: Option<bool>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ApplyResult {
pub r#type: &'static str,
pub path: String,
pub format_detected: String,
pub hunks_applied: u64,
pub bytes_before: u64,
pub bytes_after: u64,
pub checksum_before: String,
pub checksum_after: String,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct BatchOpResult<'a> {
pub r#type: &'static str,
pub index: u64,
pub op: &'a str,
pub status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct BatchSummary {
pub r#type: &'static str,
pub operations: u64,
pub succeeded: u64,
pub failed: u64,
pub dry_run: bool,
pub elapsed_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub committed: Option<bool>,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct DiffStatOutput {
pub r#type: &'static str,
pub identical: bool,
pub file_a: String,
pub file_b: String,
pub insertions: u64,
pub deletions: u64,
pub similarity_ratio: f32,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct DiffUnifiedOutput {
pub r#type: &'static str,
pub identical: bool,
pub format: &'static str,
pub content: String,
pub similarity_ratio: f32,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct DiffChangeOutput<'a> {
pub r#type: &'static str,
pub tag: &'static str,
pub line: usize,
pub text: &'a str,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct DiffSummaryOutput {
pub r#type: &'static str,
pub identical: bool,
pub file_a: String,
pub file_b: String,
pub lines_a: usize,
pub lines_b: usize,
pub similarity_ratio: f32,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct HashOutput {
pub r#type: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<&'static str>,
pub algorithm: &'static str,
pub value: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verified: Option<bool>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct TransferPlan {
pub r#type: &'static str,
pub operation: &'static str,
pub source: String,
pub target: String,
pub would_modify: bool,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct MoveOutput {
pub r#type: &'static str,
pub source: String,
pub target: String,
pub bytes: u64,
pub checksum: String,
pub cross_device: bool,
pub atomic: bool,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct CopyOutput {
pub r#type: &'static str,
pub source: String,
pub target: String,
pub bytes: usize,
pub checksum: String,
pub verified: bool,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct DeleteOutput {
pub r#type: &'static str,
pub path: String,
pub bytes: u64,
pub checksum_before: String,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct CountByExtOutput {
pub r#type: &'static str,
pub mode: &'static str,
pub by_extension: std::collections::BTreeMap<String, ExtCountOutput>,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct CountTotalOutput {
pub r#type: &'static str,
pub mode: &'static str,
pub total: CountTotals,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct CountTotals {
pub files: u64,
pub lines: u64,
pub blank: u64,
pub bytes: u64,
}
#[derive(Default, Debug, PartialEq, Serialize, JsonSchema)]
pub struct ExtCountOutput {
pub files: u64,
pub lines: u64,
pub blank: u64,
pub bytes: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct BackupPlan {
pub r#type: &'static str,
pub operation: &'static str,
pub path: String,
pub bytes: u64,
pub checksum: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct BackupSummary {
pub r#type: &'static str,
pub files_backed_up: u64,
pub total_bytes: u64,
pub dry_run: bool,
pub elapsed_ms: u64,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct RollbackPlan {
pub r#type: &'static str,
pub operation: &'static str,
pub path: String,
pub restore_from: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ApplyPlan {
pub r#type: &'static str,
pub operation: &'static str,
pub path: String,
pub format_detected: String,
pub hunks: usize,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ReplacePreview {
pub r#type: &'static str,
pub path: String,
pub replacements: u64,
pub diff: String,
}
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct ReplaceErrorEvent {
pub status: &'static str,
pub path: String,
pub message: String,
pub error_class: &'static str,
pub retryable: bool,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct TextFieldsOutput<'a> {
pub r#type: &'static str,
pub fields: Vec<&'a str>,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct TextValuesOutput<'a> {
pub r#type: &'static str,
pub values: Vec<&'a str>,
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_valid_ndjson_object<T: serde::Serialize>(val: &T) {
let json = serde_json::to_value(val).expect("serialize to Value");
assert!(json.is_object(), "expected JSON object, got: {json}");
let obj = json.as_object().unwrap();
assert!(obj.contains_key("type"), "missing 'type' field");
}
fn assert_roundtrip_json<T: serde::Serialize>(val: &T) {
let json_str = serde_json::to_string(val).expect("serialize to string");
let reparsed: serde_json::Value =
serde_json::from_str(&json_str).expect("reparse from string");
assert!(reparsed.is_object(), "roundtrip produced non-object");
}
#[test]
fn roundtrip_write_output() {
let val = WriteOutput {
r#type: "write",
status: "ok",
path: "/tmp/test.rs".into(),
bytes_written: 42,
checksum: "abc123".into(),
checksum_before: None,
backup_path: None,
elapsed_ms: 5,
platform: PlatformInfo {
fsync: "sync_data",
dir_fsync: "sync_all",
},
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_batch_summary() {
let val = BatchSummary {
r#type: "summary",
operations: 10,
succeeded: 9,
failed: 1,
dry_run: false,
elapsed_ms: 100,
transaction: Some(true),
committed: Some(false),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_diff_stat() {
let val = DiffStatOutput {
r#type: "diff",
identical: false,
file_a: "a.rs".into(),
file_b: "b.rs".into(),
insertions: 10,
deletions: 5,
similarity_ratio: 0.85,
elapsed_ms: 3,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_summary() {
let val = Summary {
r#type: "summary",
files_visited: 100,
files_matched: 5,
files_modified: Some(3),
files_skipped: None,
total_matches: Some(42),
total_replacements: None,
elapsed_ms: 200,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_edit_output() {
let val = EditOutput {
r#type: "edit",
path: "/tmp/edit.rs".into(),
edits: 1,
mode: "old_new".into(),
bytes_before: 100,
bytes_after: 110,
checksum_before: "aaa".into(),
checksum_after: "bbb".into(),
lines_before: 10,
lines_after: 11,
elapsed_ms: 2,
fuzzy: Some(true),
strategy: Some("block_anchor".into()),
strategies_tried: Some(8),
similarity: Some(0.95),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_diff_summary() {
let val = DiffSummaryOutput {
r#type: "summary",
identical: true,
file_a: "x.rs".into(),
file_b: "y.rs".into(),
lines_a: 50,
lines_b: 50,
similarity_ratio: 1.0,
elapsed_ms: 1,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn skip_serializing_if_omits_none_fields() {
let val = WriteOutput {
r#type: "write",
status: "ok",
path: "/tmp/t.rs".into(),
bytes_written: 10,
checksum: "x".into(),
checksum_before: None,
backup_path: None,
elapsed_ms: 1,
platform: PlatformInfo {
fsync: "sync_data",
dir_fsync: "best_effort",
},
};
let json = serde_json::to_value(&val).unwrap();
let obj = json.as_object().unwrap();
assert!(!obj.contains_key("checksum_before"));
assert!(!obj.contains_key("backup_path"));
}
#[test]
fn roundtrip_read_output() {
let val = ReadOutput {
r#type: "read",
path: "/tmp/read.rs".into(),
content: Some("hello".into()),
lines: 1,
bytes: 5,
checksum: "abc".into(),
permissions: "0644".into(),
modified: "2026-01-01T00:00:00Z".into(),
kind: "file".into(),
binary: false,
range: None,
verified: None,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_search_match() {
let val = SearchMatch {
r#type: "match",
path: "/tmp/s.rs".into(),
line_number: 10,
lines: "fn main()".into(),
byte_offset: 42,
submatches: vec![],
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_replace_result() {
let val = ReplaceResult {
r#type: "replaced",
path: "/tmp/r.rs".into(),
replacements: 3,
bytes_before: 100,
bytes_after: 110,
checksum_before: "aaa".into(),
checksum_after: "bbb".into(),
elapsed_ms: 5,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_transform_result() {
let val = TransformResult {
r#type: "transform",
path: "/tmp/t.rs".into(),
language: "rust".into(),
matches: 2,
replacements: 2,
bytes_before: 50,
bytes_after: 55,
checksum_before: "aa".into(),
checksum_after: "bb".into(),
elapsed_ms: 3,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_scope_result() {
let val = ScopeResult {
r#type: "scope",
path: "/tmp/sc.rs".into(),
language: "rust".into(),
query: "comments".into(),
action: "delete".into(),
scopes_matched: 5,
bytes_before: 200,
bytes_after: 180,
checksum_before: "x".into(),
checksum_after: "y".into(),
elapsed_ms: 10,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_backup_result() {
let val = BackupResult {
r#type: "backup",
path: "/tmp/src.rs".into(),
backup_path: "/tmp/src.rs.bak".into(),
checksum: "hash".into(),
bytes: 500,
elapsed_ms: 2,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_rollback_result() {
let val = RollbackResult {
r#type: "rollback",
path: "/tmp/rb.rs".into(),
restored_from: "/tmp/rb.rs.bak".into(),
checksum_before: Some("old".into()),
checksum_after: "new".into(),
verified: Some(true),
elapsed_ms: 3,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_apply_result() {
let val = ApplyResult {
r#type: "apply",
path: "/tmp/ap.rs".into(),
format_detected: "unified".into(),
hunks_applied: 2,
bytes_before: 100,
bytes_after: 120,
checksum_before: "a".into(),
checksum_after: "b".into(),
elapsed_ms: 4,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_hash_output() {
let val = HashOutput {
r#type: "hash",
path: Some("/tmp/h.rs".into()),
source: None,
algorithm: "blake3",
value: "blake3hash".into(),
bytes: Some(1024),
verified: None,
elapsed_ms: 1,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_calc_output() {
let val = CalcOutput {
r#type: "calc",
expression: "2+2".into(),
result: "4".into(),
elapsed_ms: 1,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_regex_output() {
let val = RegexOutput {
r#type: "regex",
regex: "\\d+".into(),
examples: 3,
anchored: false,
elapsed_ms: 1,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_list_entry() {
let val = ListEntry {
r#type: "entry",
path: "/tmp/le.rs".into(),
kind: "file".into(),
size: Some(100),
modified: None,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_list_summary() {
let val = ListSummary {
r#type: "summary",
files: 10,
dirs: 3,
symlinks: 0,
total_bytes: Some(5000),
by_extension: None,
elapsed_ms: 15,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_dry_run_plan() {
let val = DryRunPlan {
r#type: "plan",
operation: "write".into(),
path: "/tmp/dr.rs".into(),
would_modify: true,
details: None,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_copy_output() {
let val = CopyOutput {
r#type: "copy",
source: "/tmp/a.rs".into(),
target: "/tmp/b.rs".into(),
bytes: 200,
checksum: "hash".into(),
verified: true,
elapsed_ms: 2,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_move_output() {
let val = MoveOutput {
r#type: "move",
source: "/tmp/old.rs".into(),
target: "/tmp/new.rs".into(),
bytes: 300,
checksum: "mhash".into(),
cross_device: false,
atomic: true,
elapsed_ms: 3,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_delete_output() {
let val = DeleteOutput {
r#type: "delete",
path: "/tmp/del.rs".into(),
bytes: 150,
checksum_before: "dhash".into(),
elapsed_ms: 1,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_count_total_output() {
let val = CountTotalOutput {
r#type: "count",
mode: "total",
total: CountTotals {
files: 50,
lines: 2000,
blank: 300,
bytes: 50000,
},
elapsed_ms: 20,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_search_begin() {
let val = SearchBegin {
r#type: "begin",
path: "/tmp/proj".into(),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_search_end() {
let val = SearchEnd {
r#type: "end",
path: "/tmp/proj".into(),
stats: FileStats {
matches: 12,
lines_searched: 50,
},
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_transfer_plan() {
let val = TransferPlan {
r#type: "plan",
operation: "copy",
source: "/tmp/a.rs".into(),
target: "/tmp/b.rs".into(),
would_modify: true,
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_backup_plan() {
let val = BackupPlan {
r#type: "plan",
operation: "backup",
path: "/tmp/src.rs".into(),
bytes: 500,
checksum: "hash".into(),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_rollback_plan() {
let val = RollbackPlan {
r#type: "plan",
operation: "rollback",
path: "/tmp/rb.rs".into(),
restore_from: "/tmp/rb.bak".into(),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
#[test]
fn roundtrip_replace_preview() {
let val = ReplacePreview {
r#type: "preview",
path: "/tmp/rp.rs".into(),
replacements: 3,
diff: "-old\n+new".into(),
};
assert_valid_ndjson_object(&val);
assert_roundtrip_json(&val);
}
}