GithubHelp home page GithubHelp logo

typeable / comparest Goto Github PK

View Code? Open in Web Editor NEW
24.0 3.0 2.0 848 KB

Compatibility checker for OpenAPI

Home Page: https://hackage.haskell.org/package/compaREST

License: MIT License

Haskell 90.91% Nix 8.89% Shell 0.21%
api openapi openapi3 openapi-specification openapi-spec

comparest's Introduction

compaREST

Hackage MIT license

Compatibility checker for OpenAPI

Using compaREST in Github Actions

Add the following step to your Github Actions workflow:

- uses: typeable/comparest
    if: ${{ github.event_name == 'pull_request' }}
    with:
      old: old-openapi.yaml
      new: new-openapi.yaml

The new and old values should be paths to your OpenAPI specifications you want to compare.

You will get something like this in your pull requests:

For more detail please see our integration guide.

An example

Your situation

You are developing a very important server with a REST API. You have clients who use your API that you do not control. Say, you are also developing a mobile app that uses your API and you can't force someone to update to the latest version. (Or you prefer not to for UX reasons.)

You have recently released version 1.0 and things are going great: user are downloading your app, servers are processing requests.

You describe your API in a file api-1.0.0.yaml:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: https://example.com
paths:
  /pets:
    get:
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 20
      responses:
        "200":
          description: ""
          headers:
            x-next:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "201":
          description: ""
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
        name:
          type: string
          minLength: 3
          maxLength: 10
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"

Evolving your product

Enthused over your initial success you hurry to release a new and improved version of your API and mobile app.

After a round of very intense programming you take a look at your new api-1.1.0.yaml:

openapi: "3.0.0"
info:
  version: 1.1.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: https://example.com
paths:
  /pets:
    get:
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 30
      responses:
        "200":
          description: ""
          headers:
            x-next:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "201":
          description: ""
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
        name:
          type: string
          minLength: 1
          maxLength: 15
        weight:
          type: integer
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"

Looking at the very large and complex API description, you grow more and more concerned that your old mobile app might stop working when you update the server. But the spec is too large and too complex to reasonably assess this manually.

Assessing compatibility automatically

Luckily, you have access to compaREST which can programmatically analyze your APIs and determine what, if anything, breaks compatibility and what doesn't.

You can call it, passing the API your client will be aware of, and the API your server will serve like so:

docker run --rm -v $(pwd):/data:rw typeable/comparest --client /data/api-1.0.0.yaml --server /data/api-1.1.0.yaml --output /data/report.md

Running this command will output a file report.md, containing the compatibility report between the two APIs:

Summary

❌ Breaking changes ⚠️ Non-breaking changes
5 6

❌ Breaking changes

GET /pets

⬅️☁️ JSON Response – 200

$[*].name(String)

  1. Maximum length of the string changed from 10 to 15.

  2. Minimum length of the string changed from 3 to 1.

POST /pets

➡️☁️ JSON Request

$.weight

  1. Values are now limited to the following types:

    • Number
  2. The property was previously implicitly described by the catch-all "additional properties" case. It is now explicitly defined.

$.weight(Number)

Value is now a multiple of 1.0.

⚠️ Non-breaking changes

GET /pets

Parameter limit

JSON Schema

$(Number)

Upper bound changed from 20.0 inclusive to 30.0 inclusive.

⬅️☁️ JSON Response – 200

$[*].weight

  1. Values are now limited to the following types:

    • Number
  2. The property was previously implicitly described by the catch-all "additional properties" case. It is now explicitly defined.

$[*].weight(Number)

Value is now a multiple of 1.0.

POST /pets

➡️☁️ JSON Request

$.name(String)

  1. Maximum length of the string changed from 10 to 15.

  2. Minimum length of the string changed from 3 to 1.

You now know exactly in what situations and in what way your 1.0 version of the app will break if you deploy your 1.1 version of the server.

Additional formats

You can also produce a self-contained HTML report that you can open in your browser by simply omitting the file extension of the output file:

docker run --rm -v $(pwd):/data:rw typeable/comparest --client /data/api-1.0.0.yaml --server /data/api-1.1.0.yaml --output /data/report

CLI docs

For more detail please see our user guide.

Usage: comparest (-c|--client ARG) (-s|--server ARG)
                 [--silent | --only-breaking | --all] [-o|--output ARG]
                 [--folding-block-quotes-style | --header-style]
                 [--signal-exit-code]
  A tool to check compatibility between two OpenAPI specifications.

  Usage examples

      Compare files old.yaml with new.yaml and output the resulting report to
      stdout:

          comparest -c old.yaml -s new.yaml

      Only output breaking changes and write a styled HTML report to file
      report.html:

          comparest -c old.yaml -s new.yaml --only-breaking -o report

      Don't output anything, only fail if there are breaking changes:

          comparest -c old.json -s new.json --silent

      Write full report suitable for embedding into a GitHub comment to
      report.html:

          comparest -c old.json -s new.json --folding-block-quotes-style -o report.html

Available options:
  -h,--help                Show this help text
  -c,--client ARG          A path to the file containing the specification that
                           will be used for the client of the API. Can be either
                           a YAML or JSON file.
  -s,--server ARG          A path to the file containing the specification that
                           will be used for the server of the API. Can be either
                           a YAML or JSON file.
  --silent                 Silence all output. Only makes sense in combination
                           with --signal-exit-code.
  --only-breaking          Only report breaking changes in the output.
  --all                    Report both incompatible and compatible changes.
                           Compatible changes will not trigger a failure exit
                           code.
  -o,--output ARG          The file path where the output should be written. If
                           the option is omitted the result will be written to
                           stdout.

                           The file extension is used to determine the type of
                           the output file.

                           Supports many formats such as markdown, html, rtf,
                           doc, txt, rst, and many more.

                           Leave out the extension to produce a self-contained
                           HTML report with styling.
  --folding-block-quotes-style
                           The report tree is structured using summary/detail
                           HTML elements and indented using block quotes. This
                           style renders well on GitHub.Intended for HTML output
                           format. Markdown has rendering bugs on GitHub.
  --header-style           The report tree is structured using increasing levels
                           of headers.
  --signal-exit-code       Signal API compatibility with the exit code.

                           Exit with 0 if there are no breaking changes.
                           Exit with 1 if there are breaking changes.
                           Exit with 2 if could not determine compatibility.

comparest's People

Contributors

ilyakooo0 avatar mniip avatar my-name-is-lad avatar s9gf4ult avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

comparest's Issues

Traces should be comonadic

Currently we sometimes pass traces around in an environment monad, using monadic idioms, and sometimes we use Traced, which is effectively a comonad, using comonadic idioms. Experience showed that the latter is the more fruitful approach.

Properly handle source error traces

Problems with the source spec are reported as semantic errors. This is not the best thing at least because the consumer can also have source spec errors.

Regex comparison

We could have complete support for comparing regexes (in e.g. the pattern field in json schema), if we compiled the regexes to automata and did set-theoretic operations on the automata (product with intersection, product with union, negation), and used an emptiness test for an automaton. These operations can be carried out somewhat efficiently. After talking a little bit with ekmett about this he suggested that we use Binary decision diagrams and in particular ZDD, for determinization (NFA -> DFA) and NFA membership test.

An error needs 2 traces

Currently our "check issue" only remembers one trace, either the producer or the consumer. However we need a way to refer to both, because in some places we "flip variance", and the resulting tree of errors has the producers and the consumers jumbled up. Otherwise we need to pass the variance to the leaves, so that they know which source to include in the error.

Splitting heuristic

If we have a conjunction of conditions in the producer, and a disjunction of conditions in the consumer, then we will test the conjunction against every item in the disjunction and see if anything succeeds. This is not always enough. We could try to heuristically split the producer's set along some e.g. enum component into a dijsunction, and then do the check for each of them (this process could be recursive).

Fix MIME types

Currently adding a type is considered a breaking change, and removing it is considered a non-breaking change.

Implement Callbacks

Callback have some non-trivial templating in the path keys that seems to not be implemented in openapi3

Support sub-PathFragment templates

The current implementation of path templates only supports the whole path fragment being a template. The OpenApi spec also allows parts of the path fragments to be templated.

Issues with normalizePath

Currently normalizePath cannot function, because after pattern matching on Snoc we don't know anything about b other than it being Steppable.

The current proposed solution is to instead define:
normalizeStep :: Step b c -> (Trace OpenApi b -> Trace OpenApi c)
as a method of Steppable, in turn making Steppable non-polykinded. The intention is that when defining the Step b c as a data family instance, we would know for which constructors normalizeStep returns a simple Snoc, and for which we instead restart from the root and return a const ....

An alternative solution is to define a series of things:

-- a type family:
type family Prev :: Type -> [Type]
-- a number of constraint implications (via superclass constraints):
APIStep a b => Elem a (Prev b)
(Subtree b, Elem a (Prev b)) => (Subtree a, APIStep a b)
(Subtree b, Steppable a b) => Elem a (Prev b)

This would allow to pattern match on Trace a c, obtaining Trace a b and Step b c, and given Subtree c conclude Subtree b, thus allowing recursion.

Add some contradiction checks

Currently if the producer schema indicates that no values of a particular type are allowed, we test the consumer formula for whether it is contradictory, i.e. whether it also allows no values.

The formula is in Disjuctive Normal Form, and every conjunct is checked for contradiction -- currently this always fails (but if there are no conjuncts the check succeeds).

Implement disjointness heuristic for oneOf

Until that is done, we always implicitly assume that oneOf clauses are disjoint. No unsupported warning is shown because that would imply a warning for every single instance of oneOf.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.