1use std::path::PathBuf;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Error, Debug)]
14#[non_exhaustive]
15pub enum Error {
16 #[error("failed to read file '{path}': {source}")]
18 FileRead {
19 path: PathBuf,
21 #[source]
23 source: std::io::Error,
24 },
25
26 #[error("failed to write file '{path}': {source}")]
28 FileWrite {
29 path: PathBuf,
31 #[source]
33 source: std::io::Error,
34 },
35
36 #[error("failed to create directory '{path}': {source}")]
38 DirectoryCreate {
39 path: PathBuf,
41 #[source]
43 source: std::io::Error,
44 },
45
46 #[error("path traversal detected: '{path}' would escape output directory")]
48 PathTraversal {
49 path: PathBuf,
51 },
52
53 #[error("invalid protobuf wire format at offset {offset}: {details}")]
55 InvalidWireFormat {
56 offset: usize,
58 details: String,
60 },
61
62 #[error("failed to decode varint at offset {offset}: buffer too small or invalid encoding")]
64 VarintDecode {
65 offset: usize,
67 },
68
69 #[error("failed to parse FileDescriptorProto: {0}")]
71 DescriptorParse(#[from] prost::DecodeError),
72
73 #[error("failed to build file descriptor: {0}")]
75 DescriptorBuild(String),
76
77 #[error("no protobuf descriptors found in input")]
79 NoDescriptorsFound,
80
81 #[error("invalid field number {number}: must be between 1 and {max}")]
83 InvalidFieldNumber {
84 number: u32,
86 max: u32,
88 },
89
90 #[error("unsupported proto syntax: '{syntax}'")]
92 UnsupportedSyntax {
93 syntax: String,
95 },
96
97 #[error("internal error: {0}")]
99 Internal(String),
100}
101
102impl Error {
103 pub fn file_read(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
105 Self::FileRead {
106 path: path.into(),
107 source,
108 }
109 }
110
111 pub fn file_write(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
113 Self::FileWrite {
114 path: path.into(),
115 source,
116 }
117 }
118
119 pub fn directory_create(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
121 Self::DirectoryCreate {
122 path: path.into(),
123 source,
124 }
125 }
126
127 pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
129 Self::PathTraversal { path: path.into() }
130 }
131
132 pub fn invalid_wire_format(offset: usize, details: impl Into<String>) -> Self {
134 Self::InvalidWireFormat {
135 offset,
136 details: details.into(),
137 }
138 }
139
140 pub fn varint_decode(offset: usize) -> Self {
142 Self::VarintDecode { offset }
143 }
144
145 pub fn descriptor_build(msg: impl Into<String>) -> Self {
147 Self::DescriptorBuild(msg.into())
148 }
149
150 pub fn internal(msg: impl Into<String>) -> Self {
152 Self::Internal(msg.into())
153 }
154
155 pub fn is_recoverable(&self) -> bool {
157 matches!(
158 self,
159 Self::DescriptorParse(_) | Self::DescriptorBuild(_) | Self::InvalidWireFormat { .. }
160 )
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_error_display() {
170 let err = Error::path_traversal("/etc/passwd");
171 assert!(err.to_string().contains("path traversal"));
172 assert!(err.to_string().contains("/etc/passwd"));
173 }
174
175 #[test]
176 fn test_is_recoverable() {
177 assert!(Error::descriptor_build("test").is_recoverable());
178 assert!(!Error::path_traversal("/test").is_recoverable());
179 }
180}