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