1use std::{fmt, io};
2
3use yaml_rt_core::{LineCol, Span, YamlDoc, YamlError};
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Location {
11 index: usize,
12 line: usize,
13 column: usize,
14}
15
16impl Location {
17 #[must_use]
19 pub const fn index(&self) -> usize {
20 self.index
21 }
22
23 #[must_use]
25 pub const fn line(&self) -> usize {
26 self.line
27 }
28
29 #[must_use]
31 pub const fn column(&self) -> usize {
32 self.column
33 }
34}
35
36#[derive(Debug)]
38pub struct Error {
39 pub(crate) message: String,
40 pub(crate) path: Option<String>,
41 pub(crate) location: Option<Location>,
42 source: Option<Box<dyn std::error::Error + Send + Sync>>,
43}
44
45impl Error {
46 pub(crate) fn message(message: impl Into<String>) -> Self {
47 Self {
48 message: message.into(),
49 path: None,
50 location: None,
51 source: None,
52 }
53 }
54
55 pub(crate) fn at(mut self, doc: &YamlDoc, span: Span) -> Self {
56 if self.location.is_none() {
57 let LineCol { line, column } = doc.source().line_col(span.start as usize);
58 self.location = Some(Location {
59 index: span.start as usize,
60 line,
61 column,
62 });
63 }
64 self
65 }
66
67 pub(crate) fn with_path(mut self, path: &str) -> Self {
68 if path != "." && self.path.is_none() {
69 self.path = Some(path.to_owned());
70 }
71 self
72 }
73
74 pub(crate) fn io(error: io::Error) -> Self {
75 Self {
76 message: error.to_string(),
77 path: None,
78 location: None,
79 source: Some(Box::new(error)),
80 }
81 }
82
83 #[must_use]
85 pub const fn location(&self) -> Option<Location> {
86 self.location
87 }
88}
89
90impl Clone for Error {
91 fn clone(&self) -> Self {
92 Self {
93 message: self.message.clone(),
94 path: self.path.clone(),
95 location: self.location,
96 source: None,
97 }
98 }
99}
100
101impl From<YamlError> for Error {
102 fn from(error: YamlError) -> Self {
103 let location = error.diagnostic.position.map(|position| Location {
104 index: error.diagnostic.span.start as usize,
105 line: position.line,
106 column: position.column,
107 });
108 Self {
109 message: error.to_string(),
110 path: None,
111 location,
112 source: Some(Box::new(error)),
113 }
114 }
115}
116
117impl From<io::Error> for Error {
118 fn from(error: io::Error) -> Self {
119 Self::io(error)
120 }
121}
122
123impl fmt::Display for Error {
124 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125 if let Some(path) = &self.path {
126 write!(formatter, "{path}: ")?;
127 }
128 formatter.write_str(&self.message)?;
129 if let Some(location) = self.location {
130 write!(
131 formatter,
132 " at line {} column {}",
133 location.line, location.column
134 )?;
135 }
136 Ok(())
137 }
138}
139
140impl std::error::Error for Error {
141 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142 self.source
143 .as_deref()
144 .map(|source| source as &(dyn std::error::Error + 'static))
145 }
146}
147
148impl serde::de::Error for Error {
149 fn custom<T>(message: T) -> Self
150 where
151 T: fmt::Display,
152 {
153 Self::message(message.to_string())
154 }
155}
156
157impl serde::ser::Error for Error {
158 fn custom<T>(message: T) -> Self
159 where
160 T: fmt::Display,
161 {
162 Self::message(message.to_string())
163 }
164}