Skip to main content

rustnetconf_yang/
serialize.rs

1//! XML serialization helpers for YANG-generated types.
2//!
3//! Converts YANG-typed Rust structs into NETCONF-compatible XML for
4//! use with `edit_config()` and deserializes XML responses back into
5//! typed structs.
6
7use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
8use quick_xml::Writer;
9use std::io::Cursor;
10
11/// Trait for types that can be serialized to NETCONF XML.
12///
13/// Implemented by the code generator for each YANG container/list.
14pub trait ToNetconfXml {
15    /// The YANG module namespace URI.
16    fn namespace(&self) -> &str;
17
18    /// The root element name.
19    fn root_element(&self) -> &str;
20
21    /// Serialize this value to NETCONF-compatible XML.
22    fn to_xml(&self) -> Result<String, XmlError>;
23}
24
25/// Trait for types that can be deserialized from NETCONF XML.
26pub trait FromNetconfXml: Sized {
27    /// Deserialize from an XML string.
28    fn from_xml(xml: &str) -> Result<Self, XmlError>;
29}
30
31/// XML serialization/deserialization error.
32#[derive(Debug, thiserror::Error)]
33pub enum XmlError {
34    #[error("XML write error: {0}")]
35    Write(String),
36
37    #[error("XML parse error: {0}")]
38    Parse(String),
39
40    #[error("missing required field: {0}")]
41    MissingField(String),
42
43    #[error("invalid value for field '{field}': {message}")]
44    InvalidValue { field: String, message: String },
45}
46
47/// Helper to write an XML element with text content.
48pub fn write_text_element(
49    writer: &mut Writer<Cursor<Vec<u8>>>,
50    name: &str,
51    value: &str,
52) -> Result<(), XmlError> {
53    let start = BytesStart::new(name);
54    writer
55        .write_event(Event::Start(start))
56        .map_err(|e| XmlError::Write(e.to_string()))?;
57    writer
58        .write_event(Event::Text(BytesText::new(value)))
59        .map_err(|e| XmlError::Write(e.to_string()))?;
60    writer
61        .write_event(Event::End(BytesEnd::new(name)))
62        .map_err(|e| XmlError::Write(e.to_string()))?;
63    Ok(())
64}
65
66/// Helper to write an XML element with a namespace attribute.
67pub fn write_start_with_ns(
68    writer: &mut Writer<Cursor<Vec<u8>>>,
69    name: &str,
70    namespace: &str,
71) -> Result<(), XmlError> {
72    let mut start = BytesStart::new(name);
73    start.push_attribute(("xmlns", namespace));
74    writer
75        .write_event(Event::Start(start))
76        .map_err(|e| XmlError::Write(e.to_string()))?;
77    Ok(())
78}
79
80/// Helper to write a closing element.
81pub fn write_end(
82    writer: &mut Writer<Cursor<Vec<u8>>>,
83    name: &str,
84) -> Result<(), XmlError> {
85    writer
86        .write_event(Event::End(BytesEnd::new(name)))
87        .map_err(|e| XmlError::Write(e.to_string()))?;
88    Ok(())
89}
90
91/// Create a new XML writer.
92pub fn new_writer() -> Writer<Cursor<Vec<u8>>> {
93    Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2)
94}
95
96/// Extract the XML string from a writer.
97pub fn finish_writer(writer: Writer<Cursor<Vec<u8>>>) -> Result<String, XmlError> {
98    let buf = writer.into_inner().into_inner();
99    String::from_utf8(buf).map_err(|e| XmlError::Write(e.to_string()))
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_write_text_element() {
108        let mut writer = new_writer();
109        write_text_element(&mut writer, "host-name", "spine-01").unwrap();
110        let xml = finish_writer(writer).unwrap();
111        assert_eq!(xml, "<host-name>spine-01</host-name>");
112    }
113
114    #[test]
115    fn test_write_start_with_ns() {
116        let mut writer = new_writer();
117        write_start_with_ns(&mut writer, "interfaces", "urn:ietf:params:xml:ns:yang:ietf-interfaces").unwrap();
118        write_end(&mut writer, "interfaces").unwrap();
119        let xml = finish_writer(writer).unwrap();
120        assert!(xml.contains("xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\""));
121    }
122}