feat: OutputFormat enum + render/render_list/read_json_input helpers
Adds -o json|yaml|table output support to the SDK output module. OutputFormat derives clap::ValueEnum behind the cli feature gate.
This commit is contained in:
@@ -1,3 +1,79 @@
|
|||||||
|
use crate::error::{Result, SunbeamError};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// OutputFormat
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
|
||||||
|
pub enum OutputFormat {
|
||||||
|
#[default]
|
||||||
|
Table,
|
||||||
|
Json,
|
||||||
|
Yaml,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render a single serialisable value in the requested format.
|
||||||
|
pub fn render<T: Serialize>(val: &T, format: OutputFormat) -> Result<()> {
|
||||||
|
match format {
|
||||||
|
OutputFormat::Json => {
|
||||||
|
println!("{}", serde_json::to_string_pretty(val)?);
|
||||||
|
}
|
||||||
|
OutputFormat::Yaml => {
|
||||||
|
print!("{}", serde_yaml::to_string(val)?);
|
||||||
|
}
|
||||||
|
OutputFormat::Table => {
|
||||||
|
// Fallback: pretty JSON when no table renderer is provided
|
||||||
|
println!("{}", serde_json::to_string_pretty(val)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render a list of items as table / json / yaml.
|
||||||
|
///
|
||||||
|
/// `to_row` converts each item into a `Vec<String>` of column values matching
|
||||||
|
/// the order of `headers`.
|
||||||
|
pub fn render_list<T: Serialize>(
|
||||||
|
rows: &[T],
|
||||||
|
headers: &[&str],
|
||||||
|
to_row: fn(&T) -> Vec<String>,
|
||||||
|
format: OutputFormat,
|
||||||
|
) -> Result<()> {
|
||||||
|
match format {
|
||||||
|
OutputFormat::Json => {
|
||||||
|
println!("{}", serde_json::to_string_pretty(rows)?);
|
||||||
|
}
|
||||||
|
OutputFormat::Yaml => {
|
||||||
|
print!("{}", serde_yaml::to_string(rows)?);
|
||||||
|
}
|
||||||
|
OutputFormat::Table => {
|
||||||
|
let table_rows: Vec<Vec<String>> = rows.iter().map(to_row).collect();
|
||||||
|
println!("{}", table(table_rows.as_slice(), headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read JSON input from a `--data` flag value or stdin when the value is `"-"`.
|
||||||
|
pub fn read_json_input(flag: Option<&str>) -> Result<serde_json::Value> {
|
||||||
|
let raw = match flag {
|
||||||
|
Some("-") | None => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)?;
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
Some(v) => v.to_string(),
|
||||||
|
};
|
||||||
|
serde_json::from_str(&raw)
|
||||||
|
.map_err(|e| SunbeamError::Other(format!("invalid JSON input: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Existing helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Print a step header.
|
/// Print a step header.
|
||||||
pub fn step(msg: &str) {
|
pub fn step(msg: &str) {
|
||||||
println!("\n==> {msg}");
|
println!("\n==> {msg}");
|
||||||
@@ -83,10 +159,35 @@ mod tests {
|
|||||||
fn test_table_column_widths() {
|
fn test_table_column_widths() {
|
||||||
let rows = vec![vec!["short".to_string(), "x".to_string()]];
|
let rows = vec![vec!["short".to_string(), "x".to_string()]];
|
||||||
let result = table(&rows, &["LongHeader", "H2"]);
|
let result = table(&rows, &["LongHeader", "H2"]);
|
||||||
// Header should set minimum width
|
|
||||||
for line in result.lines().skip(2) {
|
for line in result.lines().skip(2) {
|
||||||
// Data row: "short" should be padded to "LongHeader" width
|
|
||||||
assert!(line.starts_with("short "));
|
assert!(line.starts_with("short "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_json() {
|
||||||
|
let val = serde_json::json!({"key": "value"});
|
||||||
|
// Just ensure it doesn't panic
|
||||||
|
render(&val, OutputFormat::Json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_yaml() {
|
||||||
|
let val = serde_json::json!({"key": "value"});
|
||||||
|
render(&val, OutputFormat::Yaml).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_list_table() {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Item { name: String }
|
||||||
|
let items = vec![Item { name: "test".into() }];
|
||||||
|
render_list(&items, &["NAME"], |i| vec![i.name.clone()], OutputFormat::Table).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_json_input_inline() {
|
||||||
|
let val = read_json_input(Some(r#"{"a":1}"#)).unwrap();
|
||||||
|
assert_eq!(val["a"], 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user