use serde_json::Value;
use shard_den_core::Result;
#[derive(Debug, Clone, Copy, Default)]
pub enum OutputFormat {
#[default]
Json,
Csv,
Text,
Yaml,
}
#[derive(Debug, Default)]
pub struct Formatter;
impl Formatter {
pub fn new() -> Self {
Self
}
pub fn format(&self, value: &Value, format: OutputFormat) -> Result<String> {
match format {
OutputFormat::Json => {
serde_json::to_string_pretty(value).map_err(shard_den_core::ShardDenError::Json)
}
OutputFormat::Csv => self.format_csv(value),
OutputFormat::Text => self.format_text(value),
OutputFormat::Yaml => self.format_yaml(value),
}
}
fn format_csv(&self, value: &Value) -> Result<String> {
match value {
Value::Array(arr) if !arr.is_empty() && arr.iter().all(|v| v.is_object()) => {
let mut headers: Vec<String> = Vec::new();
for item in arr {
if let Value::Object(obj) = item {
for key in obj.keys() {
if !headers.contains(key) {
headers.push(key.clone());
}
}
}
}
if headers.is_empty() {
return Ok(String::new());
}
let mut csv = headers.join(",") + "\n";
for item in arr {
if let Value::Object(obj) = item {
let row: Vec<String> = headers
.iter()
.map(|h| {
obj.get(h)
.map(|v| self.escape_csv_value(v))
.unwrap_or_default()
})
.collect();
csv.push_str(&row.join(","));
csv.push('\n');
}
}
Ok(csv)
}
Value::Array(arr) => {
let values: Vec<String> = arr.iter().map(|v| self.escape_csv_value(v)).collect();
Ok(values.join(","))
}
_ => Ok(self.escape_csv_value(value)),
}
}
fn escape_csv_value(&self, value: &Value) -> String {
match value {
Value::String(s) => {
if s.contains(',') || s.contains('"') || s.contains('\n') {
format!("\"{}\"", s.replace('"', "\"\""))
} else {
s.clone()
}
}
Value::Null => "".to_string(),
_ => value.to_string(),
}
}
fn format_text(&self, value: &Value) -> Result<String> {
match value {
Value::String(s) => Ok(s.clone()),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| self.value_to_text(v)).collect();
Ok(items.join("\n"))
}
_ => Ok(self.value_to_text(value)),
}
}
fn value_to_text(&self, value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| self.value_to_text(v)).collect();
format!("[{}]", items.join(", "))
}
Value::Object(obj) => {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{}: {}", k, self.value_to_text(v)))
.collect();
format!("{{{}}}", pairs.join(", "))
}
}
}
fn format_yaml(&self, value: &Value) -> Result<String> {
serde_yaml::to_string(value).map_err(shard_den_core::ShardDenError::Yaml)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_format_json() {
let formatter = Formatter::new();
let value = json!({"key": "value"});
let result = formatter.format(&value, OutputFormat::Json);
assert!(result.is_ok());
}
#[test]
fn test_format_json_pretty() {
let formatter = Formatter::new();
let value = json!({"a": 1, "b": 2});
let result = formatter.format(&value, OutputFormat::Json).unwrap();
assert!(result.contains("a"));
assert!(result.contains("1"));
}
#[test]
fn test_format_text_string() {
let formatter = Formatter::new();
let value = json!("hello world");
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert_eq!(result, "hello world");
}
#[test]
fn test_format_text_number() {
let formatter = Formatter::new();
let value = json!(42);
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert_eq!(result, "42");
}
#[test]
fn test_format_text_boolean() {
let formatter = Formatter::new();
let value = json!(true);
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert_eq!(result, "true");
}
#[test]
fn test_format_text_null() {
let formatter = Formatter::new();
let value = json!(null);
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert_eq!(result, "null");
}
#[test]
fn test_format_text_array() {
let formatter = Formatter::new();
let value = json!([1, 2, 3]);
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert!(result.contains("1"));
}
#[test]
fn test_format_text_object() {
let formatter = Formatter::new();
let value = json!({"a": 1});
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert!(result.contains("a"));
}
#[test]
fn test_format_csv_array_of_objects() {
let formatter = Formatter::new();
let value = json!([
{"id": 1, "name": "alice"},
{"id": 2, "name": "bob"}
]);
let result = formatter.format(&value, OutputFormat::Csv).unwrap();
let expected = "id,name\n1,alice\n2,bob\n";
assert_eq!(result, expected);
}
#[test]
fn test_format_csv_simple_array() {
let formatter = Formatter::new();
let value = json!([1, 2, 3]);
let result = formatter.format(&value, OutputFormat::Csv).unwrap();
assert_eq!(result, "1,2,3");
}
#[test]
fn test_format_csv_single_value() {
let formatter = Formatter::new();
let value = json!("hello");
let result = formatter.format(&value, OutputFormat::Csv).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_format_csv_escape_quotes() {
let formatter = Formatter::new();
let value = json!(["hello,world", "test\"value"]);
let result = formatter.format(&value, OutputFormat::Csv).unwrap();
assert!(result.contains("\""));
}
#[test]
fn test_format_yaml() {
let formatter = Formatter::new();
let value = json!({"key": "value"});
let result = formatter.format(&value, OutputFormat::Yaml);
assert!(result.is_ok());
}
#[test]
fn test_format_yaml_array() {
let formatter = Formatter::new();
let value = json!([1, 2, 3]);
let result = formatter.format(&value, OutputFormat::Yaml).unwrap();
assert!(result.contains("- 1"));
}
#[test]
fn test_format_csv_with_null() {
let formatter = Formatter::new();
let value = json!([{"name": null}, {"name": "test"}]);
let result = formatter.format(&value, OutputFormat::Csv);
assert!(result.is_ok());
}
#[test]
fn test_format_csv_empty_array() {
let formatter = Formatter::new();
let value = json!([]);
let result = formatter.format(&value, OutputFormat::Csv);
assert!(result.is_ok());
}
#[test]
fn test_value_to_text_string() {
let formatter = Formatter::new();
let value = json!("hello");
let result = formatter.format(&value, OutputFormat::Text);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "hello");
}
#[test]
fn test_value_to_text_nested_array() {
let formatter = Formatter::new();
let value = json!([[1, 2], [3, 4]]);
let result = formatter.format(&value, OutputFormat::Text);
assert!(result.is_ok());
}
#[test]
fn test_format_csv_empty_headers() {
let formatter = Formatter::new();
let value = json!([{}, {}]);
let result = formatter.format(&value, OutputFormat::Csv).unwrap();
assert_eq!(result, "");
}
#[test]
fn test_value_to_text_direct_string() {
let formatter = Formatter::new();
let value = json!("direct_string");
let result = formatter.format(&value, OutputFormat::Text).unwrap();
assert_eq!(result, "direct_string");
}
}