1pub mod api;
11pub mod config;
12pub mod csr;
13pub mod invoice;
14
15#[repr(i32)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum ErrorKind {
19 InvalidInput = 1,
20 Validation = 2,
21 Parse = 3,
22 Xml = 4,
23 Crypto = 5,
24 Io = 6,
25 Network = 7,
26 Unauthorized = 8,
27 Internal = 9,
28 Api = 10,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Error {
34 kind: ErrorKind,
35 message: String,
36}
37
38impl Error {
39 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
40 Self {
41 kind,
42 message: message.into(),
43 }
44 }
45
46 pub fn kind(&self) -> ErrorKind {
47 self.kind
48 }
49
50 pub fn message(&self) -> &str {
51 &self.message
52 }
53}
54
55impl std::fmt::Display for Error {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.message)
58 }
59}
60
61impl std::error::Error for Error {}
62
63impl From<config::EnvironmentParseError> for Error {
64 fn from(err: config::EnvironmentParseError) -> Self {
65 Error::new(ErrorKind::InvalidInput, err.to_string())
66 }
67}
68
69impl From<csr::CsrError> for Error {
70 fn from(err: csr::CsrError) -> Self {
71 let kind = match err {
72 csr::CsrError::Io { .. } => ErrorKind::Io,
73 csr::CsrError::PropertiesRead { .. } => ErrorKind::Parse,
74 csr::CsrError::MissingProperty { .. } => ErrorKind::InvalidInput,
75 csr::CsrError::InvalidSubject { .. } => ErrorKind::InvalidInput,
76 csr::CsrError::InvalidSan { .. } => ErrorKind::InvalidInput,
77 csr::CsrError::RequestBuild { .. } => ErrorKind::Crypto,
78 csr::CsrError::AddExtension { .. } => ErrorKind::Crypto,
79 csr::CsrError::CsrBuild { .. } => ErrorKind::Crypto,
80 csr::CsrError::DerEncode { .. } => ErrorKind::Crypto,
81 csr::CsrError::KeyDecode { .. } => ErrorKind::InvalidInput,
82 csr::CsrError::KeyEncode { .. } => ErrorKind::Crypto,
83 csr::CsrError::Validation { .. } => ErrorKind::Validation,
84 };
85 Error::new(kind, err.to_string())
86 }
87}
88
89impl From<invoice::InvoiceError> for Error {
90 fn from(err: invoice::InvoiceError) -> Self {
91 let kind = match err {
92 invoice::InvoiceError::Validation(_) => ErrorKind::Validation,
93 invoice::InvoiceError::InvalidCountryCode(_)
94 | invoice::InvoiceError::InvalidCurrencyCode(_)
95 | invoice::InvoiceError::InvalidTimestamp(_)
96 | invoice::InvoiceError::InvalidIssueDate(_)
97 | invoice::InvoiceError::MissingVatForSeller
98 | invoice::InvoiceError::MissingBuyerId
99 | invoice::InvoiceError::InvalidVatFormat => ErrorKind::InvalidInput,
100 };
101 Error::new(kind, err.to_string())
102 }
103}
104
105impl From<invoice::sign::SigningError> for Error {
106 fn from(err: invoice::sign::SigningError) -> Self {
107 Error::new(ErrorKind::Crypto, err.to_string())
108 }
109}
110
111impl From<invoice::QrCodeError> for Error {
112 fn from(err: invoice::QrCodeError) -> Self {
113 let kind = match err {
114 invoice::QrCodeError::MissingSellerName
115 | invoice::QrCodeError::MissingSellerVat
116 | invoice::QrCodeError::ValueTooLong { .. }
117 | invoice::QrCodeError::EncodedTooLong { .. } => ErrorKind::InvalidInput,
118 invoice::QrCodeError::Xml(_) => ErrorKind::Xml,
119 };
120 Error::new(kind, err.to_string())
121 }
122}
123
124impl From<invoice::xml::InvoiceXmlError> for Error {
125 fn from(err: invoice::xml::InvoiceXmlError) -> Self {
126 Error::new(ErrorKind::Xml, err.to_string())
127 }
128}
129
130impl From<invoice::xml::parse::ParseError> for Error {
131 fn from(err: invoice::xml::parse::ParseError) -> Self {
132 let kind = match err {
133 invoice::xml::parse::ParseError::XmlParse(_) => ErrorKind::Xml,
134 invoice::xml::parse::ParseError::XPath(_) => ErrorKind::Parse,
135 invoice::xml::parse::ParseError::MissingField(_)
136 | invoice::xml::parse::ParseError::InvalidValue { .. } => ErrorKind::InvalidInput,
137 };
138 Error::new(kind, err.to_string())
139 }
140}
141
142impl From<invoice::validation::XmlValidationError> for Error {
143 fn from(err: invoice::validation::XmlValidationError) -> Self {
144 let kind = match err {
145 invoice::validation::XmlValidationError::InvalidXsdPath { .. } => ErrorKind::InvalidInput,
146 invoice::validation::XmlValidationError::SchemaParse { .. } => ErrorKind::Parse,
147 invoice::validation::XmlValidationError::XmlParse { .. } => ErrorKind::Xml,
148 invoice::validation::XmlValidationError::SchemaValidation { .. } => ErrorKind::Validation,
149 };
150 Error::new(kind, err.to_string())
151 }
152}
153
154impl From<api::ZatcaError> for Error {
155 fn from(err: api::ZatcaError) -> Self {
156 let kind = match err {
157 api::ZatcaError::NetworkError(_) => ErrorKind::Network,
158 api::ZatcaError::InvalidResponse(_) => ErrorKind::Parse,
159 api::ZatcaError::Unauthorized(_) => ErrorKind::Unauthorized,
160 api::ZatcaError::ServerError(_) => ErrorKind::Api,
161 api::ZatcaError::Http(_) => ErrorKind::Network,
162 api::ZatcaError::ClientState(_) => ErrorKind::Internal,
163 };
164 Error::new(kind, err.to_string())
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::{Error, ErrorKind};
171 use crate::{
172 api::ZatcaError,
173 csr::CsrError,
174 invoice::{
175 InvoiceError, QrCodeError, ValidationError, ValidationIssue, ValidationKind, InvoiceField,
176 },
177 };
178 use crate::invoice::sign::SigningError;
179 use crate::invoice::xml::InvoiceXmlError;
180 use crate::invoice::xml::parse::ParseError;
181 use crate::invoice::validation::XmlValidationError;
182 use quick_xml::se::SeError;
183
184 #[test]
185 fn error_conversions_cover_variants() {
186 let invoice_err = InvoiceError::Validation(ValidationError::new(vec![
187 ValidationIssue::new(InvoiceField::Id, ValidationKind::Missing, None),
188 ]));
189 let err: Error = invoice_err.into();
190 assert_eq!(err.kind(), ErrorKind::Validation);
191
192 let err: Error = SigningError::SigningError("sign".into()).into();
193 assert_eq!(err.kind(), ErrorKind::Crypto);
194
195 let err: Error = QrCodeError::MissingSellerName.into();
196 assert_eq!(err.kind(), ErrorKind::InvalidInput);
197
198 let xml_err = InvoiceXmlError::Serialize {
199 source: SeError::Custom("xml".into()),
200 };
201 let err: Error = xml_err.into();
202 assert_eq!(err.kind(), ErrorKind::Xml);
203
204 let err: Error = ParseError::MissingField("uuid").into();
205 assert_eq!(err.kind(), ErrorKind::InvalidInput);
206
207 let err: Error = XmlValidationError::XmlParse {
208 message: "bad".into(),
209 }
210 .into();
211 assert_eq!(err.kind(), ErrorKind::Xml);
212
213 let err: Error = ZatcaError::ClientState("state".into()).into();
214 assert_eq!(err.kind(), ErrorKind::Internal);
215
216 let err: Error = CsrError::Validation {
217 message: "csr".into(),
218 }
219 .into();
220 assert_eq!(err.kind(), ErrorKind::Validation);
221 }
222}
223
224#[cfg(test)]
225mod fixture_hash_sign;