elasticctl-api 0.10.0

Typed detection-rule model and endpoint wrappers for Elastic Security.
Documentation
use crate::diff::{Change, FieldChange};
use crate::model::{ExceptionItem, ExceptionList, Rule};
use serde::Serialize;
use serde_json::Value;

#[derive(Debug, Clone, PartialEq)]
pub struct StackIdentity {
    pub profile: String,
    pub host: String,
    pub space: String,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct PullReport {
    pub pulled: usize,
    pub exception_lists: usize,
    pub exception_items: usize,
    pub dir: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub selected: Option<usize>,
    #[serde(skip_serializing_if = "is_zero")]
    pub value_lists: usize,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub value_list_warnings: Vec<crate::value_lists::Warning>,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DiffReport {
    pub clean: bool,
    pub local: usize,
    pub remote: usize,
    pub changes: Vec<Change>,
    pub exceptions: ExceptionDrift,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value_lists: Option<super::values::ValueListDrift>,
    #[serde(skip_serializing_if = "is_zero")]
    pub out_of_scope: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub selected: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub local_total: Option<usize>,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct PushReport {
    pub applied: bool,
    pub created: usize,
    pub updated: usize,
    pub skipped_remote_only: usize,
    pub failed: usize,
    pub pending: usize,
    pub lists_created: usize,
    pub lists_updated: usize,
    pub items_created: usize,
    pub items_updated: usize,
    pub items_removed: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub value_lists_created: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub value_lists_updated: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub values_added: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub values_removed: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub value_lists_unknown: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub value_lists_not_reached: usize,
    #[serde(skip_serializing_if = "is_zero")]
    pub out_of_scope: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub selected: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub local_total: Option<usize>,
}

fn is_zero(n: &usize) -> bool {
    *n == 0
}

#[derive(Debug)]
pub struct Mirror {
    pub rules: Vec<Rule>,
    pub lists: Vec<ExceptionList>,
    pub items: Vec<ExceptionItem>,
    pub value_lists: Vec<crate::value_lists::LocalPair>,
    pub has_value_lists: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ExceptionDrift {
    pub local: usize,
    pub remote: usize,
    pub changes: Vec<ListChange>,
    pub dangling: Vec<DanglingPointer>,
}

impl ExceptionDrift {
    pub fn is_clean(&self) -> bool {
        self.changes
            .iter()
            .all(|c| matches!(c, ListChange::Unchanged { .. }))
            && self.dangling.is_empty()
    }
}

#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "change", rename_all = "snake_case")]
pub enum ListChange {
    Added {
        list_id: String,
        name: String,
    },
    Modified {
        list_id: String,
        name: String,
        fields: Vec<FieldChange>,
    },
    Unchanged {
        list_id: String,
    },
    RemoteOnly {
        list_id: String,
        name: String,
    },
    ItemAdded {
        list_id: String,
        item_id: String,
    },
    ItemModified {
        list_id: String,
        item_id: String,
        fields: Vec<FieldChange>,
    },
    ItemRemoved {
        list_id: String,
        item_id: String,
    },
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DanglingPointer {
    pub rule_id: String,
    pub list_id: String,
    pub stored_id: Value,
    pub live_id: Option<String>,
}