use facet::Facet;
use facet_json::{from_str, to_string};
use facet_testhelpers::test;
#[derive(Facet, Clone, Debug)]
#[facet(transparent)]
pub struct HexString(pub String);
#[derive(Facet, Clone, Debug)]
#[facet(transparent)]
pub struct BinaryString(pub String);
#[derive(Facet, Debug, Clone, PartialEq)]
pub struct FormatAwareValue {
pub name: String,
#[facet(json::proxy = HexString)]
#[facet(proxy = BinaryString)]
pub value: u32,
}
impl TryFrom<HexString> for u32 {
type Error = std::num::ParseIntError;
fn try_from(proxy: HexString) -> Result<Self, Self::Error> {
let s = proxy.0.trim_start_matches("0x").trim_start_matches("0X");
u32::from_str_radix(s, 16)
}
}
impl From<&u32> for HexString {
fn from(v: &u32) -> Self {
HexString(format!("0x{:x}", v))
}
}
impl TryFrom<BinaryString> for u32 {
type Error = std::num::ParseIntError;
fn try_from(proxy: BinaryString) -> Result<Self, Self::Error> {
u32::from_str_radix(proxy.0.trim_start_matches("0b"), 2)
}
}
impl From<&u32> for BinaryString {
fn from(v: &u32) -> Self {
BinaryString(format!("0b{:b}", v))
}
}
#[test]
fn test_format_specific_proxy_serialization() {
let data = FormatAwareValue {
name: "test".to_string(),
value: 255,
};
let json = to_string(&data).unwrap();
assert!(
json.contains("0xff"),
"JSON should use hex format, got: {json}"
);
}
#[test]
fn test_hex_string_conversion() {
let hex = HexString("0x1a".to_string());
let value: u32 = hex.try_into().unwrap();
assert_eq!(value, 0x1a);
}
#[test]
fn test_format_specific_proxy_deserialization() {
let json = r#"{"name":"test","value":"0x1a"}"#;
let data: FormatAwareValue = from_str(json).unwrap();
assert_eq!(data.name, "test");
assert_eq!(data.value, 0x1a);
}
#[derive(Facet, Debug, Clone, PartialEq)]
pub struct JsonOnlyProxy {
pub label: String,
#[facet(json::proxy = HexString)]
pub id: u32,
}
#[test]
fn test_json_only_proxy_roundtrip() {
let original = JsonOnlyProxy {
label: "item".to_string(),
id: 0xbeef,
};
let json = to_string(&original).unwrap();
assert!(
json.contains("0xbeef"),
"JSON should use hex format, got: {json}"
);
let roundtripped: JsonOnlyProxy = from_str(&json).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn test_format_proxy_field_metadata() {
use facet::Facet;
use facet_core::{Type, UserType};
let shape = <FormatAwareValue as Facet>::SHAPE;
let struct_type = match shape.ty {
Type::User(UserType::Struct(s)) => s,
_ => panic!("Expected struct type, got {:?}", shape.ty),
};
let value_field = struct_type
.fields
.iter()
.find(|f| f.name == "value")
.expect("Should have value field");
assert!(
!value_field.format_proxies.is_empty(),
"Should have format-specific proxies"
);
let json_proxy = value_field.format_proxy("json");
assert!(json_proxy.is_some(), "Should have json proxy");
assert!(value_field.proxy.is_some(), "Should have default proxy");
let effective_json = value_field.effective_proxy(Some("json"));
assert!(effective_json.is_some());
let effective_xml = value_field.effective_proxy(Some("xml"));
assert!(effective_xml.is_some(), "Should fall back to default proxy");
assert_ne!(
effective_json.map(|p| p.shape.id),
effective_xml.map(|p| p.shape.id),
"JSON and XML should use different proxies"
);
}
#[derive(Facet, Debug, Clone, PartialEq)]
#[facet(transparent)]
pub struct JsonHexNumber(pub u32);
#[derive(Facet, Clone, Debug)]
#[facet(transparent)]
pub struct JsonNumberProxy(pub String);
#[derive(Facet, Clone, Debug)]
#[facet(transparent)]
pub struct DefaultNumberProxy(pub String);
#[derive(Facet, Debug, Clone, PartialEq)]
#[facet(json::proxy = JsonNumberProxy)]
#[facet(proxy = DefaultNumberProxy)]
pub struct ContainerFormatProxy {
pub inner: u32,
}
impl TryFrom<JsonNumberProxy> for ContainerFormatProxy {
type Error = std::num::ParseIntError;
fn try_from(proxy: JsonNumberProxy) -> Result<Self, Self::Error> {
let s = proxy.0.trim_start_matches("0x").trim_start_matches("0X");
let inner = u32::from_str_radix(s, 16)?;
Ok(ContainerFormatProxy { inner })
}
}
impl From<&ContainerFormatProxy> for JsonNumberProxy {
fn from(v: &ContainerFormatProxy) -> Self {
JsonNumberProxy(format!("0x{:x}", v.inner))
}
}
impl TryFrom<DefaultNumberProxy> for ContainerFormatProxy {
type Error = std::num::ParseIntError;
fn try_from(proxy: DefaultNumberProxy) -> Result<Self, Self::Error> {
let inner = proxy.0.parse::<u32>()?;
Ok(ContainerFormatProxy { inner })
}
}
impl From<&ContainerFormatProxy> for DefaultNumberProxy {
fn from(v: &ContainerFormatProxy) -> Self {
DefaultNumberProxy(format!("{}", v.inner))
}
}
#[test]
fn test_container_format_specific_proxy_serialization() {
let data = ContainerFormatProxy { inner: 255 };
let json = to_string(&data).unwrap();
assert!(
json.contains("0xff"),
"JSON should use hex format via container proxy, got: {json}"
);
}
#[test]
fn test_container_format_specific_proxy_deserialization() {
let json = r#""0x1a""#;
let data: ContainerFormatProxy = from_str(json).unwrap();
assert_eq!(data.inner, 0x1a);
}
#[test]
fn test_container_format_specific_proxy_roundtrip() {
let original = ContainerFormatProxy { inner: 0xbeef };
let json = to_string(&original).unwrap();
assert!(
json.contains("0xbeef"),
"JSON should use hex format, got: {json}"
);
let roundtripped: ContainerFormatProxy = from_str(&json).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn test_container_format_proxy_shape_metadata() {
use facet::Facet;
let shape = <ContainerFormatProxy as Facet>::SHAPE;
assert!(
!shape.format_proxies.is_empty(),
"Should have container-level format-specific proxies"
);
let json_proxy = shape.format_proxy("json");
assert!(
json_proxy.is_some(),
"Should have json proxy at container level"
);
assert!(
shape.proxy.is_some(),
"Should have default container-level proxy"
);
let effective_json = shape.effective_proxy(Some("json"));
assert!(effective_json.is_some());
let effective_xml = shape.effective_proxy(Some("xml"));
assert!(effective_xml.is_some(), "Should fall back to default proxy");
assert_ne!(
effective_json.map(|p| p.shape.id),
effective_xml.map(|p| p.shape.id),
"JSON and XML should use different container proxies"
);
}
#[derive(Facet, Debug, Clone, PartialEq)]
#[facet(json::proxy = JsonNumberProxy)]
pub struct JsonOnlyContainerProxy {
pub inner: u32,
}
impl TryFrom<JsonNumberProxy> for JsonOnlyContainerProxy {
type Error = std::num::ParseIntError;
fn try_from(proxy: JsonNumberProxy) -> Result<Self, Self::Error> {
let s = proxy.0.trim_start_matches("0x").trim_start_matches("0X");
let inner = u32::from_str_radix(s, 16)?;
Ok(JsonOnlyContainerProxy { inner })
}
}
impl From<&JsonOnlyContainerProxy> for JsonNumberProxy {
fn from(v: &JsonOnlyContainerProxy) -> Self {
JsonNumberProxy(format!("0x{:x}", v.inner))
}
}
#[test]
fn test_json_only_container_proxy_roundtrip() {
let original = JsonOnlyContainerProxy { inner: 0xcafe };
let json = to_string(&original).unwrap();
assert!(
json.contains("0xcafe"),
"JSON should use hex format, got: {json}"
);
let roundtripped: JsonOnlyContainerProxy = from_str(&json).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn test_json_only_container_proxy_metadata() {
use facet::Facet;
let shape = <JsonOnlyContainerProxy as Facet>::SHAPE;
assert!(
!shape.format_proxies.is_empty(),
"Should have container-level format-specific proxies"
);
let json_proxy = shape.format_proxy("json");
assert!(
json_proxy.is_some(),
"Should have json proxy at container level"
);
assert!(
shape.proxy.is_none(),
"Should NOT have default container-level proxy"
);
let effective_json = shape.effective_proxy(Some("json"));
assert!(effective_json.is_some());
let effective_xml = shape.effective_proxy(Some("xml"));
assert!(
effective_xml.is_none(),
"Should NOT have fallback proxy for xml"
);
}
#[derive(Facet, Clone, Debug)]
#[facet(transparent)]
pub struct StringRepr(pub String);
impl TryFrom<StringRepr> for JsonConstValue {
type Error = &'static str;
fn try_from(value: StringRepr) -> Result<Self, Self::Error> {
value.0.parse()
}
}
impl From<&JsonConstValue> for StringRepr {
fn from(_value: &JsonConstValue) -> Self {
StringRepr("CONST_VALUE".to_string())
}
}
#[derive(Debug, Default, Clone, Copy, Facet, PartialEq)]
#[facet(json::proxy = StringRepr)]
pub struct JsonConstValue;
impl core::fmt::Display for JsonConstValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "CONST_VALUE")
}
}
impl core::str::FromStr for JsonConstValue {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "CONST_VALUE" {
Ok(Self)
} else {
Err("expected `CONST_VALUE`")
}
}
}
#[derive(Facet, Debug, PartialEq)]
struct StructWithContainerProxyField {
name: String,
const_val: JsonConstValue,
}
#[test]
fn test_container_level_proxy_in_field_deserialization() {
let json = r#"{"name":"test","const_val":"CONST_VALUE"}"#;
let data: StructWithContainerProxyField = from_str(json).unwrap();
assert_eq!(data.name, "test");
assert_eq!(data.const_val, JsonConstValue);
}
#[test]
fn test_container_level_proxy_in_field_serialization() {
let data = StructWithContainerProxyField {
name: "test".to_string(),
const_val: JsonConstValue,
};
let json = to_string(&data).unwrap();
assert!(
json.contains("CONST_VALUE"),
"JSON should contain 'CONST_VALUE', got: {json}"
);
}
#[test]
fn test_container_level_proxy_in_field_roundtrip() {
let original = StructWithContainerProxyField {
name: "example".to_string(),
const_val: JsonConstValue,
};
let json = to_string(&original).unwrap();
let roundtripped: StructWithContainerProxyField = from_str(&json).unwrap();
assert_eq!(original, roundtripped);
}