Skip to main content

satay_codegen/error/
validation.rs

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