use anyhow::{Context, Result};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum ValueFormat {
#[default]
Pretty,
Json,
Yaml,
Toml,
}
impl ValueFormat {
pub(crate) fn parse(s: &str) -> Self {
match s.to_ascii_lowercase().as_str() {
"json" => Self::Json,
"yaml" | "yml" => Self::Yaml,
"toml" => Self::Toml,
_ => Self::Pretty,
}
}
pub(crate) const fn is_pretty(self) -> bool {
matches!(self, Self::Pretty)
}
}
pub(crate) fn emit_structured<T: Serialize>(value: &T, format: ValueFormat) -> Result<bool> {
match format {
ValueFormat::Pretty => Ok(false),
ValueFormat::Json => {
let s = serde_json::to_string_pretty(value).context("Failed to render JSON")?;
println!("{s}");
Ok(true)
},
ValueFormat::Yaml => {
let s = serde_yaml::to_string(value).context("Failed to render YAML")?;
print!("{s}");
Ok(true)
},
ValueFormat::Toml => {
let try_direct = toml::to_string_pretty(value);
let s = if let Ok(direct) = try_direct {
direct
} else {
let wrapper = TomlWrapper { value };
toml::to_string_pretty(&wrapper).context("Failed to render TOML")?
};
print!("{s}");
Ok(true)
},
}
}
#[derive(Serialize)]
struct TomlWrapper<'a, T: Serialize> {
value: &'a T,
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Serialize)]
struct Sample {
name: String,
n: u32,
}
#[test]
fn parse_recognises_known_formats() {
assert_eq!(ValueFormat::parse("json"), ValueFormat::Json);
assert_eq!(ValueFormat::parse("YAML"), ValueFormat::Yaml);
assert_eq!(ValueFormat::parse("yml"), ValueFormat::Yaml);
assert_eq!(ValueFormat::parse("toml"), ValueFormat::Toml);
assert_eq!(ValueFormat::parse("pretty"), ValueFormat::Pretty);
assert_eq!(ValueFormat::parse("anything-else"), ValueFormat::Pretty);
}
#[test]
fn pretty_returns_false_so_caller_renders() {
let s = Sample {
name: "x".into(),
n: 1,
};
let written = emit_structured(&s, ValueFormat::Pretty).unwrap();
assert!(!written);
}
#[test]
fn json_round_trip_is_parseable() {
let s = Sample {
name: "alice".into(),
n: 42,
};
let json = serde_json::to_string_pretty(&s).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["name"], "alice");
assert_eq!(parsed["n"], 42);
}
#[test]
fn yaml_serialization_is_parseable() {
let s = Sample {
name: "bob".into(),
n: 7,
};
let yaml = serde_yaml::to_string(&s).unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(parsed["name"].as_str(), Some("bob"));
}
#[test]
fn toml_handles_struct_round_trip() {
let s = Sample {
name: "c".into(),
n: 3,
};
let toml = toml::to_string_pretty(&s).unwrap();
assert!(toml.contains("name = \"c\""));
assert!(toml.contains("n = 3"));
}
}