serde_v8vs 0.1.0

serde support for V8's ValueSerializer format
Documentation
use core::fmt::Debug;
use std::{
	collections::{HashMap, HashSet},
	process::Command,
	time::{Duration, SystemTime}
};

use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

use crate::{SerializeOptions, de::DeserializeOptions};

fn test_roundtrip<T: Serialize + for<'a> Deserialize<'a> + PartialEq + Debug>(js: &str, rust: &T) {
	let mut output = Command::new("node")
		.arg("-e")
		.arg(format!(
			r#"
import {{ deepStrictEqual }} from 'node:assert/strict';
import process from 'node:process';
import {{ Serializer, deserialize }} from 'node:v8';

const gt = {js};
deepStrictEqual(deserialize(new Uint8Array({serialized:?})), gt);

const serializer = new Serializer();
serializer.writeHeader();
serializer.writeValue(gt);
process.stdout.write(serializer.releaseBuffer());
"#,
			serialized = super::ser::to_boxed_slice(rust, SerializeOptions::default()).unwrap()
		))
		.output()
		.unwrap();
	if !output.status.success() {
		panic!("{}", String::from_utf8_lossy(&output.stderr));
	}

	let x: T = super::de::from_reader(output.stdout.as_slice(), DeserializeOptions::default()).unwrap();
	assert_eq!(rust, &x);
	let x: T = super::de::from_slice(&mut output.stdout, DeserializeOptions::default()).unwrap();
	assert_eq!(rust, &x);
}

#[test]
fn test_int_array() {
	test_roundtrip("[1, 2, 3]", &vec![1, 2, 3])
}

#[test]
fn test_int_set() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(transparent)]
	struct Foo(#[serde(serialize_with = "crate::as_set")] HashSet<i32>);
	test_roundtrip("new Set([1, 2, 3])", &Foo(HashSet::from([1i32, 2, 3])))
}

#[test]
fn test_array_property() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	struct Foo {
		x: [i32; 3]
	}
	test_roundtrip("{ x: [1, 2, 3] }", &Foo { x: [1, 2, 3] })
}

#[test]
fn test_float_array() {
	test_roundtrip("[1.0, 2.5, 3.14]", &vec![1.0, 2.5, 3.14])
}

#[test]
fn test_object_array() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	struct Foo {
		x: i8
	}
	test_roundtrip("[ { x: 1 }, { x: -2 }, { x: 3 } ]", &[Foo { x: 1 }, Foo { x: -2 }, Foo { x: 3 }])
}

#[test]
fn test_bytes() {
	test_roundtrip("new Uint8Array(340).fill(127)", &ByteBuf::from(vec![127u8; 340]))
}

#[test]
fn test_null() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	struct Foo {
		x: ()
	}
	test_roundtrip("{ x: null }", &Foo { x: () })
}

#[test]
fn test_null_array() {
	test_roundtrip("[ null, null ]", &[(), ()])
}

#[test]
fn test_null_hole_array() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(untagged)]
	enum Foo {
		Int(i8),
		Str(String),
		Hole(()),
		Object {}
	}
	test_roundtrip("[ 1, 'x', null, {} ]", &[Foo::Int(1), Foo::Str(String::from("x")), Foo::Hole(()), Foo::Object {}])
}

#[test]
fn test_tagged_enums() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(rename_all = "camelCase")]
	enum Foo {
		Int(i8),
		Str(String),
		Hole(()),
		Object {}
	}
	test_roundtrip("[ { int: 1 }, { str: 'x' }, { hole: null }, { object: {} } ]", &[Foo::Int(1), Foo::Str(String::from("x")), Foo::Hole(()), Foo::Object {}])
}

#[test]
fn test_bigints() {
	test_roundtrip("[ 255n, -1738000n, 2n ** 76n, -(2n ** 31n + 4n) ]", &[255i128, -1738000i128, 2i128.pow(76), -(2i128.pow(31) + 4)])
}

#[test]
fn test_smis() {
	test_roundtrip("[ 2 ** 30 - 1, 2 ** 30, -(2 ** 30) ]", &[2i32.pow(30) - 1, 2i32.pow(30), -(2i32.pow(30))])
}

#[test]
fn test_latin1() {
	test_roundtrip("'\\xE6\\xD0\\xF0pqrs\\xFF'", &String::from("æÐðpqrsÿ"));

	// longer string to trigger SIMD paths
	test_roundtrip("'\\xE6\\xD0\\xF0pqrs\\xFF'.repeat(64)", &String::from("æÐðpqrsÿ").repeat(64));
}

#[test]
fn test_unicode() {
	test_roundtrip(
		"decodeURIComponent('effective.%20Power%20%D9%84%D9%8F%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5%A3%20%E0%A5%A3%20%E5%86%97')",
		&String::from("effective. Power لُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗")
	);

	// longer string to trigger SIMD paths
	test_roundtrip(
		"decodeURIComponent('effective.%20Power%20%D9%84%D9%8F%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5%A3%20%E0%A5%A3%20%E5%86%97'.repeat(64))",
		&String::from("effective. Power لُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗").repeat(64)
	);
}

#[test]
fn test_map() {
	test_roundtrip(r#"new Map([ ["x", 123], ["y", 456] ])"#, &HashMap::<String, i32>::from([("x".to_string(), 123), ("y".to_string(), 456)]));
}

#[test]
fn test_map_as_object() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(transparent)]
	struct Foo(#[serde(serialize_with = "crate::as_object")] HashMap<String, i32>);
	test_roundtrip(r#"{ "x": 123, "y": 456 }"#, &Foo(HashMap::<String, i32>::from([("x".to_string(), 123), ("y".to_string(), 456)])));
}

#[test]
fn test_map_weird_keys() {
	#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
	#[serde(untagged)]
	enum Foo {
		Int(i8),
		Str(String),
		Hole(()),
		Object {}
	}
	test_roundtrip(
		r#"new Map([ [42, 6], [decodeURIComponent("%F0%9F%91%A8%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7%E2%80%8D%F0%9F%91%A6"), 12], [null, 18], [{}, 24] ])"#,
		&HashMap::<Foo, i32>::from([(Foo::Int(42), 6), (Foo::Str("👨‍👩‍👧‍👦".to_string()), 12), (Foo::Hole(()), 18), (Foo::Object {}, 24)])
	);
}

#[test]
fn test_date() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(transparent)]
	struct Foo(#[serde(with = "crate::as_date")] SystemTime);
	let time = SystemTime::UNIX_EPOCH + Duration::from_millis(1811030400000);
	test_roundtrip(r#"new Date("2027-05-23")"#, &Foo(time));
}

#[test]
#[cfg(feature = "jiff-0_2")]
fn test_date_jiff_0_2() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(transparent)]
	struct Foo(#[serde(with = "crate::as_date")] jiff_0_2::Timestamp);
	let time = jiff_0_2::Timestamp::from_millisecond(1811030400000).unwrap();
	test_roundtrip(r#"new Date("2027-05-23")"#, &Foo(time));
}

#[test]
#[cfg(feature = "chrono-0_4")]
fn test_date_chrono_0_4() {
	#[derive(Serialize, Deserialize, Debug, PartialEq)]
	#[serde(transparent)]
	struct Foo(#[serde(with = "crate::as_date")] chrono_0_4::DateTime<chrono_0_4::Utc>);
	let time = chrono_0_4::DateTime::from_timestamp_millis(1811030400000).unwrap();
	test_roundtrip(r#"new Date("2027-05-23")"#, &Foo(time));
}