1use std::{error::Error, fmt, path::PathBuf};
2
3use httpgenerator_core::NormalizedHttpMethod;
4use reqwest::StatusCode;
5use url::Url;
6
7use crate::{OpenApiContentFormat, OpenApiSource, OpenApiSpecificationVersion};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum SourceClassificationError {
11 EmptyInput,
12 UnsupportedUrlScheme(String),
13 InvalidUrl { value: String, reason: String },
14}
15
16impl fmt::Display for SourceClassificationError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::EmptyInput => write!(f, "OpenAPI source input cannot be empty"),
20 Self::UnsupportedUrlScheme(scheme) => {
21 write!(f, "unsupported OpenAPI source URL scheme '{scheme}'")
22 }
23 Self::InvalidUrl { value, reason } => {
24 write!(f, "invalid OpenAPI source URL '{value}': {reason}")
25 }
26 }
27 }
28}
29
30impl Error for SourceClassificationError {}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum ContentFormatDetectionError {
34 EmptyContent,
35 UnknownFormat,
36}
37
38impl fmt::Display for ContentFormatDetectionError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::EmptyContent => write!(f, "OpenAPI content cannot be empty"),
42 Self::UnknownFormat => write!(f, "unable to detect OpenAPI content format"),
43 }
44 }
45}
46
47impl Error for ContentFormatDetectionError {}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum RawOpenApiLoadError {
51 SourceClassification(SourceClassificationError),
52 FileRead {
53 path: PathBuf,
54 reason: String,
55 },
56 HttpRequest {
57 url: Url,
58 reason: String,
59 },
60 HttpStatus {
61 url: Url,
62 status: StatusCode,
63 },
64 HttpBodyRead {
65 url: Url,
66 reason: String,
67 },
68 FormatDetection {
69 source: OpenApiSource,
70 error: ContentFormatDetectionError,
71 },
72 Decode {
73 source: OpenApiSource,
74 format: OpenApiContentFormat,
75 reason: String,
76 },
77}
78
79impl fmt::Display for RawOpenApiLoadError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::SourceClassification(error) => {
83 write!(f, "failed to classify OpenAPI source: {error}")
84 }
85 Self::FileRead { path, reason } => {
86 write!(
87 f,
88 "failed to read OpenAPI file '{}': {reason}",
89 path.display()
90 )
91 }
92 Self::HttpRequest { url, reason } => {
93 write!(f, "failed to fetch OpenAPI URL '{url}': {reason}")
94 }
95 Self::HttpStatus { url, status } => {
96 write!(f, "OpenAPI URL '{url}' returned HTTP {status}")
97 }
98 Self::HttpBodyRead { url, reason } => {
99 write!(
100 f,
101 "failed to read OpenAPI response body from '{url}': {reason}"
102 )
103 }
104 Self::FormatDetection { source, error } => {
105 write!(
106 f,
107 "failed to detect OpenAPI content format for '{source}': {error}"
108 )
109 }
110 Self::Decode {
111 source,
112 format,
113 reason,
114 } => {
115 write!(
116 f,
117 "failed to decode {format} OpenAPI document from '{source}': {reason}"
118 )
119 }
120 }
121 }
122}
123
124impl Error for RawOpenApiLoadError {}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum SpecificationVersionDetectionError {
128 MissingVersionField,
129 InvalidVersionFieldType { field: &'static str },
130 UnsupportedVersion { field: &'static str, value: String },
131}
132
133impl fmt::Display for SpecificationVersionDetectionError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 Self::MissingVersionField => {
137 write!(
138 f,
139 "OpenAPI document is missing a top-level 'openapi' or 'swagger' version field"
140 )
141 }
142 Self::InvalidVersionFieldType { field } => {
143 write!(f, "OpenAPI document field '{field}' must be a string")
144 }
145 Self::UnsupportedVersion { field, value } => {
146 write!(
147 f,
148 "unsupported OpenAPI version '{value}' in field '{field}'"
149 )
150 }
151 }
152 }
153}
154
155impl Error for SpecificationVersionDetectionError {}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum OpenApiInspectionError {
159 Load(RawOpenApiLoadError),
160 VersionDetection(SpecificationVersionDetectionError),
161}
162
163impl fmt::Display for OpenApiInspectionError {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::Load(error) => write!(f, "{error}"),
167 Self::VersionDetection(error) => write!(f, "{error}"),
168 }
169 }
170}
171
172impl Error for OpenApiInspectionError {}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum TypedOpenApiParseError {
176 VersionDetection {
177 source: OpenApiSource,
178 error: SpecificationVersionDetectionError,
179 },
180 UnsupportedVersion {
181 source: OpenApiSource,
182 version: OpenApiSpecificationVersion,
183 },
184 Deserialize {
185 source: OpenApiSource,
186 version: OpenApiSpecificationVersion,
187 reason: String,
188 },
189}
190
191impl fmt::Display for TypedOpenApiParseError {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 match self {
194 Self::VersionDetection { source, error } => {
195 write!(
196 f,
197 "failed to detect OpenAPI specification version for '{source}': {error}"
198 )
199 }
200 Self::UnsupportedVersion { source, version } => {
201 write!(
202 f,
203 "typed OpenAPI parsing is not implemented for {version} documents from '{source}'"
204 )
205 }
206 Self::Deserialize {
207 source,
208 version,
209 reason,
210 } => {
211 write!(
212 f,
213 "failed to deserialize {version} document from '{source}': {reason}"
214 )
215 }
216 }
217 }
218}
219
220impl Error for TypedOpenApiParseError {}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum OpenApiDocumentLoadError {
224 RawLoad(RawOpenApiLoadError),
225 TypedParse(TypedOpenApiParseError),
226}
227
228impl fmt::Display for OpenApiDocumentLoadError {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 Self::RawLoad(error) => write!(f, "{error}"),
232 Self::TypedParse(error) => write!(f, "{error}"),
233 }
234 }
235}
236
237impl Error for OpenApiDocumentLoadError {}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub enum OpenApiNormalizationError {
241 InvalidStructure {
242 path: String,
243 context: String,
244 },
245 UnsupportedPathItemReference {
246 path: String,
247 reference: String,
248 },
249 UnsupportedParameterReference {
250 path: String,
251 method: NormalizedHttpMethod,
252 reference: String,
253 },
254 UnsupportedRequestBodyReference {
255 path: String,
256 method: NormalizedHttpMethod,
257 reference: String,
258 },
259}
260
261impl fmt::Display for OpenApiNormalizationError {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 match self {
264 Self::InvalidStructure { path, context } => {
265 write!(
266 f,
267 "OpenAPI document contains an unexpected structure at '{path}' ({context})"
268 )
269 }
270 Self::UnsupportedPathItemReference { path, reference } => {
271 write!(
272 f,
273 "path item '{path}' uses unsupported $ref '{reference}' during normalization"
274 )
275 }
276 Self::UnsupportedParameterReference {
277 path,
278 method,
279 reference,
280 } => {
281 write!(
282 f,
283 "{method:?} operation '{path}' uses unsupported parameter $ref '{reference}' during normalization"
284 )
285 }
286 Self::UnsupportedRequestBodyReference {
287 path,
288 method,
289 reference,
290 } => {
291 write!(
292 f,
293 "{method:?} operation '{path}' uses unsupported requestBody $ref '{reference}' during normalization"
294 )
295 }
296 }
297 }
298}
299
300impl Error for OpenApiNormalizationError {}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub enum OpenApiDocumentNormalizationError {
304 Load(OpenApiDocumentLoadError),
305 Normalize(OpenApiNormalizationError),
306}
307
308impl fmt::Display for OpenApiDocumentNormalizationError {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 match self {
311 Self::Load(error) => write!(f, "{error}"),
312 Self::Normalize(error) => write!(f, "{error}"),
313 }
314 }
315}
316
317impl Error for OpenApiDocumentNormalizationError {}