Skip to main content

elasticctl_api/
ops.rs

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}