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