use crate::printer::{CclPrinter, PrinterConfig};
use crate::{CclObject, Result};
use serde::ser::{self, Serialize};
use std::fmt;
pub fn to_string<T>(value: &T) -> Result<String>
where
T: Serialize,
{
to_string_with_config(value, PrinterConfig::default())
}
pub fn to_string_with_config<T>(value: &T, config: PrinterConfig) -> Result<String>
where
T: Serialize,
{
let mut serializer = Serializer::new();
value.serialize(&mut serializer)?;
let model = serializer.into_model();
let printer = CclPrinter::with_config(config);
Ok(printer.print(&model))
}
pub struct Serializer {
current_key: Option<String>,
stack: Vec<CclObject>,
}
impl Serializer {
pub fn new() -> Self {
Serializer {
current_key: None,
stack: vec![CclObject::new()],
}
}
pub fn into_model(mut self) -> CclObject {
self.stack.pop().unwrap_or_default()
}
fn current_object(&mut self) -> &mut CclObject {
self.stack.last_mut().expect("stack should not be empty")
}
fn insert_value(&mut self, value: String) {
if let Some(key) = self.current_key.take() {
self.current_object().insert_string(&key, value);
}
}
}
impl Default for Serializer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SerError {
msg: String,
}
impl SerError {
fn custom(msg: impl fmt::Display) -> Self {
SerError {
msg: msg.to_string(),
}
}
}
impl fmt::Display for SerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
impl std::error::Error for SerError {}
impl ser::Error for SerError {
fn custom<T: fmt::Display>(msg: T) -> Self {
SerError::custom(msg)
}
}
impl From<SerError> for crate::Error {
fn from(e: SerError) -> Self {
crate::Error::ValueError(e.msg)
}
}
impl<'a> ser::Serializer for &'a mut Serializer {
type Ok = ();
type Error = SerError;
type SerializeSeq = SeqSerializer<'a>;
type SerializeTuple = SeqSerializer<'a>;
type SerializeTupleStruct = SeqSerializer<'a>;
type SerializeTupleVariant = SeqSerializer<'a>;
type SerializeMap = MapSerializer<'a>;
type SerializeStruct = MapSerializer<'a>;
type SerializeStructVariant = MapSerializer<'a>;
fn serialize_bool(self, v: bool) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_i8(self, v: i8) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_i16(self, v: i16) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_i32(self, v: i32) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_i64(self, v: i64) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_u8(self, v: u8) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_u16(self, v: u16) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_u32(self, v: u32) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_u64(self, v: u64) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_f32(self, v: f32) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_f64(self, v: f64) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_char(self, v: char) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_str(self, v: &str) -> std::result::Result<(), Self::Error> {
self.insert_value(v.to_string());
Ok(())
}
fn serialize_bytes(self, v: &[u8]) -> std::result::Result<(), Self::Error> {
self.insert_value(String::from_utf8_lossy(v).to_string());
Ok(())
}
fn serialize_none(self) -> std::result::Result<(), Self::Error> {
self.current_key = None;
Ok(())
}
fn serialize_some<T>(self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_unit(self) -> std::result::Result<(), Self::Error> {
Ok(())
}
fn serialize_unit_struct(self, _name: &'static str) -> std::result::Result<(), Self::Error> {
Ok(())
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> std::result::Result<(), Self::Error> {
self.insert_value(variant.to_string());
Ok(())
}
fn serialize_newtype_struct<T>(
self,
_name: &'static str,
value: &T,
) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
self.current_key = Some(variant.to_string());
value.serialize(self)
}
fn serialize_seq(
self,
_len: Option<usize>,
) -> std::result::Result<Self::SerializeSeq, Self::Error> {
Ok(SeqSerializer {
ser: self,
items: Vec::new(),
nested_items: Vec::new(),
})
}
fn serialize_tuple(
self,
_len: usize,
) -> std::result::Result<Self::SerializeTuple, Self::Error> {
Ok(SeqSerializer {
ser: self,
items: Vec::new(),
nested_items: Vec::new(),
})
}
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> std::result::Result<Self::SerializeTupleStruct, Self::Error> {
Ok(SeqSerializer {
ser: self,
items: Vec::new(),
nested_items: Vec::new(),
})
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> std::result::Result<Self::SerializeTupleVariant, Self::Error> {
Ok(SeqSerializer {
ser: self,
items: Vec::new(),
nested_items: Vec::new(),
})
}
fn serialize_map(
self,
_len: Option<usize>,
) -> std::result::Result<Self::SerializeMap, Self::Error> {
let parent_key = self.current_key.take();
self.stack.push(CclObject::new());
Ok(MapSerializer {
ser: self,
parent_key,
})
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> std::result::Result<Self::SerializeStruct, Self::Error> {
let parent_key = self.current_key.take();
self.stack.push(CclObject::new());
Ok(MapSerializer {
ser: self,
parent_key,
})
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> std::result::Result<Self::SerializeStructVariant, Self::Error> {
let parent_key = self.current_key.take();
self.stack.push(CclObject::new());
Ok(MapSerializer {
ser: self,
parent_key,
})
}
}
pub struct SeqSerializer<'a> {
ser: &'a mut Serializer,
items: Vec<String>,
nested_items: Vec<CclObject>,
}
impl<'a> ser::SerializeSeq for SeqSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_element<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
let mut element_ser = Serializer::new();
element_ser.current_key = Some(String::new());
value.serialize(&mut element_ser)?;
let model = element_ser.into_model();
if let Some((_, values)) = model.iter_map().find(|(k, _)| k.is_empty()) {
if let Some(inner) = values.first() {
if inner.len() == 1 {
if let Some((key, children)) = inner.iter_map().next() {
if children.iter().all(|c| c.is_empty()) {
self.items.push(key.clone());
return Ok(());
}
}
}
self.nested_items.push(inner.clone());
}
}
Ok(())
}
fn end(self) -> std::result::Result<(), Self::Error> {
if let Some(key) = self.ser.current_key.take() {
if !self.nested_items.is_empty() {
let mut list_obj = CclObject::new();
let map = list_obj.inner_mut();
map.insert("".to_string(), self.nested_items);
self.ser.current_object().insert_object(&key, list_obj);
} else if !self.items.is_empty() {
self.ser.current_object().insert_list(&key, self.items);
}
}
Ok(())
}
}
impl<'a> ser::SerializeTuple for SeqSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_element<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> std::result::Result<(), Self::Error> {
ser::SerializeSeq::end(self)
}
}
impl<'a> ser::SerializeTupleStruct for SeqSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_field<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> std::result::Result<(), Self::Error> {
ser::SerializeSeq::end(self)
}
}
impl<'a> ser::SerializeTupleVariant for SeqSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_field<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> std::result::Result<(), Self::Error> {
ser::SerializeSeq::end(self)
}
}
pub struct MapSerializer<'a> {
ser: &'a mut Serializer,
parent_key: Option<String>,
}
impl<'a> ser::SerializeMap for MapSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_key<T>(&mut self, key: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
let mut key_ser = Serializer::new();
key_ser.current_key = Some(String::new());
key.serialize(&mut key_ser)?;
let model = key_ser.into_model();
if let Some((_, inner)) = model.iter().next() {
if let Some((key_str, _)) = inner.iter().next() {
self.ser.current_key = Some(key_str.clone());
}
}
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(&mut *self.ser)
}
fn end(self) -> std::result::Result<(), Self::Error> {
if self.ser.stack.len() > 1 {
let completed = self.ser.stack.pop().unwrap();
if let Some(key) = self.parent_key {
self.ser.current_object().insert_object(&key, completed);
} else {
let current = self.ser.current_object();
for (k, v) in completed.iter() {
current.insert_object(k, v.clone());
}
}
}
Ok(())
}
}
impl<'a> ser::SerializeStruct for MapSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_field<T>(
&mut self,
key: &'static str,
value: &T,
) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
self.ser.current_key = Some(key.to_string());
value.serialize(&mut *self.ser)
}
fn end(self) -> std::result::Result<(), Self::Error> {
ser::SerializeMap::end(self)
}
}
impl<'a> ser::SerializeStructVariant for MapSerializer<'a> {
type Ok = ();
type Error = SerError;
fn serialize_field<T>(
&mut self,
key: &'static str,
value: &T,
) -> std::result::Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
ser::SerializeStruct::serialize_field(self, key, value)
}
fn end(self) -> std::result::Result<(), Self::Error> {
ser::SerializeMap::end(self)
}
}
#[cfg(test)]
mod serde_validation_tests {
#![allow(dead_code)]
use super::*;
use crate::printer::PrinterConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[test]
fn test_i8() {
#[derive(Serialize)]
struct S {
value: i8,
}
let s = S { value: -128 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = -128"));
}
#[test]
fn test_i8_positive() {
#[derive(Serialize)]
struct S {
value: i8,
}
let s = S { value: 127 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 127"));
}
#[test]
fn test_i16() {
#[derive(Serialize)]
struct S {
value: i16,
}
let s = S { value: -32768 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = -32768"));
}
#[test]
fn test_i32() {
#[derive(Serialize)]
struct S {
value: i32,
}
let s = S { value: -2147483648 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = -2147483648"));
}
#[test]
fn test_i64() {
#[derive(Serialize)]
struct S {
value: i64,
}
let s = S {
value: -9223372036854775808,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = -9223372036854775808"));
}
#[test]
fn test_u8() {
#[derive(Serialize)]
struct S {
value: u8,
}
let s = S { value: 255 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 255"));
}
#[test]
fn test_u16() {
#[derive(Serialize)]
struct S {
value: u16,
}
let s = S { value: 65535 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 65535"));
}
#[test]
fn test_u32() {
#[derive(Serialize)]
struct S {
value: u32,
}
let s = S { value: 4294967295 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 4294967295"));
}
#[test]
fn test_u64() {
#[derive(Serialize)]
struct S {
value: u64,
}
let s = S {
value: 18446744073709551615,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 18446744073709551615"));
}
#[test]
fn test_f32() {
#[derive(Serialize)]
struct S {
value: f32,
}
let s = S { value: 3.14 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 3.14"));
}
#[test]
fn test_f64() {
#[derive(Serialize)]
struct S {
value: f64,
}
let s = S {
value: 3.141592653589793,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 3.141592653589793"));
}
#[test]
fn test_f32_negative() {
#[derive(Serialize)]
struct S {
value: f32,
}
let s = S { value: -2.5 };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = -2.5"));
}
#[test]
fn test_bool_true() {
#[derive(Serialize)]
struct S {
value: bool,
}
let s = S { value: true };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = true"));
}
#[test]
fn test_bool_false() {
#[derive(Serialize)]
struct S {
value: bool,
}
let s = S { value: false };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = false"));
}
#[test]
fn test_char() {
#[derive(Serialize)]
struct S {
value: char,
}
let s = S { value: 'X' };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = X"));
}
#[test]
fn test_char_unicode() {
#[derive(Serialize)]
struct S {
value: char,
}
let s = S { value: '日' };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 日"));
}
#[test]
fn test_string() {
#[derive(Serialize)]
struct S {
value: String,
}
let s = S {
value: "hello world".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = hello world"));
}
#[test]
fn test_str_ref() {
#[derive(Serialize)]
struct S<'a> {
value: &'a str,
}
let s = S { value: "borrowed" };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = borrowed"));
}
#[test]
fn test_bytes() {
#[derive(Serialize)]
struct S {
#[serde(serialize_with = "serialize_bytes")]
data: Vec<u8>,
}
fn serialize_bytes<S: serde::Serializer>(
bytes: &[u8],
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_bytes(bytes)
}
let s = S {
data: vec![72, 101, 108, 108, 111], };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("data = Hello"));
}
#[test]
fn test_option_some_string() {
#[derive(Serialize)]
struct S {
value: Option<String>,
}
let s = S {
value: Some("present".to_string()),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = present"));
}
#[test]
fn test_option_none() {
#[derive(Serialize)]
struct S {
name: String,
value: Option<String>,
}
let s = S {
name: "test".to_string(),
value: None,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("name = test"));
assert!(!ccl.contains("value"));
}
#[test]
fn test_option_some_number() {
#[derive(Serialize)]
struct S {
port: Option<u16>,
}
let s = S { port: Some(8080) };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("port = 8080"));
}
#[test]
fn test_option_some_bool() {
#[derive(Serialize)]
struct S {
enabled: Option<bool>,
}
let s = S {
enabled: Some(true),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("enabled = true"));
}
#[test]
fn test_unit() {
let ccl = to_string(&()).unwrap();
assert!(ccl.is_empty() || ccl.trim().is_empty());
}
#[test]
fn test_unit_struct() {
#[derive(Serialize)]
struct Unit;
let ccl = to_string(&Unit).unwrap();
assert!(ccl.is_empty() || ccl.trim().is_empty());
}
#[test]
fn test_newtype_struct() {
#[derive(Serialize)]
struct Wrapper(String);
#[derive(Serialize)]
struct S {
wrapped: Wrapper,
}
let s = S {
wrapped: Wrapper("inner value".to_string()),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("wrapped = inner value"));
}
#[test]
fn test_newtype_struct_number() {
#[derive(Serialize)]
struct Port(u16);
#[derive(Serialize)]
struct S {
port: Port,
}
let s = S { port: Port(8080) };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("port = 8080"));
}
#[test]
fn test_tuple() {
#[derive(Serialize)]
struct S {
point: (i32, i32),
}
let s = S { point: (10, 20) };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("point ="));
assert!(ccl.contains("10"));
assert!(ccl.contains("20"));
}
#[test]
fn test_tuple_struct() {
#[derive(Serialize)]
struct Point(i32, i32);
#[derive(Serialize)]
struct S {
location: Point,
}
let s = S {
location: Point(100, 200),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("location ="));
assert!(ccl.contains("100"));
assert!(ccl.contains("200"));
}
#[test]
fn test_tuple_three_elements() {
#[derive(Serialize)]
struct S {
rgb: (u8, u8, u8),
}
let s = S { rgb: (255, 128, 0) };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("rgb ="));
assert!(ccl.contains("255"));
assert!(ccl.contains("128"));
assert!(ccl.contains("0"));
}
#[test]
fn test_vec_strings() {
#[derive(Serialize)]
struct S {
items: Vec<String>,
}
let s = S {
items: vec![
"apple".to_string(),
"banana".to_string(),
"cherry".to_string(),
],
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("items ="));
assert!(ccl.contains("apple"));
assert!(ccl.contains("banana"));
assert!(ccl.contains("cherry"));
}
#[test]
fn test_vec_numbers() {
#[derive(Serialize)]
struct S {
values: Vec<i32>,
}
let s = S {
values: vec![1, 2, 3, 4, 5],
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("values ="));
for n in 1..=5 {
assert!(ccl.contains(&n.to_string()));
}
}
#[test]
fn test_vec_empty() {
#[derive(Serialize)]
struct S {
name: String,
items: Vec<String>,
}
let s = S {
name: "test".to_string(),
items: vec![],
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("name = test"));
}
#[test]
fn test_vec_single_item() {
#[derive(Serialize)]
struct S {
items: Vec<String>,
}
let s = S {
items: vec!["only".to_string()],
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("only"));
}
#[test]
fn test_hashmap_string_string() {
let mut map: HashMap<String, String> = HashMap::new();
map.insert("HOME".to_string(), "/home/user".to_string());
map.insert("PATH".to_string(), "/usr/bin".to_string());
let ccl = to_string(&map).unwrap();
assert!(ccl.contains("HOME = /home/user"));
assert!(ccl.contains("PATH = /usr/bin"));
}
#[test]
fn test_hashmap_nested_in_struct() {
#[derive(Serialize)]
struct S {
env: HashMap<String, String>,
}
let mut env = HashMap::new();
env.insert("KEY".to_string(), "value".to_string());
let s = S { env };
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("env ="));
assert!(ccl.contains("KEY = value"));
}
#[test]
fn test_hashmap_empty() {
let map: HashMap<String, String> = HashMap::new();
let ccl = to_string(&map).unwrap();
assert!(ccl.is_empty() || ccl.trim().is_empty());
}
#[test]
fn test_hashmap_with_struct_values() {
#[derive(Serialize)]
struct Inner {
value: i32,
}
let mut map: HashMap<String, Inner> = HashMap::new();
map.insert("first".to_string(), Inner { value: 1 });
map.insert("second".to_string(), Inner { value: 2 });
let ccl = to_string(&map).unwrap();
assert!(ccl.contains("first ="));
assert!(ccl.contains("second ="));
assert!(ccl.contains("value = 1") || ccl.contains("value = 2"));
}
#[test]
fn test_simple_struct() {
#[derive(Serialize)]
struct Config {
name: String,
version: String,
}
let config = Config {
name: "MyApp".to_string(),
version: "1.0.0".to_string(),
};
let ccl = to_string(&config).unwrap();
assert!(ccl.contains("name = MyApp"));
assert!(ccl.contains("version = 1.0.0"));
}
#[test]
fn test_nested_struct() {
#[derive(Serialize)]
struct Database {
host: String,
port: u16,
}
#[derive(Serialize)]
struct Config {
name: String,
database: Database,
}
let config = Config {
name: "MyApp".to_string(),
database: Database {
host: "localhost".to_string(),
port: 5432,
},
};
let ccl = to_string(&config).unwrap();
assert!(ccl.contains("name = MyApp"));
assert!(ccl.contains("database ="));
assert!(ccl.contains("host = localhost"));
assert!(ccl.contains("port = 5432"));
}
#[test]
fn test_deeply_nested_struct() {
#[derive(Serialize)]
struct Level3 {
value: String,
}
#[derive(Serialize)]
struct Level2 {
level3: Level3,
}
#[derive(Serialize)]
struct Level1 {
level2: Level2,
}
let config = Level1 {
level2: Level2 {
level3: Level3 {
value: "deep".to_string(),
},
},
};
let ccl = to_string(&config).unwrap();
assert!(ccl.contains("level2 ="));
assert!(ccl.contains("level3 ="));
assert!(ccl.contains("value = deep"));
}
#[test]
fn test_struct_with_multiple_fields() {
#[derive(Serialize)]
struct Config {
name: String,
port: u16,
enabled: bool,
timeout: f64,
}
let config = Config {
name: "server".to_string(),
port: 8080,
enabled: true,
timeout: 30.5,
};
let ccl = to_string(&config).unwrap();
assert!(ccl.contains("name = server"));
assert!(ccl.contains("port = 8080"));
assert!(ccl.contains("enabled = true"));
assert!(ccl.contains("timeout = 30.5"));
}
#[test]
fn test_unit_variant() {
#[derive(Serialize)]
enum Status {
Active,
Inactive,
Pending,
}
#[derive(Serialize)]
struct S {
status: Status,
}
let s = S {
status: Status::Active,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("status = Active"));
let s2 = S {
status: Status::Inactive,
};
let ccl2 = to_string(&s2).unwrap();
assert!(ccl2.contains("status = Inactive"));
}
#[test]
fn test_newtype_variant() {
#[derive(Serialize)]
enum Value {
Text(String),
Number(i32),
}
#[derive(Serialize)]
struct S {
data: Value,
}
let s = S {
data: Value::Text("hello".to_string()),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("Text = hello") || ccl.contains("hello"));
}
#[test]
fn test_tuple_variant() {
#[derive(Serialize)]
enum Point {
TwoD(i32, i32),
ThreeD(i32, i32, i32),
}
#[derive(Serialize)]
struct S {
point: Point,
}
let s = S {
point: Point::TwoD(10, 20),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("10"));
assert!(ccl.contains("20"));
}
#[test]
fn test_struct_variant() {
#[derive(Serialize)]
enum Message {
Request { id: u32, method: String },
Response { id: u32, result: String },
}
#[derive(Serialize)]
struct S {
msg: Message,
}
let s = S {
msg: Message::Request {
id: 1,
method: "GET".to_string(),
},
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("id = 1") || ccl.contains("1"));
assert!(ccl.contains("method = GET") || ccl.contains("GET"));
}
#[test]
fn test_rename_field() {
#[derive(Serialize)]
struct S {
#[serde(rename = "custom_name")]
original: String,
}
let s = S {
original: "value".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("custom_name = value"));
assert!(!ccl.contains("original"));
}
#[test]
fn test_skip_field() {
#[derive(Serialize)]
struct S {
visible: String,
#[serde(skip)]
hidden: String,
}
let s = S {
visible: "shown".to_string(),
hidden: "secret".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("visible = shown"));
assert!(!ccl.contains("hidden"));
assert!(!ccl.contains("secret"));
}
#[test]
fn test_skip_serializing_if() {
#[derive(Serialize)]
struct S {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
optional: Option<String>,
}
let s = S {
name: "test".to_string(),
optional: None,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("name = test"));
assert!(!ccl.contains("optional"));
}
#[test]
fn test_flatten() {
#[derive(Serialize)]
struct Inner {
inner_field: String,
}
#[derive(Serialize)]
struct Outer {
outer_field: String,
#[serde(flatten)]
inner: Inner,
}
let s = Outer {
outer_field: "outer".to_string(),
inner: Inner {
inner_field: "inner".to_string(),
},
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("outer_field = outer"));
assert!(ccl.contains("inner_field = inner"));
}
#[test]
fn test_custom_printer_config() {
#[derive(Serialize)]
struct S {
name: String,
}
let s = S {
name: "test".to_string(),
};
let config = PrinterConfig {
indent_size: 4, ..Default::default()
};
let ccl = to_string_with_config(&s, config).unwrap();
assert!(ccl.contains("name = test"));
}
#[test]
fn test_roundtrip_simple() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Config {
name: String,
port: u16,
enabled: bool,
}
let original = Config {
name: "MyApp".to_string(),
port: 8080,
enabled: true,
};
let ccl = to_string(&original).unwrap();
let parsed: Config = crate::from_str(&ccl).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_roundtrip_nested() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Database {
host: String,
port: u16,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Config {
name: String,
database: Database,
}
let original = Config {
name: "MyApp".to_string(),
database: Database {
host: "localhost".to_string(),
port: 5432,
},
};
let ccl = to_string(&original).unwrap();
let parsed: Config = crate::from_str(&ccl).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_roundtrip_with_option() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Config {
name: String,
#[serde(default)]
description: Option<String>,
}
let original = Config {
name: "App".to_string(),
description: Some("A description".to_string()),
};
let ccl = to_string(&original).unwrap();
let parsed: Config = crate::from_str(&ccl).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_roundtrip_floats() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Config {
ratio: f64,
}
let original = Config { ratio: 3.14159 };
let ccl = to_string(&original).unwrap();
let parsed: Config = crate::from_str(&ccl).unwrap();
assert!((original.ratio - parsed.ratio).abs() < 1e-10);
}
#[test]
fn test_issue_69_hashmap_string_string_roundtrip() {
use std::collections::HashMap;
let mut map: HashMap<String, String> = HashMap::new();
map.insert("key1".to_string(), "value1".to_string());
map.insert("key2".to_string(), "value2".to_string());
let ccl = to_string(&map).unwrap();
let deserialized: HashMap<String, String> = crate::from_str(&ccl).unwrap();
assert_eq!(map, deserialized);
}
#[test]
fn test_issue_68_hashmap_struct_roundtrip() {
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
struct ConfigValue {
name: String,
value: i32,
}
let mut map: HashMap<String, ConfigValue> = HashMap::new();
map.insert(
"item1".to_string(),
ConfigValue {
name: "first".to_string(),
value: 1,
},
);
map.insert(
"item2".to_string(),
ConfigValue {
name: "second".to_string(),
value: 2,
},
);
let ccl = to_string(&map).unwrap();
let deserialized: HashMap<String, ConfigValue> = crate::from_str(&ccl).unwrap();
assert_eq!(map, deserialized);
}
#[test]
fn test_issue_67_vec_struct_roundtrip() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Item {
name: String,
value: i32,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Container {
#[serde(default)]
items: Vec<Item>,
}
let original = Container {
items: vec![
Item {
name: "first".to_string(),
value: 1,
},
Item {
name: "second".to_string(),
value: 2,
},
],
};
let ccl = to_string(&original).unwrap();
let deserialized: Container = crate::from_str(&ccl).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_roundtrip_all_integer_types() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct AllInts {
a: i8,
b: i16,
c: i32,
d: i64,
e: u8,
f: u16,
g: u32,
h: u64,
}
let original = AllInts {
a: -1,
b: -2,
c: -3,
d: -4,
e: 1,
f: 2,
g: 3,
h: 4,
};
let ccl = to_string(&original).unwrap();
let parsed: AllInts = crate::from_str(&ccl).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_empty_string() {
#[derive(Serialize)]
struct S {
value: String,
}
let s = S {
value: String::new(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value ="));
}
#[test]
fn test_string_with_spaces() {
#[derive(Serialize)]
struct S {
value: String,
}
let s = S {
value: "hello world with spaces".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = hello world with spaces"));
}
#[test]
fn test_unicode_content() {
#[derive(Serialize)]
struct S {
value: String,
}
let s = S {
value: "日本語テスト".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("value = 日本語テスト"));
}
#[test]
fn test_emoji_content() {
#[derive(Serialize)]
struct S {
value: String,
}
let s = S {
value: "🎉🚀✨".to_string(),
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("🎉🚀✨"));
}
#[test]
fn test_zero_values() {
#[derive(Serialize)]
struct S {
int_zero: i32,
float_zero: f64,
}
let s = S {
int_zero: 0,
float_zero: 0.0,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("int_zero = 0"));
assert!(ccl.contains("float_zero = 0"));
}
#[test]
fn test_negative_numbers() {
#[derive(Serialize)]
struct S {
negative_int: i32,
negative_float: f64,
}
let s = S {
negative_int: -42,
negative_float: -3.14,
};
let ccl = to_string(&s).unwrap();
assert!(ccl.contains("negative_int = -42"));
assert!(ccl.contains("negative_float = -3.14"));
}
#[test]
fn test_serializer_default() {
let ser = Serializer::default();
assert!(ser.current_key.is_none());
assert_eq!(ser.stack.len(), 1);
}
#[test]
fn test_ser_error_display() {
let err = SerError::custom("test error message");
assert_eq!(format!("{}", err), "test error message");
}
#[test]
fn test_ser_error_into_ccl_error() {
let ser_err = SerError::custom("serialization failed");
let ccl_err: crate::Error = ser_err.into();
match ccl_err {
crate::Error::ValueError(msg) => assert_eq!(msg, "serialization failed"),
_ => panic!("Expected ValueError"),
}
}
}