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
//! Types for errors that can occur while working with DBN.
use thiserror::Error;

/// An error that can occur while processing DBN data.
#[derive(Debug, Error)]
pub enum Error {
    /// An I/O error while reading or writing DBN or another encoding.
    #[error("IO error: {source:?} while {context}")]
    Io {
        /// The original error.
        #[source]
        source: std::io::Error,
        /// The context in which the error occurred.
        context: String,
    },
    /// An error while decoding from DBN.
    #[error("decoding error: {0}")]
    Decode(String),
    /// An error with text encoding.
    #[error("encoding error: {0}")]
    Encode(String),
    /// An conversion error between types or encodings.
    #[error("couldn't convert {input} to {desired_type}")]
    Conversion {
        /// The input to the conversion.
        input: String,
        /// The desired type or encoding.
        desired_type: &'static str,
    },
    /// An error with conversion of bytes to UTF-8.
    #[error("UTF-8 error: {source:?} while {context}")]
    Utf8 {
        /// The original error.
        #[source]
        source: std::str::Utf8Error,
        /// The context in which the error occurred.
        context: String,
    },
}
/// An alias for a `Result` with [`dbn::Error`](crate::Error) as the error type.
pub type Result<T> = std::result::Result<T, Error>;

impl From<csv::Error> for Error {
    fn from(value: csv::Error) -> Self {
        match value.into_kind() {
            csv::ErrorKind::Io(io) => Self::io(io, "While writing CSV"),
            csv::ErrorKind::Utf8 { pos, err } => {
                Self::Encode(format!("UTF-8 error {err:?}{}", Self::opt_pos(&pos)))
            }
            csv::ErrorKind::UnequalLengths {
                pos,
                expected_len,
                len,
            } => Self::Encode(format!(
                "unequal CSV row lengths{}: expected {expected_len}, found {len}",
                Self::opt_pos(&pos)
            )),
            e => Self::Encode(format!("{e:?}")),
        }
    }
}

impl Error {
    /// Creates a new I/O [`dbn::Error`](crate::Error).
    pub fn io(error: std::io::Error, context: impl ToString) -> Self {
        Self::Io {
            source: error,
            context: context.to_string(),
        }
    }

    /// Creates a new decode [`dbn::Error`](crate::Error).
    pub fn decode(msg: impl ToString) -> Self {
        Self::Decode(msg.to_string())
    }

    /// Creates a new encode [`dbn::Error`](crate::Error).
    pub fn encode(msg: impl ToString) -> Self {
        Self::Encode(msg.to_string())
    }

    /// Creates a new conversion [`dbn::Error`](crate::Error) where `desired_type` is `T`.
    pub fn conversion<T>(input: impl ToString) -> Self {
        Self::Conversion {
            input: input.to_string(),
            desired_type: std::any::type_name::<T>(),
        }
    }

    /// Creates a new UTF-8 [`dbn::Error`](crate::Error).
    pub fn utf8(error: std::str::Utf8Error, context: impl ToString) -> Self {
        Self::Utf8 {
            source: error,
            context: context.to_string(),
        }
    }

    fn opt_pos(pos: &Option<csv::Position>) -> String {
        if let Some(pos) = pos.as_ref() {
            format!(" at {pos:?}")
        } else {
            String::default()
        }
    }
}

pub(crate) fn silence_eof_error<T>(err: std::io::Error) -> std::io::Result<Option<T>> {
    if err.kind() == std::io::ErrorKind::UnexpectedEof {
        Ok(None)
    } else {
        Err(err)
    }
}