Skip to main content

openbim_dt/
document.rs

1//! Lossless, namespace-aware XML tree used by ISO 23387 documents.
2//!
3//! Two serializers are provided and they answer different questions:
4//!
5//! * [`Document::to_xml_string`] rebuilds the document from the *semantic*
6//!   tree. Equivalent syntax is normalized (quote style, character-reference
7//!   spelling, whitespace inside tags), so the output is well-formed and
8//!   information-preserving but not necessarily byte-for-byte identical.
9//! * [`Document::to_xml_string_exact`] replays the retained source spans and
10//!   reproduces the parsed bytes exactly. It fails closed rather than silently
11//!   normalizing when span provenance is unavailable.
12
13use std::{borrow::Cow, error::Error, fmt, io::Cursor, sync::Arc};
14
15use quick_xml::{
16    events::{
17        attributes::Attribute as XmlAttribute, BytesCData, BytesDecl, BytesEnd, BytesPI,
18        BytesStart, BytesText, Event,
19    },
20    name::QName,
21    Writer,
22};
23
24use crate::parser::{parse_document, ParseError, ParseOptions};
25
26/// Half-open byte range into the document's retained source text.
27///
28/// Positions are `u64` to match `quick-xml`'s reader positions directly; the
29/// parser never has to narrow, so a document larger than `usize` on a 32-bit
30/// host fails the byte budget rather than silently truncating a span.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub(crate) struct Span {
33    pub(crate) start: u64,
34    pub(crate) end: u64,
35}
36
37impl Span {
38    pub(crate) const fn new(start: u64, end: u64) -> Self {
39        Self { start, end }
40    }
41
42    /// Smallest span covering both inputs. Used when adjacent text events are
43    /// coalesced into a single [`Node::Text`]; those events are contiguous, so
44    /// the union is exactly the original byte run.
45    pub(crate) const fn union(self, other: Self) -> Self {
46        Self {
47            start: if self.start < other.start {
48                self.start
49            } else {
50                other.start
51            },
52            end: if self.end > other.end {
53                self.end
54            } else {
55                other.end
56            },
57        }
58    }
59
60    fn slice(self, source: &str) -> Option<&str> {
61        let start = usize::try_from(self.start).ok()?;
62        let end = usize::try_from(self.end).ok()?;
63        source.get(start..end)
64    }
65}
66
67/// An ordered list of child nodes plus the source span each node came from.
68///
69/// The two vectors are always the same length; every mutation goes through
70/// [`NodeList::push`], which is the single place that invariant is maintained.
71///
72/// Equality deliberately ignores spans: they are provenance, not content. Two
73/// lists holding the same nodes are equal even if one came from a document
74/// that spelled the same content differently.
75#[derive(Debug, Clone, Default, Eq)]
76pub(crate) struct NodeList {
77    nodes: Vec<Node>,
78    spans: Vec<Option<Span>>,
79}
80
81impl PartialEq for NodeList {
82    fn eq(&self, other: &Self) -> bool {
83        self.nodes == other.nodes
84    }
85}
86
87impl NodeList {
88    pub(crate) const fn new() -> Self {
89        Self {
90            nodes: Vec::new(),
91            spans: Vec::new(),
92        }
93    }
94
95    pub(crate) fn nodes(&self) -> &[Node] {
96        &self.nodes
97    }
98
99    fn spans(&self) -> &[Option<Span>] {
100        &self.spans
101    }
102
103    fn is_empty(&self) -> bool {
104        self.nodes.is_empty()
105    }
106
107    /// Appends a node, coalescing runs of adjacent text into one node.
108    ///
109    /// Coalescing keeps `direct_text()` faithful for inputs such as
110    /// `A&#38;B`, which the reader reports as three separate events. The
111    /// merged span is the union of the merged events' spans, which is exact
112    /// because those events are contiguous in the source.
113    pub(crate) fn push(&mut self, node: Node, span: Option<Span>) {
114        if let Node::Text(value) = node {
115            if let Some(Node::Text(existing)) = self.nodes.last_mut() {
116                existing.push_str(&value);
117                let last = self.spans.len() - 1;
118                self.spans[last] = match (self.spans[last], span) {
119                    (Some(previous), Some(next)) => Some(previous.union(next)),
120                    _ => None,
121                };
122                return;
123            }
124            self.nodes.push(Node::Text(value));
125        } else {
126            self.nodes.push(node);
127        }
128        self.spans.push(span);
129    }
130}
131
132/// XML declaration values retained by the document model.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct XmlDeclaration {
135    pub version: String,
136    pub encoding: Option<String>,
137    pub standalone: Option<String>,
138}
139
140/// One XML attribute with both lexical and resolved namespace identity.
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct Attribute {
143    qname: String,
144    prefix: Option<String>,
145    local_name: String,
146    namespace_uri: Option<Arc<str>>,
147    value: String,
148}
149
150impl Attribute {
151    pub(crate) fn parsed(
152        qname: String,
153        prefix: Option<String>,
154        local_name: String,
155        namespace_uri: Option<Arc<str>>,
156        value: String,
157    ) -> Self {
158        Self {
159            qname,
160            prefix,
161            local_name,
162            namespace_uri,
163            value,
164        }
165    }
166
167    /// Builds an unqualified or prefixed attribute for programmatic construction.
168    ///
169    /// `namespace_uri` is the attribute's resolved namespace, independent of
170    /// `prefix`: callers building a standalone tree are responsible for also
171    /// declaring the `xmlns:*` binding on an enclosing element if one is
172    /// required, since this constructor does not synthesize declarations.
173    #[must_use]
174    pub fn new(
175        prefix: Option<&str>,
176        local_name: &str,
177        namespace_uri: Option<&str>,
178        value: impl Into<String>,
179    ) -> Self {
180        let qname = match prefix {
181            Some(prefix) if !prefix.is_empty() => format!("{prefix}:{local_name}"),
182            _ => local_name.to_owned(),
183        };
184        Self {
185            qname,
186            prefix: prefix.filter(|value| !value.is_empty()).map(str::to_owned),
187            local_name: local_name.to_owned(),
188            namespace_uri: namespace_uri.map(Arc::from),
189            value: value.into(),
190        }
191    }
192
193    #[must_use]
194    pub fn qname(&self) -> &str {
195        &self.qname
196    }
197
198    #[must_use]
199    pub fn prefix(&self) -> Option<&str> {
200        self.prefix.as_deref()
201    }
202
203    #[must_use]
204    pub fn local_name(&self) -> &str {
205        &self.local_name
206    }
207
208    #[must_use]
209    pub fn namespace_uri(&self) -> Option<&str> {
210        self.namespace_uri.as_deref()
211    }
212
213    #[must_use]
214    pub fn value(&self) -> &str {
215        &self.value
216    }
217}
218
219/// An XML element retaining names, namespace resolution, attributes, and order.
220///
221/// Equality compares semantic identity and content only. Source spans are
222/// provenance for the exact writer, so two elements parsed from differently
223/// spelled but equivalent syntax remain equal.
224#[derive(Debug, Clone, Eq)]
225pub struct Element {
226    qname: String,
227    prefix: Option<String>,
228    local_name: String,
229    namespace_uri: Option<Arc<str>>,
230    attributes: Vec<Attribute>,
231    nodes: NodeList,
232    empty_style: bool,
233    /// Source bytes of `<name …>` or, for empty-element syntax, `<name …/>`.
234    open_span: Option<Span>,
235    /// Source bytes of `</name>`; `None` for empty-element syntax.
236    close_span: Option<Span>,
237}
238
239impl PartialEq for Element {
240    fn eq(&self, other: &Self) -> bool {
241        // `empty_style` is excluded on purpose: `<a/>` and `<a></a>` are the
242        // same element. The exact writer distinguishes them via spans; the
243        // semantic model must not.
244        self.qname == other.qname
245            && self.prefix == other.prefix
246            && self.local_name == other.local_name
247            && self.namespace_uri == other.namespace_uri
248            && self.attributes == other.attributes
249            && self.nodes == other.nodes
250    }
251}
252
253impl Element {
254    pub(crate) fn parsed(
255        qname: String,
256        prefix: Option<String>,
257        local_name: String,
258        namespace_uri: Option<Arc<str>>,
259        attributes: Vec<Attribute>,
260        empty_style: bool,
261        open_span: Option<Span>,
262    ) -> Self {
263        Self {
264            qname,
265            prefix,
266            local_name,
267            namespace_uri,
268            attributes,
269            nodes: NodeList::new(),
270            empty_style,
271            open_span,
272            close_span: None,
273        }
274    }
275
276    pub(crate) fn set_close_span(&mut self, span: Option<Span>) {
277        self.close_span = span;
278    }
279
280    /// Builds a fresh element with no retained source provenance.
281    ///
282    /// `namespace` is the element's resolved namespace URI, independent of
283    /// `prefix`. Elements built this way carry no spans, so
284    /// [`Document::to_xml_string`] works on them but
285    /// [`Document::to_xml_string_exact`] fails closed with
286    /// [`ExactWriteError::NoSource`] — there is no retained source to slice.
287    /// This constructor does not emit an `xmlns` declaration for `namespace`;
288    /// use [`Document::standalone`] to wrap a built root with the `xmlns:dt`
289    /// binding codecs need.
290    #[must_use]
291    pub fn new(namespace: Option<&str>, prefix: Option<&str>, local_name: &str) -> Self {
292        let prefix = prefix.filter(|value| !value.is_empty()).map(str::to_owned);
293        let qname = match &prefix {
294            Some(prefix) => format!("{prefix}:{local_name}"),
295            None => local_name.to_owned(),
296        };
297        Self {
298            qname,
299            prefix,
300            local_name: local_name.to_owned(),
301            namespace_uri: namespace.map(Arc::from),
302            attributes: Vec::new(),
303            nodes: NodeList::new(),
304            empty_style: false,
305            open_span: None,
306            close_span: None,
307        }
308    }
309
310    /// Appends an attribute, returning `self` for chained construction.
311    #[must_use]
312    pub fn with_attribute(mut self, attribute: Attribute) -> Self {
313        self.attributes.push(attribute);
314        self
315    }
316
317    /// Appends a child element as a node, returning `self` for chained
318    /// construction.
319    #[must_use]
320    pub fn with_child(mut self, child: Element) -> Self {
321        self.nodes.push(Node::Element(child), None);
322        self
323    }
324
325    /// Appends a text node, returning `self` for chained construction.
326    #[must_use]
327    pub fn with_text(mut self, text: impl Into<String>) -> Self {
328        self.nodes.push(Node::Text(text.into()), None);
329        self
330    }
331
332    /// The element's full source extent: `<a …>` through `</a>`, or the single
333    /// `<a …/>` tag. `None` when either end was not recorded, which forces the
334    /// exact writer to fail closed rather than emit a partial slice.
335    pub(crate) fn full_span(&self) -> Option<Span> {
336        let open = self.open_span?;
337        match self.close_span {
338            Some(close) => Some(open.union(close)),
339            // Empty-element syntax has no close tag; the open span is complete.
340            None if self.empty_style => Some(open),
341            None => None,
342        }
343    }
344
345    #[must_use]
346    pub fn qname(&self) -> &str {
347        &self.qname
348    }
349
350    #[must_use]
351    pub fn prefix(&self) -> Option<&str> {
352        self.prefix.as_deref()
353    }
354
355    #[must_use]
356    pub fn local_name(&self) -> &str {
357        &self.local_name
358    }
359
360    #[must_use]
361    pub fn namespace_uri(&self) -> Option<&str> {
362        self.namespace_uri.as_deref()
363    }
364
365    #[must_use]
366    pub fn attributes(&self) -> &[Attribute] {
367        &self.attributes
368    }
369
370    #[must_use]
371    pub fn nodes(&self) -> &[Node] {
372        self.nodes.nodes()
373    }
374
375    /// Whether the source used an empty-element tag such as `<dt:Property/>`.
376    #[must_use]
377    pub const fn was_empty_element(&self) -> bool {
378        self.empty_style
379    }
380
381    /// Direct child elements in document order.
382    pub fn children(&self) -> impl Iterator<Item = &Element> {
383        self.nodes.nodes().iter().filter_map(Node::as_element)
384    }
385
386    /// Finds an attribute by resolved namespace URI and local name.
387    #[must_use]
388    pub fn attribute_ns(&self, namespace_uri: Option<&str>, local_name: &str) -> Option<&str> {
389        self.attributes
390            .iter()
391            .find(|attribute| {
392                attribute.namespace_uri() == namespace_uri && attribute.local_name() == local_name
393            })
394            .map(Attribute::value)
395    }
396
397    /// Direct semantic text and CDATA content, preserving order.
398    #[must_use]
399    pub fn direct_text(&self) -> String {
400        let mut result = String::new();
401        for node in self.nodes.nodes() {
402            match node {
403                Node::Text(value) | Node::CData(value) => result.push_str(value),
404                _ => {}
405            }
406        }
407        result
408    }
409
410    pub(crate) fn push(&mut self, node: Node, span: Option<Span>) {
411        self.nodes.push(node, span);
412    }
413}
414
415/// XML node kinds retained by the document model.
416#[derive(Debug, Clone, PartialEq, Eq)]
417pub enum Node {
418    Element(Element),
419    Text(String),
420    CData(String),
421    Comment(String),
422    /// Complete processing-instruction content: target plus optional data.
423    ProcessingInstruction(String),
424}
425
426impl Node {
427    #[must_use]
428    pub fn as_element(&self) -> Option<&Element> {
429        match self {
430            Self::Element(element) => Some(element),
431            _ => None,
432        }
433    }
434}
435
436/// A well-formed XML document with one root element.
437///
438/// Equality is semantic: two documents are equal when their declaration,
439/// prolog, root, and epilog match. Retained source text and spans are
440/// provenance and do not participate, so a document compares equal to itself
441/// after [`Document::without_source`].
442#[derive(Debug, Clone, Eq)]
443pub struct Document {
444    declaration: Option<XmlDeclaration>,
445    prolog: NodeList,
446    root: Element,
447    epilog: NodeList,
448    /// Retained parse input, enabling byte-exact reserialization.
449    source: Option<Arc<str>>,
450    declaration_span: Option<Span>,
451}
452
453impl PartialEq for Document {
454    fn eq(&self, other: &Self) -> bool {
455        self.declaration == other.declaration
456            && self.prolog == other.prolog
457            && self.root == other.root
458            && self.epilog == other.epilog
459    }
460}
461
462impl Document {
463    /// Parses with conservative production defaults.
464    pub fn parse(xml: &str) -> Result<Self, ParseError> {
465        Self::parse_with_options(xml, ParseOptions::default())
466    }
467
468    /// Parses with explicit resource limits.
469    pub fn parse_with_options(xml: &str, options: ParseOptions) -> Result<Self, ParseError> {
470        parse_document(xml, options)
471    }
472
473    pub(crate) fn parsed(
474        declaration: Option<XmlDeclaration>,
475        prolog: NodeList,
476        root: Element,
477        epilog: NodeList,
478        source: Option<Arc<str>>,
479        declaration_span: Option<Span>,
480    ) -> Self {
481        Self {
482            declaration,
483            prolog,
484            root,
485            epilog,
486            source,
487            declaration_span,
488        }
489    }
490
491    #[must_use]
492    pub const fn declaration(&self) -> Option<&XmlDeclaration> {
493        self.declaration.as_ref()
494    }
495
496    #[must_use]
497    pub fn prolog(&self) -> &[Node] {
498        self.prolog.nodes()
499    }
500
501    #[must_use]
502    pub const fn root(&self) -> &Element {
503        &self.root
504    }
505
506    #[must_use]
507    pub fn epilog(&self) -> &[Node] {
508        self.epilog.nodes()
509    }
510
511    /// The exact text this document was parsed from, when it is still retained.
512    #[must_use]
513    pub fn source(&self) -> Option<&str> {
514        self.source.as_deref()
515    }
516
517    /// Drops the retained source text, keeping the semantic tree.
518    ///
519    /// Useful when a caller wants to hold many documents in memory and does not
520    /// need byte-exact output. Afterwards [`Document::to_xml_string_exact`]
521    /// fails closed instead of reconstructing approximate bytes.
522    #[must_use]
523    pub fn without_source(mut self) -> Self {
524        self.source = None;
525        self
526    }
527
528    /// Serializes the semantic XML tree without dropping retained content.
529    ///
530    /// Equivalent syntax may be normalized; use [`Document::to_xml_string_exact`]
531    /// when the output must match the parsed bytes.
532    pub fn to_xml_string(&self) -> Result<String, WriteError> {
533        let mut writer = Writer::new(Cursor::new(Vec::new()));
534        if let Some(declaration) = &self.declaration {
535            writer.write_event(Event::Decl(BytesDecl::new(
536                &declaration.version,
537                declaration.encoding.as_deref(),
538                declaration.standalone.as_deref(),
539            )))?;
540        }
541        for node in self.prolog.nodes() {
542            write_node(&mut writer, node)?;
543        }
544        write_element(&mut writer, &self.root)?;
545        for node in self.epilog.nodes() {
546            write_node(&mut writer, node)?;
547        }
548        String::from_utf8(writer.into_inner().into_inner()).map_err(WriteError::Utf8)
549    }
550
551    /// Reproduces the parsed document byte-for-byte from retained source spans.
552    ///
553    /// Every construct is emitted from the exact bytes the reader consumed, so
554    /// attribute quoting, character-reference spelling, intra-tag whitespace,
555    /// CDATA versus escaped text, and empty-element syntax all survive
556    /// unchanged. The call fails rather than falling back to normalization when
557    /// span provenance is missing.
558    pub fn to_xml_string_exact(&self) -> Result<String, ExactWriteError> {
559        let source = self.source.as_deref().ok_or(ExactWriteError::NoSource)?;
560        let mut output = String::with_capacity(source.len());
561        if self.declaration.is_some() {
562            push_span(&mut output, source, self.declaration_span)?;
563        }
564        push_nodes(&mut output, source, &self.prolog)?;
565        push_element(&mut output, source, &self.root)?;
566        push_nodes(&mut output, source, &self.epilog)?;
567        Ok(output)
568    }
569
570    pub(crate) fn take_root(self) -> Element {
571        self.root
572    }
573
574    /// Builds a standalone document from a built root, adding the `xmlns:dt`
575    /// declaration codecs need.
576    ///
577    /// `to_element` on owned types deliberately emits no `xmlns` declarations
578    /// — the embedding document owns them (LOIN embeds `dt:`-qualified
579    /// children under an unqualified LOIN-local outer element). This
580    /// constructor exists for callers, including this crate's own schema
581    /// validation tests, that need a document whose root is dt-namespaced and
582    /// self-contained.
583    #[must_use]
584    pub fn standalone(root: Element) -> Self {
585        let root = root.with_attribute(Attribute::new(
586            Some("xmlns"),
587            "dt",
588            Some("http://www.w3.org/2000/xmlns/"),
589            crate::NAMESPACE,
590        ));
591        Self {
592            declaration: None,
593            prolog: NodeList::new(),
594            root,
595            epilog: NodeList::new(),
596            source: None,
597            declaration_span: None,
598        }
599    }
600}
601
602fn push_span(output: &mut String, source: &str, span: Option<Span>) -> Result<(), ExactWriteError> {
603    let span = span.ok_or(ExactWriteError::MissingSpan)?;
604    output.push_str(span.slice(source).ok_or(ExactWriteError::SpanOutOfRange)?);
605    Ok(())
606}
607
608fn push_nodes(output: &mut String, source: &str, list: &NodeList) -> Result<(), ExactWriteError> {
609    for (node, span) in list.nodes().iter().zip(list.spans()) {
610        match node {
611            Node::Element(element) => push_element(output, source, element)?,
612            _ => push_span(output, source, *span)?,
613        }
614    }
615    Ok(())
616}
617
618fn push_element(
619    output: &mut String,
620    source: &str,
621    element: &Element,
622) -> Result<(), ExactWriteError> {
623    push_span(output, source, element.open_span)?;
624    if element.close_span.is_none() {
625        // Empty-element syntax: the open span already covers `<name …/>`.
626        if element.nodes.is_empty() {
627            return Ok(());
628        }
629        return Err(ExactWriteError::MissingSpan);
630    }
631    push_nodes(output, source, &element.nodes)?;
632    push_span(output, source, element.close_span)
633}
634
635fn write_element(
636    writer: &mut Writer<Cursor<Vec<u8>>>,
637    element: &Element,
638) -> Result<(), WriteError> {
639    let mut start = BytesStart::new(element.qname());
640    for attribute in element.attributes() {
641        start.push_attribute(XmlAttribute {
642            key: QName(attribute.qname().as_bytes()),
643            value: Cow::Owned(escape_attribute_value(attribute.value())),
644        });
645    }
646    if element.empty_style && element.nodes.is_empty() {
647        writer.write_event(Event::Empty(start))?;
648        return Ok(());
649    }
650    writer.write_event(Event::Start(start))?;
651    for node in element.nodes() {
652        write_node(writer, node)?;
653    }
654    writer.write_event(Event::End(BytesEnd::new(element.qname())))?;
655    Ok(())
656}
657
658fn escape_attribute_value(value: &str) -> Vec<u8> {
659    let mut output = String::with_capacity(value.len());
660    for character in value.chars() {
661        match character {
662            '&' => output.push_str("&amp;"),
663            '<' => output.push_str("&lt;"),
664            '"' => output.push_str("&quot;"),
665            '\t' => output.push_str("&#x9;"),
666            '\n' => output.push_str("&#xA;"),
667            '\r' => output.push_str("&#xD;"),
668            _ => output.push(character),
669        }
670    }
671    output.into_bytes()
672}
673
674fn escape_text_value(value: &str) -> String {
675    let mut output = String::with_capacity(value.len());
676    for character in value.chars() {
677        match character {
678            '&' => output.push_str("&amp;"),
679            '<' => output.push_str("&lt;"),
680            '>' => output.push_str("&gt;"),
681            '\r' => output.push_str("&#13;"),
682            _ => output.push(character),
683        }
684    }
685    output
686}
687
688fn write_node(writer: &mut Writer<Cursor<Vec<u8>>>, node: &Node) -> Result<(), WriteError> {
689    match node {
690        Node::Element(element) => write_element(writer, element),
691        Node::Text(value) => writer
692            .write_event(Event::Text(BytesText::from_escaped(escape_text_value(
693                value,
694            ))))
695            .map_err(Into::into),
696        Node::CData(value) => writer
697            .write_event(Event::CData(BytesCData::new(value)))
698            .map_err(Into::into),
699        // Comments carry literal character data, not entity references; writing
700        // them escaped would corrupt any comment containing `&`, `<`, or `>`.
701        Node::Comment(value) => writer
702            .write_event(Event::Comment(BytesText::from_escaped(value.as_str())))
703            .map_err(Into::into),
704        Node::ProcessingInstruction(value) => writer
705            .write_event(Event::PI(BytesPI::new(value)))
706            .map_err(Into::into),
707    }
708}
709
710/// XML serialization failure.
711#[derive(Debug)]
712pub enum WriteError {
713    Xml(std::io::Error),
714    Utf8(std::string::FromUtf8Error),
715}
716
717impl fmt::Display for WriteError {
718    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
719        match self {
720            Self::Xml(error) => write!(formatter, "could not write XML: {error}"),
721            Self::Utf8(error) => write!(formatter, "XML writer produced invalid UTF-8: {error}"),
722        }
723    }
724}
725
726impl Error for WriteError {
727    fn source(&self) -> Option<&(dyn Error + 'static)> {
728        match self {
729            Self::Xml(error) => Some(error),
730            Self::Utf8(error) => Some(error),
731        }
732    }
733}
734
735impl From<std::io::Error> for WriteError {
736    fn from(value: std::io::Error) -> Self {
737        Self::Xml(value)
738    }
739}
740
741/// Why a byte-exact reserialization could not be produced.
742///
743/// Every variant means the same thing to a caller: the exact bytes are not
744/// recoverable, so no output is returned rather than a normalized guess.
745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
746pub enum ExactWriteError {
747    /// The document does not retain its parse input.
748    NoSource,
749    /// A retained construct has no recorded source span.
750    MissingSpan,
751    /// A recorded span does not address a character boundary of the source.
752    SpanOutOfRange,
753}
754
755impl fmt::Display for ExactWriteError {
756    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
757        let message = match self {
758            Self::NoSource => "document does not retain its parse input",
759            Self::MissingSpan => "a retained node has no recorded source span",
760            Self::SpanOutOfRange => "a recorded source span is not a character boundary",
761        };
762        formatter.write_str(message)
763    }
764}
765
766impl Error for ExactWriteError {}