openapiv3/
operation.rs

1use crate::*;
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5/// Describes a single API operation on a path.
6#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
7#[serde(rename_all = "camelCase")]
8pub struct Operation {
9    /// A list of tags for API documentation control.
10    /// Tags can be used for logical grouping of operations
11    /// by resources or any other qualifier.
12    #[serde(default)]
13    #[serde(skip_serializing_if = "Vec::is_empty")]
14    pub tags: Vec<String>,
15    /// A short summary of what the operation does.
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub summary: Option<String>,
18    /// A verbose explanation of the operation behavior.
19    /// CommonMark syntax MAY be used for rich text representation.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub description: Option<String>,
22    /// Additional external documentation for this operation.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub external_docs: Option<ExternalDocumentation>,
25    /// Unique string used to identify the operation.
26    /// The id MUST be unique among all operations described in the API.
27    /// Tools and libraries MAY use the operationId to uniquely identify
28    /// an operation, therefore, it is RECOMMENDED to follow common
29    /// programming naming conventions.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub operation_id: Option<String>,
32    /// A list of parameters that are applicable for this operation.
33    /// If a parameter is already defined at the Path Item, the new
34    /// definition will override it but can never remove it.
35    /// The list MUST NOT include duplicated parameters. A unique
36    /// parameter is defined by a combination of a name and location.
37    /// The list can use the Reference Object to link to parameters
38    /// that are defined at the OpenAPI Object's components/parameters.
39    #[serde(default)]
40    #[serde(skip_serializing_if = "Vec::is_empty")]
41    pub parameters: Vec<RefOr<Parameter>>,
42    /// The request body applicable for this operation.
43    /// The requestBody is only supported in HTTP methods
44    /// where the HTTP 1.1 specification RFC7231 has explicitly
45    /// defined semantics for request bodies. In other cases where
46    /// the HTTP spec is vague, requestBody SHALL be ignored by consumers.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub request_body: Option<RefOr<RequestBody>>,
49    /// REQUIRED. The list of possible responses as they are returned
50    /// from executing this operation.
51    pub responses: Responses,
52    /// Declares this operation to be deprecated.Default value is false.
53    #[serde(default, skip_serializing_if = "is_false")]
54    pub deprecated: bool,
55    /// A declaration of which security mechanisms can be used for this operation.
56    /// The list of values includes alternative security requirement objects that can
57    /// be used. Only one of the security requirement objects need to be satisfied to
58    /// authorize a request. This definition overrides any declared top-level security.
59    /// To remove a top-level security declaration, an empty array can be used.
60    #[serde(default)]
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub security: Option<Vec<SecurityRequirement>>,
63    /// An alternative server array to service this operation.
64    /// If an alternative server object is specified at the
65    /// Path Item Object or Root level, it will be overridden by this value.
66    #[serde(default)]
67    #[serde(skip_serializing_if = "Vec::is_empty")]
68    pub servers: Vec<Server>,
69    /// Inline extensions to this object.
70    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
71    pub extensions: IndexMap<String, serde_json::Value>,
72}
73
74impl Operation {
75    pub fn add_response_error_json(&mut self, status: u16, message: String) {
76        let status_code_enum = StatusCode::Code(status);
77        self.responses.responses.insert(
78            status_code_enum, // OpenAPI keys are strings
79            RefOr::Item(Response {
80                description: message,
81                content: IndexMap::new(),
82                ..Response::default()
83            }),
84        );
85    }
86
87    pub fn add_response_success_json(&mut self, schema: Option<RefOr<Schema>>) {
88        self.responses.responses.insert(
89            StatusCode::Code(200),
90            RefOr::Item({
91                let mut content = indexmap::IndexMap::new();
92                content.insert(
93                    "application/json".to_string(),
94                    MediaType {
95                        schema,
96                        ..MediaType::default()
97                    },
98                );
99                Response {
100                    description: "OK".to_string(),
101                    content,
102                    ..Response::default()
103                }
104            }),
105        );
106    }
107
108    pub fn add_request_body_json(&mut self, schema: Option<RefOr<Schema>>) {
109        let mut content = indexmap::IndexMap::new();
110        content.insert(
111            "application/json".to_string(),
112            MediaType {
113                schema,
114                ..MediaType::default()
115            },
116        );
117        self.request_body = Some(RefOr::Item(RequestBody {
118            content,
119            required: true,
120            ..RequestBody::default()
121        }));
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use crate::{Operation, RefOr, Responses, Schema, StatusCode};
128    use indexmap::IndexMap;
129    use serde_yaml::from_str;
130
131    #[test]
132    fn deserialize_responses() {
133        assert_eq!(
134            Operation {
135                responses: Responses {
136                    default: None,
137                    responses: {
138                        let mut map = IndexMap::new();
139                        map.insert(StatusCode::Code(200), RefOr::ref_("test"));
140                        map
141                    },
142                    ..Default::default()
143                },
144                ..Default::default()
145            },
146            from_str("{ responses: { 200: { $ref: 'test' } } }").unwrap(),
147        );
148
149        assert_eq!(
150            Operation {
151                responses: Responses {
152                    default: None,
153                    responses: {
154                        let mut map = IndexMap::new();
155                        map.insert(StatusCode::Code(666), RefOr::ref_("demo"));
156                        map
157                    },
158                    ..Default::default()
159                },
160                ..Default::default()
161            },
162            from_str("{ responses: { \"666\": { $ref: 'demo' } } }").unwrap(),
163        );
164
165        assert_eq!(
166            Operation {
167                responses: Responses {
168                    default: Some(RefOr::ref_("def")),
169                    responses: {
170                        let mut map = IndexMap::new();
171                        map.insert(StatusCode::Code(666), RefOr::ref_("demo"));
172                        map.insert(StatusCode::Code(418), RefOr::ref_("demo"));
173                        map
174                    },
175                    ..Default::default()
176                },
177                ..Default::default()
178            },
179            from_str("{ responses: { default: { $ref: 'def' }, \"666\": { $ref: 'demo' }, 418: { $ref: 'demo' } } }").unwrap(),
180        );
181    }
182
183    #[test]
184    fn test_basic() {
185        let mut op = Operation::default();
186        op.add_request_body_json(Some(RefOr::Item(Schema::new_string())));
187    }
188}