Skip to main content

elasticctl_api/data_views_ops/
defaults.rs

1//! Default data view selection and updates.
2
3use super::{
4    DataViewSummary, Error, ErrorKind, MutationPlan, Result, Transport, data_views, invalid_plan,
5    resolve_from_summaries,
6};
7
8/// An immutable, guard-ready default data-view change.
9#[derive(Debug, Clone, PartialEq)]
10pub struct DefaultPlan {
11    pub preview: MutationPlan,
12    pub before: Option<String>,
13    pub after: Option<String>,
14}
15/// Resolve a data view and prepare a guarded default change.
16pub async fn plan_default_set(transport: &Transport, selector: &str) -> Result<DefaultPlan> {
17    let views = data_views::list(transport).await?;
18    let target = resolve_from_summaries(&views, selector)?;
19    let before = data_views::get_default(transport).await?;
20    let label = default_label(&target);
21    Ok(default_plan(before, Some(target.id), Some(label)))
22}
23
24/// Prepare a guarded request that explicitly clears the current default.
25pub async fn plan_default_unset(transport: &Transport) -> Result<DefaultPlan> {
26    Ok(default_plan(
27        data_views::get_default(transport).await?,
28        None,
29        None,
30    ))
31}
32
33/// Apply a guarded default change after checking the observed default remains
34/// the exact snapshot shown to the operator.
35pub async fn apply_default(transport: &Transport, plan: &DefaultPlan) -> Result<()> {
36    validate_default_plan(plan)?;
37    let current = data_views::get_default(transport).await?;
38    if current != plan.before {
39        return Err(Error::new(
40            ErrorKind::Conflict,
41            "default data view changed since preview",
42        ));
43    }
44    if plan.before == plan.after {
45        return Ok(());
46    }
47    data_views::set_default(transport, plan.after.as_deref()).await
48}
49
50fn default_plan(
51    before: Option<String>,
52    after: Option<String>,
53    target_label: Option<String>,
54) -> DefaultPlan {
55    let (preview_action, preview_details, targets) = match (&before, &after) {
56        (before, Some(after)) => {
57            let label = target_label.expect("set default plans always have a resolved label");
58            let detail = if before.as_deref() == Some(after) {
59                format!("{after}  already default")
60            } else {
61                format!(
62                    "{}  default -> {after}",
63                    before.as_deref().unwrap_or("none")
64                )
65            };
66            (
67                format!("Set default data view to {after} ({label})"),
68                vec![detail],
69                vec![after.clone()],
70            )
71        }
72        (Some(before), None) => (
73            format!("Unset default data view {before}"),
74            vec![format!("{before}  default -> unset")],
75            vec![before.clone()],
76        ),
77        (None, None) => (
78            "Unset default data view".into(),
79            vec!["no default data view set".into()],
80            Vec::new(),
81        ),
82    };
83    DefaultPlan {
84        preview: MutationPlan {
85            preview_action,
86            preview_details,
87            targets,
88        },
89        before,
90        after,
91    }
92}
93
94fn default_label(view: &DataViewSummary) -> String {
95    view.name.clone().unwrap_or_else(|| view.title.clone())
96}
97
98fn validate_default_plan(plan: &DefaultPlan) -> Result<()> {
99    for id in [&plan.before, &plan.after].into_iter().flatten() {
100        if id.trim().is_empty() {
101            return invalid_plan("default data-view ids must not be empty");
102        }
103    }
104    match (&plan.before, &plan.after) {
105        (Some(before), Some(after)) if before == after => {
106            if plan.preview.targets != [after.clone()]
107                || plan.preview.preview_details != [format!("{after}  already default")]
108                || !plan
109                    .preview
110                    .preview_action
111                    .starts_with(&format!("Set default data view to {after} ("))
112                || !plan.preview.preview_action.ends_with(')')
113            {
114                return invalid_plan("default preview does not match its snapshots");
115            }
116        }
117        (before, Some(after)) => {
118            if plan.preview.targets != [after.clone()]
119                || plan.preview.preview_details
120                    != [format!(
121                        "{}  default -> {after}",
122                        before.as_deref().unwrap_or("none")
123                    )]
124                || !plan
125                    .preview
126                    .preview_action
127                    .starts_with(&format!("Set default data view to {after} ("))
128                || !plan.preview.preview_action.ends_with(')')
129            {
130                return invalid_plan("default preview does not match its snapshots");
131            }
132        }
133        (Some(before), None) => {
134            if plan.preview.targets != [before.clone()]
135                || plan.preview.preview_action != format!("Unset default data view {before}")
136                || plan.preview.preview_details != [format!("{before}  default -> unset")]
137            {
138                return invalid_plan("default preview does not match its snapshots");
139            }
140        }
141        (None, None) => {
142            if !plan.preview.targets.is_empty()
143                || plan.preview.preview_action != "Unset default data view"
144                || plan.preview.preview_details != ["no default data view set"]
145            {
146                return invalid_plan("default preview does not match its snapshots");
147            }
148        }
149    }
150    Ok(())
151}