opticdev / optic

OpenAPI linting, diffing and testing. Optic helps prevent breaking changes, publish accurate documentation and improve the design of your APIs.
https://useoptic.com
MIT License
1.35k stars 79 forks source link

An ajv error is falsely reported when validating examples #2631

Closed klaude closed 8 months ago

klaude commented 8 months ago

Hey howdy, Optic folks! 👋

Describe the bug The ajv validator used by Optic counts a schema's id property as the schema $id. When it validates a schema it throws an error if schema ids aren't unique. That's perfect for schema validation, but it's problematic when working in OpenAPI examples which could legitimately have the same id property.

If a de-referenced schema has the same example and the Optic standard includes example verification, then optic diff and optic diff-all fail with the error reference <the id property value in the schema's example> resolves to more than one schema.

I worked around this by disabling example validation in my Optic standard, but I'd like to turn it back on so Optic can continue to report example accuracy.

To Reproduce Given an OpenAPI spec that includes duplicate examples by way of a shared model (see the comments in the YAML snippet for more information):

openapi: 3.0.3
x-optic-url: <snip>
x-optic-standard: <snip> # The standard includes example validation
info:
  title: My no-op API service
  description: It doesn't do anything!
  version: "1.0"
servers:
  - url: http://localhost:3000
tags:
  - name: no-op
    description: The operation is intentionally not executable
paths:
  /models:
    get:
      operationId: GetModels
      summary: Get all common models
      description: A non-existent endpoint that returns model references so linters can validate model schemas
      tags: [no-op]
      responses:
        '200':
          description: OK
      requestBody:
        content:
          application/json:
            schema:
              anyOf:
                - type: object
                  nullable: true
                - type: object
                  properties:
                    link:
                      anyOf:
                        # A Link is oneOf a LinkExternal or a LinkInternal. This creates duplicate examples 
                        # when the spec is de-referenced.
                        - $ref: https://backmarket.github.io/api-models/Link.yaml
                        - type: object
                          nullable: true
                      example: {}
                    linkExternal:
                      anyOf:
                        - $ref: https://backmarket.github.io/api-models/LinkExternal.yaml
                        - type: object
                          nullable: true
                      example: {}
                    linkInternal:
                      anyOf:
                        # Note that this schema has an example whose `id` property is "3". This means
                        # that two examples with an id of "3" exist in the de-referenced spec.
                        - $ref: https://backmarket.github.io/api-models/LinkInternal.yaml
                        - type: object
                          nullable: true
                      example: {}

Run optic diff[-all] on this specification and receive the error reference '3' resolves to more than one schema.

Expected behavior Optic should validate the example matches the schema but allow for example id fields with the same value.

Screenshots n/a

Details (please complete the following information):

Additional context Spectral encountered the same problem and introduced a workaround earlier this year to absorb these errors when they're found in schema examples. See stoplightio/spectral#2081 for discussion on the issue. It looks the same as what I see with Optic.

Thanks for looking, y'all. ❤️ Please let me know what you think.

niclim commented 8 months ago

Hi @klaude - thanks for the detailed issue! I've released a new version that includes this fix (similar to what spectral implemented) in 0.53.19 and I believe that this should fix the issue with example rulesets