clojure-reader 0.6.1

A Clojure reader
Documentation
#[cfg(feature = "serde")]
mod test {
	extern crate alloc;

	use alloc::collections::BTreeMap;
	use alloc::string::String;
	use alloc::vec::Vec;

	use clojure_reader::ser::{to_string, to_string_pretty};
	use serde::ser;
	use serde_derive::Serialize;

	#[test]
	fn pretty() {
		#[derive(Serialize)]
		struct Person {
			name: String,
			roles: Vec<String>,
		}

		#[derive(Serialize)]
		struct Team {
			name: String,
			people: Vec<Person>,
			metadata: BTreeMap<String, String>,
		}

		let team = Team {
			name: "Readers".to_string(),
			people: vec![
				Person {
					name: "caTEXAS".to_string(),
					roles: vec!["admin".to_string(), "user".to_string()],
				},
				Person { name: "CAt".to_string(), roles: Vec::new() },
			],
			metadata: BTreeMap::from([
				("region".to_string(), "Chat Land".to_string()),
				("tier".to_string(), "silly".to_string()),
			]),
		};
		let expected = r#"{
	:name "Readers",
	:people [
		{
			:name "caTEXAS",
			:roles [
				"admin"
				"user"
			]
		}
		{
			:name "CAt",
			:roles []
		}
	],
	:metadata {
		"region" "Chat Land",
		"tier" "silly"
	}
}"#;

		let pretty = to_string_pretty(&team).unwrap();
		assert_eq!(pretty, expected);
		assert_eq!(clojure_reader::to_string_pretty(&team).unwrap(), expected);
		assert!(clojure_reader::edn::read_string(&pretty).is_ok());
	}

	#[derive(Serialize)]
	#[serde(untagged)]
	enum Nested {
		Scalar(i64),
		Sequence(Vec<Self>),
		Map(BTreeMap<String, Self>),
	}

	fn nested_sequences(mut value: Nested, depth: usize) -> Nested {
		for _ in 0..depth {
			value = Nested::Sequence(vec![value]);
		}
		value
	}

	#[test]
	fn pretty_falls_back_to_compact_formatting() {
		let pretty = to_string_pretty(&nested_sequences(Nested::Scalar(0), 45)).unwrap();
		let compact_line = alloc::format!("{}[[[0]]]", "\t".repeat(42));

		assert!(pretty.lines().any(|line| line == compact_line));
		assert_eq!(
			pretty.lines().map(|line| line.chars().take_while(|c| *c == '\t').count()).max(),
			Some(42)
		);
		assert!(clojure_reader::edn::read_string(&pretty).is_ok());
		assert_eq!(to_string(&nested_sequences(Nested::Scalar(0), 3)).unwrap(), "[[[0]]]");

		let deeper = to_string_pretty(&nested_sequences(Nested::Scalar(0), 145)).unwrap();
		assert_eq!(deeper.len() - pretty.len(), 200);
	}

	#[test]
	fn compact_fallback_preserves_collection_separators() {
		let indent = "\t".repeat(42);
		let sequence =
			nested_sequences(Nested::Sequence(vec![Nested::Scalar(1), Nested::Scalar(2)]), 42);
		let map = nested_sequences(
			Nested::Map(BTreeMap::from([
				("a".to_string(), Nested::Scalar(1)),
				("b".to_string(), Nested::Scalar(2)),
			])),
			42,
		);

		for (value, compact_line) in [(sequence, "[1 2]"), (map, r#"{"a" 1, "b" 2}"#)] {
			let pretty = to_string_pretty(&value).unwrap();
			assert!(pretty.lines().any(|line| line == alloc::format!("{indent}{compact_line}")));
			assert!(clojure_reader::edn::read_string(&pretty).is_ok());
		}
	}

	#[test]
	fn maybe() {
		#[derive(Serialize)]
		struct Empty {}

		#[derive(Serialize)]
		struct UnitStruct;

		#[derive(Serialize)]
		struct MaybeEmpty {
			maybe: Option<bool>,
		}

		#[derive(Serialize)]
		struct MetricUnits(i64);

		#[derive(Serialize)]
		struct MuricaUnits(i64, i64);

		assert_eq!(to_string::<Option<()>>(&None).unwrap(), "nil");
		assert_eq!(to_string(&vec![1, 2, 3]).unwrap(), "[1 2 3]");
		assert_eq!(to_string(&Empty {}).unwrap(), "{}");
		assert_eq!(to_string(&UnitStruct).unwrap(), "nil");
		assert_eq!(to_string(&MetricUnits(424242)).unwrap(), "424242");
		assert_eq!(to_string(&MuricaUnits(424242, 19847)).unwrap(), "[424242 19847]");
		assert_eq!(to_string(&MaybeEmpty { maybe: None }).unwrap(), "{:maybe nil}");
		assert_eq!(to_string(&MaybeEmpty { maybe: Some(true) }).unwrap(), "{:maybe true}");
	}

	#[test]
	fn strings_are_not_escaped() {
		let value = "a\"b\\c\n\r\t\u{0001}";
		assert_eq!(to_string(&value).unwrap(), alloc::format!("\"{value}\""));
	}

	#[test]
	fn sequence_separator_uses_state() {
		assert_eq!(to_string(&vec!['[', 'x']).unwrap(), r#"[\[ \x]"#);
	}

	#[test]
	fn large_unsigned_values() {
		#[cfg(feature = "arbitrary-nums")]
		assert_eq!(to_string(&u64::MAX).unwrap(), "18446744073709551615N");

		#[cfg(not(feature = "arbitrary-nums"))]
		assert!(to_string(&u64::MAX).is_err());
	}

	#[test]
	fn test_struct() {
		#[derive(Serialize)]
		struct Test {
			int: u32,
		}

		assert_eq!("{:int 1}", to_string(&Test { int: 1 }).unwrap());

		#[derive(Serialize)]
		struct FooBar {
			tests: Vec<Test>,
		}

		let test = FooBar { tests: alloc::vec![Test { int: 4 }, Test { int: 2 }] };
		assert_eq!("{:tests [{:int 4} {:int 2}]}", to_string(&test).unwrap());
	}

	#[test]
	fn complex_struct() {
		#[derive(Serialize)]
		struct Nums {
			num_i16: i16,
			num_i32: i32,
			num_f32: f32,
			num_f64: f64,
		}

		#[derive(Serialize)]
		struct Seqs {
			tup: (u8, String),
			empty: (),
		}

		#[derive(Serialize)]
		struct Test {
			int: u32,
			silly_cat: bool,
			foo: BTreeMap<u8, i8>,
			bar: Vec<u16>,
			some_nums: Nums,
			character: char,
			fancy_char: char,
			seqs: Seqs,
		}

		let test = Test {
			int: 42,
			silly_cat: true,
			foo: BTreeMap::from([(1, -1), (2, -42)]),
			bar: vec![1, 2, 42, 3],
			some_nums: Nums { num_i16: 42, num_i32: 9042, num_f32: 9000.42f32, num_f64: 904200.42f64 },
			character: 'c',
			fancy_char: '\n',
			seqs: Seqs { tup: (42, "".to_string()), empty: () },
		};

		let expected = "{:int 42, :silly_cat true, \
                     :foo {1 -1, 2 -42}, :bar [1 2 42 3], \
                     :some_nums {:num_i16 42, :num_i32 9042, :num_f32 9000.419921875, :num_f64 904200.42}, \
                     :character \\c, :fancy_char \\newline, :seqs {:tup [42 \"\"], :empty nil}}";
		assert_eq!(expected, to_string(&test).unwrap());
	}

	#[test]
	fn test_enum() {
		#[derive(Serialize)]
		enum E {
			Unit,
			Newtype(u32),
			Tuple(u32, u32),
			Struct { a: u32, b: usize },
		}

		assert_eq!(r#"#E/Unit nil"#, to_string(&E::Unit).unwrap());
		assert_eq!(r#"#E/Newtype 1"#, to_string(&E::Newtype(1)).unwrap());
		assert_eq!(r#"#E/Tuple [1 2]"#, to_string(&E::Tuple(1, 2)).unwrap());
		assert_eq!(r#"#E/Struct {:a 1, :b 42}"#, to_string(&E::Struct { a: 1, b: 42 }).unwrap());
	}

	#[test]
	fn internally_tagged_enum() {
		#[derive(Serialize)]
		#[serde(tag = "cafe", rename_all = "kebab-case", rename_all_fields = "kebab-case")]
		enum CatCafe {
			BeforeOpening,
			HerdingKittens { count: u8 },
			ServingTreats { bowls: u8, favorite_flavor: String },
			NapTime { sunny_spots: u8 },
		}

		assert_eq!(r#"{:cafe "before-opening"}"#, to_string(&CatCafe::BeforeOpening).unwrap());
		assert_eq!(
			r#"{:cafe "herding-kittens", :count 7}"#,
			to_string(&CatCafe::HerdingKittens { count: 7 }).unwrap()
		);
		assert_eq!(
			r#"{:cafe "serving-treats", :bowls 3, :favorite-flavor "salmon"}"#,
			to_string(&CatCafe::ServingTreats { bowls: 3, favorite_flavor: "salmon".to_string() })
				.unwrap()
		);
		assert_eq!(
			r#"{:cafe "nap-time", :sunny-spots 2}"#,
			to_string(&CatCafe::NapTime { sunny_spots: 2 }).unwrap()
		);
	}

	#[test]
	fn bytes() {
		#[derive(Serialize)]
		struct Refs<'a> {
			bytes: &'a [u8],
			owned_bytes: [u8; 4],
		}

		let s = String::from("yay cats");
		let refs = Refs { bytes: s.as_bytes(), owned_bytes: [1, 2, 3, 4] };
		let expected = "{:bytes [121 97 121 32 99 97 116 115], :owned_bytes [1 2 3 4]}";
		assert_eq!(expected, to_string(&refs).unwrap());
	}

	#[test]
	fn direct_serialize_bytes() {
		struct Bytes([u8; 3]);

		impl serde::Serialize for Bytes {
			fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
			where
				S: serde::Serializer,
			{
				serializer.serialize_bytes(&self.0)
			}
		}

		assert_eq!("[1 2 3]", to_string(&Bytes([1, 2, 3])).unwrap());
	}

	#[test]
	fn serialize_custom_error() {
		struct Fails;

		impl serde::Serialize for Fails {
			fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
			where
				S: serde::Serializer,
			{
				Err(ser::Error::custom("silly cats"))
			}
		}

		assert_eq!(
			format!("{:?}", to_string(&Fails)),
			"Err(EdnError { code: Serde(\"silly cats\"), line: None, column: None, ptr: None })"
		);
	}
}