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
//! Errors returned by the public API and internal validation.
use std::io;
use std::path::{Path, PathBuf};
use thiserror::Error;
/// Failures exposed by the library.
#[derive(Debug, Error)]
pub enum TicitError {
/// A caller supplied an invalid size, record id, mask, or other value.
#[error("{message}")]
InvalidInput {
/// Human-readable validation failure.
message: String,
},
/// Circuit source is syntactically malformed.
#[error("{message}")]
Parse {
/// One-based source line where parsing failed.
line: usize,
/// Human-readable parse failure, including the line number.
message: String,
},
/// A circuit file could not be read.
#[error("{message}")]
Io {
/// Path ticit attempted to read.
path: PathBuf,
/// Stable high-level error message.
message: String,
/// Original operating-system I/O error.
#[source]
source: io::Error,
},
/// The input uses a valid construct that ticit does not implement.
#[error("{message}")]
Unsupported {
/// Human-readable unsupported-feature description.
message: String,
},
/// An internal invariant failed without panicking.
#[error("{message}")]
Internal {
/// Human-readable invariant failure.
message: String,
},
/// A scoped CPU sampling worker panicked.
#[error("batch worker panicked")]
WorkerPanic,
}
impl PartialEq for TicitError {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
&& self.message() == other.message()
}
}
impl Eq for TicitError {}
impl TicitError {
pub(crate) fn new(message: impl Into<String>) -> Self {
Self::InvalidInput {
message: message.into(),
}
}
pub(crate) fn parse(line: usize, detail: impl std::fmt::Display) -> Self {
Self::Parse {
line,
message: format!("line {line}: {detail}"),
}
}
pub(crate) fn io(path: &Path, source: io::Error) -> Self {
Self::Io {
message: format!("failed to read circuit file: {}", path.to_string_lossy()),
path: path.to_owned(),
source,
}
}
pub(crate) fn unsupported(message: impl Into<String>) -> Self {
Self::Unsupported {
message: message.into(),
}
}
pub(crate) fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
/// Returns the stable human-readable message carried by this error.
///
/// This is the same text emitted by [`std::fmt::Display`], but borrowing it
/// avoids allocating a `String` when an embedding API maps errors.
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::InvalidInput { message }
| Self::Parse { message, .. }
| Self::Io { message, .. }
| Self::Unsupported { message }
| Self::Internal { message } => message,
Self::WorkerPanic => "batch worker panicked",
}
}
}
/// Result type used by ticit's parsing, preparation, and CPU sampling APIs.
pub type Result<T> = std::result::Result<T, TicitError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_variants_preserve_messages_and_context() {
let error = TicitError::parse(7, "boom");
assert_eq!(error.to_string(), "line 7: boom");
assert_eq!(error.message(), "line 7: boom");
assert!(matches!(error, TicitError::Parse { line: 7, .. }));
}
}