1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::marker::PhantomData;

use crate::*;
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct Responses {
    /// The documentation of responses other than the ones declared
    /// for specific HTTP response codes. Use this field to cover
    /// undeclared responses. A Reference Object can link to a response
    /// that the OpenAPI Object's components/responses section defines.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<ReferenceOr<Response>>,
    /// Any HTTP status code can be used as the property name,
    /// but only one property per code, to describe the expected
    /// response for that HTTP status code. A Reference Object
    /// can link to a response that is defined in the OpenAPI Object's
    /// components/responses section. This field MUST be enclosed in
    /// quotation marks (for example, "200") for compatibility between
    /// JSON and YAML. To define a range of response codes, this field
    /// MAY contain the uppercase wildcard character X. For example,
    /// 2XX represents all response codes between [200-299]. The following
    /// range definitions are allowed: 1XX, 2XX, 3XX, 4XX, and 5XX.
    /// If a response range is defined using an explicit code, the
    /// explicit code definition takes precedence over the range
    /// definition for that code.
    #[serde(flatten, deserialize_with = "deserialize_responses")]
    pub responses: IndexMap<StatusCode, ReferenceOr<Response>>,
    /// Inline extensions to this object.
    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
    pub extensions: IndexMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct Response {
    /// REQUIRED. A short description of the response.
    /// CommonMark syntax MAY be used for rich text representation.
    pub description: String,

    /// Maps a header name to its definition.
    /// RFC7230 states header names are case insensitive.
    /// If a response header is defined with the name "Content-Type",
    /// it SHALL be ignored.
    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
    pub headers: IndexMap<String, ReferenceOr<Header>>,

    /// A map containing descriptions of potential response payloads.
    /// The key is a media type or media type range and the value
    /// describes it. For responses that match multiple keys,
    /// only the most specific key is applicable. e.g. text/plain
    /// overrides text/*
    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
    pub content: IndexMap<String, MediaType>,

    /// A map of operations links that can be followed from the response.
    /// The key of the map is a short name for the link, following
    /// the naming constraints of the names for Component Objects.
    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
    pub links: IndexMap<String, ReferenceOr<Link>>,

    /// Inline extensions to this object.
    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
    pub extensions: IndexMap<String, serde_json::Value>,
}

fn deserialize_responses<'de, D>(
    deserializer: D,
) -> Result<IndexMap<StatusCode, ReferenceOr<Response>>, D::Error>
where
    D: Deserializer<'de>,
{
    // We rely on the result of StatusCode::deserialize to act as our
    // predicate; it will succeed only for status code.
    deserializer.deserialize_map(PredicateVisitor(|_: &StatusCode| true, PhantomData))
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use crate::{ReferenceOr, Response, Responses, StatusCode};

    #[test]
    fn test_responses() {
        let responses = serde_json::from_str::<Responses>(
            r#"{
            "404": {
                "description": "xxx"
            },
            "x-foo": "bar",
            "ignored": "wat"
         }"#,
        )
        .unwrap();

        assert_eq!(
            responses.responses.get(&StatusCode::Code(404)),
            Some(&ReferenceOr::Item(Response {
                description: "xxx".to_string(),
                ..Default::default()
            }))
        );
        assert_eq!(responses.extensions.get("x-foo"), Some(&json!("bar")));
    }
}