1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
use crate::libyml::safe_cstr::CStr;
#[allow(clippy::unsafe_removed_from_name)]
use libyml as sys;
use std::{
    fmt::{self, Debug, Display},
    mem::MaybeUninit,
    ptr::NonNull,
};

/// A type alias for a `Result` with an `Error` as the error type.
pub type Result<T> = std::result::Result<T, Error>;

/// Represents an error that occurred during YAML processing.
#[derive(Clone, Copy)]
pub struct Error {
    /// The kind of error that occurred.
    ///
    /// This field uses the `yaml_error_type_t` type from the `libyml` crate,
    /// which represents different types of errors.
    pub kind: sys::YamlErrorTypeT,

    /// A null-terminated string describing the problem that caused the error.
    ///
    /// The `CStr<'static>` type represents a borrowed C-style string with a static lifetime.
    pub problem: CStr<'static>,

    /// The offset of the problem that caused the error.
    pub problem_offset: u64,

    /// The mark indicating the position of the problem that caused the error.
    ///
    /// The `Mark` type represents a position in the YAML input.
    pub problem_mark: Mark,

    /// An optional null-terminated string providing additional context for the error.
    ///
    /// The `CStr<'static>` type represents a borrowed C-style string with a static lifetime.
    pub context: Option<CStr<'static>>,

    /// The mark indicating the position of the context related to the error.
    ///
    /// The `Mark` type represents a position in the YAML input.
    pub context_mark: Mark,
}

impl Error {
    /// Constructs an `Error` from a `YamlParserT` pointer.
    ///
    /// # Safety
    ///
    /// This function is unsafe because it dereferences raw pointers and assumes
    /// the validity of the `YamlParserT` pointer.
    pub unsafe fn parse_error(parser: *const sys::YamlParserT) -> Self {
        Error {
            kind: unsafe { (*parser).error },
            problem: match NonNull::new(unsafe {
                (*parser).problem as *mut _
            }) {
                Some(problem) => CStr::from_ptr(problem),
                None => CStr::from_bytes_with_nul(
                    b"libyml parser failed but there is no error\0",
                )
                .expect("Error creating CStr from bytes"),
            },
            problem_offset: unsafe { (*parser).problem_offset },
            problem_mark: Mark {
                sys: unsafe { (*parser).problem_mark },
            },
            #[allow(clippy::manual_map)]
            context: match NonNull::new(unsafe {
                (*parser).context as *mut _
            }) {
                Some(context) => Some(CStr::from_ptr(context)),
                None => None,
            },
            context_mark: Mark {
                sys: unsafe { (*parser).context_mark },
            },
        }
    }

    /// Constructs an `Error` from a `YamlEmitterT` pointer.
    ///
    /// # Safety
    ///
    /// This function is unsafe because it dereferences raw pointers and assumes
    /// the validity of the `YamlEmitterT` pointer.
    pub unsafe fn emit_error(
        emitter: *const sys::YamlEmitterT,
    ) -> Self {
        Error {
            kind: unsafe { (*emitter).error },
            problem: match NonNull::new(unsafe {
                (*emitter).problem as *mut _
            }) {
                Some(problem) => CStr::from_ptr(problem),
                None => CStr::from_bytes_with_nul(
                    b"libyml emitter failed but there is no error\0",
                )
                .expect("Error creating CStr from bytes"),
            },
            problem_offset: 0,
            problem_mark: Mark {
                sys: unsafe {
                    MaybeUninit::<sys::YamlMarkT>::zeroed()
                        .assume_init()
                },
            },
            context: None,
            context_mark: Mark {
                sys: unsafe {
                    MaybeUninit::<sys::YamlMarkT>::zeroed()
                        .assume_init()
                },
            },
        }
    }

    /// Returns the mark indicating the position of the problem that caused the error.
    pub fn mark(&self) -> Mark {
        self.problem_mark
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}", self.problem)?;
        if self.problem_mark.sys.line != 0
            || self.problem_mark.sys.column != 0
        {
            write!(formatter, " at {}", self.problem_mark)?;
        } else if self.problem_offset != 0 {
            write!(formatter, " at position {}", self.problem_offset)?;
        }
        if let Some(context) = &self.context {
            write!(formatter, ", {}", context)?;
            if (self.context_mark.sys.line != 0
                || self.context_mark.sys.column != 0)
                && (self.context_mark.sys.line
                    != self.problem_mark.sys.line
                    || self.context_mark.sys.column
                        != self.problem_mark.sys.column)
            {
                write!(formatter, " at {}", self.context_mark)?;
            }
        }
        Ok(())
    }
}

impl Debug for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut formatter = formatter.debug_struct("Error");
        if let Some(kind) = match self.kind {
            sys::YamlMemoryError => Some("MEMORY"),
            sys::YamlReaderError => Some("READER"),
            sys::YamlScannerError => Some("SCANNER"),
            sys::YamlParserError => Some("PARSER"),
            sys::YamlComposerError => Some("COMPOSER"),
            sys::YamlWriterError => Some("WRITER"),
            sys::YamlEmitterError => Some("EMITTER"),
            _ => None,
        } {
            formatter.field("kind", &format_args!("{}", kind));
        }
        formatter.field("problem", &self.problem);
        if self.problem_mark.sys.line != 0
            || self.problem_mark.sys.column != 0
        {
            formatter.field("problem_mark", &self.problem_mark);
        } else if self.problem_offset != 0 {
            formatter.field("problem_offset", &self.problem_offset);
        }
        if let Some(context) = &self.context {
            formatter.field("context", context);
            if self.context_mark.sys.line != 0
                || self.context_mark.sys.column != 0
            {
                formatter.field("context_mark", &self.context_mark);
            }
        }
        formatter.finish()
    }
}

/// Represents a mark in a YAML document.
/// A mark indicates a specific position or location within the document.
#[derive(Copy, Clone)]
pub struct Mark {
    /// The underlying system representation of the mark.
    ///
    /// This field is marked as `pub(super)`, which means it is accessible within the current module
    /// and its parent module, but not from outside the crate.
    pub sys: sys::YamlMarkT,
}

impl Mark {
    /// Retrieves the index of the mark.
    ///
    /// The index represents the position of the mark within the YAML input.
    ///
    /// # Returns
    ///
    /// Returns the index of the mark as a `u64`.
    pub fn index(&self) -> u64 {
        self.sys.index
    }

    /// Retrieves the line number of the mark.
    ///
    /// The line number indicates the line in the YAML input where the mark is located.
    ///
    /// # Returns
    ///
    /// Returns the line number of the mark as a `u64`.
    pub fn line(&self) -> u64 {
        self.sys.line
    }

    /// Retrieves the column number of the mark.
    ///
    /// The column number indicates the column within the line where the mark is located.
    ///
    /// # Returns
    ///
    /// Returns the column number of the mark as a `u64`.
    pub fn column(&self) -> u64 {
        self.sys.column
    }
}

impl Display for Mark {
    /// Formats the mark for display purposes.
    ///
    /// If the line and column numbers are non-zero, the mark is formatted as "line X column Y".
    /// Otherwise, the mark is formatted as "position Z", where Z is the index.
    ///
    /// # Arguments
    ///
    /// * `formatter` - The formatter to write the display output to.
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` if the formatting was successful, or an error otherwise.
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.sys.line != 0 || self.sys.column != 0 {
            write!(
                formatter,
                "line {} column {}",
                self.sys.line + 1,
                self.sys.column + 1,
            )
        } else {
            write!(formatter, "position {}", self.sys.index)
        }
    }
}

impl Debug for Mark {
    /// Formats the mark for debugging purposes.
    ///
    /// The mark is formatted as a debug struct with either the line and column numbers
    /// or the index, depending on their values.
    ///
    /// # Arguments
    ///
    /// * `formatter` - The formatter to write the debug output to.
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` if the formatting was successful, or an error otherwise.
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut formatter = formatter.debug_struct("Mark");
        if self.sys.line != 0 || self.sys.column != 0 {
            formatter.field("line", &(self.sys.line + 1));
            formatter.field("column", &(self.sys.column + 1));
        } else {
            formatter.field("index", &self.sys.index);
        }
        formatter.finish()
    }
}