1pub mod header;
4pub mod style;
5
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::{
12 Map,
13 model::{
14 body::media_type::MediaType,
15 example::{
16 Example, Examples, ExamplesConflict, examples_from, examples_into, examples_with_named,
17 },
18 extensions::Extensions,
19 parameter::style::Style,
20 reference::{Ref, RefOr},
21 schema::Schema,
22 },
23};
24
25#[non_exhaustive]
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub enum ParameterIn {
35 #[default]
40 Query,
41 Header,
48 Path,
50 Cookie,
52 #[cfg(feature = "openapi32")]
59 Querystring,
60}
61
62impl ParameterIn {
63 #[must_use]
65 pub fn as_str(self) -> &'static str {
66 match self {
67 Self::Query => "query",
68 Self::Header => "header",
69 Self::Path => "path",
70 Self::Cookie => "cookie",
71 #[cfg(feature = "openapi32")]
72 Self::Querystring => "querystring",
73 }
74 }
75}
76
77impl fmt::Display for ParameterIn {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(self.as_str())
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
89#[serde(try_from = "RawParameter", into = "RawParameter")]
90pub struct Parameter {
91 pub name: String,
96
97 #[serde(rename = "in")]
99 pub location: ParameterIn,
100
101 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub description: Option<String>,
106
107 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub required: Option<bool>,
113
114 pub deprecated: Option<bool>,
116
117 pub allow_empty_value: Option<bool>,
121
122 shape: ParameterShape,
123
124 examples: Option<Examples>,
125
126 pub extensions: Extensions,
128}
129
130#[derive(Clone, Debug, PartialEq)]
138pub enum ParameterShape {
139 Schema {
141 schema: Schema,
143
144 style: Option<Style>,
146
147 explode: Option<bool>,
149
150 allow_reserved: Option<bool>,
152 },
153
154 Content {
161 media_type: String,
163
164 value: Box<MediaType>,
166 },
167}
168
169impl Parameter {
170 pub fn new(name: impl Into<String>, location: ParameterIn, schema: Schema) -> Self {
172 Self::shaped(
173 name,
174 location,
175 ParameterShape::Schema {
176 schema,
177 style: None,
178 explode: None,
179 allow_reserved: None,
180 },
181 )
182 }
183
184 pub fn with_content(
189 name: impl Into<String>,
190 location: ParameterIn,
191 media_type: impl Into<String>,
192 value: MediaType,
193 ) -> Self {
194 Self::shaped(
195 name,
196 location,
197 ParameterShape::Content {
198 media_type: media_type.into(),
199 value: Box::new(value),
200 },
201 )
202 }
203
204 fn shaped(name: impl Into<String>, location: ParameterIn, shape: ParameterShape) -> Self {
205 Self {
206 name: name.into(),
207 location,
208 description: None,
209 required: (location == ParameterIn::Path).then_some(true),
212 deprecated: None,
213 allow_empty_value: None,
214 shape,
215 examples: None,
216 extensions: Extensions::default(),
217 }
218 }
219
220 pub fn path(name: impl Into<String>, schema: Schema) -> Self {
222 Self::new(name, ParameterIn::Path, schema)
223 }
224
225 pub fn query(name: impl Into<String>, schema: Schema) -> Self {
227 Self::new(name, ParameterIn::Query, schema)
228 }
229
230 pub fn header(name: impl Into<String>, schema: Schema) -> Self {
232 Self::new(name, ParameterIn::Header, schema)
233 }
234
235 pub fn cookie(name: impl Into<String>, schema: Schema) -> Self {
237 Self::new(name, ParameterIn::Cookie, schema)
238 }
239
240 #[must_use]
242 pub fn required(mut self, required: bool) -> Self {
243 self.required = Some(required);
244 self
245 }
246
247 #[must_use]
249 pub fn with_description(mut self, description: impl Into<String>) -> Self {
250 self.description = Some(description.into());
251 self
252 }
253
254 #[must_use]
258 pub fn with_style(mut self, style: Style, explode: bool) -> Self {
259 if let ParameterShape::Schema {
260 style: slot,
261 explode: exploded,
262 ..
263 } = &mut self.shape
264 {
265 *slot = Some(style);
266 *exploded = Some(explode);
267 }
268 self
269 }
270
271 #[must_use]
276 pub fn with_example(mut self, value: impl Into<Value>) -> Self {
277 self.examples = Some(Examples::Inline(value.into()));
278 self
279 }
280
281 #[must_use]
283 pub fn with_named_example(mut self, name: impl Into<String>, example: Example) -> Self {
284 self.examples = Some(examples_with_named(
285 self.examples,
286 name.into(),
287 RefOr::Item(example),
288 ));
289 self
290 }
291
292 #[must_use]
295 pub fn with_named_example_ref(mut self, name: impl Into<String>, example: Ref) -> Self {
296 self.examples = Some(examples_with_named(
297 self.examples,
298 name.into(),
299 RefOr::Ref(example),
300 ));
301 self
302 }
303
304 #[must_use]
306 pub fn shape(&self) -> &ParameterShape {
307 &self.shape
308 }
309
310 pub fn shape_mut(&mut self) -> &mut ParameterShape {
316 &mut self.shape
317 }
318
319 #[must_use]
321 pub fn schema(&self) -> Option<&Schema> {
322 match &self.shape {
323 ParameterShape::Schema { schema, .. } => Some(schema),
324 ParameterShape::Content { .. } => None,
325 }
326 }
327
328 #[must_use]
330 pub fn content(&self) -> Option<(&str, &MediaType)> {
331 match &self.shape {
332 ParameterShape::Content { media_type, value } => Some((media_type, &**value)),
333 ParameterShape::Schema { .. } => None,
334 }
335 }
336
337 #[must_use]
340 pub fn style(&self) -> Option<Style> {
341 match self.shape {
342 ParameterShape::Schema { style, .. } => style,
343 ParameterShape::Content { .. } => None,
344 }
345 }
346
347 #[must_use]
349 pub fn allow_reserved(&self) -> Option<bool> {
350 match self.shape {
351 ParameterShape::Schema { allow_reserved, .. } => allow_reserved,
352 ParameterShape::Content { .. } => None,
353 }
354 }
355
356 #[must_use]
358 pub fn examples(&self) -> Option<&Examples> {
359 self.examples.as_ref()
360 }
361
362 #[must_use]
364 pub fn example(&self) -> Option<&Value> {
365 match &self.examples {
366 Some(Examples::Inline(value)) => Some(value),
367 Some(Examples::Named(_)) | None => None,
368 }
369 }
370
371 #[must_use]
373 pub fn named_examples(&self) -> Option<&Map<RefOr<Example>>> {
374 match &self.examples {
375 Some(Examples::Named(named)) => Some(named),
376 Some(Examples::Inline(_)) | None => None,
377 }
378 }
379
380 #[must_use]
385 pub fn effective_style(&self) -> Option<Style> {
386 match self.shape {
387 ParameterShape::Schema { style, .. } => {
388 Some(style.unwrap_or_else(|| Style::default_for(self.location)))
389 }
390 ParameterShape::Content { .. } => None,
391 }
392 }
393
394 #[must_use]
396 pub fn effective_explode(&self) -> Option<bool> {
397 match self.shape {
398 ParameterShape::Schema { explode, .. } => Some(
399 explode
400 .unwrap_or_else(|| self.effective_style().is_some_and(Style::default_explode)),
401 ),
402 ParameterShape::Content { .. } => None,
403 }
404 }
405}
406
407#[derive(Serialize, Deserialize)]
409struct RawParameter {
410 name: String,
411
412 #[serde(rename = "in")]
413 location: ParameterIn,
414
415 #[serde(default, skip_serializing_if = "Option::is_none")]
416 description: Option<String>,
417
418 #[serde(default, skip_serializing_if = "Option::is_none")]
419 required: Option<bool>,
420
421 #[serde(default, skip_serializing_if = "Option::is_none")]
422 deprecated: Option<bool>,
423
424 #[serde(
425 rename = "allowEmptyValue",
426 default,
427 skip_serializing_if = "Option::is_none"
428 )]
429 allow_empty_value: Option<bool>,
430
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 style: Option<Style>,
433
434 #[serde(default, skip_serializing_if = "Option::is_none")]
435 explode: Option<bool>,
436
437 #[serde(
438 rename = "allowReserved",
439 default,
440 skip_serializing_if = "Option::is_none"
441 )]
442 allow_reserved: Option<bool>,
443
444 #[serde(default, skip_serializing_if = "Option::is_none")]
445 schema: Option<Schema>,
446
447 #[serde(
448 default,
449 deserialize_with = "crate::model::nullable::some",
450 skip_serializing_if = "Option::is_none"
451 )]
452 example: Option<Value>,
453
454 #[serde(default, skip_serializing_if = "Option::is_none")]
455 examples: Option<Map<RefOr<Example>>>,
456
457 #[serde(default, skip_serializing_if = "Map::is_empty")]
458 content: Map<MediaType>,
459
460 #[serde(flatten)]
461 extensions: Extensions,
462}
463
464#[derive(Debug)]
469pub(crate) enum ParameterConflict {
470 Shape(ShapeConflict),
472
473 Examples(ExamplesConflict),
475}
476
477impl fmt::Display for ParameterConflict {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 match self {
480 Self::Shape(conflict) => conflict.fmt(f),
481 Self::Examples(conflict) => conflict.fmt(f),
482 }
483 }
484}
485
486impl From<ShapeConflict> for ParameterConflict {
487 fn from(conflict: ShapeConflict) -> Self {
488 Self::Shape(conflict)
489 }
490}
491
492impl From<ExamplesConflict> for ParameterConflict {
493 fn from(conflict: ExamplesConflict) -> Self {
494 Self::Examples(conflict)
495 }
496}
497
498#[derive(Debug)]
500pub(crate) enum ShapeConflict {
501 Neither,
503
504 Both,
506
507 ContentNotSingular(usize),
509}
510
511impl fmt::Display for ShapeConflict {
512 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513 match self {
514 Self::Neither => f.write_str("one of `schema` and `content` is required"),
515 Self::Both => f.write_str("`schema` and `content` are mutually exclusive"),
516 Self::ContentNotSingular(found) => {
517 write!(f, "`content` must hold exactly one entry, found {found}")
518 }
519 }
520 }
521}
522
523pub(crate) fn shape_from(
525 schema: Option<Schema>,
526 content: Map<MediaType>,
527) -> Result<(Schema, Option<(String, MediaType)>), ShapeConflict> {
528 match (schema, content.len()) {
529 (Some(_), 1..) => Err(ShapeConflict::Both),
530 (None, 0) => Err(ShapeConflict::Neither),
531 (None, 1) => {
532 let (media_type, value) = content
533 .into_iter()
534 .next()
535 .expect("a map of length one has an entry");
536 Ok((Schema::Bool(true), Some((media_type, value))))
537 }
538 (None, found) => Err(ShapeConflict::ContentNotSingular(found)),
539 (Some(schema), 0) => Ok((schema, None)),
540 }
541}
542
543impl TryFrom<RawParameter> for Parameter {
544 type Error = ParameterConflict;
545
546 fn try_from(raw: RawParameter) -> Result<Self, Self::Error> {
547 let shape = match shape_from(raw.schema, raw.content)? {
548 (_, Some((media_type, value))) => ParameterShape::Content {
549 media_type,
550 value: Box::new(value),
551 },
552 (schema, None) => ParameterShape::Schema {
553 schema,
554 style: raw.style,
555 explode: raw.explode,
556 allow_reserved: raw.allow_reserved,
557 },
558 };
559
560 Ok(Self {
561 name: raw.name,
562 location: raw.location,
563 description: raw.description,
564 required: raw.required,
565 deprecated: raw.deprecated,
566 allow_empty_value: raw.allow_empty_value,
567 shape,
568 examples: examples_from(raw.example, raw.examples)?,
569 extensions: raw.extensions,
570 })
571 }
572}
573
574impl From<Parameter> for RawParameter {
575 fn from(parameter: Parameter) -> Self {
576 let (schema, style, explode, allow_reserved, content) = match parameter.shape {
577 ParameterShape::Schema {
578 schema,
579 style,
580 explode,
581 allow_reserved,
582 } => (Some(schema), style, explode, allow_reserved, Map::new()),
583 ParameterShape::Content { media_type, value } => (
584 None,
585 None,
586 None,
587 None,
588 Map::from_iter([(media_type, *value)]),
589 ),
590 };
591
592 let (example, examples) = examples_into(parameter.examples);
593
594 Self {
595 name: parameter.name,
596 location: parameter.location,
597 description: parameter.description,
598 required: parameter.required,
599 deprecated: parameter.deprecated,
600 allow_empty_value: parameter.allow_empty_value,
601 style,
602 explode,
603 allow_reserved,
604 schema,
605 example,
606 examples,
607 content,
608 extensions: parameter.extensions,
609 }
610 }
611}
612
613#[cfg(test)]
614mod tests;