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
use std::convert::From;
use std::fmt::{Debug, Display, Error};
use std::{fmt, io};
use thiserror::Error;
/// Position represents the location within an Ion stream where an error has been
/// identified. For all formats `byte_offset` will contain the number of bytes into the stream
/// that have been processed prior to encountering the error. When working with the text format,
/// `line_column` will be updated to contain the line and column as well.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Position {
pub(crate) byte_offset: usize,
pub(crate) line_column: Option<(usize, usize)>,
}
impl Position {
/// Creates a new Position with the provided offset in bytes.
/// Line and Column offsets can be added using [`Self::with_text_position()`].
pub fn with_offset(offset: usize) -> Self {
Position {
byte_offset: offset,
line_column: None,
}
}
/// Add line and column information to the current Position.
pub fn with_text_position(&self, line: usize, column: usize) -> Self {
Position {
line_column: Some((line, column)),
..*self
}
}
/// Returns the offset from the start of the Ion stream in bytes.
pub fn byte_offset(&self) -> usize {
self.byte_offset
}
/// If available returns the text position as line and column offsets.
pub fn text_position(&self) -> Option<(usize, usize)> {
self.line_column
}
/// If available returns the line component of the text position.
pub fn line(&self) -> Option<usize> {
self.line_column.map(|(line, _column)| line)
}
/// If available returns the column component of the text position.
pub fn column(&self) -> Option<usize> {
self.line_column.map(|(_line, column)| column)
}
/// Returns true if the current Position contains line and column offsets.
pub fn has_text_position(&self) -> bool {
self.line_column.is_some()
}
}
impl Display for Position {
// Formats the position based on whether we have a LineAndColumn or not.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), Error> {
match &self.line_column {
None => write!(f, "{}", self.byte_offset),
Some((line, column)) => {
write!(f, "{} ({}:{})", self.byte_offset, line, column)
}
}
}
}
/// A unified Result type representing the outcome of method calls that may fail.
pub type IonResult<T> = Result<T, IonError>;
/// Represents the different types of high-level failures that might occur when reading Ion data.
#[derive(Debug, Error)]
pub enum IonError {
/// Indicates that an IO error was encountered while reading or writing.
#[error("{source:?}")]
IoError {
#[from]
source: io::Error,
},
/// Indicates that the input buffer did not contain enough data to perform the requested read
/// operation. If the input source contains more data, the reader can append it to the buffer
/// and try again.
#[error("ran out of input while reading {label} at offset {position}")]
Incomplete {
label: &'static str,
position: Position,
},
/// Indicates that the writer encountered a problem while serializing a given piece of data.
#[error("{description}")]
EncodingError { description: String },
/// Indicates that the data stream being read contained illegal or otherwise unreadable data.
#[error("{description}")]
DecodingError { description: String },
/// Returned when the user has performed an illegal operation (for example: calling stepOut()
/// on the cursor at the top level.)
#[error(
"The user has performed an operation that is not legal in the current state: {operation}"
)]
IllegalOperation { operation: String },
}
impl From<fmt::Error> for IonError {
fn from(error: Error) -> Self {
IonError::EncodingError {
description: error.to_string(),
}
}
}
// io::Error does not implement Clone, which precludes us from simply deriving an implementation.
// IonError needs a Clone implementation because we use a jump table of cached IonResult values when
// parsing type descriptor bytes. The only error type that will be cloned by virtue of using the jump
// table is DecodingError.
impl Clone for IonError {
fn clone(&self) -> Self {
use IonError::*;
match self {
IoError { source } => IoError {
// io::Error implements From<ErrorKind>, and ErrorKind is cloneable.
source: io::Error::from(source.kind()),
},
Incomplete { label, position } => Incomplete {
label,
position: position.clone(),
},
EncodingError { description } => EncodingError {
description: description.clone(),
},
DecodingError { description } => DecodingError {
description: description.clone(),
},
IllegalOperation { operation } => IllegalOperation {
operation: operation.clone(),
},
}
}
}
// io::Error does not implement PartialEq, which precludes us from simply deriving an implementation.
// Providing an implementation of PartialEq allows IonResult values to be the left or right side in
// an assert_eq!() statement.
impl PartialEq for IonError {
fn eq(&self, other: &Self) -> bool {
use IonError::*;
match (self, other) {
// We can compare the io::Errors' ErrorKinds, offering a weak definition of equality.
(IoError { source: s1 }, IoError { source: s2 }) => s1.kind() == s2.kind(),
(
Incomplete {
label: l1,
position: p1,
},
Incomplete {
label: l2,
position: p2,
},
) => l1 == l2 && p1 == p2,
(EncodingError { description: s1 }, EncodingError { description: s2 }) => s1 == s2,
(DecodingError { description: s1 }, DecodingError { description: s2 }) => s1 == s2,
(IllegalOperation { operation: s1 }, IllegalOperation { operation: s2 }) => s1 == s2,
_ => false,
}
}
}
pub fn incomplete_data_error<T>(label: &'static str, offset: usize) -> IonResult<T> {
Err(incomplete_data_error_raw(label, offset))
}
pub fn incomplete_data_error_raw(label: &'static str, offset: usize) -> IonError {
IonError::Incomplete {
label,
position: Position::with_offset(offset),
}
}
pub fn incomplete_text_error<T>(label: &'static str, position: Position) -> IonResult<T> {
Err(incomplete_text_error_raw(label, position))
}
pub fn incomplete_text_error_raw(label: &'static str, position: Position) -> IonError {
IonError::Incomplete { label, position }
}
/// A convenience method for creating an IonResult containing an IonError::DecodingError with the
/// provided description text.
pub fn decoding_error<T, S: AsRef<str>>(description: S) -> IonResult<T> {
Err(decoding_error_raw(description))
}
/// A convenience method for creating an IonError::DecodingError with the provided operation
/// text. Useful for calling Option#ok_or_else.
#[inline(never)]
pub fn decoding_error_raw<S: AsRef<str>>(description: S) -> IonError {
IonError::DecodingError {
description: description.as_ref().to_string(),
}
}
/// A convenience method for creating an IonResult containing an IonError::EncodingError with the
/// provided description text.
pub fn encoding_error<T, S: AsRef<str>>(description: S) -> IonResult<T> {
Err(encoding_error_raw(description))
}
/// A convenience method for creating an IonError::EncodingError with the provided operation
/// text. Useful for calling Option#ok_or_else.
#[inline(never)]
pub fn encoding_error_raw<S: AsRef<str>>(description: S) -> IonError {
IonError::EncodingError {
description: description.as_ref().to_string(),
}
}
/// A convenience method for creating an IonResult containing an IonError::IllegalOperation with the
/// provided operation text.
pub fn illegal_operation<T, S: AsRef<str>>(operation: S) -> IonResult<T> {
Err(illegal_operation_raw(operation))
}
/// A convenience method for creating an IonError::IllegalOperation with the provided operation
/// text. Useful for calling Option#ok_or_else.
#[inline(never)]
pub fn illegal_operation_raw<S: AsRef<str>>(operation: S) -> IonError {
IonError::IllegalOperation {
operation: operation.as_ref().to_string(),
}
}