ion_rs/
result.rs

1use std::convert::From;
2use std::fmt::{Debug, Display, Error};
3use std::{fmt, io};
4
5use thiserror::Error;
6
7/// Position represents the location within an Ion stream where an error has been
8/// identified. For all formats `byte_offset` will contain the number of bytes into the stream
9/// that have been processed prior to encountering the error. When working with the text format,
10/// `line_column` will be updated to contain the line and column as well.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Position {
13    pub(crate) byte_offset: usize,
14    pub(crate) line_column: Option<(usize, usize)>,
15}
16
17impl Position {
18    /// Creates a new Position with the provided offset in bytes.
19    /// Line and Column offsets can be added using [`Self::with_text_position()`].
20    pub fn with_offset(offset: usize) -> Self {
21        Position {
22            byte_offset: offset,
23            line_column: None,
24        }
25    }
26
27    /// Add line and column information to the current Position.
28    pub fn with_text_position(&self, line: usize, column: usize) -> Self {
29        Position {
30            line_column: Some((line, column)),
31            ..*self
32        }
33    }
34
35    /// Returns the offset from the start of the Ion stream in bytes.
36    pub fn byte_offset(&self) -> usize {
37        self.byte_offset
38    }
39
40    /// If available returns the text position as line and column offsets.
41    pub fn text_position(&self) -> Option<(usize, usize)> {
42        self.line_column
43    }
44
45    /// If available returns the line component of the text position.
46    pub fn line(&self) -> Option<usize> {
47        self.line_column.map(|(line, _column)| line)
48    }
49
50    /// If available returns the column component of the text position.
51    pub fn column(&self) -> Option<usize> {
52        self.line_column.map(|(_line, column)| column)
53    }
54
55    /// Returns true if the current Position contains line and column offsets.
56    pub fn has_text_position(&self) -> bool {
57        self.line_column.is_some()
58    }
59}
60
61impl Display for Position {
62    // Formats the position based on whether we have a LineAndColumn or not.
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), Error> {
64        match &self.line_column {
65            None => write!(f, "{}", self.byte_offset),
66            Some((line, column)) => {
67                write!(f, "{} ({}:{})", self.byte_offset, line, column)
68            }
69        }
70    }
71}
72
73/// A unified Result type representing the outcome of method calls that may fail.
74pub type IonResult<T> = Result<T, IonError>;
75
76/// Represents the different types of high-level failures that might occur when reading Ion data.
77#[derive(Debug, Error)]
78pub enum IonError {
79    /// Indicates that an IO error was encountered while reading or writing.
80    #[error("{source:?}")]
81    IoError {
82        #[from]
83        source: io::Error,
84    },
85
86    /// Indicates that the input buffer did not contain enough data to perform the requested read
87    /// operation. If the input source contains more data, the reader can append it to the buffer
88    /// and try again.
89    #[error("ran out of input while reading {label} at offset {position}")]
90    Incomplete {
91        label: &'static str,
92        position: Position,
93    },
94
95    /// Indicates that the writer encountered a problem while serializing a given piece of data.
96    #[error("{description}")]
97    EncodingError { description: String },
98
99    /// Indicates that the data stream being read contained illegal or otherwise unreadable data.
100    #[error("{description}")]
101    DecodingError { description: String },
102
103    /// Returned when the user has performed an illegal operation (for example: calling stepOut()
104    /// on the cursor at the top level.)
105    #[error(
106        "The user has performed an operation that is not legal in the current state: {operation}"
107    )]
108    IllegalOperation { operation: String },
109}
110
111impl From<fmt::Error> for IonError {
112    fn from(error: Error) -> Self {
113        IonError::EncodingError {
114            description: error.to_string(),
115        }
116    }
117}
118
119// io::Error does not implement Clone, which precludes us from simply deriving an implementation.
120// IonError needs a Clone implementation because we use a jump table of cached IonResult values when
121// parsing type descriptor bytes. The only error type that will be cloned by virtue of using the jump
122// table is DecodingError.
123impl Clone for IonError {
124    fn clone(&self) -> Self {
125        use IonError::*;
126        match self {
127            IoError { source } => IoError {
128                // io::Error implements From<ErrorKind>, and ErrorKind is cloneable.
129                source: io::Error::from(source.kind()),
130            },
131            Incomplete { label, position } => Incomplete {
132                label,
133                position: position.clone(),
134            },
135            EncodingError { description } => EncodingError {
136                description: description.clone(),
137            },
138            DecodingError { description } => DecodingError {
139                description: description.clone(),
140            },
141            IllegalOperation { operation } => IllegalOperation {
142                operation: operation.clone(),
143            },
144        }
145    }
146}
147
148// io::Error does not implement PartialEq, which precludes us from simply deriving an implementation.
149// Providing an implementation of PartialEq allows IonResult values to be the left or right side in
150// an assert_eq!() statement.
151impl PartialEq for IonError {
152    fn eq(&self, other: &Self) -> bool {
153        use IonError::*;
154        match (self, other) {
155            // We can compare the io::Errors' ErrorKinds, offering a weak definition of equality.
156            (IoError { source: s1 }, IoError { source: s2 }) => s1.kind() == s2.kind(),
157            (
158                Incomplete {
159                    label: l1,
160                    position: p1,
161                },
162                Incomplete {
163                    label: l2,
164                    position: p2,
165                },
166            ) => l1 == l2 && p1 == p2,
167            (EncodingError { description: s1 }, EncodingError { description: s2 }) => s1 == s2,
168            (DecodingError { description: s1 }, DecodingError { description: s2 }) => s1 == s2,
169            (IllegalOperation { operation: s1 }, IllegalOperation { operation: s2 }) => s1 == s2,
170            _ => false,
171        }
172    }
173}
174
175pub fn incomplete_data_error<T>(label: &'static str, offset: usize) -> IonResult<T> {
176    Err(incomplete_data_error_raw(label, offset))
177}
178
179pub fn incomplete_data_error_raw(label: &'static str, offset: usize) -> IonError {
180    IonError::Incomplete {
181        label,
182        position: Position::with_offset(offset),
183    }
184}
185
186pub fn incomplete_text_error<T>(label: &'static str, position: Position) -> IonResult<T> {
187    Err(incomplete_text_error_raw(label, position))
188}
189
190pub fn incomplete_text_error_raw(label: &'static str, position: Position) -> IonError {
191    IonError::Incomplete { label, position }
192}
193
194/// A convenience method for creating an IonResult containing an IonError::DecodingError with the
195/// provided description text.
196pub fn decoding_error<T, S: AsRef<str>>(description: S) -> IonResult<T> {
197    Err(decoding_error_raw(description))
198}
199
200/// A convenience method for creating an IonError::DecodingError with the provided operation
201/// text. Useful for calling Option#ok_or_else.
202#[inline(never)]
203pub fn decoding_error_raw<S: AsRef<str>>(description: S) -> IonError {
204    IonError::DecodingError {
205        description: description.as_ref().to_string(),
206    }
207}
208
209/// A convenience method for creating an IonResult containing an IonError::EncodingError with the
210/// provided description text.
211pub fn encoding_error<T, S: AsRef<str>>(description: S) -> IonResult<T> {
212    Err(encoding_error_raw(description))
213}
214
215/// A convenience method for creating an IonError::EncodingError with the provided operation
216/// text. Useful for calling Option#ok_or_else.
217#[inline(never)]
218pub fn encoding_error_raw<S: AsRef<str>>(description: S) -> IonError {
219    IonError::EncodingError {
220        description: description.as_ref().to_string(),
221    }
222}
223
224/// A convenience method for creating an IonResult containing an IonError::IllegalOperation with the
225/// provided operation text.
226pub fn illegal_operation<T, S: AsRef<str>>(operation: S) -> IonResult<T> {
227    Err(illegal_operation_raw(operation))
228}
229
230/// A convenience method for creating an IonError::IllegalOperation with the provided operation
231/// text. Useful for calling Option#ok_or_else.
232#[inline(never)]
233pub fn illegal_operation_raw<S: AsRef<str>>(operation: S) -> IonError {
234    IonError::IllegalOperation {
235        operation: operation.as_ref().to_string(),
236    }
237}