Skip to main content

satay_codegen/error/
validation.rs

1/// Errors that can occur while validating an OpenAPI document.
2///
3/// This enum is [`non_exhaustive`](https://doc.rust-lang.org/reference/attributes/type_system.html)
4/// so new variants may be added in future releases without a semver break.
5#[derive(Debug, thiserror::Error)]
6#[non_exhaustive]
7pub enum ValidationError {
8    // -- Document and component shape validation --
9    /// The OpenAPI version is not supported.
10    ///
11    /// Error message: `unsupported OpenAPI version `{version}`; Satay supports OpenAPI 3.1`
12    #[error("unsupported OpenAPI version `{version}`; Satay supports OpenAPI 3.1")]
13    UnsupportedOpenApiVersion { version: String },
14
15    /// A schema component uses a type that is not supported.
16    ///
17    /// Error message: `unsupported type `{kind}` in schema `{schema}``
18    #[error("unsupported type `{kind}` in schema `{schema}`")]
19    UnsupportedComponentType { schema: String, kind: String },
20
21    /// A schema component is missing a required `type`, `$ref`, `enum`, or `properties` declaration.
22    ///
23    /// Error message: `schema `{schema}` must declare `type`, `$ref`, `enum`, or `properties``
24    #[error("schema `{schema}` must declare `type`, `$ref`, `enum`, or `properties`")]
25    MissingComponentSchemaType { schema: String },
26
27    /// An object schema is missing the required `properties` field.
28    ///
29    /// Error message: `object schema `{schema}` must declare `properties``
30    #[error("object schema `{schema}` must declare `properties`")]
31    MissingObjectProperties { schema: String },
32
33    /// The OpenAPI document is missing the required `paths` field.
34    ///
35    /// Error message: `OpenAPI document must declare `paths``
36    #[error("OpenAPI document must declare `paths`")]
37    MissingPaths,
38
39    // -- Enum and schema type validation --
40    /// A schema uses an enum with a non-string type.
41    ///
42    /// Error message: `{context} uses enum type `{kind}`; only string enums are supported`
43    #[error("{context} uses enum type `{kind}`; only string enums are supported")]
44    UnsupportedEnumType { context: String, kind: String },
45
46    /// A schema declares an enum that is not an array.
47    ///
48    /// Error message: `{context} has a non-array enum`
49    #[error("{context} has a non-array enum")]
50    NonArrayEnum { context: String },
51
52    /// A schema declares an enum with no values.
53    ///
54    /// Error message: `{context} has an empty enum`
55    #[error("{context} has an empty enum")]
56    EmptyEnum { context: String },
57
58    /// A schema enum contains a non-string value.
59    ///
60    /// Error message: `{context} contains a non-string enum value; only string enums are supported`
61    #[error("{context} contains a non-string enum value; only string enums are supported")]
62    NonStringEnumValue { context: String },
63
64    /// An `x-satay.enum-variants` value is not an object.
65    ///
66    /// Error message: `{context}.x-satay.enum-variants must be an object`
67    #[error("{context}.x-satay.enum-variants must be an object")]
68    InvalidSatayEnumVariants { context: String },
69
70    /// An `x-satay.enum-variants` entry points at a value that is not in the enum.
71    ///
72    /// Error message: `{context}.x-satay.enum-variants contains `{wire_name}`, which is not declared in the enum`
73    #[error(
74        "{context}.x-satay.enum-variants contains `{wire_name}`, which is not declared in the enum"
75    )]
76    UnknownSatayEnumVariantValue { context: String, wire_name: String },
77
78    /// An `x-satay.enum-variants` entry has a non-string Rust variant name.
79    ///
80    /// Error message: `{context}.x-satay.enum-variants[{wire_name:?}] must be a string`
81    #[error("{context}.x-satay.enum-variants[{wire_name:?}] must be a string")]
82    InvalidSatayEnumVariantName { context: String, wire_name: String },
83
84    /// An `x-satay.enum-variants` entry uses a name reserved for generated fallback variants.
85    ///
86    /// Error message: `{context}.x-satay.enum-variants[{wire_name:?}] uses reserved fallback variant `{rust_name}``
87    #[error(
88        "{context}.x-satay.enum-variants[{wire_name:?}] uses reserved fallback variant `{rust_name}`"
89    )]
90    ReservedSatayEnumVariantName {
91        context: String,
92        wire_name: String,
93        rust_name: String,
94    },
95
96    /// Two `x-satay.enum-variants` entries produce the same Rust variant name.
97    ///
98    /// Error message: `{context}.x-satay.enum-variants maps multiple values to `{rust_name}``
99    #[error("{context}.x-satay.enum-variants maps multiple values to `{rust_name}`")]
100    DuplicateSatayEnumVariantName { context: String, rust_name: String },
101
102    /// A schema has a `required` field that is not an array.
103    ///
104    /// Error message: `{context} has a non-array `required` field`
105    #[error("{context} has a non-array `required` field")]
106    NonArrayRequired { context: String },
107
108    /// A schema `required` array contains a non-string element.
109    ///
110    /// Error message: `{context} has a non-string required field name`
111    #[error("{context} has a non-string required field name")]
112    NonStringRequiredField { context: String },
113
114    /// An integer schema uses an unsupported format.
115    ///
116    /// Error message: `{context} uses unsupported integer format `{format}``
117    #[error("{context} uses unsupported integer format `{format}`")]
118    UnsupportedIntegerFormat { context: String, format: String },
119
120    /// A number schema uses an unsupported format.
121    ///
122    /// Error message: `{context} uses unsupported number format `{format}``
123    #[error("{context} uses unsupported number format `{format}`")]
124    UnsupportedNumberFormat { context: String, format: String },
125
126    /// An `x-satay.parse-as` value is not a supported target type.
127    ///
128    /// Error message: `{context} uses unsupported x-satay.parse-as `{parse_as}``
129    #[error("{context} uses unsupported x-satay.parse-as `{parse_as}`")]
130    UnsupportedSatayParseAs { context: String, parse_as: String },
131
132    /// An `x-satay.parse-as` value is not a string.
133    ///
134    /// Error message: `{context}.x-satay.parse-as must be a string`
135    #[error("{context}.x-satay.parse-as must be a string")]
136    InvalidSatayParseAs { context: String },
137
138    /// `x-satay.parse-as` was applied to an unsupported wire schema.
139    ///
140    /// Error message: `{context} uses x-satay.parse-as `{parse_as}` on `{kind}`; supported parse-as wire schemas are string schemas, plus integer schemas for bool`
141    #[error(
142        "{context} uses x-satay.parse-as `{parse_as}` on `{kind}`; supported parse-as wire schemas are string schemas, plus integer schemas for bool"
143    )]
144    SatayParseAsRequiresString {
145        context: String,
146        parse_as: String,
147        kind: String,
148    },
149
150    /// An `x-satay.integer-type` value is not a supported Rust integer type.
151    ///
152    /// Error message: `{context} uses unsupported x-satay.integer-type `{integer_type}``
153    #[error("{context} uses unsupported x-satay.integer-type `{integer_type}`")]
154    UnsupportedSatayIntegerType {
155        context: String,
156        integer_type: String,
157    },
158
159    /// An `x-satay.integer-type` value is not a string.
160    ///
161    /// Error message: `{context}.x-satay.integer-type must be a string`
162    #[error("{context}.x-satay.integer-type must be a string")]
163    InvalidSatayIntegerType { context: String },
164
165    /// `x-satay.integer-type` was applied to a non-integer schema.
166    ///
167    /// Error message: `{context} uses x-satay.integer-type `{integer_type}` on `{kind}`; supported integer-type wire schemas are integer schemas and string schemas with x-satay.parse-as integer-range`
168    #[error(
169        "{context} uses x-satay.integer-type `{integer_type}` on `{kind}`; supported integer-type wire schemas are integer schemas and string schemas with x-satay.parse-as integer-range"
170    )]
171    SatayIntegerTypeRequiresInteger {
172        context: String,
173        integer_type: String,
174        kind: String,
175    },
176
177    /// An array schema is missing the required `items` field.
178    ///
179    /// Error message: `{context} array schema must declare `items``
180    #[error("{context} array schema must declare `items`")]
181    MissingArrayItems { context: String },
182
183    /// A schema defines an inline object instead of using a `$ref`.
184    ///
185    /// Error message: `{context} is an inline object schema; move it to components/schemas and use `$ref``
186    #[error("{context} is an inline object schema; move it to components/schemas and use `$ref`")]
187    InlineObjectSchema { context: String },
188
189    /// An object schema has no properties (i.e. acts as a map/dictionary), which is unsupported.
190    ///
191    /// Error message: `{context} is an object without properties; map/object schemas are not supported yet`
192    #[error("{context} is an object without properties; map/object schemas are not supported yet")]
193    UnsupportedMapObjectSchema { context: String },
194
195    /// A schema uses an unsupported type.
196    ///
197    /// Error message: `{context} uses unsupported schema type `{kind}``
198    #[error("{context} uses unsupported schema type `{kind}`")]
199    UnsupportedSchemaType { context: String, kind: String },
200
201    /// A schema is missing a required `type`, `$ref`, or `enum` declaration.
202    ///
203    /// Error message: `{context} must declare `type`, `$ref`, or `enum``
204    #[error("{context} must declare `type`, `$ref`, or `enum`")]
205    MissingSchemaType { context: String },
206
207    /// A JSON Schema boolean schema was used; Satay has no IR equivalent yet.
208    ///
209    /// Error message: `{context} is a boolean schema; boolean JSON Schemas are not supported yet`
210    #[error("{context} is a boolean schema; boolean JSON Schemas are not supported yet")]
211    UnsupportedBooleanSchema { context: String },
212
213    /// A schema type array contains more than one non-null type.
214    ///
215    /// Error message: `{context} declares multiple non-null schema types; Satay supports at most one plus null`
216    #[error(
217        "{context} declares multiple non-null schema types; Satay supports at most one plus null"
218    )]
219    MultipleNonNullSchemaTypesUnsupported { context: String },
220
221    /// A schema uses a composition keyword (`allOf`, `anyOf`, `oneOf`) in an unsupported context.
222    ///
223    /// Error message: `{context} uses `{keyword}`, which is not supported in this context`
224    #[error("{context} uses `{keyword}`, which is not supported in this context")]
225    UnsupportedComposition {
226        context: String,
227        keyword: &'static str,
228    },
229
230    /// An `allOf` schema combines supported struct flattening with another schema keyword.
231    ///
232    /// Error message: `{context} uses `allOf` with `{keyword}`; only object branch flattening is supported`
233    #[error("{context} uses `allOf` with `{keyword}`; only object branch flattening is supported")]
234    UnsupportedAllOfSiblingKeyword { context: String, keyword: String },
235
236    /// An `allOf` branch cannot be flattened into a generated Rust struct.
237    ///
238    /// Error message: `{context}.allOf[{index}] must be a local component schema reference or object schema with properties`
239    #[error(
240        "{context}.allOf[{index}] must be a local component schema reference or object schema with properties"
241    )]
242    UnsupportedAllOfBranch { context: String, index: usize },
243
244    /// Two `allOf` branches declare the same object property.
245    ///
246    /// Error message: `{context} declares duplicate `allOf` property `{property}``
247    #[error("{context} declares duplicate `allOf` property `{property}`")]
248    DuplicateAllOfProperty { context: String, property: String },
249
250    /// `allOf` component schemas form a recursive flattening cycle.
251    ///
252    /// Error message: `{context} forms a recursive `allOf` cycle through schema `{schema}``
253    #[error("{context} forms a recursive `allOf` cycle through schema `{schema}`")]
254    RecursiveAllOf { context: String, schema: String },
255
256    /// An `anyOf` schema combines a supported union with another schema keyword.
257    ///
258    /// Error message: `{context} uses `anyOf` with `{keyword}`; only annotation siblings are supported`
259    #[error("{context} uses `anyOf` with `{keyword}`; only annotation siblings are supported")]
260    UnsupportedAnyOfSiblingKeyword { context: String, keyword: String },
261
262    /// An `anyOf` branch is not a supported union branch.
263    ///
264    /// Error message: `{context}.anyOf[{index}] must be a local component schema reference, inline string enum, inline primitive schema, or null schema`
265    #[error(
266        "{context}.anyOf[{index}] must be a local component schema reference, inline string enum, inline primitive schema, or null schema"
267    )]
268    UnsupportedAnyOfBranch { context: String, index: usize },
269
270    /// A `oneOf` schema combines a supported union with another schema keyword.
271    ///
272    /// Error message: `{context} uses `oneOf` with `{keyword}`; only annotation siblings are supported`
273    #[error("{context} uses `oneOf` with `{keyword}`; only annotation siblings are supported")]
274    UnsupportedOneOfSiblingKeyword { context: String, keyword: String },
275
276    /// A `oneOf` branch is not a supported union branch.
277    ///
278    /// Error message: `{context}.oneOf[{index}] must be a local component schema reference, inline string enum, inline primitive schema, or null schema`
279    #[error(
280        "{context}.oneOf[{index}] must be a local component schema reference, inline string enum, inline primitive schema, or null schema"
281    )]
282    UnsupportedOneOfBranch { context: String, index: usize },
283
284    /// A plain `anyOf` or `oneOf` union has more than one null branch.
285    ///
286    /// Error message: `{context}.{keyword}[{index}] duplicates the union null branch`
287    #[error("{context}.{keyword}[{index}] duplicates the union null branch")]
288    DuplicateUnionNullBranch {
289        context: String,
290        keyword: &'static str,
291        index: usize,
292    },
293
294    /// A nullable plain `anyOf` or `oneOf` union has no non-null branches.
295    ///
296    /// Error message: `{context}.{keyword} must declare at least one non-null branch`
297    #[error("{context}.{keyword} must declare at least one non-null branch")]
298    NullableUnionWithoutVariants {
299        context: String,
300        keyword: &'static str,
301    },
302
303    /// A plain `anyOf` or `oneOf` union has a branch that is statically shadowed by an earlier branch.
304    ///
305    /// Error message: `{context}.{keyword}[{index}] is shadowed by earlier branch {shadowed_by} under ordered serde untagged deserialization`
306    #[error(
307        "{context}.{keyword}[{index}] is shadowed by earlier branch {shadowed_by} under ordered serde untagged deserialization"
308    )]
309    ShadowedUnionBranch {
310        context: String,
311        keyword: &'static str,
312        index: usize,
313        shadowed_by: usize,
314    },
315
316    /// A composition schema declares no branches.
317    ///
318    /// Raised for empty `anyOf` and, today, for empty component `allOf` that is routed
319    /// through the shared empty composition-shape check.
320    ///
321    /// Error message: `{context} must declare at least one `anyOf` branch`
322    #[error("{context} must declare at least one `anyOf` branch")]
323    EmptyAnyOf { context: String },
324
325    /// `anyOf` component schemas form a recursive union cycle.
326    ///
327    /// Error message: `{context} forms a recursive `anyOf` cycle through schema `{schema}``
328    #[error("{context} forms a recursive `anyOf` cycle through schema `{schema}`")]
329    RecursiveAnyOf { context: String, schema: String },
330
331    /// A discriminator union does not use exactly one non-empty `anyOf` or `oneOf` branch list.
332    ///
333    /// Error message: `{context} discriminator unions must declare exactly one non-empty `anyOf` or `oneOf` branch list`
334    #[error(
335        "{context} discriminator unions must declare exactly one non-empty `anyOf` or `oneOf` branch list"
336    )]
337    InvalidDiscriminatorUnion { context: String },
338
339    /// A discriminator union branch is not a local component schema reference.
340    ///
341    /// Error message: `{context}.{keyword}[{index}] must be a local component schema reference when using `discriminator``
342    #[error(
343        "{context}.{keyword}[{index}] must be a local component schema reference when using `discriminator`"
344    )]
345    UnsupportedDiscriminatorBranch {
346        context: String,
347        keyword: &'static str,
348        index: usize,
349    },
350
351    /// A discriminator union branch target does not generate as an object struct.
352    ///
353    /// Error message: `{context} discriminator branch `{schema}` must be an object struct component`
354    #[error("{context} discriminator branch `{schema}` must be an object struct component")]
355    DiscriminatorBranchNotObject { context: String, schema: String },
356
357    /// A discriminator branch object contains the discriminator property.
358    ///
359    /// Error message: `{context} discriminator branch `{schema}` contains discriminator property `{property}``
360    #[error(
361        "{context} discriminator branch `{schema}` contains discriminator property `{property}`"
362    )]
363    DiscriminatorPropertyConflict {
364        context: String,
365        schema: String,
366        property: String,
367    },
368
369    /// A discriminator branch object contains an invalid embedded discriminator property.
370    ///
371    /// Error message: `{context} discriminator branch `{schema}` property `{property}` must be {expected}`
372    #[error("{context} discriminator branch `{schema}` property `{property}` must be {expected}")]
373    InvalidDiscriminatorProperty {
374        context: String,
375        schema: String,
376        property: String,
377        expected: &'static str,
378    },
379
380    /// A discriminator mapping entry targets a non-local schema or a schema outside the union branches.
381    ///
382    /// Error message: `{context}.discriminator.mapping[{value:?}] targets `{target}`, which is not a local union branch schema`
383    #[error(
384        "{context}.discriminator.mapping[{value:?}] targets `{target}`, which is not a local union branch schema"
385    )]
386    InvalidDiscriminatorMapping {
387        context: String,
388        value: String,
389        target: String,
390    },
391
392    /// Multiple discriminator mapping values target the same union branch schema.
393    ///
394    /// Error message: `{context}.discriminator.mapping maps multiple values to branch schema `{schema}``
395    #[error("{context}.discriminator.mapping maps multiple values to branch schema `{schema}`")]
396    DuplicateDiscriminatorMapping { context: String, schema: String },
397
398    /// A discriminator mapping value disagrees with a branch's embedded discriminator property value.
399    ///
400    /// Error message: `{context}.discriminator.mapping maps value `{value}` to branch schema `{schema}`, but the branch declares discriminator value `{actual}`
401    #[error(
402        "{context}.discriminator.mapping maps value `{value}` to branch schema `{schema}`, but the branch declares discriminator value `{actual}`"
403    )]
404    DiscriminatorMappingValueMismatch {
405        context: String,
406        schema: String,
407        value: String,
408        actual: String,
409    },
410
411    /// Multiple discriminator branches resolve to the same discriminator value after implicit defaults are applied.
412    ///
413    /// Error message: `{context}.discriminator resolves multiple branch schemas to value `{value}``
414    #[error("{context}.discriminator resolves multiple branch schemas to value `{value}`")]
415    DuplicateDiscriminatorValue { context: String, value: String },
416
417    // -- Schema constraint validation --
418    /// A string schema specifies a `minLength` greater than its `maxLength`.
419    ///
420    /// Error message: `{context} has minLength {min_length} greater than maxLength {max_length}`
421    #[error("{context} has minLength {min_length} greater than maxLength {max_length}")]
422    InvalidStringLengthBounds {
423        context: String,
424        min_length: u64,
425        max_length: u64,
426    },
427
428    /// A schema uses `uniqueItems`, which cannot be enforced by generated `Vec`-backed types.
429    ///
430    /// Error message: `{context} uses `uniqueItems`; generated Vec-backed types cannot enforce uniqueness yet`
431    #[error(
432        "{context} uses `uniqueItems`; generated Vec-backed types cannot enforce uniqueness yet"
433    )]
434    UniqueItemsUnsupported { context: String },
435
436    /// An array schema specifies `minItems` greater than `maxItems`.
437    ///
438    /// Error message: `{context} has minItems {min_items} greater than maxItems {max_items}`
439    #[error("{context} has minItems {min_items} greater than maxItems {max_items}")]
440    InvalidArrayLengthBounds {
441        context: String,
442        min_items: u64,
443        max_items: u64,
444    },
445
446    /// A schema uses a keyword that is not safely supported.
447    ///
448    /// Error message: `{context} uses `{keyword}`, which is not safely supported yet`
449    #[error("{context} uses `{keyword}`, which is not safely supported yet")]
450    UnsupportedKeyword {
451        context: String,
452        keyword: &'static str,
453    },
454
455    /// A schema keyword that must be a non-negative integer has an invalid value.
456    ///
457    /// Error message: `{context}.{keyword} must be a non-negative integer`
458    #[error("{context}.{keyword} must be a non-negative integer")]
459    InvalidNonNegativeIntegerKeyword {
460        context: String,
461        keyword: &'static str,
462    },
463
464    /// A schema keyword that must be a boolean has an invalid value.
465    ///
466    /// Error message: `{context}.{keyword} must be a boolean`
467    #[error("{context}.{keyword} must be a boolean")]
468    InvalidBooleanKeyword {
469        context: String,
470        keyword: &'static str,
471    },
472
473    /// An `exclusiveMinimum`/`exclusiveMaximum` keyword is present but the corresponding bound is missing.
474    ///
475    /// Error message: `{context}.{exclusive_keyword} requires `{keyword}``
476    #[error("{context}.{exclusive_keyword} requires `{keyword}`")]
477    ExclusiveLimitRequiresBound {
478        context: String,
479        exclusive_keyword: &'static str,
480        keyword: &'static str,
481    },
482
483    /// A schema keyword that must be a finite number has a non-finite value.
484    ///
485    /// Error message: `{context}.{keyword} must be a finite number`
486    #[error("{context}.{keyword} must be a finite number")]
487    InvalidFiniteNumberKeyword {
488        context: String,
489        keyword: &'static str,
490    },
491
492    /// A value expected to be an integer is not.
493    ///
494    /// Error message: `{context} must be an integer`
495    #[error("{context} must be an integer")]
496    ExpectedInteger { context: String },
497
498    /// Integer bounds (minimum/maximum) do not permit any value.
499    ///
500    /// Error message: `{context} integer bounds do not allow any value`
501    #[error("{context} integer bounds do not allow any value")]
502    EmptyIntegerBounds { context: String },
503
504    /// An exclusive integer minimum overflows `i64`.
505    ///
506    /// Error message: `exclusive integer minimum overflows`
507    #[error("exclusive integer minimum overflows")]
508    ExclusiveIntegerMinimumOverflow,
509
510    /// An exclusive integer maximum overflows `i64`.
511    ///
512    /// Error message: `exclusive integer maximum overflows`
513    #[error("exclusive integer maximum overflows")]
514    ExclusiveIntegerMaximumOverflow,
515
516    /// Number bounds (minimum/maximum) do not permit any value.
517    ///
518    /// Error message: `{context} number bounds do not allow any value`
519    #[error("{context} number bounds do not allow any value")]
520    EmptyNumberBounds { context: String },
521
522    // -- Operation, parameter, and response validation --
523    /// An operation does not declare any responses.
524    ///
525    /// Error message: `operation `{operation_id}` must declare responses`
526    #[error("operation `{operation_id}` must declare responses")]
527    MissingOperationResponses { operation_id: String },
528
529    /// A value expected to be an array is not.
530    ///
531    /// Error message: `{context} must be an array`
532    #[error("{context} must be an array")]
533    ExpectedArray { context: String },
534
535    /// A parameter uses an unsupported location (e.g. cookie) instead of path, query, or header.
536    ///
537    /// Error message: `{context} parameter `{wire_name}` is in `{location}`; only path, query, and header parameters are supported`
538    #[error(
539        "{context} parameter `{wire_name}` is in `{location}`; only path, query, and header parameters are supported"
540    )]
541    UnsupportedParameterLocation {
542        context: String,
543        wire_name: String,
544        location: String,
545    },
546
547    /// A parameter uses `content` instead of `schema`.
548    ///
549    /// Error message: `{context} parameter `{wire_name}` uses `content`; schema parameters are required`
550    #[error("{context} parameter `{wire_name}` uses `content`; schema parameters are required")]
551    ContentParameterUnsupported { context: String, wire_name: String },
552
553    /// A parameter is missing a required `schema` declaration.
554    ///
555    /// Error message: `{context} parameter `{wire_name}` must declare schema`
556    #[error("{context} parameter `{wire_name}` must declare schema")]
557    MissingParameterSchema { context: String, wire_name: String },
558
559    /// A parameter is nullable, which is not supported.
560    ///
561    /// Error message: `parameter `{wire_name}` is nullable; nullable parameters are not supported`
562    #[error("parameter `{wire_name}` is nullable; nullable parameters are not supported")]
563    NullableParameterUnsupported { wire_name: String },
564
565    /// A parameter uses `anyOf`, which is not supported for URI/header encoding yet.
566    ///
567    /// Error message: `parameter `{wire_name}` uses `anyOf`; anyOf parameters are not supported yet`
568    #[error("parameter `{wire_name}` uses `anyOf`; anyOf parameters are not supported yet")]
569    AnyOfParameterUnsupported { wire_name: String },
570
571    /// A path parameter is an array, which is not supported.
572    ///
573    /// Error message: `path parameter `{wire_name}` is an array; array path parameter styles are not supported`
574    #[error(
575        "path parameter `{wire_name}` is an array; array path parameter styles are not supported"
576    )]
577    ArrayPathParameterUnsupported { wire_name: String },
578
579    /// A header parameter is an array, which is not supported.
580    ///
581    /// Error message: `header parameter `{wire_name}` is an array; array header parameter styles are not supported`
582    #[error(
583        "header parameter `{wire_name}` is an array; array header parameter styles are not supported"
584    )]
585    ArrayHeaderParameterUnsupported { wire_name: String },
586
587    /// A path parameter does not set `required: true`.
588    ///
589    /// Error message: `path parameter `{wire_name}` must set required: true`
590    #[error("path parameter `{wire_name}` must set required: true")]
591    PathParameterNotRequired { wire_name: String },
592
593    /// A context is missing a required `content` declaration.
594    ///
595    /// Error message: `{context} must declare content`
596    #[error("{context} must declare content")]
597    MissingContent { context: String },
598
599    /// A context is missing the required `application/json` content type.
600    ///
601    /// Error message: `{context} must declare application/json content`
602    #[error("{context} must declare application/json content")]
603    MissingJsonContent { context: String },
604
605    /// A context's `application/json` content is missing a schema.
606    ///
607    /// Error message: `{context} application/json content must declare schema`
608    #[error("{context} application/json content must declare schema")]
609    MissingJsonSchema { context: String },
610
611    /// A response body uses the `default` status, which is not yet supported for decoding.
612    ///
613    /// Error message: `{context} contains a default response body; default response decoding is not supported yet`
614    #[error(
615        "{context} contains a default response body; default response decoding is not supported yet"
616    )]
617    DefaultResponseBodyUnsupported { context: String },
618
619    /// A response contains an invalid HTTP status code string.
620    ///
621    /// Error message: `{context} contains invalid status code `{status}``
622    #[error("{context} contains invalid status code `{status}`")]
623    InvalidStatusCode { context: String, status: String },
624
625    /// A response contains a status code outside the valid 100–599 range.
626    ///
627    /// Error message: `{context} contains out-of-range status code `{status_code}``
628    #[error("{context} contains out-of-range status code `{status_code}`")]
629    OutOfRangeStatusCode { context: String, status_code: u16 },
630
631    /// A response for a given status code is missing `application/json` content.
632    ///
633    /// Error message: `{context} {status} response must declare application/json content`
634    #[error("{context} {status} response must declare application/json content")]
635    MissingResponseJsonContent { context: String, status: String },
636
637    /// A path template contains a parameter that is never closed.
638    ///
639    /// Error message: `path `{path}` contains an unclosed parameter`
640    #[error("path `{path}` contains an unclosed parameter")]
641    UnclosedPathParameter { path: String },
642
643    /// A path template contains an empty parameter (e.g. `{}`).
644    ///
645    /// Error message: `path `{path}` contains an empty parameter`
646    #[error("path `{path}` contains an empty parameter")]
647    EmptyPathParameter { path: String },
648
649    /// A path template references a parameter that is not declared in the operation's parameters.
650    ///
651    /// Error message: `path `{path}` uses parameter `{name}` but it is not declared`
652    #[error("path `{path}` uses parameter `{name}` but it is not declared")]
653    UndeclaredPathParameter { path: String, name: String },
654
655    /// A parameter is declared for a path but never used in the path template.
656    ///
657    /// Error message: `path parameter `{name}` is declared but not used in path `{path}``
658    #[error("path parameter `{name}` is declared but not used in path `{path}`")]
659    UnusedPathParameter { path: String, name: String },
660
661    // -- Reference resolution and JSON shape validation --
662    /// A `$ref` could not be resolved because the referenced component failed validation.
663    ///
664    /// Error message: `failed to resolve reference `{reference}` in {context}: {source}`
665    #[error("failed to resolve reference `{reference}` in {context}: {source}")]
666    ResolveReference {
667        reference: String,
668        context: String,
669        #[source]
670        source: Box<ValidationError>,
671    },
672
673    /// A reference points to an external document; only local (`#`) references are supported.
674    ///
675    /// Error message: `only local references are supported`
676    #[error("only local references are supported")]
677    NonLocalReference,
678
679    /// A local reference is not a valid JSON pointer.
680    ///
681    /// Error message: `local reference must be a JSON pointer`
682    #[error("local reference must be a JSON pointer")]
683    InvalidLocalReference,
684
685    /// A JSON pointer is missing a required token segment.
686    ///
687    /// Error message: `missing `{token}``
688    #[error("missing `{token}`")]
689    MissingJsonPointerToken { token: String },
690
691    /// A `$ref` does not point to the expected `#/components/{section}/…` path.
692    ///
693    /// Error message: `reference `{reference}` must point to #/components/{section}/...`
694    #[error("reference `{reference}` must point to #/components/{section}/...")]
695    InvalidComponentReference {
696        reference: String,
697        section: &'static str,
698    },
699
700    /// A local `$ref` chain references itself.
701    ///
702    /// Error message: `circular reference `{reference}``
703    #[error("circular reference `{reference}`")]
704    CircularReference { reference: String },
705
706    /// A value expected to be an object is not.
707    ///
708    /// Error message: `{context} must be an object`
709    #[error("{context} must be an object")]
710    ExpectedObject { context: String },
711
712    /// A nested field expected to be an object is not.
713    ///
714    /// Error message: `{context}.{field} must be an object`
715    #[error("{context}.{field} must be an object")]
716    ExpectedObjectField {
717        context: String,
718        field: &'static str,
719    },
720}