Skip to main content

redispatch_xml/
serialize.rs

1//! Serialization helpers for Redispatch 2.0 XML documents.
2
3use serde::Serialize;
4
5use crate::error::RedispatchXmlError;
6use crate::parse::Document;
7
8// ── Public API ────────────────────────────────────────────────────────────────
9
10/// Serialise a [`Document`] to an XML byte vector.
11///
12/// An `<?xml version="1.0" encoding="UTF-8"?>` declaration is prepended
13/// automatically.
14///
15/// # Errors
16///
17/// Returns [`RedispatchXmlError::Xml`] on serialization failure.
18pub fn serialize(doc: &Document) -> Result<Vec<u8>, RedispatchXmlError> {
19    let doc_type = doc.document_type();
20    let bytes = match doc {
21        Document::Activation(d) => serialize_as(d.as_ref(), true)?,
22        Document::PlannedResourceSchedule(d) => serialize_as(d.as_ref(), true)?,
23        Document::Acknowledgement(d) => serialize_as(d.as_ref(), true)?,
24        Document::Stammdaten(d) => serialize_as(d.as_ref(), true)?,
25        Document::StatusRequest(d) => serialize_as(d.as_ref(), true)?,
26        Document::Unavailability(d) => serialize_as(d.as_ref(), true)?,
27        Document::Kaskade(d) => serialize_as(d.as_ref(), true)?,
28        Document::NetworkConstraint(d) => serialize_as(d.as_ref(), true)?,
29        Document::Kostenblatt(d) => serialize_as(d.as_ref(), true)?,
30    };
31    Ok(inject_default_namespace(bytes, doc_type))
32}
33
34/// Insert the document type's default `xmlns` into the root element.
35///
36/// `quick-xml`'s serde serializer cannot emit namespace declarations, so a
37/// serialized document previously failed the namespace check of [`parse`] —
38/// round-trips only worked through the unchecked `parse_as` path. Injecting
39/// the XSD's default namespace makes `parse(serialize(d))` genuinely
40/// lossless. Documents without an XSD namespace pass through unchanged.
41///
42/// [`parse`]: crate::parse::parse
43fn inject_default_namespace(bytes: Vec<u8>, doc_type: crate::documents::DocumentType) -> Vec<u8> {
44    let Some(ns) = doc_type.expected_namespace() else {
45        return bytes;
46    };
47    let Ok(text) = String::from_utf8(bytes) else {
48        return Vec::new(); // unreachable: serializer produced valid UTF-8
49    };
50    // Root element = first '<' after the XML declaration.
51    let Some(root_start) = text
52        .find("?>")
53        .map(|i| i + 2)
54        .and_then(|from| text[from..].find('<').map(|j| from + j))
55    else {
56        return text.into_bytes();
57    };
58    if text[root_start..].contains("xmlns=")
59        && text[root_start
60            ..text[root_start..]
61                .find('>')
62                .map_or(text.len(), |k| root_start + k)]
63            .contains("xmlns=")
64    {
65        return text.into_bytes(); // already namespaced
66    }
67    let Some(rel_end) = text[root_start..].find(['>', ' ']) else {
68        return text.into_bytes();
69    };
70    let insert_at = root_start + rel_end;
71    let mut out = String::with_capacity(text.len() + ns.len() + 10);
72    out.push_str(&text[..insert_at]);
73    out.push_str(&format!(" xmlns=\"{ns}\""));
74    out.push_str(&text[insert_at..]);
75    out.into_bytes()
76}
77
78/// Serialise a specific document type `T` to an XML byte vector.
79///
80/// # Errors
81///
82/// Returns [`RedispatchXmlError::Xml`] on serialization failure.
83pub fn serialize_as<T: Serialize>(
84    doc: &T,
85    add_xml_decl: bool,
86) -> Result<Vec<u8>, RedispatchXmlError> {
87    let xml_str =
88        quick_xml::se::to_string(doc).map_err(|e| RedispatchXmlError::Serialize(e.to_string()))?;
89    let mut out = Vec::with_capacity(xml_str.len() + 40);
90    if add_xml_decl {
91        out.extend_from_slice(b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
92    }
93    out.extend_from_slice(xml_str.as_bytes());
94    Ok(out)
95}