Skip to main content

elasticctl_api/
content_codec.rs

1//! Portable JSON and YAML sequence codecs.
2
3use elasticctl_core::{Error, ErrorKind, Result};
4use serde::{Serialize, de::DeserializeOwned};
5use serde_json::Value;
6use std::path::Path;
7
8/// The portable content artifact format.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ContentFormat {
11    Json,
12    Yaml,
13}
14
15impl ContentFormat {
16    /// YAML is selected only by its conventional filename extensions.
17    pub fn from_path(path: &Path) -> Self {
18        match path.extension().and_then(|extension| extension.to_str()) {
19            Some("yaml") | Some("yml") => Self::Yaml,
20            _ => Self::Json,
21        }
22    }
23}
24
25/// Encode portable content as a reviewable sequence.
26pub fn encode_sequence<T: Serialize>(values: &[T], format: ContentFormat) -> Result<String> {
27    match format {
28        ContentFormat::Json => serde_json::to_string_pretty(values)
29            .map_err(|error| Error::new(ErrorKind::Error, format!("encoding JSON: {error}"))),
30        ContentFormat::Yaml => serde_yaml_ng::to_string(values)
31            .map_err(|error| Error::new(ErrorKind::Error, format!("encoding YAML: {error}"))),
32    }
33}
34
35/// Decode a portable JSON or YAML sequence.
36///
37/// Callers decide whether an empty artifact is meaningful. Element failures
38/// name both the content kind and the zero-based element index.
39pub fn decode_sequence<T: DeserializeOwned>(
40    body: &str,
41    format: ContentFormat,
42    item_name: &str,
43) -> Result<Vec<T>> {
44    let values: Vec<Value> = match format {
45        ContentFormat::Json => serde_json::from_str(body).map_err(|error| {
46            Error::new(
47                ErrorKind::Error,
48                format!("parsing JSON {item_name} sequence: {error}"),
49            )
50        })?,
51        ContentFormat::Yaml => serde_yaml_ng::from_str(body).map_err(|error| {
52            Error::new(
53                ErrorKind::Error,
54                format!("parsing YAML {item_name} sequence: {error}"),
55            )
56        })?,
57    };
58
59    values
60        .into_iter()
61        .enumerate()
62        .map(|(index, value)| {
63            serde_json::from_value(value).map_err(|error| {
64                Error::new(
65                    ErrorKind::Error,
66                    format!("{item_name} at index {index}: {error}"),
67                )
68            })
69        })
70        .collect()
71}