Coder Social home page Coder Social logo

openapitools / openapi-diff Goto Github PK

View Code? Open in Web Editor NEW
750.0 20.0 153.0 1.32 MB

Utility for comparing two OpenAPI specifications.

License: Apache License 2.0

Java 99.03% CSS 0.75% HTML 0.09% Dockerfile 0.13%
openapi openapi3 swagger diff api openapi-specification openapi-diff

openapi-diff's Introduction

OpenAPI-diff

Compare two OpenAPI specifications (3.x) and render the difference to HTML plaintext, Markdown files, or JSON files.

Build Quality Gate Status Maven Central

Contribute with Gitpod Join the Slack chat room

Docker Cloud Automated build Docker Cloud Build Status Docker Image Version

Requirements

  • Java 8

Feature

  • Supports OpenAPI spec v3.0.
  • Depth comparison of parameters, responses, endpoint, http method (GET,POST,PUT,DELETE...)
  • Supports swagger api Authorization
  • Render difference of property with Expression Language
  • HTML, Markdown, Asciidoc & JSON render

Maven

Available on Maven Central

<dependency>
  <groupId>org.openapitools.openapidiff</groupId>
  <artifactId>openapi-diff-core</artifactId>
  <version>${openapi-diff-version}</version>
</dependency>

Docker

Available on Docker Hub as openapitools/openapi-diff.

# docker run openapitools/openapi-diff:latest
usage: openapi-diff <old> <new>
    --asciidoc <file>           export diff as asciidoc in given file
    --debug                     Print debugging information
    --error                     Print error information
    --fail-on-changed           Fail if API changed but is backward
                                compatible
    --fail-on-incompatible      Fail only if API changes broke backward
                                compatibility
    --config-file               Config file to override default behavior. Supported file formats: .yaml
    --config-prop               Config property to override default behavior with key:value format (e.g. my.prop:true)
 -h,--help                      print this message
    --header <property=value>   use given header for authorisation
    --html <file>               export diff as html in given file
    --info                      Print additional information
    --json <file>               export diff as json in given file
 -l,--log <level>               use given level for log (TRACE, DEBUG,
                                INFO, WARN, ERROR, OFF). Default: ERROR
    --markdown <file>           export diff as markdown in given file
    --off                       No information printed
    --query <property=value>    use query param for authorisation
    --state                     Only output diff state: no_changes,
                                incompatible, compatible
    --text <file>               export diff as text in given file
    --trace                     be extra verbose
    --version                   print the version information and exit
    --warn                      Print warning information

Build the image

This is only required if you want to try new changes in the Dockerfile of this project.

docker build -t local-openapi-diff .

You can replace the local image name local-openapi-diff by any name of your choice.

Run an instance

In this example the $(pwd)/core/src/test/resources directory is mounted in the /specs directory of the container in readonly mode (ro).

docker run --rm -t \
  -v $(pwd)/core/src/test/resources:/specs:ro \
  openapitools/openapi-diff:latest /specs/path_1.yaml /specs/path_2.yaml

The remote name openapitools/openapi-diff can be replaced with local-openapi-diff or the name you gave to your local image.

Usage

openapi-diff can read OpenAPI specs from JSON files or HTTP URLs.

Command Line

$ openapi-diff --help
usage: openapi-diff <old> <new>
    --asciidoc <file>           export diff as asciidoc in given file
    --debug                     Print debugging information
    --error                     Print error information
 -h,--help                      print this message
    --header <property=value>   use given header for authorisation
    --html <file>               export diff as html in given file
    --info                      Print additional information
    --json <file>               export diff as json in given file
 -l,--log <level>               use given level for log (TRACE, DEBUG,
                                INFO, WARN, ERROR, OFF). Default: ERROR
    --markdown <file>           export diff as markdown in given file
    --off                       No information printed
    --query <property=value>    use query param for authorisation
    --state                     Only output diff state: no_changes,
                                incompatible, compatible
    --fail-on-incompatible      Fail only if API changes broke backward compatibility
    --fail-on-changed           Fail if API changed but is backward compatible
    --config-file               Config file to override default behavior. Supported file formats: .yaml
    --config-prop               Config property to override default behavior with key:value format (e.g. my.prop:true)
    --trace                     be extra verbose
    --version                   print the version information and exit
    --warn                      Print warning information

Maven Plugin

Add openapi-diff to your POM to show diffs when you test your Maven project. You may opt to throw an error if you have broken backwards compatibility or if your API has changed.

<plugin>
  <groupId>org.openapitools.openapidiff</groupId>
  <artifactId>openapi-diff-maven</artifactId>
  <version>${openapi-diff-version}</version>
  <executions>
    <execution>
      <goals>
        <goal>diff</goal>
      </goals>
      <configuration>
        <!-- Reference specification (perhaps your prod schema) -->
        <oldSpec>https://petstore3.swagger.io/api/v3/openapi.json</oldSpec>
        <!-- Specification generated by your project in the compile phase -->
        <newSpec>${project.basedir}/target/openapi.yaml</newSpec>
        <!-- Fail only if API changes broke backward compatibility (default: false) -->
        <failOnIncompatible>true</failOnIncompatible>
        <!-- Fail if API changed (default: false) -->
        <failOnChanged>true</failOnChanged>
        <!-- Supply file path for console output to file if desired. -->
        <consoleOutputFileName>${project.basedir}/../maven/target/diff.txt</consoleOutputFileName>
        <!-- Supply file path for json output to file if desired. -->
        <jsonOutputFileName>${project.basedir}/../maven/target/diff.json</jsonOutputFileName>
        <!-- Supply file path for markdown output to file if desired. -->
        <markdownOutputFileName>${project.basedir}/../maven/target/diff.md</markdownOutputFileName>
        <!-- Supply config file(s), e.g. to disable incompatibility checks. Later files override earlier files -->
        <configFiles>
          <configFile>my/config-file.yaml</configFile>
        </configFiles>
        <!-- Supply config properties, e.g. to disable incompatibility checks. Overrides configFiles. -->
        <configProps>
          <incompatible.response.enum.increased>false</incompatible.response.enum.increased>
        </configProps>
      </configuration>
    </execution>
  </executions>
</plugin>

Direct Invocation

public class Main {
    public static final String OPENAPI_DOC1 = "petstore_v3_1.json";
    public static final String OPENAPI_DOC2 = "petstore_v2_2.yaml";
        
    public static void main(String[] args) {
        ChangedOpenApi diff = OpenApiCompare.fromLocations(OPENAPI_DOC1, OPENAPI_DOC2);

        //...
    }
}

Render difference


HTML

String html = new HtmlRender("Changelog",
        "http://deepoove.com/swagger-diff/stylesheets/demo.css")
                .render(diff);

try {
    FileWriter fw = new FileWriter("testNewApi.html");
    fw.write(html);
    fw.close();
} catch (IOException e) {
    e.printStackTrace();
}

Markdown

String render = new MarkdownRender().render(diff);
try {
    FileWriter fw = new FileWriter("testDiff.md");
    fw.write(render);
    fw.close();
} catch (IOException e) {
    e.printStackTrace();
}

Asciidoc

String render = new AsciidocRender().render(diff);
try {
    FileWriter fw = new FileWriter("testDiff.adoc");
    fw.write(render);
    fw.close();
} catch (IOException e) {
    e.printStackTrace();
}

JSON

String render = new JsonRender().render(diff);
try {
    FileWriter fw = new FileWriter("testDiff.json");
    fw.write(render);
    fw.close();
} catch (IOException e) {
    e.printStackTrace();
}

Extensions

This project uses Java Service Provider Inteface (SPI) so additional extensions can be added.

To build your own extension, you simply need to create a src/main/resources/META-INF/services/org.openapitools.openapidiff.core.compare.ExtensionDiff file with the full classname of your implementation. Your class must also implement the org.openapitools.openapidiff.core.compare.ExtensionDiff interface. Then, including your library with the openapi-diff module will cause it to be triggered automatically.

Examples

CLI Output

==========================================================================
==                            API CHANGE LOG                            ==
==========================================================================
                             Swagger Petstore                             
--------------------------------------------------------------------------
--                              What's New                              --
--------------------------------------------------------------------------
- GET    /pet/{petId}

--------------------------------------------------------------------------
--                            What's Deleted                            --
--------------------------------------------------------------------------
- POST   /pet/{petId}

--------------------------------------------------------------------------
--                          What's Deprecated                           --
--------------------------------------------------------------------------
- GET    /user/logout

--------------------------------------------------------------------------
--                            What's Changed                            --
--------------------------------------------------------------------------
- PUT    /pet
  Request:
        - Deleted application/xml
        - Changed application/json
          Schema: Backward compatible
- POST   /pet
  Parameter:
    - Add tags in query
  Request:
        - Changed application/xml
          Schema: Backward compatible
        - Changed application/json
          Schema: Backward compatible
- GET    /pet/findByStatus
  Parameter:
    - Deprecated status in query
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed application/xml
          Schema: Broken compatibility
        - Changed application/json
          Schema: Broken compatibility
- GET    /pet/findByTags
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed application/xml
          Schema: Broken compatibility
        - Changed application/json
          Schema: Broken compatibility
- DELETE /pet/{petId}
  Parameter:
    - Add newHeaderParam in header
- POST   /pet/{petId}/uploadImage
  Parameter:
    - Changed petId in path
- POST   /user
  Request:
        - Changed application/json
          Schema: Backward compatible
- POST   /user/createWithArray
  Request:
        - Changed application/json
          Schema: Backward compatible
- POST   /user/createWithList
  Request:
        - Changed application/json
          Schema: Backward compatible
- GET    /user/login
  Parameter:
    - Delete password in query
- GET    /user/logout
- GET    /user/{username}
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed application/xml
          Schema: Broken compatibility
        - Changed application/json
          Schema: Broken compatibility
- PUT    /user/{username}
  Request:
        - Changed application/json
          Schema: Backward compatible
--------------------------------------------------------------------------
--                                Result                                --
--------------------------------------------------------------------------
                 API changes broke backward compatibility                 
--------------------------------------------------------------------------

Markdown

### What's New
---
* `GET` /pet/{petId} Find pet by ID

### What's Deleted
---
* `POST` /pet/{petId} Updates a pet in the store with form data

### What's Deprecated
---
* `GET` /user/logout Logs out current logged in user session

### What's Changed
---
* `PUT` /pet Update an existing pet  
    Request

        Deleted request body : [application/xml]
        Changed response : [application/json]
* `POST` /pet Add a new pet to the store  
    Parameter

        Add tags //add new query param demo
    Request

        Changed response : [application/xml]
        Changed response : [application/json]
* `GET` /pet/findByStatus Finds Pets by status  
    Parameter

    Return Type

        Changed response : [200] //successful operation
* `GET` /pet/findByTags Finds Pets by tags  
    Return Type

        Changed response : [200] //successful operation
* `DELETE` /pet/{petId} Deletes a pet  
    Parameter

        Add newHeaderParam
* `POST` /pet/{petId}/uploadImage uploads an image for pet  
    Parameter

        petId Notes ID of pet to update change into ID of pet to update, default false
* `POST` /user Create user  
    Request

        Changed response : [application/json]
* `POST` /user/createWithArray Creates list of users with given input array  
    Request

        Changed response : [application/json]
* `POST` /user/createWithList Creates list of users with given input array  
    Request

        Changed response : [application/json]
* `GET` /user/login Logs user into the system  
    Parameter

        Delete password //The password for login in clear text
* `GET` /user/logout Logs out current logged in user session  
* `PUT` /user/{username} Updated user  
    Request

        Changed response : [application/json]
* `GET` /user/{username} Get user by user name  
    Return Type

        Changed response : [200] //successful operation

JSON

{
    "changedElements": [...],
    "changedExtensions": null,
    "changedOperations": [...],
    "compatible": false,
    "deprecatedEndpoints": [...],
    "different": true,
    "incompatible": true,
    "missingEndpoints": [...],
    "newEndpoints": [
        {
            "method": "GET",
            "operation": {
                "callbacks": null,
                "deprecated": null,
                "description": "Returns a single pet",
                "extensions": null,
                "externalDocs": null,
                "operationId": "getPetById",
                "parameters": [
                    {
                        "$ref": null,
                        "allowEmptyValue": null,
                        "allowReserved": null,
                        "content": null,
                        "deprecated": null,
                        "description": "ID of pet to return",
                        "example": null,
                        "examples": null,
                        "explode": false,
                        "extensions": null,
                        "in": "path",
                        "name": "petId",
                        "required": true,
                        "schema": {...},
                        "style": "SIMPLE"
                    }
                ],
                "requestBody": null,
                "responses": {...},
                "security": [
                    {
                        "api_key": []
                    }
                ],
                "servers": null,
                "summary": "Find pet by ID",
                "tags": [
                    "pet"
                ]
            },
            "path": null,
            "pathUrl": "/pet/{petId}",
            "summary": "Find pet by ID"
        }
    ],
    "newSpecOpenApi": {...},
    "oldSpecOpenApi": {...},
    "unchanged": false
}

License

openapi-diff is released under the Apache License 2.0.

Thanks

openapi-diff's People

Contributors

damianw avatar dependabot-preview[bot] avatar dependabot[bot] avatar derjust avatar gloeglm avatar gsdatta avatar hiddewie avatar itsjavi avatar jimschubert avatar jlamaille avatar jmini avatar joschi avatar jrouly avatar kaschula avatar mikkela avatar mishukdutta-toast avatar mohamed-el-habib avatar nithintatikonda1 avatar quen2404 avatar slinstaedt avatar stoeren avatar studur avatar sullis avatar thefreaky avatar thiagoarrais avatar timobehrendt avatar timur27 avatar trundle avatar westse avatar wing328 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openapi-diff's Issues

Stackoverflow error with cyclic references involving allOf

Given this schema definition

openapi: 3.0.1
info:
  title: recursive test
  version: '1.0'
servers:
  - url: 'http://localhost:8000/'
paths:
  /ping:
    get:
      operationId: ping
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/B'
components:
  schemas:
    B:
      type: object
      properties:
        message:
          type: string
        message2:
          type: string
        details:
          type: array
          items:
            allOf:
              - $ref: '#/components/schemas/B'

and running openapi-diff spec.yaml spec.yaml I get stack overflow error

Exception in thread "main" java.lang.StackOverflowError
        at java.base/java.util.Arrays.spliterator(Arrays.java:5482)
        at java.base/java.util.Arrays.stream(Arrays.java:5633)
        at java.base/java.util.Arrays.stream(Arrays.java:5614)
        at java.base/java.util.stream.Stream.of(Stream.java:1188)
        at com.qdesrame.openapi.diff.model.ChangedExtensions.getChangedElements(ChangedExtensions.java:37)
        at com.qdesrame.openapi.diff.model.ComposedChanged.isChanged(ComposedChanged.java:19)
        at com.qdesrame.openapi.diff.model.Changed.isUnchanged(Changed.java:21)
        at com.qdesrame.openapi.diff.utils.ChangedUtils.isUnchanged(ChangedUtils.java:9)
        at com.qdesrame.openapi.diff.utils.ChangedUtils.isChanged(ChangedUtils.java:17)
        at com.qdesrame.openapi.diff.compare.ExtensionsDiff.diff(ExtensionsDiff.java:79)
        at com.qdesrame.openapi.diff.compare.schemadiffresult.SchemaDiffResult.diff(SchemaDiffResult.java:61)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:316)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:27)
        at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:51)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:282)
        at com.qdesrame.openapi.diff.compare.schemadiffresult.SchemaDiffResult.diff(SchemaDiffResult.java:75)
        at com.qdesrame.openapi.diff.compare.schemadiffresult.ComposedSchemaDiffResult.diff(ComposedSchemaDiffResult.java:85)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:316)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:27)
        at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:51)
        at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:282)
        at com.qdesrame.openapi.diff.compare.schemadiffresult.ArraySchemaDiffResult.diff(ArraySchemaDiffResult.java:31)

The problem occurs only with allOf. Replacing it with oneOf fixes the problem.

Provide a maven plugin

It'd be nice to be able to run this tool in a maven build and make it fail it if non-backwards compatible changes are found.

Two path items have the same signature - must include Http-Method into check

Hi,

I think I found a little bug in the diff algorithm ... please correct my if I'm wrong 😃

My schema contains something like this:
GET /api/vi/configuration/{configurationId}
POST /api/vi/configuration/{productId}

If run the diff then I got an error because of found more then one "/api/vi/configuration/{}".

I tried to fix this by adding an additional check:

Optional<String> result =
        right.keySet().stream()
             .filter(s -> normalizePath(s).equals(template))
             .findFirst();

to

Optional<String> result =
        right.keySet().stream()
            .filter(s -> normalizePath(s).equals(template) && right.get(s).readOperations().equals(leftPath.readOperations()))
            .findFirst();

to com.qdesrame.openapi.diff.core.compare.PathsDiff.diff(Map<String, PathItem>, Map<String, PathItem>)

The exception is gone ... but now I have strange compare results if the api is 100% equal. So I guess my fix is not 100% complete yet.

Any ideas?

Supports OpenAPi spec v2.0 / v1.2

Thanks a lot for a great tool. I really want to use it in my projects but it is impossible because there is no support of older versions of OpenAPi spec: 1.2 and 2.0. Is there any plans to fix it?

Stackoverflow Error with cyclic reference and adding new property

Hi there!

When we have a cyclic reference and one of the properties gets renamed, the tool throws a Stackoverflow Error.

Here is a minimal working example. The only thing that differs between those two is the new property propname2 in B.

Old yaml

openapi: 3.0.1
info:
  title: recursive test
  version: "1.0"
servers:
  - url: "http://localhost:8000/"
paths:
  /ping:
    get:
      operationId: ping
      responses:
        "200":
          description: OK
          content:
            text/plain:
              schema:
                $ref: "#/components/schemas/A"
components:
  schemas:
    A:
      type: object
      properties:
        propname:
          $ref: "#/components/schemas/B"
    B:
      type: object
      properties:
        propname:
          $ref: "#/components/schemas/A"

New yaml

openapi: 3.0.1
info:
  title: recursive test
  version: "1.0"
servers:
  - url: "http://localhost:8000/"
paths:
  /ping:
    get:
      operationId: ping
      responses:
        "200":
          description: OK
          content:
            text/plain:
              schema:
                $ref: "#/components/schemas/A"
components:
  schemas:
    A:
      type: object
      properties:
        propname2:
          $ref: "#/components/schemas/B"
    B:
      type: object
      properties:
        propname2:
          $ref: "#/components/schemas/A"

Beginning of the output:

==========================================================================
==                            API CHANGE LOG                            ==
==========================================================================
                              recursive test                              
--------------------------------------------------------------------------
--                            What's Changed                            --
--------------------------------------------------------------------------
- GET    /ping
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed text/plain
          Schema: Broken compatibility
          Missing property: propname (object)
--------------------------------------------------------------------------
--                                Result                                --
--------------------------------------------------------------------------
                 API changes broke backward compatibility                 
--------------------------------------------------------------------------

Exception in thread "main" java.lang.StackOverflowError
	at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660)
	at java.util.regex.Pattern$Branch.match(Pattern.java:4606)
	at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3800)
	at java.util.regex.Pattern$Start.match(Pattern.java:3463)
	at java.util.regex.Matcher.search(Matcher.java:1248)
	at java.util.regex.Matcher.find(Matcher.java:664)
	at java.util.Formatter.parse(Formatter.java:2549)
	at java.util.Formatter.format(Formatter.java:2501)
	at java.util.Formatter.format(Formatter.java:2455)
	at java.lang.String.format(String.java:2940)

IllegalArgumentException: discriminator or property not found for oneOf schema

I am doing a test with a schema containing a oneOf schema (see documentation Data Models (Schemas) > oneOf, anyOf, allOf, not)

I think my specification is valid and makes sense (please do not hesitate to discuss it)

In this test case, one of the oneOf referenced schemas was removed between the old and the new version. Depending on the operation (GET or POST) the ComposedSchema is located:

  • in the input (request body of the POST method)
  • in the output (answer of the GET method).

My feeling is that a removal in the output is still backward compatible (GET Operation), and not in the input (POST Operation).

But for the moment the diff tool is crashing before (see stacktrace)

Old Spec:

openapi: 3.0.1
info:
  title: oneOf test
  version: '1.0'
servers:
  - url: 'http://localhost:8000/'
paths:
  /state:
    get:
      operationId: getState
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/A'
                  - $ref: '#/components/schemas/B'
                  - $ref: '#/components/schemas/C'
    post:
      operationId: update
      requestBody:
        content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/A'
                  - $ref: '#/components/schemas/B'
                  - $ref: '#/components/schemas/C'
        required: true
      responses:
        '201':
          description: OK
components:
  schemas:
    A:
      type: object
      properties:
        message:
          type: string
    B:
      type: object
      properties:
        description:
          type: string
        code:
          type: integer
          format: int32
    C:
      type: object
      properties:
        case:
          type: integer
          format: int64

New Spec:

openapi: 3.0.1
info:
  title: oneOf test
  version: '1.0'
servers:
  - url: 'http://localhost:8000/'
paths:
  /state:
    get:
      operationId: getState
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/A'
                  - $ref: '#/components/schemas/B'
    post:
      operationId: update
      requestBody:
        content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/A'
                  - $ref: '#/components/schemas/B'
        required: true
      responses:
        '201':
          description: OK
components:
  schemas:
    A:
      type: object
      properties:
        message:
          type: string
    B:
      type: object
      properties:
        description:
          type: string
        code:
          type: integer
          format: int32
    C:
      type: object
      properties:
        case:
          type: integer
          format: int64

Stacktrace:

Exception in thread "main" java.lang.IllegalArgumentException: discriminator or property not found for oneOf schema
	at com.qdesrame.openapi.diff.compare.schemadiffresult.ComposedSchemaDiffResult.diff(ComposedSchemaDiffResult.java:41)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:90)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:16)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:62)
	at com.qdesrame.openapi.diff.compare.ContentDiff.diff(ContentDiff.java:38)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.computeDiff(ResponseDiff.java:38)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.computeDiff(ResponseDiff.java:15)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.diff(ResponseDiff.java:28)
	at com.qdesrame.openapi.diff.compare.ApiResponseDiff.diff(ApiResponseDiff.java:32)
	at com.qdesrame.openapi.diff.compare.OperationDiff.diff(OperationDiff.java:41)
	at com.qdesrame.openapi.diff.compare.PathDiff.diff(PathDiff.java:31)
	at com.qdesrame.openapi.diff.compare.PathsDiff.lambda$diff$2(PathsDiff.java:50)
	at java.util.LinkedHashMap$LinkedKeySet.forEach(LinkedHashMap.java:559)
	at com.qdesrame.openapi.diff.compare.PathsDiff.diff(PathsDiff.java:36)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:92)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:66)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromSpecifications(OpenApiCompare.java:99)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromLocations(OpenApiCompare.java:88)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromFiles(OpenApiCompare.java:65)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromFiles(OpenApiCompare.java:53)

NPE for APIResponseDiff

Thank for you excellent tool!
When I tried compare two my swagger files, I got an error:

java.lang.NullPointerException
	at com.qdesrame.openapi.diff.compare.ApiResponseDiff.diff(ApiResponseDiff.java:41)
	at com.qdesrame.openapi.diff.compare.OperationDiff.diff(OperationDiff.java:59)
	at com.qdesrame.openapi.diff.compare.PathDiff.diff(PathDiff.java:35)
	at com.qdesrame.openapi.diff.compare.PathsDiff.lambda$diff$2(PathsDiff.java:69)
	at java.util.LinkedHashMap$LinkedKeySet.forEach(LinkedHashMap.java:559)
	at com.qdesrame.openapi.diff.compare.PathsDiff.diff(PathsDiff.java:40)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:97)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:68)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromSpecifications(OpenApiCompare.java:101)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromLocations(OpenApiCompare.java:90)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromLocations(OpenApiCompare.java:77)
	at com.qdesrame.openapi.diff.Main.main(Main.java:141)
openApiDiff
        .getExtensionsDiff()
        .diff(left.getExtensions(), right.getExtensions(), context)
        .ifPresent(changedApiResponse::setExtensions);

I see in debugger and found that in method diff variable left equal to null.

Support oneOf without $ref references

Hey folks, I was trying to diff two large OpenAPI descriptions and I could not get it working due to this exception:

Unexpected exception. Reason: invalid oneOf schema

It's coming from here: https://github.com/OpenAPITools/openapi-diff/blob/master/core/src/main/java/com/qdesrame/openapi/diff/core/compare/schemadiffresult/ComposedSchemaDiffResult.java#L101-L104

I noticed it seems to expect that the schemas under oneOf are references, and not an inline schema? For example:

schema:
  oneOf:
  - type: string
  - type: object
     properties:
       a:
         type: string

Should be valid OpenAPI☝️ But this causes the exception.

Publish jar-with-dependencies to maven

By publishing jar-with-dependencies build to maven it is possible to use this as a plugin dependency in maven exec plugin and testing API breakage during the test phase and similar. A dedicated maven plugin would be nice obviously but it is a start.

Please publish a fresh maven release

Hi,
we use openapi-diff actively in our projects to find breaking API changes and found out that in version 1.2. NullPointerExceptions can happen in case of incompatibilities. Additionally with 1.2 we have to tweak especially the io.swagger.parser.v3 version in our pom to avoid conflicts. Both is fixed in the master.

Thus it would be great to have a newly tagged maven release in the mvnrepository with all the library updates and fixes since 1.2.

README Still References Original Project

HTML Render difference still using swagger-diff's css.

I figure if you're going to try to pass off the original project's work as your own without giving it any credit, you may as well go ahead and make sure you host the css yourself as well.

https://github.com/Sayi/swagger-diff

Seriously lol... not even in thanks section of the README... smh

Add documentation for detectable breaking/non-breaking changes

We are using this library to power a GitHub Action that generates a backward compatibility report between two versions of an OpenAPI spec. We love this library, but it is not clear what changes are detectable by this library, and what changes this library classifies as "breaking" or "non-breaking" changes. It would be really valuable to us if this repo could include a markdown file outlining all the OAS changes that this library can detect.

missing execution field in pom.xml in release 1.2.0

execution field is missing in pom.xml in release 1.2.0 and the with-dependency jar is missing in the target.

             <plugin>                                                                                                                                                   
                 <artifactId>maven-assembly-plugin</artifactId>                                                                                                         
                 <configuration>                                                                                                                                        
                     <archive>                                                                                                                                          
                         <manifest>                                                                                                                                     
                             <mainClass>com.qdesrame.openapi.diff.Main</mainClass>                                                                                      
                         </manifest>                                                                                                                                    
                     </archive>                                                                                                                                         
                     <descriptorRefs>                                                                                                                                   
                         <descriptorRef>jar-with-dependencies</descriptorRef>                                                                                           
                     </descriptorRefs>                                                                                                                                  
                 </configuration>                                                                                                                                       
                 <!--- missing execution here -->                                                                                           
             </plugin> 
            <plugin>                                                                                                                                                   
                 <artifactId>maven-assembly-plugin</artifactId>                                                                                                         
                 <configuration>                                                                                                                                        
                     <archive>                                                                                                                                          
                         <manifest>                                                                                                                                     
                             <mainClass>com.qdesrame.openapi.diff.Main</mainClass>                                                                                      
                         </manifest>                                                                                                                                    
                     </archive>                                                                                                                                         
                     <descriptorRefs>                                                                                                                                   
                         <descriptorRef>jar-with-dependencies</descriptorRef>                                                                                           
                     </descriptorRefs>                                                                                                                                  
                 </configuration>                                                                                                                                       
                 <executions>                                                                                                                                           
                     <execution>                                                                                                                                        
                         <id>make-assembly</id>                                                                                                                         
                         <phase>package</phase>                                                                                                                         
                         <goals>                                                                                                                                        
                             <goal>single</goal>                                                                                                                        
                         </goals>                                                                                                                                       
                     </execution>                                                                                                                                       
                 </executions>                                                                                                                                          
             </plugin>                                                                                                                                                  
         </plugins> 

Export Normalized Values

Is there any way to export both, fully normalized schema?

I would like to compare them as "text only" :-)

Activity of this project – Fork

Hi everyone,

it currently looks like the development activity in this project has stalled. The last commit was on January 8, 2020 (443246f) and issues and PRs don't seem to be addressed.

I've tried to contact @quen2404 via email but unfortunately didn't receive a response.

So I've forked this project, applied many of the open PRs and added some other bugfixes, and published the new version(s) to Maven Central and Docker Hub:

Since I don't just want to start yet another fork of this project with limited lifetime, I've contacted the fine people at https://github.com/OpenAPITools/ to discuss whether openapi-diff could live in their GitHub organization.

@elibracha I've seen that you've also been working on a fork (https://github.com/elibracha/openapi-diff) and an extension (https://github.com/elibracha/openapi-diff-ignore) to this project. Would you be interested in joining forces?

IllegalArgumentException: "Invalid ref...", while having attribute with the same name in different 'components' sections

Hello! Thanks for the nice util, but I faced one problem that doesn't make me possible to use this. I have the following API definition sections:

components:
  parameters:
    gender:
      name: gender
      description: Customer gender
      in: query
      schema:
        $ref: '#/components/schemas/Gender'
  schemas:
    Gender:
      type: string
      enum:
      - MALE
      - FEMALE
      - NOT_SPECIFIED

And the comparision of versions fails with the following exception:

java.lang.IllegalArgumentException: Invalid ref: #/components/schemas/Gender
	at com.qdesrame.openapi.diff.utils.RefPointer.getRefName(RefPointer.java:69) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.utils.RefPointer.resolveRef(RefPointer.java:20) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.ParametersDiff.diff(ParametersDiff.java:46) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.OperationDiff.diff(OperationDiff.java:34) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.PathDiff.diff(PathDiff.java:31) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.PathsDiff.lambda$diff$2(PathsDiff.java:53) ~[openapi-diff-1.2.0.jar:na]
	at java.util.LinkedHashMap$LinkedKeySet.forEach(LinkedHashMap.java:559) ~[na:1.8.0_191]
	at com.qdesrame.openapi.diff.compare.PathsDiff.diff(PathsDiff.java:36) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:92) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:66) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.OpenApiCompare.fromSpecifications(OpenApiCompare.java:99) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.OpenApiCompare.fromContents(OpenApiCompare.java:42) ~[openapi-diff-1.2.0.jar:na]
	at com.qdesrame.openapi.diff.OpenApiCompare.fromContents(OpenApiCompare.java:30) ~[openapi-diff-1.2.0.jar:na]

I suppose that this happening because of my parameters and schemas sections have attributes with the same name.

StackOverflowError with recursion

First of all, thank you for this work! I am evaluating if we will use it to detect breaking changes between two versions of our API and I think we will.


I have run your tool against the items provided in the test suite of this project: Azure/openapi-diff
(with some modifications, I can provide more details on my approach if you are interested)

By doing this, I have noticed that the recursion case is not properly handled by this tool.

With this spec as input:

openapi: 3.0.1
info:
  title: recursive test
  version: '1.0'
servers:
  - url: 'http://localhost:8000/'
paths:
  /ping:
    get:
      operationId: ping
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/B'
components:
  schemas:
    B:
      type: object
      properties:
        message:
          type: string
        details:
          type: array
          items:
            $ref: '#/components/schemas/B'

You get a Stacktrace like this:

Exception in thread "main" java.lang.StackOverflowError
	at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
	at sun.util.locale.provider.LocaleProviderAdapter.getAdapter(LocaleProviderAdapter.java:245)
	at java.text.DecimalFormatSymbols.initialize(DecimalFormatSymbols.java:611)
	at java.text.DecimalFormatSymbols.<init>(DecimalFormatSymbols.java:113)
	at sun.util.locale.provider.DecimalFormatSymbolsProviderImpl.getInstance(DecimalFormatSymbolsProviderImpl.java:85)
	at java.text.DecimalFormatSymbols.getInstance(DecimalFormatSymbols.java:180)
	at java.util.Formatter.getZero(Formatter.java:2283)
	at java.util.Formatter.<init>(Formatter.java:1892)
	at java.util.Formatter.<init>(Formatter.java:1914)
	at java.lang.String.format(String.java:2940)
	at com.qdesrame.openapi.diff.utils.RefPointer.getBaseRefForType(RefPointer.java:56)
	at com.qdesrame.openapi.diff.utils.RefPointer.getRefName(RefPointer.java:67)
	at com.qdesrame.openapi.diff.utils.RefPointer.resolveRef(RefPointer.java:20)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:75)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:16)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:62)
	at com.qdesrame.openapi.diff.compare.schemadiffresult.ArraySchemaDiffResult.diff(ArraySchemaDiffResult.java:23)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:90)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:16)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:62)
	at com.qdesrame.openapi.diff.compare.schemadiffresult.SchemaDiffResult.diff(SchemaDiffResult.java:53)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:90)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:16)

I think that projects like swagger-codegen-generators have some mechanisms to handle those cases.

Something similar should be implemented here.

Getting a runtime error while trying to compare two yaml file

getting the below error:

Exception in thread "main" java.lang.RuntimeException: Cannot read old OpenAPI spec
	at com.qdesrame.openapi.diff.core.OpenApiCompare.notNull(OpenApiCompare.java:106)
	at com.qdesrame.openapi.diff.core.OpenApiCompare.fromSpecifications(OpenApiCompare.java:101)
	at com.qdesrame.openapi.diff.core.OpenApiCompare.fromLocations(OpenApiCompare.java:90)
	at com.qdesrame.openapi.diff.core.OpenApiCompare.fromLocations(OpenApiCompare.java:77)
	at com.comcast.ymldiff.App.main(App.java:18)

both are valid yaml file.

Refactoring a schma with "$ref" results in false positive breaking change

The bug
When splitting a schema into two schemas where one refers with allOf to the other the diff shows breaking change, even if the end structure is the same.

To Reproduce

Example schema old.yaml:

openapi: "3.0.0"
info:
  title: Sample API
  description: API description in Markdown.
  version: 1.0.0
servers: []
paths:
  /test:
    get:
      summary: Your GET endpoint
      tags: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/working'
components:
  schemas:
    working:
      type: object
      properties:
        foo:
          type: string
        bar:
          type: integer

Example schema new.yaml:

openapi: 3.0.0
info:
  title: Sample API
  description: API description in Markdown.
  version: 1.1.0
servers: []
paths:
  /test:
    get:
      summary: Your GET endpoint
      tags: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/working'
      operationId: get-test
components:
  schemas:
    working:
      allOf:
        - type: object
          properties:
            foo:
              type: string
        - $ref: '#/components/schemas/bar'
    bar:
      type: object
      properties:
        bar:
          type: integer

Result of docker run -v $(pwd):/tmp openapitools/openapi-diff:latest /tmp/old.yaml /tmp/new.yaml:

==========================================================================
==                            API CHANGE LOG                            ==
==========================================================================
                                Sample API                                
--------------------------------------------------------------------------
--                            What's Changed                            --
--------------------------------------------------------------------------
- GET    /test
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed application/json
          Schema: Broken compatibility
          Changed property type:  (object -> object)
--------------------------------------------------------------------------
--                                Result                                --
--------------------------------------------------------------------------
                 API changes broke backward compatibility                 
--------------------------------------------------------------------------

Expected behavior
The tool is expected to notice a change but not a breaking one since the allOf mapping results in the same schema after resolving.

Add official Github action for openapi-diff

Openapi-diff is well suited to be integrate into Github actions in order to:

  • Highlight breaking in a pull request
  • Document changes in OpenAPI
  • etc.

For example, today we have an internal github repo which on every commit triggers generating Redoc OpenAPI documentation and publishes it to Github pages for the repository. In this documentation we'd like to include a openapi-diff markdown report of the changes.

Or, when ever we do a pull request we want to review the changes made. Openapi-diff will allow us to identify automatically breaking changes.

There are a couple of unofficial limited Github actions based on other openapi diff tool

RequestBody changes with metadata only changes (i.e. description) causes nullpointer exception

When specifiying a Markdown file output and only changing the description under a requestBody the application fails with a NullPointerException in this bit of code

if (operation.resultRequestBody().isDifferent()) {
  details
           .append(titleH5("Request:"))
           .append(bodyContent(operation.getRequestBody().getContent()));
}

It recognises the operation to have changed but the content of the getRequestBody is null in this scenario.

A potential fix is to have a null check in the bodyContent method i.e.:

protected String bodyContent(String prefix, ChangedContent changedContent){
  if (changedContent == null) {
    return ""; // or prefix?
  }
  ...
}

False positive breaking change reported when removing an optional field from a response

I’ve noticed that removing an optional field from a response is flagged as a breaking change.

Old OpenAPI document

{
  "openapi": "3.0.0",
  "info": {
    "title": "API to manipulate a counter",
    "version": "1.0.0"
  },
  "paths": {
    "/counter": {
      "get": {
        "responses": {
          "200": {
            "description": "The counter current value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/counter.Counter"
                }
              }
            }
          },
          "400": {
            "description": "Client error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          }
        }
      },
      "post": {
        "responses": {
          "200": {
            "description": "The counter current value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/counter.Counter"
                }
              }
            }
          },
          "400": {
            "description": "Client error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/counter.Operation"
              }
            }
          },
          "description": "The operation to apply to the counter"
        }
      }
    }
  },
  "components": {
    "schemas": {
      "counter.Operation.Set": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Set"
            ],
            "example": "Set"
          },
          "value": {
            "type": "integer",
            "format": "int32"
          }
        },
        "required": [
          "type",
          "value"
        ]
      },
      "counter.Counter": {
        "type": "object",
        "properties": {
          "value": {
            "type": "integer",
            "format": "int32"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "value"
        ]
      },
      "counter.Operation": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/counter.Operation.Add"
          },
          {
            "$ref": "#/components/schemas/counter.Operation.Set"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "Add": "#/components/schemas/counter.Operation.Add",
            "Set": "#/components/schemas/counter.Operation.Set"
          }
        }
      },
      "counter.Operation.Add": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Add"
            ],
            "example": "Add"
          },
          "delta": {
            "type": "integer",
            "format": "int32"
          }
        },
        "required": [
          "type",
          "delta"
        ]
      },
      "endpoints.Errors": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    },
    "securitySchemes": {}
  }
}

Note the presence of the optional field timestamp in the counter.Counter component.

New OpenAPI document

{
  "openapi": "3.0.0",
  "info": {
    "title": "API to manipulate a counter",
    "version": "1.0.0"
  },
  "paths": {
    "/counter": {
      "get": {
        "responses": {
          "200": {
            "description": "The counter current value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/counter.Counter"
                }
              }
            }
          },
          "400": {
            "description": "Client error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          }
        }
      },
      "post": {
        "responses": {
          "200": {
            "description": "The counter current value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/counter.Counter"
                }
              }
            }
          },
          "400": {
            "description": "Client error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/endpoints.Errors"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/counter.Operation"
              }
            }
          },
          "description": "The operation to apply to the counter"
        }
      }
    }
  },
  "components": {
    "schemas": {
      "counter.Operation.Set": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Set"
            ],
            "example": "Set"
          },
          "value": {
            "type": "integer",
            "format": "int32"
          }
        },
        "required": [
          "type",
          "value"
        ]
      },
      "counter.Counter": {
        "type": "object",
        "properties": {
          "value": {
            "type": "integer",
            "format": "int32"
          }
        },
        "required": [
          "value"
        ]
      },
      "counter.Operation": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/counter.Operation.Add"
          },
          {
            "$ref": "#/components/schemas/counter.Operation.Set"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "Add": "#/components/schemas/counter.Operation.Add",
            "Set": "#/components/schemas/counter.Operation.Set"
          }
        }
      },
      "counter.Operation.Add": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Add"
            ],
            "example": "Add"
          },
          "delta": {
            "type": "integer",
            "format": "int32"
          }
        },
        "required": [
          "type",
          "delta"
        ]
      },
      "endpoints.Errors": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    },
    "securitySchemes": {}
  }
}

Note that the field timestamp is absent from the counter.Counter component.

I expect such a change to be backward compatible: clients already know how to deal with the absence of the field timestamp since this one was optional in the first place.

Feature: compatibility detection for Operation ID

At the moment, there is no change detection for operation IDs. Changing an Operation ID can break API clients, because the operation ID is usually the way to refer to a specific operation.

Example test case:

Before

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      responses:
        '200':
          description: A paged array of pets
        default:
          description: unexpected error

After

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: changed
      responses:
        '200':
          description: A paged array of pets
        default:
          description: unexpected error

The output contains no changed operations, while the ID changed.

I am willing to create a pull request to add this feature.

Adding option to ignore user specified changes

Hi,

I think that an option to let user ignore some changes would be nice, like letting the user define a yaml file with all the ignore changes he would like. and not fail if the changes are ignored. I am willing to help with this and create the necessary modules.

Would you be interested in this ?

Introduce check concept from "Azure/openapi-diff" project

The project Azure/openapi-diff produce following output when two versions of a spec are compared: a list of items (error, warning, info) is computed.

Example:

$ oad compare old/added_path.json new/added_path.json
{
  "id": "1001",
  "code": "NoVersionChange",
  "message": "The versions have not changed.",
  "jsonref": "#",
  "json-path": "#",
  "type": "Info"
}
{
  "id": "1039",
  "code": "AddedOperation",
  "message": "The new version is adding an operation that was not found in the old version.",
  "jsonref": "#/paths/~1api~1Operations/post",
  "json-path": "#/paths/api/Operations/post",
  "type": "Info"
}
{
  "id": "1038",
  "code": "AddedPath",
  "message": "The new version is adding a path that was not found in the old version.",
  "jsonref": "#/paths/~1api~1Paths",
  "json-path": "#/paths/api/Paths",
  "type": "Info"
}

The list of rules that are currently checked can be found in their git repository: docs
I had a look at their test suite in this folder: Swagger

To run their the oad tool against it, I had to perform some changes: Azure/openapi-diff#108
Then I have converted the old Swagger 2.0 format to OpenAPI 3.0.1 format.

Of course, I will share my work.
We can then continue the discussion and see how this could be integrated.

Doesn't take pattern field into account

Hi I have the following contract

openapi: 3.0.0
info:
  title: ban-check
  version: '1.0'
servers:
  - url: 'http://localhost:3000'
paths:
  /demo-app/ban-check:
    post:
      summary: ban-check
      tags: []
      responses:
        201:
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - BANNED
                      - NOT_BANNED
                  tom:
                    type: string
                    enum:
                      - BANNED
                      - NOT_BANNED
                required:
                  - status
      parameters:
        - schema:
            type: string
            enum:
              - VENTURE1
              - VENTURE2
          in: header
          name: Venture-Name
          required: true
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                dni:
                  type: string
                  minLength: 9
                  maxLength: 9
                  pattern: '^[A-Za-z0-9]+$'
              required:
                - dni
        description: Dni request
components:
  schemas: {}

To reproduce:

When I compare it with another contract that has a stricter pattern on the REGEX for example only numbers or only lower case, the change is not detected by the diff tool.

Expected behaviour:

It shows that the regex has changed and thus is a breaking change

Invalid backward compatibility result for new read-only property in PUT operations

Hi, thank you for creating this tool which helps us a lot in our project. We noticed the following deviation between the tool's responses concerning backward compatibility and our expectation. I would like to discuss that here to see if you agree with our perspective:

We consider the addition of a new read-only property into an API model as a breaking change for PUT operations. A client not knowing of the new read-only property would get a resource from the API which includes the new read-only property and would send a full update back via PUT omitting that new property. The result is that the property would be cleared on the server side as the client did not sent the retrieved value back.

I will shortly provide a pull request fixing this issue (if it is one) and appreciate a lot your comments and opinions on whether this is an issue or not.

Exception when the new schema is composed

If the schema change from standard schema to composed schema (anyOf, oneOf, allOf). an exception is raised:

Exception in thread "main" java.lang.ClassCastException: io.swagger.v3.oas.models.media.Schema cannot be cast to io.swagger.v3.oas.models.media.ComposedSchema
	at com.qdesrame.openapi.diff.compare.schemadiffresult.ComposedSchemaDiffResult.diff(ComposedSchemaDiffResult.java:32)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:86)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.computeDiff(SchemaDiff.java:16)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:62)
	at com.qdesrame.openapi.diff.compare.ContentDiff.diff(ContentDiff.java:38)
	at com.qdesrame.openapi.diff.compare.RequestBodyDiff.computeDiff(RequestBodyDiff.java:56)
	at com.qdesrame.openapi.diff.compare.RequestBodyDiff.computeDiff(RequestBodyDiff.java:15)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:39)
	at com.qdesrame.openapi.diff.compare.RequestBodyDiff.diff(RequestBodyDiff.java:24)
	at com.qdesrame.openapi.diff.compare.OperationDiff.diff(OperationDiff.java:26)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:160)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:93)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:59)
	at com.qdesrame.openapi.diff.Main.main(Main.java:91)

ExtensionDiff for operations

I wanted to try com.qdesrame.openapi.diff.compare.ExtensionDiff introduced with #44.

We are using custom extensions in Operations, but it seems to me that com.qdesrame.openapi.diff.compare.ExtensionsDiff.diff(Map<String, Object>, Map<String, Object>, DiffContext) is only called in SchemaDiffResult.


Could the mechanism be extended to be called for other items supporting extensions:

(the list I found so far):

io.swagger.v3.oas.models.callbacks.Callback
io.swagger.v3.oas.models.examples.Example
io.swagger.v3.oas.models.headers.Header
io.swagger.v3.oas.models.info.Contact
io.swagger.v3.oas.models.info.Info
io.swagger.v3.oas.models.info.License
io.swagger.v3.oas.models.links.Link
io.swagger.v3.oas.models.links.LinkParameter
io.swagger.v3.oas.models.media.Encoding
io.swagger.v3.oas.models.media.EncodingProperty
io.swagger.v3.oas.models.media.MediaType
io.swagger.v3.oas.models.media.Schema
io.swagger.v3.oas.models.media.XML
io.swagger.v3.oas.models.parameters.Parameter
io.swagger.v3.oas.models.parameters.RequestBody
io.swagger.v3.oas.models.responses.ApiResponse
io.swagger.v3.oas.models.responses.ApiResponses
io.swagger.v3.oas.models.security.OAuthFlow
io.swagger.v3.oas.models.security.OAuthFlows
io.swagger.v3.oas.models.security.Scopes
io.swagger.v3.oas.models.security.SecurityScheme
io.swagger.v3.oas.models.servers.Server
io.swagger.v3.oas.models.servers.ServerVariable
io.swagger.v3.oas.models.servers.ServerVariables
io.swagger.v3.oas.models.tags.Tag
io.swagger.v3.oas.models.Components
io.swagger.v3.oas.models.ExternalDocumentation
io.swagger.v3.oas.models.OpenAPI
io.swagger.v3.oas.models.Operation
io.swagger.v3.oas.models.PathItem
io.swagger.v3.oas.models.Paths

Maybe we can start with:

  • io.swagger.v3.oas.models.Operation
  • io.swagger.v3.oas.models.PathItem
  • io.swagger.v3.oas.models.parameters.RequestBody
  • io.swagger.v3.oas.models.parameters.Parameter
  • ..

Cannot run on Windows

java -jar "openapi-diff-1.2.0.jar"
java : no main manifest attribute, in openapi-diff-1.2.0.jar
At line:1 char:1
+ java -jar "openapi-diff-1.2.0.jar" --help
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (no main manifes...-diff-1.2.0.jar:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
java -cp "openapi-diff-1.2.0.jar" com.qdesrame.openapi.diff.Main 
java : java.lang.NoClassDefFoundError: org/apache/commons/cli/ParseException
At line:1 char:1
+ java -cp "openapi-diff-1.2.0.jar" com.qdesrame.openapi.diff.Main
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (java.lang.NoCla.../ParseException:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
    at java.lang.Class.privateGetMethodRecursive(Unknown Source)
    at java.lang.Class.getMethod0(Unknown Source)
    at java.lang.Class.getMethod(Unknown Source)
    at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
    at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.cli.ParseException
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    ... 7 more
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" 

NullPointerException if schema is missing from content

A NullPointerException is thrown if the schema is missing from the content

The files

I have included a zip with two files, broken-openapi.yml which throws the nullpointer and working-openapi.yml which works as expected.

openapi-examples.zip

What's different?

The difference between the files is that the following part in working-openapi.yml :

  content:
    application/json:
      schema:
        $ref: '#/components/schemas/User'

is replaced with the following:

  content:
    text/plain: {}

Reproduction & error

The code which I've used to test this is as follows:

public static void main(String[] args) {
    ChangedOpenApi diff =
            OpenApiCompare.fromLocations("broken-openapi.yml", "broken-openapi.yml");
}

A nullpointer is thrown, see the stacktrace below:

Exception in thread "main" java.lang.NullPointerException
	at com.qdesrame.openapi.diff.compare.SchemaDiff.diff(SchemaDiff.java:62)
	at com.qdesrame.openapi.diff.compare.ContentDiff.diff(ContentDiff.java:35)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.computeDiff(ResponseDiff.java:39)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.computeDiff(ResponseDiff.java:16)
	at com.qdesrame.openapi.diff.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:54)
	at com.qdesrame.openapi.diff.compare.ResponseDiff.diff(ResponseDiff.java:29)
	at com.qdesrame.openapi.diff.compare.ApiResponseDiff.diff(ApiResponseDiff.java:32)
	at com.qdesrame.openapi.diff.compare.OperationDiff.diff(OperationDiff.java:41)
	at com.qdesrame.openapi.diff.compare.PathDiff.diff(PathDiff.java:31)
	at com.qdesrame.openapi.diff.compare.PathsDiff.lambda$diff$2(PathsDiff.java:53)
	at java.util.LinkedHashMap$LinkedKeySet.forEach(LinkedHashMap.java:559)
	at com.qdesrame.openapi.diff.compare.PathsDiff.diff(PathsDiff.java:36)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:92)
	at com.qdesrame.openapi.diff.compare.OpenApiDiff.compare(OpenApiDiff.java:66)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromSpecifications(OpenApiCompare.java:99)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromLocations(OpenApiCompare.java:88)
	at com.qdesrame.openapi.diff.OpenApiCompare.fromLocations(OpenApiCompare.java:76)
	at com.github.msnijder30.openapidifftest.Main.main(Main.java:19)

Question

Is there a way this could be fixed? A schema of {} in OpenAPI is valid, the documents provided in this issue are generated by swagger-core itself.

Description & summary of Operations not showing in changelog.

Description

I've been testing what this library can and cannot do, and it appears that it does not keep track of description & summary of the operations?

I have also tested this with the description of parameters, and they do update.

It does show the summary directly after the path of the operation, but it only shows the latest one and it does not show it has been changed.

The code

  /pets/{id}:
    get:
      description: A description    // This is not in changelog when changed
      summary: This is a summary    // This is not in changelog when changed
      operationId: find pet by id
      parameters:
        - name: aaadda
          in: path
          description: Param desc   // This is in changelog when changed
          required: true
          schema:
            type: integer
            format: int64

Question

Am I correct in that those properties are not in the Changelog? Or am I making a mistake.

Reference resolved against wrong components.

I have two APIs, Old and New say. In Old there's a reference to a schema S that generates an error in the RefPointer class when it tries to resolve the reference, even though the API defines this schema.. But in API New there is no such schema. When I add some debug code to RefPointer::resolveRef I see that it's trying to resolve the reference in Old with the schemas from New, which explains why it can't resolve it. So are there two separate environments, one for each API, and somewhere it's just using the wrong one? Before I start tracking down the problem further I just wanted to check how this is supposed to work.

Thanks

Kevin

Compile against Swagger 2.0.0-SNAPSHOT

The master branch uses the last rc release of swagger-parser-v3 as dependency:

<dependency>
    <groupId>io.swagger.parser.v3</groupId>
    <artifactId>swagger-parser-v3</artifactId>
    <version>2.0.0.unblu-3</version>
</dependency>

Maybe an other branch (develop or swagger-snapshot) could be added to this repository in order to use 2.0.0-SNAPSHOT as dependency:

<dependency>
    <groupId>io.swagger.parser.v3</groupId>
    <artifactId>swagger-parser-v3</artifactId>
    <version>2.0.0-SNAPSHOT</version>
</dependency>

This way, when the next rc is released, the necessary work would already be prepared on this new branch.

The only breaking change I have found so far is:
swagger-api/swagger-core@9674c95

If you are interested, I would submit a Pull Request in order to fix the build for this branch.

Spec transformation to stay backward-compatible

This is more a question or a feature request than a real issue.

We would like to try following approach:


Given two specifications (old.yaml and new.yaml) representing two versions (v1 and v2) of an API. They are similar (say 90% for example) but there are braking changes between the two specs.

There could be a way to compute a second version of old.yaml (say old2.yaml) containing only the backward-compatible changes that are requested in order to transform old.yaml into new.yaml.


Is this something that someone has already investigated?
I think that the diff computed by openapi-diff tool is a good basis to realise a tool like this.
Is someone else interested?

Thank you in advance for your feedback.

Do not hesitate to tell, if I am not clear enough about what I mean.

question: how to check if there are breaking changes from the command line?

I am planning to have a CI step/job in one of my projects to check for backwards incompatible changes between an old spec and new one.
Reading the documentation I am a bit confused about how to do that without parsing the output "result" message.

I am not interested in the output in that particular step, I only want openapi-diff to exit with 0 if there are no breaking changes or with an error if there was breaking changes.

Is there any way to accomplish that?

Btw, thanks for this amazing tool ❤️

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.