use crate::value::format::FormatError;
use serde::Deserialize;
use serde_json::Value;
pub struct YamlSupport;
impl YamlSupport {
pub fn parse(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}"))
})?;
Self::yaml_to_json(yaml_value)
}
pub fn to_string(value: &Value) -> Result<String, FormatError> {
serde_yaml::to_string(value).map_err(|e| {
FormatError::SerializeError(format!("YAML serialize error: {e}"))
})
}
pub fn is_valid_yaml(input: &str) -> bool {
serde_yaml::from_str::<serde_yaml::Value>(input).is_ok()
}
fn yaml_to_json(
yaml_value: serde_yaml::Value,
) -> Result<Value, FormatError> {
match yaml_value {
serde_yaml::Value::Null => Ok(Value::Null),
serde_yaml::Value::Bool(b) => Ok(Value::Bool(b)),
serde_yaml::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(Value::Number(serde_json::Number::from(i)))
} else if let Some(u) = n.as_u64() {
Ok(Value::Number(serde_json::Number::from(u)))
} else if let Some(f) = n.as_f64() {
serde_json::Number::from_f64(f)
.map(Value::Number)
.ok_or_else(|| {
FormatError::ParseError(
"Invalid float number".to_string(),
)
})
} else {
Err(FormatError::ParseError(
"Unsupported number format".to_string(),
))
}
}
serde_yaml::Value::String(s) => Ok(Value::String(s)),
serde_yaml::Value::Sequence(seq) => {
let mut json_array = Vec::new();
for item in seq {
json_array.push(Self::yaml_to_json(item)?);
}
Ok(Value::Array(json_array))
}
serde_yaml::Value::Mapping(map) => {
let mut json_object = serde_json::Map::new();
for (key, value) in map {
let key_str = match key {
serde_yaml::Value::String(s) => s,
serde_yaml::Value::Number(n) => n.to_string(),
serde_yaml::Value::Bool(b) => b.to_string(),
_ => {
return Err(FormatError::ParseError(
"Invalid key type in YAML mapping".to_string(),
))
}
};
json_object.insert(key_str, Self::yaml_to_json(value)?);
}
Ok(Value::Object(json_object))
}
serde_yaml::Value::Tagged(tagged) => {
Self::yaml_to_json(tagged.value)
}
}
}
pub fn has_document_separator(input: &str) -> bool {
input.contains("---") || input.contains("...")
}
pub fn parse_multi_document(
input: &str,
) -> Result<Vec<Value>, FormatError> {
let mut documents = Vec::new();
let deserializer = serde_yaml::Deserializer::from_str(input);
for document in deserializer {
let yaml_value =
serde_yaml::Value::deserialize(document).map_err(|e| {
FormatError::ParseError(format!(
"YAML document parse error: {e}"
))
})?;
documents.push(Self::yaml_to_json(yaml_value)?);
}
Ok(documents)
}
}
pub struct YamlSpecialValues;
impl YamlSpecialValues {
pub fn is_yaml_null(s: &str) -> bool {
matches!(s.to_lowercase().as_str(), "null" | "~" | "nil" | "")
}
pub fn is_yaml_bool(s: &str) -> Option<bool> {
match s.to_lowercase().as_str() {
"true" | "yes" | "on" => Some(true),
"false" | "no" | "off" => Some(false),
_ => None,
}
}
pub fn parse_yaml_number(s: &str) -> Option<Value> {
if let Ok(i) = s.parse::<i64>() {
return Some(Value::Number(serde_json::Number::from(i)));
}
if let Ok(f) = s.parse::<f64>() {
if let Some(n) = serde_json::Number::from_f64(f) {
return Some(Value::Number(n));
}
}
if s.contains('e') || s.contains('E') {
if let Ok(f) = s.parse::<f64>() {
if let Some(n) = serde_json::Number::from_f64(f) {
return Some(Value::Number(n));
}
}
}
None
}
}
pub struct YamlFormatter {
pub indent: usize,
pub width: usize,
}
impl Default for YamlFormatter {
fn default() -> Self {
Self {
indent: 2,
width: 80,
}
}
}
impl YamlFormatter {
pub fn new() -> Self {
Self::default()
}
pub fn with_indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
pub fn with_width(mut self, width: usize) -> Self {
self.width = width;
self
}
pub fn format(&self, value: &Value) -> Result<String, FormatError> {
YamlSupport::to_string(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_yaml_parse() {
let input = r#"
name: Alice
age: 30
active: true
scores: [85, 92, 78]
address:
street: 123 Main St
city: Anytown
"#;
let value = YamlSupport::parse(input).unwrap();
assert_eq!(value["name"], "Alice");
assert_eq!(value["age"], 30);
assert_eq!(value["active"], true);
assert_eq!(value["scores"][0], 85);
assert_eq!(value["address"]["street"], "123 Main St");
}
#[test]
fn test_yaml_serialize() {
let value = json!({
"name": "Alice",
"age": 30,
"scores": [85, 92, 78]
});
let yaml_str = YamlSupport::to_string(&value).unwrap();
assert!(yaml_str.contains("name: Alice"));
assert!(yaml_str.contains("age: 30"));
assert!(yaml_str.contains("- 85"));
}
#[test]
fn test_yaml_multi_document() {
let input = r#"
---
name: Alice
age: 30
---
name: Bob
age: 25
"#;
let documents = YamlSupport::parse_multi_document(input).unwrap();
assert_eq!(documents.len(), 2);
assert_eq!(documents[0]["name"], "Alice");
assert_eq!(documents[1]["name"], "Bob");
}
#[test]
fn test_yaml_special_values() {
assert!(YamlSpecialValues::is_yaml_null("null"));
assert!(YamlSpecialValues::is_yaml_null("~"));
assert!(YamlSpecialValues::is_yaml_null(""));
assert_eq!(YamlSpecialValues::is_yaml_bool("true"), Some(true));
assert_eq!(YamlSpecialValues::is_yaml_bool("yes"), Some(true));
assert_eq!(YamlSpecialValues::is_yaml_bool("false"), Some(false));
assert_eq!(YamlSpecialValues::is_yaml_bool("no"), Some(false));
}
#[test]
fn test_yaml_number_parsing() {
assert_eq!(
YamlSpecialValues::parse_yaml_number("42").unwrap(),
json!(42)
);
assert_eq!(
YamlSpecialValues::parse_yaml_number("3.15").unwrap(),
json!(3.15)
);
assert_eq!(
YamlSpecialValues::parse_yaml_number("1.23e4").unwrap(),
json!(12300.0)
);
}
#[test]
fn test_yaml_formatter() {
let value = json!({"name": "Alice", "age": 30});
let formatter = YamlFormatter::new().with_indent(4);
let formatted = formatter.format(&value).unwrap();
assert!(formatted.contains("name: Alice"));
}
}