Skip to main content

serde_saphyr/ser/
error.rs

1use std::{fmt, io};
2
3/// Error type used by the YAML serializer.
4///
5/// This type is re-exported as `serde_saphyr::SerializeError` and
6/// `serde_saphyr::ser::Error`, and is returned by
7/// the public serialization APIs (for example `serde_saphyr::to_string`).
8///
9/// It implements `serde::ser::Error`, which allows user `Serialize` impls and
10/// Serde derives to report failures via `S::Error::custom(...)`. Such
11/// free‑form messages are stored in the `Message` variant.
12///
13/// Other variants wrap concrete underlying failures that can occur while
14/// serializing:
15/// - `Format` wraps a `std::fmt::Error` produced when writing to a
16///   `fmt::Write` target.
17/// - `IO` wraps a `std::io::Error` produced when writing to an `io::Write`
18///   target.
19/// - `SingleQuotedRequiresEscaping` reports a `SingleQuoted` wrapper value
20///   that needs YAML escape sequences and therefore cannot be emitted in
21///   single-quoted style.
22/// - `EmptyResolvedTag` reports a `Tagged` wrapper whose explicit resolved tag
23///   is empty. Use `None` to represent the absence of a tag.
24/// - `InvalidGlobalTagUri` reports a non-local `Tagged` identity that is not an
25///   absolute URI.
26/// - `CoreTypeTagAsEnumVariant` reports a YAML core type tag whose
27///   name collides with a non-unit enum variant.
28/// - `Unexpected` is used internally for invariant violations (e.g., around
29///   anchors). It should not normally surface; if it does, please file a bug.
30#[non_exhaustive]
31#[derive(Debug)]
32pub enum Error {
33    /// Free-form error.
34    Message { msg: String },
35    /// Wrapper for formatting errors.
36    Format { error: fmt::Error },
37    /// Wrapper for I/O errors.
38    IO { error: io::Error },
39    /// This is used with anchors and should normally not surface, please report bug if it does.
40    Unexpected { msg: String },
41    /// Options used would produce invalid YAML (0 indentation, etc)
42    InvalidOptions(String),
43    /// A [`crate::SingleQuoted`] value contains a character that cannot be represented safely in
44    /// YAML single-quoted style.
45    SingleQuotedRequiresEscaping { ch: char },
46    /// A [`crate::Tagged`] value uses an empty string as an explicit resolved tag identity.
47    ///
48    /// Use `None` to represent a value without an explicit tag.
49    EmptyResolvedTag,
50    /// A non-local [`crate::Tagged`] identity is not a valid absolute URI.
51    InvalidGlobalTagUri { tag: String },
52    /// A YAML core type tag cannot act as the discriminator for a same-named non-unit enum
53    /// variant.
54    CoreTypeTagAsEnumVariant { tag: String, variant: String },
55}
56
57impl serde_core::ser::Error for Error {
58    fn custom<T: fmt::Display>(msg: T) -> Self {
59        Error::Message {
60            msg: msg.to_string(),
61        }
62    }
63}
64
65impl From<fmt::Error> for Error {
66    fn from(error: fmt::Error) -> Self {
67        Error::Format { error }
68    }
69}
70
71impl From<io::Error> for Error {
72    fn from(error: io::Error) -> Self {
73        Error::IO { error }
74    }
75}
76
77impl From<String> for Error {
78    fn from(message: String) -> Self {
79        Error::Message { msg: message }
80    }
81}
82
83impl From<&String> for Error {
84    fn from(message: &String) -> Self {
85        Error::Message {
86            msg: message.clone(),
87        }
88    }
89}
90
91impl From<&str> for Error {
92    fn from(message: &str) -> Self {
93        Error::Message {
94            msg: message.to_string(),
95        }
96    }
97}
98
99impl Error {
100    #[cold]
101    #[inline(never)]
102    pub(crate) fn unexpected(message: &str) -> Self {
103        Error::Unexpected {
104            msg: message.to_string(),
105        }
106    }
107}
108
109impl fmt::Display for Error {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match self {
112            Error::Message { msg } => f.write_str(msg),
113            Error::Format { error } => write!(f, "formatting error: {error}"),
114            Error::IO { error } => write!(f, "I/O error: {error}"),
115            Error::Unexpected { msg } => write!(f, "unexpected internal error: {msg}"),
116            Error::InvalidOptions(msg) => write!(f, "invalid serialization options: {msg}"),
117            Error::SingleQuotedRequiresEscaping { ch } => {
118                // Debug formatting keeps rejected control characters escaped in the error message.
119                write!(
120                    f,
121                    "Single quotes cannot be used for a string containing {ch:?}. Use double quoting for values that require YAML escape sequences"
122                )
123            }
124            Error::EmptyResolvedTag => {
125                f.write_str("cannot serialize an explicit YAML tag with an empty resolved identity")
126            }
127            Error::InvalidGlobalTagUri { tag } => write!(
128                f,
129                "cannot serialize resolved YAML tag {tag:?}: non-local tag identities must be absolute URIs"
130            ),
131            Error::CoreTypeTagAsEnumVariant { tag, variant } => write!(
132                f,
133                "cannot use YAML core type tag {tag:?} to select enum variant {variant:?}"
134            ),
135        }
136    }
137}
138
139impl std::error::Error for Error {
140    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
141        match self {
142            Error::Format { error } => Some(error),
143            Error::IO { error } => Some(error),
144            Error::Message { .. }
145            | Error::Unexpected { .. }
146            | Error::InvalidOptions(_)
147            | Error::SingleQuotedRequiresEscaping { .. }
148            | Error::EmptyResolvedTag
149            | Error::InvalidGlobalTagUri { .. }
150            | Error::CoreTypeTagAsEnumVariant { .. } => None,
151        }
152    }
153}