use serde_json::Value;
use std::fmt;
#[derive(Debug, Clone)]
pub enum FormatError {
ParseError(String),
SerializeError(String),
UnsupportedFormat(String),
}
impl fmt::Display for FormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FormatError::ParseError(msg) => write!(f, "Parse error: {msg}"),
FormatError::SerializeError(msg) => {
write!(f, "Serialize error: {msg}")
}
FormatError::UnsupportedFormat(format) => {
write!(f, "Unsupported format: {format}")
}
}
}
}
impl std::error::Error for FormatError {}
pub trait ValueFormat: Send + Sync {
fn parse(&self, input: &str) -> Result<Value, FormatError>;
fn to_string(&self, value: &Value) -> Result<String, FormatError>;
fn name(&self) -> &'static str;
}
pub struct JsonFormat;
impl ValueFormat for JsonFormat {
fn parse(&self, input: &str) -> Result<Value, FormatError> {
serde_json::from_str(input).map_err(|e| {
FormatError::ParseError(format!("JSON parse error: {e}"))
})
}
fn to_string(&self, value: &Value) -> Result<String, FormatError> {
serde_json::to_string_pretty(value).map_err(|e| {
FormatError::SerializeError(format!("JSON serialize error: {e}"))
})
}
fn name(&self) -> &'static str {
"json"
}
}
pub struct YamlFormat;
impl ValueFormat for YamlFormat {
fn parse(&self, input: &str) -> Result<Value, FormatError> {
let yaml_value: serde_yaml::Value = serde_yaml::from_str(input)
.map_err(|e| {
FormatError::ParseError(format!("YAML parse error: {e}"))
})?;
let json_str = serde_json::to_string(&yaml_value).map_err(|e| {
FormatError::SerializeError(format!(
"YAML to JSON conversion error: {e}"
))
})?;
serde_json::from_str(&json_str).map_err(|e| {
FormatError::ParseError(format!(
"JSON parse error during YAML conversion: {e}"
))
})
}
fn to_string(&self, value: &Value) -> Result<String, FormatError> {
serde_yaml::to_string(value).map_err(|e| {
FormatError::SerializeError(format!("YAML serialize error: {e}"))
})
}
fn name(&self) -> &'static str {
"yaml"
}
}
pub fn detect_format(input: &str) -> Result<Box<dyn ValueFormat>, FormatError> {
let trimmed = input.trim_start();
if trimmed.is_empty() {
return Err(FormatError::UnsupportedFormat("empty input".to_string()));
}
if trimmed.starts_with('{') || trimmed.starts_with('[') {
Ok(Box::new(JsonFormat))
} else {
Ok(Box::new(YamlFormat))
}
}
pub struct FormatRegistry {
formats: std::collections::HashMap<String, Box<dyn ValueFormat>>,
}
impl FormatRegistry {
pub fn new() -> Self {
let mut registry = Self {
formats: std::collections::HashMap::new(),
};
registry.register("json".to_string(), Box::new(JsonFormat));
registry.register("yaml".to_string(), Box::new(YamlFormat));
registry.register("yml".to_string(), Box::new(YamlFormat));
registry
}
pub fn register(&mut self, name: String, format: Box<dyn ValueFormat>) {
self.formats.insert(name, format);
}
pub fn get(&self, name: &str) -> Option<&dyn ValueFormat> {
self.formats.get(name).map(|f| f.as_ref())
}
pub fn list_formats(&self) -> Vec<&str> {
self.formats.keys().map(|s| s.as_str()).collect()
}
}
impl Default for FormatRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_format() {
let format = JsonFormat;
let input = r#"{"name": "Alice", "age": 30}"#;
let value = format.parse(input).unwrap();
assert_eq!(value["name"], "Alice");
assert_eq!(value["age"], 30);
let output = format.to_string(&value).unwrap();
assert!(output.contains("Alice"));
}
#[test]
fn test_yaml_format() {
let format = YamlFormat;
let input = r#"
name: Alice
age: 30
"#;
let value = format.parse(input).unwrap();
assert_eq!(value["name"], "Alice");
assert_eq!(value["age"], 30);
let output = format.to_string(&value).unwrap();
assert!(output.contains("Alice"));
}
#[test]
fn test_detect_json_format() {
let input = r#"{"name": "Alice"}"#;
let format = detect_format(input).unwrap();
assert_eq!(format.name(), "json");
}
#[test]
fn test_detect_yaml_format() {
let input = r#"name: Alice"#;
let format = detect_format(input).unwrap();
assert_eq!(format.name(), "yaml");
}
#[test]
fn test_format_registry() {
let registry = FormatRegistry::new();
let json_format = registry.get("json").unwrap();
assert_eq!(json_format.name(), "json");
let yaml_format = registry.get("yaml").unwrap();
assert_eq!(yaml_format.name(), "yaml");
let formats = registry.list_formats();
assert!(formats.contains(&"json"));
assert!(formats.contains(&"yaml"));
}
}