Skip to content

Commit 5cd0b20

Browse files
Merge pull request #20 from facet-rs/gh-17
fix: support enums as XML attribute values
2 parents 4e04cbb + 2ac5d27 commit 5cd0b20

File tree

3 files changed

+117
-3
lines changed

3 files changed

+117
-3
lines changed

facet-dom/src/deserializer/mod.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,13 +869,40 @@ where
869869
///
870870
/// # Type Handling
871871
///
872-
/// Delegates to `facet_dessert::set_string_value` which handles parsing the string
873-
/// into the appropriate scalar type (String, &str, integers, floats, bools, etc.).
872+
/// For enums, matches the string against variant names using lowerCamelCase conversion
873+
/// (matching the serialization behavior). For other types, delegates to
874+
/// `facet_dessert::set_string_value` which handles parsing the string into the
875+
/// appropriate scalar type (String, &str, integers, floats, bools, etc.).
874876
pub(crate) fn set_string_value(
875877
&mut self,
876-
wip: Partial<'de, BORROW>,
878+
mut wip: Partial<'de, BORROW>,
877879
value: Cow<'de, str>,
878880
) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
881+
// Handle enums specially - match variant names with lowerCamelCase conversion
882+
if let Type::User(UserType::Enum(enum_def)) = &wip.shape().ty {
883+
// Find matching variant
884+
for (idx, variant) in enum_def.variants.iter().enumerate() {
885+
// Only unit variants can be deserialized from a plain string
886+
if variant.data.kind != StructKind::Unit {
887+
continue;
888+
}
889+
890+
// Compute the expected string for this variant (same logic as serialization)
891+
let variant_str: Cow<'_, str> = if variant.rename.is_some() {
892+
Cow::Borrowed(variant.effective_name())
893+
} else {
894+
to_element_name(variant.name)
895+
};
896+
897+
if value == variant_str {
898+
wip = wip.select_nth_variant(idx)?;
899+
return Ok(wip);
900+
}
901+
}
902+
903+
// No match found - fall through to facet_dessert which will give a proper error
904+
}
905+
879906
Ok(facet_dessert::set_string_value(
880907
wip,
881908
value,

facet-xml/src/serializer.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ fn write_scalar_value(
3939
write!(out, "{}", value)?;
4040
return Ok(true);
4141
}
42+
43+
// Handle enums - unit variants serialize to their variant name
44+
if let Ok(enum_) = value.into_enum()
45+
&& let Ok(variant) = enum_.active_variant()
46+
&& variant.data.kind == facet_core::StructKind::Unit
47+
{
48+
// Use effective_name() if there's a rename, otherwise convert to lowerCamelCase
49+
let variant_name = if variant.rename.is_some() {
50+
Cow::Borrowed(variant.effective_name())
51+
} else {
52+
facet_dom::naming::to_element_name(variant.name)
53+
};
54+
out.write_all(variant_name.as_bytes())?;
55+
return Ok(true);
56+
}
57+
4258
return Ok(false);
4359
};
4460

facet-xml/tests/eenum.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,77 @@ fn enum_as_attribute_value_with_option() {
204204
assert_eq!(task2.priority, None);
205205
}
206206

207+
// ============================================================================
208+
// Enum attribute roundtrip tests (issue #17)
209+
// ============================================================================
210+
211+
#[test]
212+
fn enum_as_attribute_value_roundtrip() {
213+
// Issue #17: enums as attribute values should serialize to variant name
214+
215+
#[derive(Debug, Clone, Copy, PartialEq, Facet)]
216+
#[repr(C)]
217+
enum Status {
218+
#[facet(rename = "active")]
219+
Active,
220+
#[facet(rename = "inactive")]
221+
Inactive,
222+
}
223+
224+
#[derive(Debug, Clone, PartialEq, Facet)]
225+
#[facet(rename = "Item")]
226+
struct Item {
227+
#[facet(xml::attribute)]
228+
id: u32,
229+
#[facet(xml::attribute)]
230+
status: Status,
231+
}
232+
233+
let item = Item {
234+
id: 42,
235+
status: Status::Active,
236+
};
237+
238+
let xml = facet_xml::to_string(&item).unwrap();
239+
assert!(xml.contains(r#"status="active""#), "xml was: {}", xml);
240+
241+
// Roundtrip
242+
let parsed: Item = facet_xml::from_str(&xml).unwrap();
243+
assert_eq!(parsed.id, 42);
244+
assert_eq!(parsed.status, Status::Active);
245+
}
246+
247+
#[test]
248+
fn enum_as_attribute_without_rename() {
249+
// Test that enums without rename use lowerCamelCase and roundtrip correctly
250+
251+
#[derive(Debug, Clone, Copy, PartialEq, Facet)]
252+
#[repr(C)]
253+
enum MyStatus {
254+
IsActive,
255+
IsInactive,
256+
}
257+
258+
#[derive(Debug, Clone, PartialEq, Facet)]
259+
#[facet(rename = "Item")]
260+
struct Item {
261+
#[facet(xml::attribute)]
262+
status: MyStatus,
263+
}
264+
265+
let item = Item {
266+
status: MyStatus::IsActive,
267+
};
268+
269+
let xml = facet_xml::to_string(&item).unwrap();
270+
// The variant name is converted to lowerCamelCase
271+
assert!(xml.contains(r#"status="isActive""#), "xml was: {}", xml);
272+
273+
// Roundtrip works because deserializer also uses lowerCamelCase matching
274+
let parsed: Item = facet_xml::from_str(&xml).unwrap();
275+
assert_eq!(parsed.status, MyStatus::IsActive);
276+
}
277+
207278
// ============================================================================
208279
// Enum variant fields with xml::attribute (issue #1855)
209280
// ============================================================================

0 commit comments

Comments
 (0)