Skip to main content

httpgenerator_openapi/
error.rs

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 {}