1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum ConformanceFeature {
6 PathParamString,
8 PathParamInteger,
9 QueryParamString,
10 QueryParamInteger,
11 QueryParamArray,
12 HeaderParam,
13 CookieParam,
14 BodyJson,
16 BodyFormUrlencoded,
17 BodyMultipart,
18 SchemaString,
20 SchemaInteger,
21 SchemaNumber,
22 SchemaBoolean,
23 SchemaArray,
24 SchemaObject,
25 CompositionOneOf,
27 CompositionAnyOf,
28 CompositionAllOf,
29 FormatDate,
31 FormatDateTime,
32 FormatEmail,
33 FormatUuid,
34 FormatUri,
35 FormatIpv4,
36 FormatIpv6,
37 ConstraintRequired,
39 ConstraintOptional,
40 ConstraintMinMax,
41 ConstraintPattern,
42 ConstraintEnum,
43 Response200,
45 Response201,
46 Response204,
47 Response400,
48 Response404,
49 MethodGet,
51 MethodPost,
52 MethodPut,
53 MethodPatch,
54 MethodDelete,
55 MethodHead,
56 MethodOptions,
57 ContentNegotiation,
59 SecurityBearer,
61 SecurityApiKey,
62 SecurityBasic,
63 ResponseValidation,
65}
66
67impl ConformanceFeature {
68 pub fn category(&self) -> &'static str {
70 match self {
71 Self::PathParamString
72 | Self::PathParamInteger
73 | Self::QueryParamString
74 | Self::QueryParamInteger
75 | Self::QueryParamArray
76 | Self::HeaderParam
77 | Self::CookieParam => "Parameters",
78 Self::BodyJson | Self::BodyFormUrlencoded | Self::BodyMultipart => "Request Bodies",
79 Self::SchemaString
80 | Self::SchemaInteger
81 | Self::SchemaNumber
82 | Self::SchemaBoolean
83 | Self::SchemaArray
84 | Self::SchemaObject => "Schema Types",
85 Self::CompositionOneOf | Self::CompositionAnyOf | Self::CompositionAllOf => {
86 "Composition"
87 }
88 Self::FormatDate
89 | Self::FormatDateTime
90 | Self::FormatEmail
91 | Self::FormatUuid
92 | Self::FormatUri
93 | Self::FormatIpv4
94 | Self::FormatIpv6 => "String Formats",
95 Self::ConstraintRequired
96 | Self::ConstraintOptional
97 | Self::ConstraintMinMax
98 | Self::ConstraintPattern
99 | Self::ConstraintEnum => "Constraints",
100 Self::Response200
101 | Self::Response201
102 | Self::Response204
103 | Self::Response400
104 | Self::Response404 => "Response Codes",
105 Self::MethodGet
106 | Self::MethodPost
107 | Self::MethodPut
108 | Self::MethodPatch
109 | Self::MethodDelete
110 | Self::MethodHead
111 | Self::MethodOptions => "HTTP Methods",
112 Self::ContentNegotiation => "Content Types",
113 Self::SecurityBearer | Self::SecurityApiKey | Self::SecurityBasic => "Security",
114 Self::ResponseValidation => "Response Validation",
115 }
116 }
117
118 pub fn check_name(&self) -> &'static str {
120 match self {
121 Self::PathParamString => "param:path:string",
122 Self::PathParamInteger => "param:path:integer",
123 Self::QueryParamString => "param:query:string",
124 Self::QueryParamInteger => "param:query:integer",
125 Self::QueryParamArray => "param:query:array",
126 Self::HeaderParam => "param:header",
127 Self::CookieParam => "param:cookie",
128 Self::BodyJson => "body:json",
129 Self::BodyFormUrlencoded => "body:form-urlencoded",
130 Self::BodyMultipart => "body:multipart",
131 Self::SchemaString => "schema:string",
132 Self::SchemaInteger => "schema:integer",
133 Self::SchemaNumber => "schema:number",
134 Self::SchemaBoolean => "schema:boolean",
135 Self::SchemaArray => "schema:array",
136 Self::SchemaObject => "schema:object",
137 Self::CompositionOneOf => "composition:oneOf",
138 Self::CompositionAnyOf => "composition:anyOf",
139 Self::CompositionAllOf => "composition:allOf",
140 Self::FormatDate => "format:date",
141 Self::FormatDateTime => "format:date-time",
142 Self::FormatEmail => "format:email",
143 Self::FormatUuid => "format:uuid",
144 Self::FormatUri => "format:uri",
145 Self::FormatIpv4 => "format:ipv4",
146 Self::FormatIpv6 => "format:ipv6",
147 Self::ConstraintRequired => "constraint:required",
148 Self::ConstraintOptional => "constraint:optional",
149 Self::ConstraintMinMax => "constraint:minmax",
150 Self::ConstraintPattern => "constraint:pattern",
151 Self::ConstraintEnum => "constraint:enum",
152 Self::Response200 => "response:200",
153 Self::Response201 => "response:201",
154 Self::Response204 => "response:204",
155 Self::Response400 => "response:400",
156 Self::Response404 => "response:404",
157 Self::MethodGet => "method:GET",
158 Self::MethodPost => "method:POST",
159 Self::MethodPut => "method:PUT",
160 Self::MethodPatch => "method:PATCH",
161 Self::MethodDelete => "method:DELETE",
162 Self::MethodHead => "method:HEAD",
163 Self::MethodOptions => "method:OPTIONS",
164 Self::ContentNegotiation => "content:negotiation",
165 Self::SecurityBearer => "security:bearer",
166 Self::SecurityApiKey => "security:apikey",
167 Self::SecurityBasic => "security:basic",
168 Self::ResponseValidation => "response:schema:validation",
169 }
170 }
171
172 pub fn all() -> &'static [ConformanceFeature] {
174 &[
175 Self::PathParamString,
176 Self::PathParamInteger,
177 Self::QueryParamString,
178 Self::QueryParamInteger,
179 Self::QueryParamArray,
180 Self::HeaderParam,
181 Self::CookieParam,
182 Self::BodyJson,
183 Self::BodyFormUrlencoded,
184 Self::BodyMultipart,
185 Self::SchemaString,
186 Self::SchemaInteger,
187 Self::SchemaNumber,
188 Self::SchemaBoolean,
189 Self::SchemaArray,
190 Self::SchemaObject,
191 Self::CompositionOneOf,
192 Self::CompositionAnyOf,
193 Self::CompositionAllOf,
194 Self::FormatDate,
195 Self::FormatDateTime,
196 Self::FormatEmail,
197 Self::FormatUuid,
198 Self::FormatUri,
199 Self::FormatIpv4,
200 Self::FormatIpv6,
201 Self::ConstraintRequired,
202 Self::ConstraintOptional,
203 Self::ConstraintMinMax,
204 Self::ConstraintPattern,
205 Self::ConstraintEnum,
206 Self::Response200,
207 Self::Response201,
208 Self::Response204,
209 Self::Response400,
210 Self::Response404,
211 Self::MethodGet,
212 Self::MethodPost,
213 Self::MethodPut,
214 Self::MethodPatch,
215 Self::MethodDelete,
216 Self::MethodHead,
217 Self::MethodOptions,
218 Self::ContentNegotiation,
219 Self::SecurityBearer,
220 Self::SecurityApiKey,
221 Self::SecurityBasic,
222 Self::ResponseValidation,
223 ]
224 }
225
226 pub fn categories() -> &'static [&'static str] {
228 &[
229 "Parameters",
230 "Request Bodies",
231 "Schema Types",
232 "Composition",
233 "String Formats",
234 "Constraints",
235 "Response Codes",
236 "HTTP Methods",
237 "Content Types",
238 "Security",
239 "Response Validation",
240 ]
241 }
242
243 pub fn category_from_cli_name(name: &str) -> Option<&'static str> {
245 match name.to_lowercase().replace('_', "-").as_str() {
246 "parameters" => Some("Parameters"),
247 "request-bodies" => Some("Request Bodies"),
248 "schema-types" => Some("Schema Types"),
249 "composition" => Some("Composition"),
250 "string-formats" => Some("String Formats"),
251 "constraints" => Some("Constraints"),
252 "response-codes" => Some("Response Codes"),
253 "http-methods" => Some("HTTP Methods"),
254 "content-types" => Some("Content Types"),
255 "security" => Some("Security"),
256 "response-validation" => Some("Response Validation"),
257 _ => None,
258 }
259 }
260
261 pub fn cli_category_names() -> Vec<(&'static str, &'static str)> {
263 vec![
264 ("parameters", "Parameters"),
265 ("request-bodies", "Request Bodies"),
266 ("schema-types", "Schema Types"),
267 ("composition", "Composition"),
268 ("string-formats", "String Formats"),
269 ("constraints", "Constraints"),
270 ("response-codes", "Response Codes"),
271 ("http-methods", "HTTP Methods"),
272 ("content-types", "Content Types"),
273 ("security", "Security"),
274 ("response-validation", "Response Validation"),
275 ]
276 }
277
278 pub fn spec_url(&self) -> &'static str {
280 match self.category() {
281 "Parameters" => "https://spec.openapis.org/oas/v3.0.0#parameter-object",
282 "Request Bodies" => "https://spec.openapis.org/oas/v3.0.0#request-body-object",
283 "Schema Types" => "https://spec.openapis.org/oas/v3.0.0#schema-object",
284 "Composition" => "https://spec.openapis.org/oas/v3.0.0#schema-object",
285 "String Formats" => "https://spec.openapis.org/oas/v3.0.0#data-types",
286 "Constraints" => "https://spec.openapis.org/oas/v3.0.0#schema-object",
287 "Response Codes" => "https://spec.openapis.org/oas/v3.0.0#responses-object",
288 "HTTP Methods" => "https://spec.openapis.org/oas/v3.0.0#path-item-object",
289 "Content Types" => "https://spec.openapis.org/oas/v3.0.0#media-type-object",
290 "Security" => "https://spec.openapis.org/oas/v3.0.0#security-scheme-object",
291 "Response Validation" => "https://spec.openapis.org/oas/v3.0.0#response-object",
292 _ => "https://spec.openapis.org/oas/v3.0.0",
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_all_features_have_categories() {
303 for feature in ConformanceFeature::all() {
304 assert!(!feature.category().is_empty());
305 assert!(!feature.check_name().is_empty());
306 }
307 }
308
309 #[test]
310 fn test_all_categories_covered() {
311 let categories: std::collections::HashSet<&str> =
312 ConformanceFeature::all().iter().map(|f| f.category()).collect();
313 for cat in ConformanceFeature::categories() {
314 assert!(categories.contains(cat), "Category '{}' has no features", cat);
315 }
316 }
317
318 #[test]
319 fn test_category_from_cli_name() {
320 assert_eq!(ConformanceFeature::category_from_cli_name("parameters"), Some("Parameters"));
321 assert_eq!(
322 ConformanceFeature::category_from_cli_name("request-bodies"),
323 Some("Request Bodies")
324 );
325 assert_eq!(
326 ConformanceFeature::category_from_cli_name("schema-types"),
327 Some("Schema Types")
328 );
329 assert_eq!(ConformanceFeature::category_from_cli_name("PARAMETERS"), Some("Parameters"));
330 assert_eq!(
331 ConformanceFeature::category_from_cli_name("Request-Bodies"),
332 Some("Request Bodies")
333 );
334 assert_eq!(ConformanceFeature::category_from_cli_name("invalid"), None);
335 }
336
337 #[test]
338 fn test_cli_category_names_complete() {
339 let cli_names = ConformanceFeature::cli_category_names();
340 let categories = ConformanceFeature::categories();
341 assert_eq!(cli_names.len(), categories.len());
342 for (_, canonical) in &cli_names {
343 assert!(
344 categories.contains(canonical),
345 "CLI name maps to unknown category: {}",
346 canonical
347 );
348 }
349 }
350
351 #[test]
352 fn test_spec_urls_not_empty() {
353 for feature in ConformanceFeature::all() {
354 assert!(!feature.spec_url().is_empty(), "Feature {:?} has empty spec URL", feature);
355 }
356 }
357}