1use serde::Serialize;
4use serde_json::Value;
5use siderite_validation::SchemaObject;
6use std::collections::BTreeMap;
7
8pub const OPENAPI_VERSION: &str = "3.1.0";
10
11#[derive(Debug, Clone, PartialEq, Serialize)]
13pub struct OpenApi {
14 pub openapi: &'static str,
16 pub info: Info,
18 pub paths: BTreeMap<String, PathItem>,
20 #[serde(skip_serializing_if = "Components::is_empty")]
22 pub components: Components,
23}
24
25impl OpenApi {
26 pub fn to_value(&self) -> Value {
28 serde_json::to_value(self).unwrap_or(Value::Null)
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize)]
35pub struct Info {
36 pub title: String,
38 pub version: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub description: Option<String>,
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Serialize)]
47#[serde(rename_all = "camelCase")]
48pub struct Components {
49 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
51 pub schemas: BTreeMap<String, SchemaObject>,
52 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
54 pub security_schemes: BTreeMap<String, Value>,
55}
56
57impl Components {
58 pub fn is_empty(&self) -> bool {
60 self.schemas.is_empty() && self.security_schemes.is_empty()
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66#[allow(missing_docs)]
67pub enum HttpMethod {
68 Get,
69 Put,
70 Post,
71 Delete,
72 Options,
73 Head,
74 Patch,
75 Trace,
76}
77
78impl HttpMethod {
79 pub const fn as_str(self) -> &'static str {
81 match self {
82 Self::Get => "get",
83 Self::Put => "put",
84 Self::Post => "post",
85 Self::Delete => "delete",
86 Self::Options => "options",
87 Self::Head => "head",
88 Self::Patch => "patch",
89 Self::Trace => "trace",
90 }
91 }
92}
93
94#[derive(Debug, Clone, Default, PartialEq, Serialize)]
96pub struct PathItem(pub BTreeMap<&'static str, Operation>);
97
98#[derive(Debug, Clone, Default, PartialEq, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct Operation {
102 #[serde(skip_serializing_if = "Vec::is_empty")]
104 pub tags: Vec<String>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub summary: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub description: Option<String>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub operation_id: Option<String>,
114 #[serde(skip_serializing_if = "Vec::is_empty")]
116 pub parameters: Vec<Parameter>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub request_body: Option<RequestBody>,
120 pub responses: BTreeMap<String, Response>,
122 #[serde(skip_serializing_if = "std::ops::Not::not")]
124 pub deprecated: bool,
125 #[serde(skip_serializing_if = "Vec::is_empty")]
127 pub security: Vec<BTreeMap<String, Vec<String>>>,
128}
129
130impl Operation {
131 pub fn add_parameter(&mut self, parameter: Parameter) {
136 let duplicate = !parameter.name.is_empty()
137 && self
138 .parameters
139 .iter()
140 .any(|p| p.name == parameter.name && p.location == parameter.location);
141 if !duplicate {
142 self.parameters.push(parameter);
143 }
144 }
145
146 pub fn set_request_body(&mut self, media_type: &str, schema: SchemaObject, required: bool) {
148 self.request_body = Some(RequestBody {
149 description: None,
150 content: BTreeMap::from([(
151 media_type.to_owned(),
152 MediaType {
153 schema: Some(schema),
154 },
155 )]),
156 required,
157 });
158 }
159
160 pub fn add_response(
162 &mut self,
163 status: impl Into<String>,
164 description: impl Into<String>,
165 content: Option<(&str, SchemaObject)>,
166 ) {
167 let content = content
168 .map(|(mime, schema)| {
169 BTreeMap::from([(
170 mime.to_owned(),
171 MediaType {
172 schema: Some(schema),
173 },
174 )])
175 })
176 .unwrap_or_default();
177 self.responses.insert(
178 status.into(),
179 Response {
180 description: description.into(),
181 content,
182 },
183 );
184 }
185
186 pub fn remap_response(&mut self, from: &str, to: &str) {
188 if let Some(r) = self.responses.remove(from) {
189 self.responses.insert(to.to_owned(), r);
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
196#[serde(rename_all = "lowercase")]
197#[allow(missing_docs)]
198pub enum ParameterLocation {
199 Path,
200 Query,
201 Header,
202 Cookie,
203}
204
205#[derive(Debug, Clone, PartialEq, Serialize)]
207pub struct Parameter {
208 pub name: String,
210 #[serde(rename = "in")]
212 pub location: ParameterLocation,
213 pub required: bool,
215 pub schema: SchemaObject,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub description: Option<String>,
220}
221
222impl Parameter {
223 pub fn new(
225 name: impl Into<String>,
226 location: ParameterLocation,
227 required: bool,
228 schema: SchemaObject,
229 ) -> Self {
230 Self {
231 name: name.into(),
232 location,
233 required,
234 schema,
235 description: None,
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize)]
242pub struct RequestBody {
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub description: Option<String>,
246 pub content: BTreeMap<String, MediaType>,
248 pub required: bool,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize)]
254pub struct MediaType {
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub schema: Option<SchemaObject>,
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize)]
262pub struct Response {
263 pub description: String,
265 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
267 pub content: BTreeMap<String, MediaType>,
268}