Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Alternative OAS3 JSON Schema #1270

Merged
merged 33 commits into from
Apr 18, 2019
Merged

[WIP] Alternative OAS3 JSON Schema #1270

merged 33 commits into from
Apr 18, 2019

Conversation

webron
Copy link
Member

@webron webron commented Jul 14, 2017

This is a proposal for an alternative JSON Schema provided by #1236.

The work done in #1236 is good but:

  • It is too permissive - it allows things that should not validate, for example, a parameter with both content and schema (and a few other cases).
  • It doesn't cover types properly.
  • As far as I understand, it adds a couple of restrictions that are not provided in the spec itself.

The reasons for creating a new PR instead of suggesting fixes to #1236:

  • Editing JSON Schemas can be a pain, and trying to navigate through different styles is not easy.
  • The amount of changes to make it less permissive is quite large.
  • I had about 80% of the schema ready...

Odd things about this version:

  • It's a JSON Schema written in YAML. It's just easier to write and edit and.. read. Easy enough to convert to JSON if this PR is accepted.
  • To make it more restrictive, there are somewhat a lot of duplication in the schema. Using allOf to reuse things didn't work for me due to additionalProperties: false. If anyone wants to take a crack and minimizing it, by all means... I just wanted to get a working version.

What's missing:

  • It doesn't cover checking of style/explode inside Request Bodies where it's multipart. That was just too much for me for now.
  • Testing. I'm willing to bet there are bugs in it.
  • JSON Schema decorations (version, id, $schema and so on).

Pinging @darrelmiller as he asked to be pinged.

Pinging @MikeRalphson for assistance in testing - would really appreciate if you can take a crack at it.

I'm sure converting the YAML to JSON is going to be easy enough.

@fehguy
Copy link
Contributor

fehguy commented Jul 14, 2017

👍

@timburks
Copy link
Contributor

Glad to see effort going into this. The schema that I automatically generated from the spec is easy to reexport as YAML, which I'll append for comparison. The most apparent difference to me is that you've flattened out some intermediate structures.

title: A JSON Schema for OpenAPI 3.0.
id: http://openapis.org/v3/schema.json#
$schema: http://json-schema.org/draft-04/schema#
type: object
description: This is the root document object of the OpenAPI definition.
required:
- openapi
- info
- paths
additionalProperties: false
patternProperties:
  ^x-:
    $ref: '#/definitions/specificationExtension'
properties:
  openapi:
    type: string
  info:
    $ref: '#/definitions/info'
  servers:
    type: array
    items:
      $ref: '#/definitions/server'
    uniqueItems: true
  paths:
    $ref: '#/definitions/paths'
  components:
    $ref: '#/definitions/components'
  security:
    type: array
    items:
      $ref: '#/definitions/securityRequirement'
    uniqueItems: true
  tags:
    type: array
    items:
      $ref: '#/definitions/tag'
    uniqueItems: true
  externalDocs:
    $ref: '#/definitions/externalDocs'
definitions:
  info:
    type: object
    description: The object provides metadata about the API. The metadata can be used
      by the clients if needed, and can be presented in editing or documentation generation
      tools for convenience.
    required:
    - title
    - version
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      title:
        type: string
      description:
        type: string
      termsOfService:
        type: string
      contact:
        $ref: '#/definitions/contact'
      license:
        $ref: '#/definitions/license'
      version:
        type: string
  contact:
    type: object
    description: Contact information for the exposed API.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      name:
        type: string
      url:
        type: string
      email:
        type: string
  license:
    type: object
    description: License information for the exposed API.
    required:
    - name
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      name:
        type: string
      url:
        type: string
  server:
    type: object
    description: An object representing a Server.
    required:
    - url
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      url:
        type: string
      description:
        type: string
      variables:
        $ref: '#/definitions/serverVariables'
  serverVariable:
    type: object
    description: An object representing a Server Variable for server URL template
      substitution.
    required:
    - default
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      enum:
        type: array
        items:
          type: string
        uniqueItems: true
      default:
        type: string
      description:
        type: string
  components:
    type: object
    description: Holds a set of reusable objects for different aspects of the OAS.
      All objects defined within the components object will have no effect on the
      API unless they are explicitly referenced from properties outside the components
      object.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      schemas:
        $ref: '#/definitions/schemasOrReferences'
      responses:
        $ref: '#/definitions/responsesOrReferences'
      parameters:
        $ref: '#/definitions/parametersOrReferences'
      examples:
        $ref: '#/definitions/examplesOrReferences'
      requestBodies:
        $ref: '#/definitions/requestBodiesOrReferences'
      headers:
        $ref: '#/definitions/headersOrReferences'
      securitySchemes:
        $ref: '#/definitions/securitySchemesOrReferences'
      links:
        $ref: '#/definitions/linksOrReferences'
      callbacks:
        $ref: '#/definitions/callbacksOrReferences'
  paths:
    type: object
    description: Holds the relative paths to the individual endpoints and their operations.
      The path is appended to the URL from the `Server Object` in order to construct
      the full URL.  The Paths MAY be empty, due to ACL constraints.
    additionalProperties: false
    patternProperties:
      ^/:
        $ref: '#/definitions/pathItem'
      ^x-:
        $ref: '#/definitions/specificationExtension'
  pathItem:
    type: object
    description: Describes the operations available on a single path. A Path Item
      MAY be empty, due to ACL constraints. The path itself is still exposed to the
      documentation viewer but they will not know which operations and parameters
      are available.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      $ref:
        type: string
      summary:
        type: string
      description:
        type: string
      get:
        $ref: '#/definitions/operation'
      put:
        $ref: '#/definitions/operation'
      post:
        $ref: '#/definitions/operation'
      delete:
        $ref: '#/definitions/operation'
      options:
        $ref: '#/definitions/operation'
      head:
        $ref: '#/definitions/operation'
      patch:
        $ref: '#/definitions/operation'
      trace:
        $ref: '#/definitions/operation'
      servers:
        type: array
        items:
          $ref: '#/definitions/server'
        uniqueItems: true
      parameters:
        type: array
        items:
          $ref: '#/definitions/parameterOrReference'
        uniqueItems: true
  operation:
    type: object
    description: Describes a single API operation on a path.
    required:
    - responses
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      tags:
        type: array
        items:
          type: string
        uniqueItems: true
      summary:
        type: string
      description:
        type: string
      externalDocs:
        $ref: '#/definitions/externalDocs'
      operationId:
        type: string
      parameters:
        type: array
        items:
          $ref: '#/definitions/parameterOrReference'
        uniqueItems: true
      requestBody:
        $ref: '#/definitions/requestBodyOrReference'
      responses:
        $ref: '#/definitions/responses'
      callbacks:
        $ref: '#/definitions/callbacksOrReferences'
      deprecated:
        type: boolean
      security:
        type: array
        items:
          $ref: '#/definitions/securityRequirement'
        uniqueItems: true
      servers:
        type: array
        items:
          $ref: '#/definitions/server'
        uniqueItems: true
  externalDocs:
    type: object
    description: Allows referencing an external resource for extended documentation.
    required:
    - url
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      description:
        type: string
      url:
        type: string
  parameter:
    type: object
    description: Describes a single operation parameter.  A unique parameter is defined
      by a combination of a name and location.
    required:
    - name
    - in
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      name:
        type: string
      in:
        type: string
      description:
        type: string
      required:
        type: boolean
      deprecated:
        type: boolean
      allowEmptyValue:
        type: boolean
      style:
        type: string
      explode:
        type: boolean
      allowReserved:
        type: boolean
      schema:
        $ref: '#/definitions/schemaOrReference'
      example:
        $ref: '#/definitions/any'
      examples:
        $ref: '#/definitions/examplesOrReferences'
      content:
        $ref: '#/definitions/mediaTypes'
  requestBody:
    type: object
    description: Describes a single request body.
    required:
    - content
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      description:
        type: string
      content:
        $ref: '#/definitions/mediaTypes'
      required:
        type: boolean
  mediaType:
    type: object
    description: Each Media Type Object provides schema and examples for the media
      type identified by its key.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      schema:
        $ref: '#/definitions/schemaOrReference'
      example:
        $ref: '#/definitions/any'
      examples:
        $ref: '#/definitions/examplesOrReferences'
      encoding:
        $ref: '#/definitions/encodings'
  encoding:
    type: object
    description: A single encoding definition applied to a single schema property.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      contentType:
        type: string
      headers:
        $ref: '#/definitions/headers'
      style:
        type: string
      explode:
        type: boolean
      allowReserved:
        type: boolean
  responses:
    type: object
    description: A container for the expected responses of an operation. The container
      maps a HTTP response code to the expected response.  The documentation is not
      necessarily expected to cover all possible HTTP response codes because they
      may not be known in advance. However, documentation is expected to cover a successful
      operation response and any known errors.  The `default` MAY be used as a default
      response object for all HTTP codes  that are not covered individually by the
      specification.  The `Responses Object` MUST contain at least one response code,
      and it  SHOULD be the response for a successful operation call.
    additionalProperties: false
    patternProperties:
      ^([0-9X]{3})$:
        $ref: '#/definitions/responseOrReference'
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      default:
        $ref: '#/definitions/responseOrReference'
  response:
    type: object
    description: Describes a single response from an API Operation, including design-time,
      static  `links` to operations based on the response.
    required:
    - description
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      description:
        type: string
      headers:
        $ref: '#/definitions/headersOrReferences'
      content:
        $ref: '#/definitions/mediaTypes'
      links:
        $ref: '#/definitions/linksOrReferences'
  callback:
    type: object
    description: A map of possible out-of band callbacks related to the parent operation.
      Each value in the map is a Path Item Object that describes a set of requests
      that may be initiated by the API provider and the expected responses. The key
      value used to identify the callback object is an expression, evaluated at runtime,
      that identifies a URL to use for the callback operation.
    additionalProperties: false
    patternProperties:
      ^:
        $ref: '#/definitions/pathItem'
      ^x-:
        $ref: '#/definitions/specificationExtension'
  example:
    type: object
    description: ""
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      summary:
        type: string
      description:
        type: string
      value:
        $ref: '#/definitions/any'
      externalValue:
        type: string
  link:
    type: object
    description: The `Link object` represents a possible design-time link for a response.
      The presence of a link does not guarantee the caller's ability to successfully
      invoke it, rather it provides a known relationship and traversal mechanism between
      responses and other operations.  Unlike _dynamic_ links (i.e. links provided
      **in** the response payload), the OAS linking mechanism does not require link
      information in the runtime response.  For computing links, and providing instructions
      to execute them, a runtime expression is used for accessing values in an operation
      and using them as parameters while invoking the linked operation.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      operationRef:
        type: string
      operationId:
        type: string
      parameters:
        $ref: '#/definitions/anysOrExpressions'
      requestBody:
        $ref: '#/definitions/anyOrExpression'
      description:
        type: string
      server:
        $ref: '#/definitions/server'
  header:
    type: object
    description: 'The Header Object follows the structure of the Parameter Object
      with the following changes:  1. `name` MUST NOT be specified, it is given in
      the corresponding `headers` map. 1. `in` MUST NOT be specified, it is implicitly
      in `header`. 1. All traits that are affected by the location MUST be applicable
      to a location of `header` (for example, `style`).'
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      description:
        type: string
      required:
        type: boolean
      deprecated:
        type: boolean
      allowEmptyValue:
        type: boolean
      style:
        type: string
      explode:
        type: boolean
      allowReserved:
        type: boolean
      schema:
        $ref: '#/definitions/schemaOrReference'
      example:
        $ref: '#/definitions/any'
      examples:
        $ref: '#/definitions/examplesOrReferences'
      content:
        $ref: '#/definitions/mediaTypes'
  tag:
    type: object
    description: Adds metadata to a single tag that is used by the Operation Object.
      It is not mandatory to have a Tag Object per tag defined in the Operation Object
      instances.
    required:
    - name
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      name:
        type: string
      description:
        type: string
      externalDocs:
        $ref: '#/definitions/externalDocs'
  examples:
    type: object
    description: ""
    additionalProperties: false
  reference:
    type: object
    description: A simple object to allow referencing other components in the specification,
      internally and externally.  The Reference Object is defined by JSON Reference
      and follows the same structure, behavior and rules.   For this specification,
      reference resolution is accomplished as defined by the JSON Reference specification
      and not by the JSON Schema specification.
    required:
    - $ref
    additionalProperties: false
    properties:
      $ref:
        type: string
  schema:
    type: object
    description: The Schema Object allows the definition of input and output data
      types. These types can be objects, but also primitives and arrays. This object
      is an extended subset of the JSON Schema Specification Wright Draft 00.  For
      more information about the properties, see JSON Schema Core and JSON Schema
      Validation. Unless stated otherwise, the property definitions follow the JSON
      Schema.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      nullable:
        type: boolean
      discriminator:
        $ref: '#/definitions/discriminator'
      readOnly:
        type: boolean
      writeOnly:
        type: boolean
      xml:
        $ref: '#/definitions/xml'
      externalDocs:
        $ref: '#/definitions/externalDocs'
      example:
        $ref: '#/definitions/any'
      deprecated:
        type: boolean
      title:
        $ref: http://json-schema.org/draft-04/schema#/properties/title
      multipleOf:
        $ref: http://json-schema.org/draft-04/schema#/properties/multipleOf
      maximum:
        $ref: http://json-schema.org/draft-04/schema#/properties/maximum
      exclusiveMaximum:
        $ref: http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum
      minimum:
        $ref: http://json-schema.org/draft-04/schema#/properties/minimum
      exclusiveMinimum:
        $ref: http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum
      maxLength:
        $ref: http://json-schema.org/draft-04/schema#/properties/maxLength
      minLength:
        $ref: http://json-schema.org/draft-04/schema#/properties/minLength
      pattern:
        $ref: http://json-schema.org/draft-04/schema#/properties/pattern
      maxItems:
        $ref: http://json-schema.org/draft-04/schema#/properties/maxItems
      minItems:
        $ref: http://json-schema.org/draft-04/schema#/properties/minItems
      uniqueItems:
        $ref: http://json-schema.org/draft-04/schema#/properties/uniqueItems
      maxProperties:
        $ref: http://json-schema.org/draft-04/schema#/properties/maxProperties
      minProperties:
        $ref: http://json-schema.org/draft-04/schema#/properties/minProperties
      required:
        $ref: http://json-schema.org/draft-04/schema#/properties/required
      enum:
        $ref: http://json-schema.org/draft-04/schema#/properties/enum
      type:
        type: string
      allOf:
        type: array
        items:
          $ref: '#/definitions/schemaOrReference'
        minItems: 1
      oneOf:
        type: array
        items:
          $ref: '#/definitions/schemaOrReference'
        minItems: 1
      anyOf:
        type: array
        items:
          $ref: '#/definitions/schemaOrReference'
        minItems: 1
      not:
        $ref: '#/definitions/schema'
      items:
        anyOf:
        - $ref: '#/definitions/schemaOrReference'
        - type: array
          items:
            $ref: '#/definitions/schemaOrReference'
          minItems: 1
      properties:
        type: object
        additionalProperties:
          $ref: '#/definitions/schemaOrReference'
      additionalProperties:
        oneOf:
        - $ref: '#/definitions/schemaOrReference'
        - type: boolean
      default:
        $ref: '#/definitions/defaultType'
      description:
        type: string
      format:
        type: string
  discriminator:
    type: object
    description: When request bodies or response payloads may be one of a number of
      different schemas, a `discriminator` object can be used to aid in serialization,
      deserialization, and validation.  The discriminator is a specific object in
      a schema which is used to inform the consumer of the specification of an alternative
      schema based on the value associated with it.  When using the discriminator,
      _inline_ schemas will not be considered.
    required:
    - propertyName
    additionalProperties: false
    properties:
      propertyName:
        type: string
      mapping:
        $ref: '#/definitions/strings'
  xml:
    type: object
    description: A metadata object that allows for more fine-tuned XML model definitions.  When
      using arrays, XML element names are *not* inferred (for singular/plural forms)
      and the `name` property SHOULD be used to add that information. See examples
      for expected behavior.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      name:
        type: string
      namespace:
        type: string
      prefix:
        type: string
      attribute:
        type: boolean
      wrapped:
        type: boolean
  securityScheme:
    type: object
    description: Defines a security scheme that can be used by the operations. Supported
      schemes are HTTP authentication, an API key (either as a header or as a query
      parameter), OAuth2's common flows (implicit, password, application and access
      code) as defined in RFC6749, and OpenID Connect Discovery.
    required:
    - type
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      type:
        type: string
      description:
        type: string
      name:
        type: string
      in:
        type: string
      scheme:
        type: string
      bearerFormat:
        type: string
      flows:
        $ref: '#/definitions/oauthFlows'
      openIdConnectUrl:
        type: string
  oauthFlows:
    type: object
    description: Allows configuration of the supported OAuth Flows.
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      implicit:
        $ref: '#/definitions/oauthFlow'
      password:
        $ref: '#/definitions/oauthFlow'
      clientCredentials:
        $ref: '#/definitions/oauthFlow'
      authorizationCode:
        $ref: '#/definitions/oauthFlow'
  oauthFlow:
    type: object
    description: Configuration details for a supported OAuth Flow
    additionalProperties: false
    patternProperties:
      ^x-:
        $ref: '#/definitions/specificationExtension'
    properties:
      authorizationUrl:
        type: string
      tokenUrl:
        type: string
      refreshUrl:
        type: string
      scopes:
        $ref: '#/definitions/strings'
  securityRequirement:
    type: object
    description: Lists the required security schemes to execute this operation. The
      name used for each property MUST correspond to a security scheme declared in
      the Security Schemes under the Components Object.  Security Requirement Objects
      that contain multiple schemes require that all schemes MUST be satisfied for
      a request to be authorized. This enables support for scenarios where multiple
      query parameters or HTTP headers are required to convey security information.  When
      a list of Security Requirement Objects is defined on the Open API object or
      Operation Object, only one of Security Requirement Objects in the list needs
      to be satisfied to authorize the request.
    additionalProperties: false
    patternProperties:
      ^[a-zA-Z0-9\.\-_]+$:
        type: array
        items:
          type: string
        uniqueItems: true
  anyOrExpression:
    oneOf:
    - $ref: '#/definitions/any'
    - $ref: '#/definitions/expression'
  callbackOrReference:
    oneOf:
    - $ref: '#/definitions/callback'
    - $ref: '#/definitions/reference'
  exampleOrReference:
    oneOf:
    - $ref: '#/definitions/example'
    - $ref: '#/definitions/reference'
  headerOrReference:
    oneOf:
    - $ref: '#/definitions/header'
    - $ref: '#/definitions/reference'
  linkOrReference:
    oneOf:
    - $ref: '#/definitions/link'
    - $ref: '#/definitions/reference'
  parameterOrReference:
    oneOf:
    - $ref: '#/definitions/parameter'
    - $ref: '#/definitions/reference'
  requestBodyOrReference:
    oneOf:
    - $ref: '#/definitions/requestBody'
    - $ref: '#/definitions/reference'
  responseOrReference:
    oneOf:
    - $ref: '#/definitions/response'
    - $ref: '#/definitions/reference'
  schemaOrReference:
    oneOf:
    - $ref: '#/definitions/schema'
    - $ref: '#/definitions/reference'
  securitySchemeOrReference:
    oneOf:
    - $ref: '#/definitions/securityScheme'
    - $ref: '#/definitions/reference'
  anysOrExpressions:
    type: object
    additionalProperties:
      $ref: '#/definitions/anyOrExpression'
  callbacksOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/callbackOrReference'
  encodings:
    type: object
    additionalProperties:
      $ref: '#/definitions/encoding'
  examplesOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/exampleOrReference'
  headers:
    type: object
    additionalProperties:
      $ref: '#/definitions/header'
  headersOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/headerOrReference'
  linksOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/linkOrReference'
  mediaTypes:
    type: object
    additionalProperties:
      $ref: '#/definitions/mediaType'
  parametersOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/parameterOrReference'
  requestBodiesOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/requestBodyOrReference'
  responsesOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/responseOrReference'
  schemasOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/schemaOrReference'
  securitySchemesOrReferences:
    type: object
    additionalProperties:
      $ref: '#/definitions/securitySchemeOrReference'
  serverVariables:
    type: object
    additionalProperties:
      $ref: '#/definitions/serverVariable'
  strings:
    type: object
    additionalProperties:
      type: string
  object:
    type: object
    additionalProperties: true
  any:
    additionalProperties: true
  expression:
    type: object
    additionalProperties: true
  specificationExtension:
    description: Any property starting with x- is valid.
    oneOf:
    - type: "null"
    - type: number
    - type: boolean
    - type: string
    - type: object
    - type: array
  defaultType:
    oneOf:
    - type: "null"
    - type: array
    - type: object
    - type: number
    - type: boolean
    - type: string

Copy link
Member

@MikeRalphson MikeRalphson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the changes implied by these comments I was able to validate a converted v2 petstore definition. It is possible by making change X I have weakened the schema, thus missing problem Y, so I would appreciate your thoughts @webron on each individually.

openapi:
type: string
enum:
- 3.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced this with a regex pattern to allow testing of 3.0.0-rc2 compatible documents. This would keep schema churn to a minimum.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this should actually change to a pattern that matches anything starting with 3.0 as according to semver, any patch level changes should not affect validation. That said, I'm embarrassed to say I've avoided learning regex as much as I can 😁

type: string
type:
type: string
enum:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation of enum:. Yay, yaml FTW! :)


Responses:
allOf:
- $ref: '#/definitions/Extensions'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'Extensions' defined anywhere? Should the following be part of the allOf array?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was from a previous life of the schema, and I just forgot to remove it. Will do.

Reference:
type: object
properties:
$ref:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should $ref be a required property? Otherwise I get false matches of both halves of a oneOf.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will fix.

type: object
patternProperties:
'^[a-zA-Z0-9\.\-_]+$':
oneOf:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both Schema and Reference include a $ref property and Reference allows additionalProperties. Either change to anyOf or disallow additionalProperties in Reference objects - which would be a change to the OAS? Multiple examples.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an excellent question. I've revisited the spec. I'll either make it anyOf or remove the $ref from the Schema definition and make sure it's always interchangeable with the Reference in the JSON Schema.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MikeRalphson - not that I recall, but it seems that right now, there's no $ref property in Schema and it stayed oneOf wherever a Schema is called. I think that should be fine. Please let me know what you're thinking - if it's fine, I'll create a definition for SchemaOrReference and use that across the board instead of repeating the oneOf everywhere.

not:
type: object
patternProperties:
'^x-': {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this requires an additionalProperties: false otherwise responses do match this sub-schema.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@webron @MikeRalphson why is this not: block here? Filtering out other stuff, you have

  Responses:
    type: object
    patternProperties:
      '^x-': {}
    additionalProperties: false
    not:
      type: object
      patternProperties:
        '^x-': {}
      additionalProperties: false

which is an impossible schema. {"type": "object", "not": {"type": "object"}} alone makes it impossible. Any keywords that are the same inside and outside of a not by definition are impossible to satisfy.

Am I misreading something here?

'^x-': {}
additionalProperties: false

ParameterWithSchemaWithExamples:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to be having problems with these - as neither example or examples is required in ParameterWithSchemaWithExampleInQuery or ParameterWithSchemaWithExamplesInQuery and there is no ParameterWithSchemaWithoutExampleInQuery type, how is the correct type to match the oneOf to be determined?

I temporarily changed examples to be a required property in ParameterWithSchemaWithExamplesIn*.


Schema:
type: object
properties:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the Schema object missing the items property?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, will add.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

oneOf:
- $ref: '#/definitions/Schema'
- $ref: '#/definitions/Reference'
examples:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I had to make examples a required property to make the oneOf detection work correctly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I made this edit after another comment, but it appears earlier in the schema. I believe this is MediaTypeWithExample vs MediaTypeWithExamples. A media-type object with neither example or examples properties matches both definitions. Suggest making examples a required property of MediaTypeWithExamples.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MikeRalphson

You can also make things mutually exclusive by forbidding the other property rather than requiring the property that you have:

oneOf:
  - properties:
        example:
            properties:
                ...
        examples:
            not: {}
  - properties:
        example:
            not: {}
        examples:
            patternProperties:
                ...

attribute:
type: boolean
default: false
wrapperd:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo wrapperd for wrapped.

- number
- object
- string
not:
Copy link
Member

@MikeRalphson MikeRalphson Jul 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we also missing definitions for the allOf, oneOf and anyOf arrays?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added.

default: {}
$ref:
type: string
format: uri-ref
Copy link
Member

@MikeRalphson MikeRalphson Jul 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this uriref in Draft 05? Multiple occurences.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a miss on my part. I actually looked it up before, and went with the wrong one 😕

- $ref: '#/definitions/Schema'
- $ref: '#/definitions/Reference'
examples:
type: array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct, or is it now a Map as elsewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires a complete rework.

description:
type: string
value:
type: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example.value should be an empty schema?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, will fix.

@MikeRalphson
Copy link
Member

MikeRalphson commented Jul 14, 2017

@webron with the changes implied by the above comments, I've now successfully validated 574 converted OpenAPI v2 definitions - with only one minor bug found in my converter (thank you).

Does

JSON Schema decorations

include descriptions ? It would be great to port those across to aid readability. Also required in future iteration: readme.

type: string
namespace:
type: string
format: url
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this format be uri-ref / uriref not url ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We changed the namespace definition to be an absolute URI.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @webron, as far as I can tell, url isn't a format that's in the official specification. It sounds like you're implying here that it's like a uri, but required to be absolute?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uri is the correct format for an absolute URI.
uriref (which became uri-reference in the next draft so that is really preferred) is for relative URI references.

It would be really great to avoid url as having uri and url is horribly confusing, particularly as RFC 3986 goes out of its way to explain that there is no hard line between url and urn (e.g. you cannot make the distinction based on the scheme)

Copy link
Member

@handrews handrews Nov 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@webron @MikeRalphson I'm planning to change this to uri-reference, even if we are saying that this schema is a draft-wright-*-00 (a.k.a. "draft-05") schema, and that draft used uriref. I'm doing this because:

  1. Literally no one has ever implemented a validator for draft-wright-*-00. Every implementation that I'm aware of skipped from draft-04 (draft-zyp-json-schema-04 + draft-fge-json-schema-validation-00) to draft-06 (draft-wright-*-01).
  2. There is no official meta-schema for draft-05, and there never will be, so there is no programmatic way to inform validators that you are using draft-05
  3. Format is extensible, so using uri-reference as a format is entirely valid even in draft-05

Any objections?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we could also publish a draft-06 version where uri-reference would be recognized, but format validation in general is such a hazy weird optional thing that, really, no one should rely on it unless they are using a specific validator and know it's exact behavior. e.g. some validators try to use a complex regex to validate the email format, but at least one popular validator just checks to see if there's an "@" character in it somewhere and doesn't bother with anything else.

@webron
Copy link
Member Author

webron commented Jul 18, 2017

@MikeRalphson No, I wasn't referring to adding the descriptions as part of the decorations. While important, those are nice to have, and I don't necessarily have the capacity to add those right now all around the schema. If we decide to merge this schema (with it's JSON representation, of course), it'd be great if people helped contributing descriptions.

Right now I'm more focused on the functionality.

@webron
Copy link
Member Author

webron commented Jul 18, 2017

@MikeRalphson Updated with your comments a possible a few additional things I've found. Would appreciate a second look.

- $ref: '#/definitions/Schema'
- $ref: '#/definitions/Reference'
items:
type: array
Copy link
Contributor

@tfesenko tfesenko Jul 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why array's item type is array and not an object(schema or reference). From the OAS3 spec:

items - Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. <...>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's me being too tired. Good catch, will fix shortly.

type: object
required:
- type
- openIdConnect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this should be openIdConnectUrl.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

properties:
url:
type: string
format: uriref
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a uri-template format, but this doesn't exist in JSON schema Draft 5. If we replace format with pattern a suitable regex seems to be (lifted from ajv which quotes https://tools.ietf.org/html/rfc6570)

/^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#.\/;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?)*\})*$/i

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not get into that. I'm not sure what the difference is between uriref and uri-template. If it's too permissive, in this case, I'm ok with it. If it's too constrict, we can drop the format altogether.

I find adding complex regex impossible to read and maintain. We could have added a regex to define media types as well and we didn't (we had a similar discussion about it around 2.0's schema).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I'm seeing that it's too restrictive being a uriref, not allowing {} characters. Dropping the format seems the best option.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will remove.

$ref: '#/definitions/PathItem'
patternProperties:
'^x-': {}
additionalProperties: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additionalProperties: false should be removed, as it overrides the other additionalProperties property when converted to JSON. This is the only such occurrence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@webron
Copy link
Member Author

webron commented Jul 18, 2017

@MikeRalphson thanks for the review - any other comments or thoughts?

callbacks:
type: object
additionalProperties:
$ref: '#/definitions/Callback'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not it be Callback or Reference:

callbacks | Map[string, Callback Object | Reference Object]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.

@MikeRalphson
Copy link
Member

@webron I'm seeing a rate of about 0.35% errors over 35,000 converted 'valid' OpenAPI 2.0 documents. All the failures appear to be correctly identified - i.e. things which have changed in the spec that the converter can't process automatically or that the 2.0 validators missed. This schema certainly validates more of the spec than the @timburks / #1236 schema, but I do think you may have made a rod for your own back by multiplying out the combinations of (for example) parameters in X with/without Y with/without a Z.

What I'm trying to say is that maintenance of this schema is going to be somewhat harder going forward (depending on what changes come along in 3.1 and subsequent minor versions) as it has more redundancy and a slightly different mental model to grok rather than modelling only those objects that the spec defines. When (if?) we have a test-suite and a reference parser/validator, some of these issues go away.

The schema seems to be performant even on large documents, though the use of oneOf means more verbose error messages, at least from the schema validator I'm using.

That said... add the id and schema properties and :shipit:

@webron
Copy link
Member Author

webron commented Jul 18, 2017

@MikeRalphson Thanks for the feedback.

Trust me, I don't like the breakdown as it is now, and I agree it's harder to maintain. The issue is derived from the way JSON Schema combines allOf with additionalProperties: false. If I had a solution to that, it'd make things much much easier. I only need one example of making it work and I can convert the rest.

- default
properties:
enum:
type: string
Copy link
Member

@MikeRalphson MikeRalphson Jul 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serverVariables.enum should be an array of string. @webron

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, should be fixed now.

@hkosova
Copy link
Contributor

hkosova commented Aug 2, 2017

@webron In this schema, ParameterWithContentNotInPath object seems to be missing the allowReserved property. Is this a bug or by design (e.g. allowReserved is incompatible with content)?

@webron
Copy link
Member Author

webron commented Aug 3, 2017

@hkosova it's by design. A parameter can have either content or a schema, and style, explode and so on are only applicable with schema.

@hkosova
Copy link
Contributor

hkosova commented Aug 8, 2017

@webron openIdConnectUrl has format: url - shouldn't it be uriref (relative URL allowed) like tokenUrl etc?

@MikeRalphson
Copy link
Member

Thanks for clarifying @handrews - I obviously misremembered the situation.

webron and others added 2 commits March 26, 2019 12:40
Reduce duplications and complexity in v3.0 JSON Schema
Added `id`, `$schema`, `description`
@webron
Copy link
Member Author

webron commented Apr 2, 2019

@MikeRalphson, @handrews - I've updated the schema with the missing metadata (id, $schema, description). Can you please run final validation and tests on it?

If you give the thumbs up, and we get an official @OAI/tsc vote, I'll add a conversion of the schema to JSON, and merge it (at last).

Copy link
Member

@handrews handrews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newly added metadata looks good, @webron

@philsturgeon
Copy link
Contributor

🚢 🚢 🚢 🚢 🚢 🚢 🚢 🚢

Copy link
Member

@MikeRalphson MikeRalphson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. No regressions found.

@webron
Copy link
Member Author

webron commented Apr 18, 2019

Added the converted JSON file and a README with some relevant details.

Thanks everyone for making it happen.

@webron webron merged commit ce93fe4 into OpenAPI.next Apr 18, 2019
@philsturgeon
Copy link
Contributor

giphy

Great work everyone, this is awesome.

@cebe
Copy link
Contributor

cebe commented Apr 18, 2019

this has been merged into OpenAPI.next, what is the process or what are the plans of getting it into master?

@webron
Copy link
Member Author

webron commented Apr 18, 2019

@cebe sigh just shows how old this PR was. I'll move the content to master as well. Thanks for catching it.

@webron webron mentioned this pull request Apr 18, 2019
shockey added a commit to swagger-api/swagger-editor that referenced this pull request Apr 18, 2019
* adopt @webron's OpenAPI 3.0 schema from OAI/OpenAPI-Specification#1270

permalink: https://github.com/OAI/OpenAPI-Specification/blob/92e15eba1d4591ebfe8c11898c48241e72854381/schemas/v3.0/schema.yaml

* add ajv-errors

* address error messages for #1808's Swagger 2.0 example

clarifies the schema and adds custom error messages for unclear error conditions

* address error messages for #1808's OpenAPI 3.0 example

* restrict underlying JSON Schema `type` field to simple types only (for #1832)

* fix limitation in JSON Pointer conversion helper

* add clear `not` error message (for #1489)

* add additionalProperties message (for #1394)

* add ajv-keywords

* use `switch` to intelligently identify inline vs referenced content (for #1853)

* use `switch` to XOR `schema` and `content` (for #1853)

* use `switch` to pivot security scheme based on type

(for #1672)

* use switch to fall-through to inline security scheme validation (for #1672)

* rewrite more Reference oneOfs (for #1519)

* add custom message for `Schema.required` type error (for #1519)

* rewrite Response/Reference oneOf (for #1489)

* use switch in ParameterLocation validation (for #1797)

* define pivot key switches for SecurityDefinitions (for #1711)

* give helpful `format: uri` messages for SecurityDefinitions (for #1711)

* eliminate NonBodyParameter; pivot on `Parameter.in` with a switch (for #1511)

* oneOf -> switch for Parameters.items reference

* (for #1711)

* remove redundant semantic validator (for #1511)

* adjust wording of custom error message (for #1853)

* add regression tests for all related issues

* revert to expect@^1.20.2

* linter fixes

* fix messaging flaw for #1832

* improve messaging for #1394

* use literal key for `$ref` in Reference Object

* remove commented legacy data from OAS3 schema

* remove superfluous quotation marks

* normalize test case paths to `/`

* normalize openapi fields to 3.0.0

* drop unused `paths` information

* ensure clear errors for 3.0 Parameter style/content exclusivity

* add `required` assertions to switch statements that pivot on a key's value

this prevents false positives when the pivot key is missing entirely

* remove stray space
@cebe
Copy link
Contributor

cebe commented Apr 19, 2019

hehe, I thought this was part of your workflow :)

shockey added a commit to shockey/swagger-editor that referenced this pull request May 23, 2019
shockey added a commit to shockey/swagger-editor that referenced this pull request May 23, 2019
…i#1985)

* adopt @webron's OpenAPI 3.0 schema from OAI/OpenAPI-Specification#1270

permalink: https://github.com/OAI/OpenAPI-Specification/blob/92e15eba1d4591ebfe8c11898c48241e72854381/schemas/v3.0/schema.yaml

* add ajv-errors

* address error messages for swagger-api#1808's Swagger 2.0 example

clarifies the schema and adds custom error messages for unclear error conditions

* address error messages for swagger-api#1808's OpenAPI 3.0 example

* restrict underlying JSON Schema `type` field to simple types only (for swagger-api#1832)

* fix limitation in JSON Pointer conversion helper

* add clear `not` error message (for swagger-api#1489)

* add additionalProperties message (for swagger-api#1394)

* add ajv-keywords

* use `switch` to intelligently identify inline vs referenced content (for swagger-api#1853)

* use `switch` to XOR `schema` and `content` (for swagger-api#1853)

* use `switch` to pivot security scheme based on type

(for swagger-api#1672)

* use switch to fall-through to inline security scheme validation (for swagger-api#1672)

* rewrite more Reference oneOfs (for swagger-api#1519)

* add custom message for `Schema.required` type error (for swagger-api#1519)

* rewrite Response/Reference oneOf (for swagger-api#1489)

* use switch in ParameterLocation validation (for swagger-api#1797)

* define pivot key switches for SecurityDefinitions (for swagger-api#1711)

* give helpful `format: uri` messages for SecurityDefinitions (for swagger-api#1711)

* eliminate NonBodyParameter; pivot on `Parameter.in` with a switch (for swagger-api#1511)

* oneOf -> switch for Parameters.items reference

* (for swagger-api#1711)

* remove redundant semantic validator (for swagger-api#1511)

* adjust wording of custom error message (for swagger-api#1853)

* add regression tests for all related issues

* revert to expect@^1.20.2

* linter fixes

* fix messaging flaw for swagger-api#1832

* improve messaging for swagger-api#1394

* use literal key for `$ref` in Reference Object

* remove commented legacy data from OAS3 schema

* remove superfluous quotation marks

* normalize test case paths to `/`

* normalize openapi fields to 3.0.0

* drop unused `paths` information

* ensure clear errors for 3.0 Parameter style/content exclusivity

* add `required` assertions to switch statements that pivot on a key's value

this prevents false positives when the pivot key is missing entirely

* remove stray space
@webron webron deleted the oas3-schema branch April 23, 2020 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet