1use elasticctl_core::{Error, ErrorKind, Result};
2use serde::Serialize;
3use serde_json::{Value, json};
4
5#[derive(Debug, Clone, PartialEq, Serialize)]
6pub struct MutationPlan {
7 pub preview_action: String,
8 pub preview_details: Vec<String>,
9 pub targets: Vec<String>,
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize)]
13pub struct ExportOutcome {
14 pub body: String,
15 pub exported: u64,
16 pub missing: Vec<Value>,
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize)]
20pub struct DeleteOutcome {
21 pub applied: bool,
22 pub deleted: Vec<Value>,
23 pub failed: Vec<Value>,
24 pub total: usize,
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct ImportPlan {
29 pub preview: MutationPlan,
30 pub ndjson: String,
31 pub total: usize,
32 pub skipped: Vec<Value>,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize)]
36pub struct ImportReport {
37 pub succeeded: Value,
38 pub failed: Value,
39}
40
41pub(crate) fn decode_import_report(body: &Value, context: &str) -> Result<ImportReport> {
42 let map = body
43 .as_object()
44 .ok_or_else(|| import_error(context, "response", "must be a JSON object"))?;
45 let success_count = map
46 .get("success_count")
47 .and_then(Value::as_u64)
48 .ok_or_else(|| import_error(context, "success_count", "must be an unsigned integer"))?;
49 let errors = map
50 .get("errors")
51 .and_then(Value::as_array)
52 .ok_or_else(|| import_error(context, "errors", "must be an array"))?;
53 Ok(ImportReport {
54 succeeded: json!(success_count),
55 failed: Value::Array(errors.clone()),
56 })
57}
58
59fn import_error(context: &str, field: &str, detail: impl std::fmt::Display) -> Error {
60 Error::new(
61 ErrorKind::Http,
62 format!("decoding {context} import response field {field}: {detail}"),
63 )
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use elasticctl_core::ErrorKind;
70 use serde_json::json;
71
72 #[test]
73 fn import_report_rejects_missing_or_wrongly_typed_fields() {
74 for body in [
75 json!({}),
76 json!({"success_count": "1", "errors": []}),
77 json!({"success_count": 1}),
78 json!({"success_count": 1, "errors": "not-an-array"}),
79 json!({"success_count": -1, "errors": []}),
80 json!({"success_count": 1.5, "errors": []}),
81 json!({"success_count": null, "errors": []}),
82 ] {
83 let error = decode_import_report(&body, "rules").unwrap_err();
84 assert_eq!(error.kind, ErrorKind::Http);
85 }
86 }
87
88 #[test]
89 fn import_report_accepts_a_valid_response() {
90 let report = decode_import_report(
91 &json!({"success_count": 2, "errors": [{"message": "x"}]}),
92 "rules",
93 )
94 .unwrap();
95 assert_eq!(report.succeeded, json!(2));
96 assert_eq!(report.failed, json!([{"message": "x"}]));
97 }
98}