1use elasticctl_core::{Error, ErrorKind, Result};
10use serde::Serialize;
11use serde_json::{Value, json};
12
13#[derive(Debug, Clone, PartialEq, Serialize)]
15pub struct MutationPlan {
16 pub preview_action: String,
17 pub preview_details: Vec<String>,
18 pub targets: Vec<String>,
21}
22
23#[derive(Debug, Clone, PartialEq, Serialize)]
25pub struct ExportOutcome {
26 pub body: String,
27 pub exported: u64,
28 pub missing: Vec<Value>,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize)]
35pub struct DeleteOutcome {
36 pub applied: bool,
37 pub deleted: Vec<Value>,
38 pub failed: Vec<Value>,
39 pub total: usize,
40}
41
42#[derive(Debug, Clone, PartialEq)]
46pub struct ImportPlan {
47 pub preview: MutationPlan,
48 pub ndjson: String,
51 pub total: usize,
53 pub skipped: Vec<Value>,
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize)]
61pub struct ImportReport {
62 pub succeeded: Value,
63 pub failed: Value,
64}
65
66pub(crate) fn decode_import_report(body: &Value, context: &str) -> Result<ImportReport> {
73 let map = body
74 .as_object()
75 .ok_or_else(|| import_error(context, "response", "must be a JSON object"))?;
76 let success_count = map
77 .get("success_count")
78 .and_then(Value::as_u64)
79 .ok_or_else(|| import_error(context, "success_count", "must be an unsigned integer"))?;
80 let errors = map
81 .get("errors")
82 .and_then(Value::as_array)
83 .ok_or_else(|| import_error(context, "errors", "must be an array"))?;
84 Ok(ImportReport {
85 succeeded: json!(success_count),
86 failed: Value::Array(errors.clone()),
87 })
88}
89
90fn import_error(context: &str, field: &str, detail: impl std::fmt::Display) -> Error {
91 Error::new(
92 ErrorKind::Http,
93 format!("decoding {context} import response field {field}: {detail}"),
94 )
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use elasticctl_core::ErrorKind;
101 use serde_json::json;
102
103 #[test]
104 fn import_report_rejects_missing_or_wrongly_typed_fields() {
105 for body in [
106 json!({}),
107 json!({"success_count": "1", "errors": []}),
108 json!({"success_count": 1}),
109 json!({"success_count": 1, "errors": "not-an-array"}),
110 ] {
111 let error = decode_import_report(&body, "rules").unwrap_err();
112 assert_eq!(error.kind, ErrorKind::Http);
113 }
114 }
115
116 #[test]
117 fn import_report_accepts_a_valid_response() {
118 let report = decode_import_report(
119 &json!({"success_count": 2, "errors": [{"message": "x"}]}),
120 "rules",
121 )
122 .unwrap();
123 assert_eq!(report.succeeded, json!(2));
124 assert_eq!(report.failed, json!([{"message": "x"}]));
125 }
126}