Skip to main content

openapi_rs/model/
parse.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use crate::observability::ValidationMetrics;
19use crate::validator::ValidateRequest;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22use std::hash::Hash;
23
24#[derive(Debug, Serialize, Deserialize)]
25pub struct OpenAPI {
26    pub openapi: String,
27    pub info: InfoObject,
28    #[serde(default)]
29    pub servers: Vec<ServerObject>,
30    pub paths: HashMap<String, PathItem>,
31    pub components: Option<ComponentsObject>,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35pub struct PathItem {
36    pub parameters: Option<Vec<Parameter>>, // Path-level parameters
37    #[serde(flatten)]
38    pub operations: HashMap<String, PathBase>, // For HTTP methods (get, post, etc.)
39    #[serde(default, skip_serializing_if = "Vec::is_empty")]
40    pub servers: Vec<ServerObject>, // Will be ignored during deserialization
41    #[serde(flatten)]
42    pub extra: serde_yaml::Value, // Catches any other fields
43}
44
45macro_rules! require_non_empty {
46    ($field:expr, $msg:expr) => {
47        if $field.is_empty() {
48            return Err($msg.to_string());
49        }
50    };
51}
52
53impl OpenAPI {
54    pub fn yaml(contents: &str) -> Result<Self, serde_yaml::Error> {
55        serde_yaml::from_str(contents)
56    }
57
58    pub fn validator(&self, valid: impl ValidateRequest) -> Result<(), String> {
59        let metrics = ValidationMetrics::from_context(&valid.context());
60
61        let result = self.perform_validation(valid);
62
63        match &result {
64            Ok(_) => metrics.record_success(),
65            Err(err) => metrics.record_failure(err.clone()),
66        }
67
68        result
69    }
70
71    fn perform_validation(&self, valid: impl ValidateRequest) -> Result<(), String> {
72        require_non_empty!(self.openapi, "OpenAPI version is required");
73        require_non_empty!(self.info.title, "Title is required");
74        require_non_empty!(self.info.version, "Version is required");
75        require_non_empty!(self.paths, "Paths are required");
76        valid
77            .method(self)
78            .map_err(|e| format!("Method validation failed: {e}"))?;
79        valid
80            .path(self)
81            .map_err(|e| format!("Path validation failed: {e}"))?;
82        valid
83            .query(self)
84            .map_err(|e| format!("Query validation failed: {e}"))?;
85        valid
86            .body(self)
87            .map_err(|e| format!("Body validation failed: {e}"))?;
88        Ok(())
89    }
90}
91
92#[derive(Debug, Serialize, Deserialize)]
93pub struct SecurityRequirementObject {
94    #[serde(rename = "type", default)]
95    pub _type: String,
96    pub scheme: Option<String>,
97    pub description: Option<String>,
98}
99
100#[derive(Debug, Serialize, Deserialize)]
101pub struct InfoObject {
102    pub title: String,
103    pub description: Option<String>,
104    pub version: String,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108pub struct ServerObject {
109    pub url: String,
110    pub description: Option<String>,
111}
112
113#[derive(Debug, Serialize, Deserialize)]
114pub struct PathBase {
115    pub summary: Option<String>,
116    pub description: Option<String>,
117    #[serde(rename = "operationId")]
118    pub operation_id: Option<String>,
119    pub parameters: Option<Vec<Parameter>>,
120    #[serde(rename = "requestBody")]
121    pub request: Option<Request>,
122    #[serde(default)]
123    pub servers: Vec<ServerObject>,
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127pub struct Parameter {
128    #[serde(rename = "$ref")]
129    pub r#ref: Option<String>,
130    pub name: Option<String>,
131    #[serde(rename = "in")]
132    pub r#in: Option<In>,
133    #[serde(default)]
134    pub required: bool,
135    pub description: Option<String>,
136    pub example: Option<serde_yaml::Value>,
137    #[serde(rename = "type")]
138    pub r#type: Option<TypeOrUnion>,
139    pub r#enum: Option<Vec<serde_yaml::Value>>,
140    pub pattern: Option<String>,
141    pub schema: Option<Box<Schema>>,
142    #[serde(flatten)]
143    pub extra: HashMap<String, serde_yaml::Value>,
144}
145
146#[derive(Debug, Serialize, Deserialize)]
147pub struct Schema {
148    #[serde(rename = "type")]
149    pub r#type: Option<TypeOrUnion>,
150    pub format: Option<Format>,
151    pub title: Option<String>,
152    pub description: Option<String>,
153    pub r#enum: Option<Vec<serde_yaml::Value>>,
154    pub pattern: Option<String>,
155    pub properties: Option<HashMap<String, Properties>>,
156    pub example: Option<serde_yaml::Value>,
157    pub examples: Option<Vec<String>>,
158    #[serde(rename = "$ref")]
159    pub r#ref: Option<String>,
160    #[serde(rename = "allOf")]
161    pub all_of: Option<Vec<ComponentProperties>>,
162    #[serde(rename = "oneOf")]
163    pub one_of: Option<Vec<ComponentProperties>>,
164    pub items: Option<Box<Schema>>,
165    #[serde(default)]
166    pub required: Vec<String>,
167    #[serde(rename = "minItems")]
168    pub min_items: Option<u64>,
169    #[serde(rename = "maxItems")]
170    pub max_items: Option<u64>,
171    #[serde(rename = "minLength")]
172    pub min_length: Option<u64>,
173    #[serde(rename = "maxLength")]
174    pub max_length: Option<u64>,
175    pub minimum: Option<f64>,
176    pub maximum: Option<f64>,
177}
178
179#[derive(Debug, Serialize, Deserialize)]
180pub struct BaseContent {
181    pub schema: Schema,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
185pub struct Request {
186    #[serde(default)]
187    pub required: bool,
188    pub content: HashMap<String, BaseContent>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
192pub enum SchemaOption {
193    OneOf,
194    AllOf,
195}
196
197#[derive(Debug, Serialize, Deserialize)]
198pub struct ComponentSchemaBase {
199    pub title: Option<String>,
200    pub description: Option<String>,
201    #[serde(rename = "type")]
202    pub r#type: Option<TypeOrUnion>,
203    pub items: Option<Box<ComponentSchemaBase>>,
204    pub properties: Option<HashMap<String, Properties>>,
205    #[serde(default)]
206    pub required: Vec<String>,
207    #[serde(rename = "allOf")]
208    pub all_of: Option<Vec<ComponentProperties>>,
209    #[serde(rename = "oneOf")]
210    pub one_of: Option<Vec<ComponentProperties>>,
211    #[serde(rename = "minItems")]
212    pub min_items: Option<u64>,
213    #[serde(rename = "maxItems")]
214    pub max_items: Option<u64>,
215}
216
217#[derive(Debug, Serialize, Deserialize)]
218pub struct ComponentProperties {
219    #[serde(rename = "type")]
220    pub r#type: Option<TypeOrUnion>,
221    pub description: Option<String>,
222    #[serde(default)]
223    pub properties: HashMap<String, Properties>,
224    #[serde(rename = "$ref")]
225    pub r#ref: Option<String>,
226}
227
228#[derive(Debug, Serialize, Deserialize)]
229pub struct Properties {
230    #[serde(rename = "type")]
231    pub r#type: Option<TypeOrUnion>,
232    pub description: Option<String>,
233    pub format: Option<Format>,
234    pub example: Option<serde_yaml::Value>,
235    pub pattern: Option<String>,
236    #[serde(rename = "minLength")]
237    pub min_length: Option<u64>,
238    #[serde(rename = "maxLength")]
239    pub max_length: Option<u64>,
240    #[serde(rename = "minItems")]
241    pub min_items: Option<u64>,
242    #[serde(rename = "maxItems")]
243    pub max_items: Option<u64>,
244    pub minimum: Option<f64>,
245    pub maximum: Option<f64>,
246    pub items: Option<Box<Properties>>,
247    pub properties: Option<HashMap<String, Properties>>,
248    #[serde(default)]
249    pub required: Vec<String>,
250    pub r#enum: Option<Vec<serde_yaml::Value>>,
251}
252
253#[derive(Debug, Serialize, Deserialize)]
254pub struct ComponentsObject {
255    #[serde(default)]
256    pub schemas: HashMap<String, ComponentSchemaBase>,
257    #[serde(default)]
258    pub parameters: HashMap<String, Parameter>,
259    #[serde(rename = "requestBodies", default)]
260    pub request_bodies: HashMap<String, Request>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
264#[serde(rename_all(deserialize = "lowercase"))]
265pub enum Type {
266    Object,
267    String,
268    Integer,
269    Number,
270    Array,
271    Boolean,
272    Null,
273    Binary,
274    Base64,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278#[serde(untagged)]
279pub enum TypeOrUnion {
280    Single(Type),
281    Union(Vec<Type>),
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
285#[serde(rename_all(deserialize = "lowercase"))]
286pub enum In {
287    Query,
288    Header,
289    Path,
290    Cookie,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
294#[serde(rename_all(deserialize = "lowercase"))]
295pub enum Format {
296    URI,
297    #[serde(rename = "uri-reference")]
298    URIReference,
299    Regex,
300    Email,
301    Time,
302    Date,
303    #[serde(rename = "date-time")]
304    DateTime,
305    UUID,
306    Hostname,
307    IPV4,
308    IPV6,
309    Password,
310    #[serde(rename = "json-pointer")]
311    JsonPointer,
312    Binary,
313    #[serde(rename = "external-ip")]
314    ExternalIP,
315    #[serde(rename = "int32")]
316    Int32,
317    #[serde(rename = "int64")]
318    Int64,
319    Svg,
320    #[serde(rename = "url")]
321    Url,
322    #[serde(other)]
323    Unknown,
324}