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}