Skip to main content

elasticctl_api/rules_ops/
create.rs

1use super::*;
2use crate::Preconditions;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct RuleCreatePlan {
6    pub rule: Rule,
7    pub preview: MutationPlan,
8}
9
10#[derive(Debug, Clone, PartialEq, serde::Serialize)]
11pub struct RuleCreateOutcome {
12    pub applied: bool,
13    pub rule_id: String,
14    pub enabled: bool,
15}
16
17pub async fn plan_create(t: &Transport, mut rule: Rule) -> Result<(RuleCreatePlan, Preconditions)> {
18    validate_local(&rule)?;
19    let id = rule.rule_id()?.to_owned();
20    rule.as_map_mut().insert("enabled".into(), json!(false));
21    match rules::get(t, &id).await {
22        Ok(_) => {
23            return Err(Error::new(
24                ErrorKind::Conflict,
25                "a rule with this rule_id already exists",
26            ));
27        }
28        Err(err) if err.kind == ErrorKind::NotFound => {}
29        Err(err) => return Err(err),
30    }
31    let mut keys: Vec<_> = rule.as_map().keys().collect();
32    keys.sort_unstable();
33    let preview_details = keys
34        .into_iter()
35        .map(|key| {
36            let mut value = rule.as_map()[key].clone();
37            sort_value(&mut value);
38            let escaped_key = serde_json::to_string(key).expect("JSON keys serialize");
39            let escaped_key = &escaped_key[1..escaped_key.len() - 1];
40            format!(
41                "{escaped_key}: {}",
42                serde_json::to_string(&value).expect("JSON values serialize")
43            )
44        })
45        .collect();
46    let preview = MutationPlan {
47        preview_action: "Create 1 rule (disabled)".into(),
48        targets: vec![id.clone()],
49        preview_details,
50    };
51    let seen = Preconditions(vec![crate::observe::Observed {
52        object: "rule",
53        id,
54        state: None,
55    }]);
56    Ok((RuleCreatePlan { rule, preview }, seen))
57}
58
59fn validate_local(rule: &Rule) -> Result<()> {
60    rule.rule_id()?;
61    for key in ["name", "type"] {
62        if !rule.as_map().get(key).is_some_and(Value::is_string) {
63            return Err(Error::new(
64                ErrorKind::Error,
65                "name and type must be strings",
66            ));
67        }
68    }
69    if crate::model::VOLATILE_FIELDS
70        .iter()
71        .any(|key| rule.as_map().contains_key(*key))
72    {
73        return Err(Error::new(
74            ErrorKind::Error,
75            "rule contains a volatile field",
76        ));
77    }
78    if rule
79        .as_map()
80        .get("enabled")
81        .is_some_and(|v| v != &json!(false))
82    {
83        return Err(Error::new(ErrorKind::Error, "rule must be disabled"));
84    }
85    for key in ["exceptions_list", "actions", "response_actions"] {
86        if rule
87            .as_map()
88            .get(key)
89            .is_some_and(|v| !v.as_array().is_some_and(Vec::is_empty))
90        {
91            return Err(Error::new(
92                ErrorKind::Unsupported,
93                "rule dependencies must be empty arrays",
94            ));
95        }
96    }
97    Ok(())
98}
99
100fn sort_value(value: &mut Value) {
101    match value {
102        Value::Object(map) => {
103            for value in map.values_mut() {
104                sort_value(value);
105            }
106            map.sort_keys();
107        }
108        Value::Array(values) => {
109            for value in values {
110                sort_value(value);
111            }
112        }
113        _ => {}
114    }
115}
116
117pub async fn apply_create(t: &Transport, plan: &RuleCreatePlan) -> Result<RuleCreateOutcome> {
118    validate_local(&plan.rule)?;
119    if plan.rule.as_map().get("enabled") != Some(&json!(false)) {
120        return Err(Error::new(ErrorKind::Error, "rule must be disabled"));
121    }
122    let id = plan.rule.rule_id()?;
123    let response = rules::create(t, &plan.rule).await?;
124    if response.rule_id().ok() != Some(id)
125        || response.as_map().get("enabled") != Some(&json!(false))
126    {
127        return Err(Error::new(
128            ErrorKind::Http,
129            "created rule has unexpected identity or enabled state",
130        ));
131    }
132    Ok(RuleCreateOutcome {
133        applied: true,
134        rule_id: id.to_owned(),
135        enabled: false,
136    })
137}