httpgenerator_openapi/
typed.rs1use 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}