Skip to main content

httpgenerator_openapi/
typed.rs

1use crate::{OpenApiSpecificationVersion, RawOpenApiDocument, TypedOpenApiParseError};
2
3pub enum TypedOpenApiDocument {
4    OpenApi30(openapiv3::OpenAPI),
5    OpenApi31(openapiv3_1::OpenApi),
6}
7
8impl TypedOpenApiDocument {
9    pub fn specification_version(&self) -> OpenApiSpecificationVersion {
10        match self {
11            Self::OpenApi30(_) => OpenApiSpecificationVersion::OpenApi30,
12            Self::OpenApi31(_) => OpenApiSpecificationVersion::OpenApi31,
13        }
14    }
15}
16
17pub fn parse_typed_document(
18    document: &RawOpenApiDocument,
19) -> Result<TypedOpenApiDocument, TypedOpenApiParseError> {
20    match document.specification_version().map_err(|error| {
21        TypedOpenApiParseError::VersionDetection {
22            source: document.source().clone(),
23            error,
24        }
25    })? {
26        OpenApiSpecificationVersion::Swagger2 => Err(TypedOpenApiParseError::UnsupportedVersion {
27            source: document.source().clone(),
28            version: OpenApiSpecificationVersion::Swagger2,
29        }),
30        OpenApiSpecificationVersion::OpenApi30 => {
31            parse_openapi30_document(document).map(TypedOpenApiDocument::OpenApi30)
32        }
33        OpenApiSpecificationVersion::OpenApi31 => {
34            parse_openapi31_document(document).map(TypedOpenApiDocument::OpenApi31)
35        }
36    }
37}
38
39pub fn parse_openapi30_document(
40    document: &RawOpenApiDocument,
41) -> Result<openapiv3::OpenAPI, TypedOpenApiParseError> {
42    parse_versioned_document(document, OpenApiSpecificationVersion::OpenApi30)
43}
44
45pub fn parse_openapi31_document(
46    document: &RawOpenApiDocument,
47) -> Result<openapiv3_1::OpenApi, TypedOpenApiParseError> {
48    parse_versioned_document(document, OpenApiSpecificationVersion::OpenApi31)
49}
50
51fn parse_versioned_document<T>(
52    document: &RawOpenApiDocument,
53    expected_version: OpenApiSpecificationVersion,
54) -> Result<T, TypedOpenApiParseError>
55where
56    T: serde::de::DeserializeOwned,
57{
58    let detected_version = document.specification_version().map_err(|error| {
59        TypedOpenApiParseError::VersionDetection {
60            source: document.source().clone(),
61            error,
62        }
63    })?;
64
65    if detected_version != expected_version {
66        return Err(TypedOpenApiParseError::UnsupportedVersion {
67            source: document.source().clone(),
68            version: detected_version,
69        });
70    }
71
72    serde_json::from_value(document.value().clone()).map_err(|error| {
73        TypedOpenApiParseError::Deserialize {
74            source: document.source().clone(),
75            version: expected_version,
76            reason: error.to_string(),
77        }
78    })
79}
80
81#[cfg(test)]
82mod tests {
83    use std::path::PathBuf;
84
85    use crate::{
86        OpenApiSource, OpenApiSpecificationVersion, TypedOpenApiParseError, decode_raw_document,
87    };
88
89    use super::{
90        TypedOpenApiDocument, parse_openapi30_document, parse_openapi31_document,
91        parse_typed_document,
92    };
93
94    #[test]
95    fn parses_openapi_thirty_documents_through_the_typed_front_door() {
96        let raw = decode_raw_document(
97            OpenApiSource::Path(PathBuf::from("petstore.json")),
98            r#"{
99                "openapi": "3.0.2",
100                "info": { "title": "Example", "version": "1.0.0" },
101                "paths": {}
102            }"#,
103        )
104        .unwrap();
105
106        let typed = parse_typed_document(&raw).unwrap();
107
108        assert!(matches!(typed, TypedOpenApiDocument::OpenApi30(_)));
109        assert_eq!(
110            typed.specification_version(),
111            OpenApiSpecificationVersion::OpenApi30
112        );
113    }
114
115    #[test]
116    fn parses_openapi_thirty_one_documents_through_the_typed_front_door() {
117        let raw = decode_raw_document(
118            OpenApiSource::Path(PathBuf::from("petstore.yaml")),
119            "openapi: 3.1.0\ninfo:\n  title: Example\n  version: 1.0.0\npaths: {}\n",
120        )
121        .unwrap();
122
123        let typed = parse_typed_document(&raw).unwrap();
124
125        assert!(matches!(typed, TypedOpenApiDocument::OpenApi31(_)));
126        assert_eq!(
127            typed.specification_version(),
128            OpenApiSpecificationVersion::OpenApi31
129        );
130    }
131
132    #[test]
133    fn rejects_swagger_two_documents_until_the_bridge_exists() {
134        let raw = decode_raw_document(
135            OpenApiSource::Path(PathBuf::from("swagger.json")),
136            r#"{
137                "swagger": "2.0",
138                "info": { "title": "Example", "version": "1.0.0" },
139                "paths": {}
140            }"#,
141        )
142        .unwrap();
143
144        match parse_typed_document(&raw) {
145            Err(error) => {
146                assert_eq!(
147                    error,
148                    TypedOpenApiParseError::UnsupportedVersion {
149                        source: OpenApiSource::Path(PathBuf::from("swagger.json")),
150                        version: OpenApiSpecificationVersion::Swagger2,
151                    }
152                );
153            }
154            Ok(_) => panic!("expected Swagger 2 documents to stay unsupported"),
155        }
156    }
157
158    #[test]
159    fn rejects_mismatched_version_specific_parsers() {
160        let raw = decode_raw_document(
161            OpenApiSource::Path(PathBuf::from("openapi.json")),
162            r#"{
163                "openapi": "3.1.0",
164                "info": { "title": "Example", "version": "1.0.0" },
165                "paths": {}
166            }"#,
167        )
168        .unwrap();
169
170        match parse_openapi30_document(&raw) {
171            Err(error) => {
172                assert_eq!(
173                    error,
174                    TypedOpenApiParseError::UnsupportedVersion {
175                        source: OpenApiSource::Path(PathBuf::from("openapi.json")),
176                        version: OpenApiSpecificationVersion::OpenApi31,
177                    }
178                );
179            }
180            Ok(_) => panic!("expected the 3.0 parser to reject a 3.1 document"),
181        }
182
183        parse_openapi31_document(&raw).unwrap();
184    }
185}