# diff

Source: https://apigenie.pl/docs/reference/cli/diff

Compares two versions of an OpenAPI, Swagger 2.0 or AsyncAPI schema, of a
Protobuf (`.proto`) file, or of a GraphQL schema, and reports the changes and
their severity.

## Usage

```sh
apigenie diff [OPTIONS] SOURCE_FILE DEST_FILE
```

## Options

| Option | Description |
| ------ | ----------- |
| `--config` | Path to the configuration file. |
| `--report` | What to print: `status`, `changelog` (default) or `detailed`. |
| `--formatter` | `html`, `json`, `markdown`, `stylish` (default), `unix` or `visualstudio`. |
| `--update-version` | Updates `info.version` of the destination file to the smallest version compatible with the changes (a new major version for breaking changes, minor for non-breaking and patch for non-functional ones), keeping the rest of the file intact. |
| `--today` | The date of the comparison (`YYYY-MM-DD`) for the `sunset_passed` condition of the OpenAPI rules: a deprecated element whose `x-sunset` date is on or before it may go. Defaults to the current date; it has no effect on the other formats. |

The command exits with status 1 when there are breaking changes, 0
otherwise, and 2 when a file can't be read or compared or the configuration is
invalid (the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries). With `--update-version` it prints nothing and exits with 0.
Protobuf and GraphQL files have no version, so `--update-version` is rejected
for them.

Both files must be OpenAPI 3.x documents, both Swagger 2.0 documents, both
AsyncAPI 3.x documents, both Protobuf files or both GraphQL files.

## Swagger documents

A file is treated as Swagger when its root object has a `swagger` field. For
Swagger documents the differ compares the host, the base
path, the schemes, the consumed and produced media types, paths, operations,
parameters, responses and their headers and examples, schemas, security
definitions, security requirements and tags.

Parameters are matched by their location and name, e.g. `query:limit`, so a
parameter moved from the query to a header is a removed and an added one. An
operation has at most one body parameter, whose name is only documentation:
it is matched as `body`. Security requirements are alternatives, matched by
the names of their schemes, so their order doesn't matter. References to
definitions, parameters and responses are followed, and their changes are
reported where they are used as well as where they are defined.

As for OpenAPI documents, the severity of a change to a schema depends on the
direction of the data: parameters, including body parameters, and consumed
media types get the `request` rules, responses and produced media types the
`response` ones. Definitions get the stricter of the two, and read-only
properties don't matter in requests.

The changelog report lists the changed metadata (including the host, the base
path, the schemes, the media types and the security of the whole API), the
added, removed and modified operations, with the changes of each modified one,
and the paths whose own parameters changed.

## AsyncAPI documents

A file is treated as AsyncAPI when its root object has an `asyncapi` field. For
AsyncAPI documents the differ compares servers, channels, operations,
messages with their payloads and headers, security schemes, bindings and
components. Bindings of HTTP, WebSockets, Kafka, AMQP and MQTT are compared
property by property, those of other protocols as whole values; schemas in
formats other than AsyncAPI and JSON Schema, e.g. Avro, are compared as whole
values too.

The severity of a change to a message depends on who sends it. The messages
of `receive` operations are sent by other parties to the application, those
of `send` operations by the application. A reply flows the other way than
the messages of its operation. For example, a new required payload property
breaks the senders of a received message, while the receivers of a sent
message can ignore it. Messages used in both directions, or by no operation,
get the stricter severity.

The changelog report lists the changed metadata, and the added, removed and
modified servers, channels and operations, with the changes of each modified
one. Changes of components are shown where the components are used.

## Protobuf files

Files with the `.proto` extension are compared as Protobuf (proto3) files;
both files must be Protobuf files. The differ compares the package, imports
and options of the files, and their messages, enums, services and extend
blocks, down to fields, oneofs, enum values, methods, reserved numbers and
names, options and comments.

Declarations are matched the way the programs that use them find them:

- messages, enums, services, oneofs and methods by their names;
- fields and enum values by their numbers, which identify them in the encoded
  data. A renamed field is a modified field; a field that got another number
  is a removed and an added one;
- extend blocks by the type they extend;
- reserved numbers as sets, so `reserved 1, 2, 3;` equals `reserved 1 to 3;`.

Type names are resolved before they are compared, so `Address`,
`User.Address` and `.shop.v1.User.Address` are the same type when they name
the same message, and renaming the package is one change rather than a change
of every type. The files are compared on their own: the files they import are
not read, and the types those declare are compared by their names. To compare
imported declarations as well, [bundle](/docs/reference/cli/bundle) both
versions first.

With the `recommended` rules, a change is breaking when it breaks the programs
exchanging encoded messages (the binary or the JSON encoding) or the code
compiled against the generated classes: removing or renaming a declaration,
changing the type, the label or the oneof of a field, the types or the
streaming of a method, the package, or an option that decides the names of the
generated code (e.g. `go_package`). Adding declarations is non-breaking, and
comments are non-functional. Removing a field or an enum value is
non-breaking when the destination file reserves its number, which is how
Protobuf files retire them.

Changes are reported at their positions in the destination file; what has
been removed is reported at the closest enclosing declaration that is still
there. The changelog report lists the changed metadata (package, imports,
file options), and the added, removed and modified messages, enums, services
and extensions, with the changes of each modified one:

```text
shop.proto
  19  1      breaking  Modified message 'Customer'
  19  1  non-breaking    Removed field 'fax'
  25  3      breaking    Modified field 'full_name'
  25  3      breaking      Modified field name
  36  3  non-breaking    Modified message 'Address'
  39  5  non-breaking      Added field 'country'
```

## GraphQL files

Files with the `.graphql`, `.graphqls` or `.gql` extension are compared as
GraphQL schemas; both files must be GraphQL files. The differ compares their
type systems: the root operation types, the types of every kind with their
fields, arguments, input fields, default values, interfaces, union members and
enum values, the directive definitions, the directives applied to all of
these, and descriptions. Operations and fragments are ignored.

Type extensions (`extend type`, `extend schema`, ...) are merged into what
they extend, so moving a field from a type into an extension, or reordering
definitions, is not a change. Everything is matched by its name. When a
schema has no `schema` definition, the types named `Query`, `Mutation` and
`Subscription` are its root operation types, so adding a `schema` definition
that names them changes nothing. The files are compared on their own: types
defined elsewhere are compared by their names.

With the `recommended` rules, a change is breaking when operations that were
valid become invalid, or when clients can no longer read their results, as
graphql-js's `findBreakingChanges()` decides it:

- removing a type, a field, an argument, an input field, an enum value, a
  union member, an implemented interface, a directive or one of its
  locations, or changing the kind of a type or a root operation type;
- adding a required argument or input field: one with a non-null type and no
  default value;
- changing a type in an incompatible way. The type of a field may become
  stricter (`String` to `String!`, `[String]` to `[String!]!`); the type of an
  argument or input field may become looser (`String!` to `String`). Named
  types must stay the same, and lists must stay lists;
- removing the default value of a non-null argument or input field, which
  operations that omitted it relied on;
- making a directive non-repeatable, or adding `@oneOf` to an input type.

Additions are non-breaking, as are changed default values and applied
directives such as `@deprecated`; descriptions are non-functional.

Changes are reported at their positions in the destination file; what has
been removed is reported at the closest enclosing definition that is still
there. The changelog report lists the changed metadata (root operation types,
the description and the directives of the schema), and the added, removed and
modified scalars, objects, interfaces, unions, enums, input objects and
directives, with the changes of each modified one:

```text
shop.graphql
  36  1      breaking  Modified object 'Customer'
  44  3  non-breaking    Added field 'phone'
  36  1      breaking    Removed field 'fax'
  38  3  non-breaking    Modified field 'name'
  38  3  non-breaking      Modified field type
```

## JSON output

With `--formatter json` every report is an object whose `formatVersion` member
is the version of the format. It changes only when a member is removed or
changes meaning (see [Compatibility](/docs/reference/cli/compatibility)).
The JSON Schema of the three reports is
[apigenie-diff-report.json](https://apigenie.pl/schemas/apigenie-diff-report.json).

- `--report status`: `{ "formatVersion": 1, "status": "breaking" }`.
- `--report changelog`: `formatVersion`, `metadata` (which top-level fields
  changed) and groups of added, modified and removed items named after the
  elements of the format, e.g. `added`, `modified` and `removed` operations
  for OpenAPI or `channels`, `operations` and `servers` for AsyncAPI.
- `--report detailed`: `formatVersion` and `changes`, every change in document
  order:

```json5
{
  "formatVersion": 1,
  "changes": [
    {
      "type": "key_removed",
      "severity": "breaking",
      "target": "paths",
      "config_key": "paths",
      "key": "/pets/{id}",
      "line": 9,
      "column": 3
    }
  ]
}
```

API Genie 0.9 and earlier wrote the reports without `formatVersion`, and the
detailed one as the bare `changes` array.

## Configuration

The differ is configured in the `commands.diff` block of the
[configuration file](/docs/reference/cli/configuration) given with
`--config`. The block itself configures the differ for OpenAPI documents; its
`swagger`, `asyncapi`, `protobuf` and `graphql` sections configure the other
formats. Without `--config`, or without a section for a format, the format is
compared with its `recommended` rules. A block or section starts only from the
rulesets in its `extends`, so list `recommended` there to change only some of
the recommended rules.

### diff

The configuration of the differ for OpenAPI documents. It accepts these keys:

| Key | Description |
| --- | ----------- |
| `extends` | Rulesets to start from: `recommended`. |
| `rules` | Severities of the changes of the properties of each node type. |
| `request`, `response` | Overlays of `rules` for the data sent by API consumers (parameters, request bodies) and by API providers (responses). Inside callbacks and webhooks the directions are swapped. A component schema takes the direction of the places it is used in (following the references between schemas, in either document); one used both ways, or nowhere, gets the stricter severity. Only node types and properties, no `defaults` or `extensions`. |
| `overrides` | A list of `where` (`location`: one pattern or a list of them, and optionally `direction`: `request` or `response`) and `rules` applied to the matching locations. |

The rules set a severity (`breaking`, `non_breaking`, `non_functional`,
`none` to ignore the change, or `nested` to take the highest severity of the
changes inside) for the properties of each node type:

| Node type | Properties |
| --------- | ---------- |
| `openapi` | `openapi`, `info`, `jsonSchemaDialect`, `servers`, `paths`, `webhooks`, `components`, `security`, `tags`, `externalDocs` |
| `info` | `title`, `summary`, `description`, `termsOfService`, `contact`, `license`, `version` |
| `server` | `url`, `description`, `variables` |
| `serverVariable` | `enum`, `default`, `description` |
| `components` | `schemas`, `responses`, `parameters`, `examples`, `requestBodies`, `headers`, `securitySchemes`, `links`, `callbacks`, `pathItems` |
| `pathItem` | `summary`, `description`, `parameters`, `operations`, `servers` |
| `operation` | `tags`, `summary`, `operationId`, `description`, `externalDocs`, `deprecated`, `parameters`, `requestBody`, `responses`, `callbacks`, `security`, `servers` |
| `parameter` | `name`, `in`, `description`, `required`, `deprecated`, `allowEmptyValue`, `style`, `explode`, `allowReserved`, `schema`, `content`, `example`, `examples` |
| `header` | the properties of `parameter`, except `in` and `style` |
| `requestBody` | `description`, `content`, `required` |
| `response` | `description`, `headers`, `content`, `links` |
| `mediaType` | `schema`, `encoding`, `example`, `examples` |
| `encoding` | `contentType`, `headers`, `style`, `explode`, `allowReserved` |
| `link` | `description`, `server`, `operationRef`, `operationId`, `parameters`, `requestBody` |
| `example` | `summary`, `description`, `value`, `externalValue` |
| `reference` | `$ref`, `summary`, `description` |
| `schema` | `id`, `dialect`, `type`, `properties`, `required`, `readOnly`, `writeOnly`, `enum`, `discriminator`, `xml`, and the other JSON Schema keywords |
| `discriminator` | `propertyName`, `mapping` |
| `securityScheme` | `name`, `description`, `type`, `in`, `scheme`, `bearerFormat`, `flows`, `openIdConnectUrl` |
| `oauthFlows` | `implicit`, `password`, `clientCredentials`, `authorizationCode` |
| `oauthFlow` | `authorizationUrl`, `tokenUrl`, `refreshUrl`, `scopes` |
| `paths`, `responses`, `callback`, `securityRequirement` | none: the node type itself is set to the rules of its items, e.g. `paths: { remove_item: breaking }` |
| `contact`, `license`, `tag`, `externalDocumentation`, `xml` | their fields |

A property is set to a severity, or to separate severities for `add`,
`remove` and `modify`; lists and maps take `add_item`, `remove_item`,
`modify_item`, `add_first_item` and `remove_last_item`. Properties that every
node has, e.g. `parameter.name` or `schema.readOnly`, can only be modified, so
they take a severity or `modify`. A severity can depend on a condition:
`{ if: CONDITION, then: SEVERITY, else: SEVERITY }`, where the condition is
`required` (the added or removed item is required), `deprecated` (it is
deprecated), `sunset_passed` (it is deprecated and its `x-sunset` date is on or
before the date of the comparison, `--today`), or `true` / `false` (the new
value of a boolean property).
The `extensions` property of a node type configures its `x-` extensions by
name, with `*` for the extensions that are not listed, and
`defaults.extensions` applies to all node types.

`sunset_passed` is how the `recommended` rules let a deprecated element go once
its window has closed: the `remove_item` of operations, path items (every
operation deprecated and sunset), parameters, component schemas and
parameters, response properties and their `required` items, and request enum
values is non-breaking when the condition holds and breaking otherwise. An
`x-sunset` is an RFC 3339 date (`2027-01-06`) or date-time next to
`deprecated: true`; a property may carry them where it is used, as siblings of
its `$ref`, or where it is defined; an enum value is deprecated when the
schema's `x-deprecated-enum` array lists it, and the schema's `x-sunset` says
when it may go. Deprecating itself (`deprecated: true` on an operation, a
parameter, a header or a schema) is non-breaking: it announces the removal,
and the removal is the breaking change. [`apigenie evolve
plan`](/docs/reference/cli/evolve) writes documents that follow this
convention.

```yaml
version: '1.0'
commands:
  diff:
    extends: [recommended]
    rules:
      defaults:
        extensions: non_functional
      pathItem:
        operations:
          # deprecated operations may be removed
          remove_item:
            if: deprecated
            then: non_breaking
            else: breaking
    response:
      schema:
        properties:
          # clients reject unknown properties
          add_item: breaking
    overrides:
      - where:
          location: /paths/~1internal~1*/**
        rules:
          operation:
            parameters: non_functional
```

The locations of `overrides` are JSON pointers into the document, where `*`
matches any part of a segment and `**` any number of segments, e.g.
`/paths/~1pets/get/parameters/query:limit`,
`/paths/**/properties/name` or `/components/schemas/Pet/**`. The Swagger,
AsyncAPI, Protobuf and GraphQL sections accept the same severities, rule
forms and location patterns.

### diff.protobuf

The configuration of the differ for Protobuf files. It accepts `extends`
(`recommended`), `rules` and `overrides`; there are no `request` and
`response` overlays. The OpenAPI settings of the `diff` block do not apply to
Protobuf files; without this block, Protobuf files are compared with the
`recommended` rules.

The rules set a severity (`breaking`, `non_breaking`, `non_functional`,
`none` to ignore the change, or `nested` to take the highest severity of the
changes inside) for the properties of each node type:

| Node type | Properties |
| --------- | ---------- |
| `file` | `package`, `imports`, `messages`, `enums`, `services`, `extends`, `comment` |
| `import` | `kind` (`public` or `weak`) |
| `message` | `fields`, `oneofs`, `messages`, `enums`, `extends`, `reservedNumbers`, `reservedNames`, `comment` |
| `field` | `name`, `type`, `label`, `oneof`, `comment` |
| `oneof` | `comment` |
| `enum` | `values`, `reservedNumbers`, `reservedNames`, `comment` |
| `enumValue` | `name`, `comment` |
| `service` | `methods`, `comment` |
| `method` | `inputType`, `outputType`, `clientStreaming`, `serverStreaming`, `comment` |
| `extend` | `fields`, `comment` |

Map fields and the fields of oneofs are configured as `field`. The `comment`
of a declaration is its leading and trailing comment, that of a file the
comment of its `syntax` statement. A property is
set to a severity, or to separate severities for `add`, `remove` and `modify`
(lists: `add_item`, `remove_item`, `modify_item`, `add_first_item`,
`remove_last_item`). Properties that every declaration has, e.g. the `name`,
`type` and `label` of a field or the types of a method, take only a severity
or `modify`. A severity can depend on a condition:

| Condition | Holds when |
| --------- | ---------- |
| `reserved` | the number of the removed field or enum value is reserved in the destination file |
| `deprecated` | the added, removed or modified field or enum value has `deprecated = true` |
| `wire_compatible` | the old and the new type (or label) of a field are encoded the same way, e.g. `int32` and `int64`, `string` and `bytes`, or a field with and without `optional` |
| `true`, `false` | the new value of a boolean option is `true` or `false` (the old value for a removed option) |

Options are configured by their names as written in the file, in the
`options` property of `file`, `message`, `field`, `oneof`, `enum`,
`enumValue`, `service` and `method`; `*` stands for the options that are not
listed, and `defaults.options` applies to all node types.

```yaml
version: '1.0'
commands:
  diff:
    protobuf:
      extends: [recommended]
      rules:
        defaults:
          options: non_functional
        field:
          # only the encoded data matters: names and compatible types may change
          name: non_breaking
          type:
            modify:
              if: wire_compatible
              then: non_breaking
              else: breaking
          options:
            json_name: non_breaking
            "(validate.rules)": breaking
        message:
          fields:
            # removing a field is breaking even when its number is reserved
            remove_item: breaking
      overrides:
        - where:
            location: /messages/Internal*/**
          rules:
            message:
              fields: none
```

The locations of `overrides` follow the structure of the file, with
declarations by their names, e.g. `/messages/User/fields/email/type`,
`/messages/User/messages/Address/**`, `/enums/Status/values/*` or
`/services/Users/methods/Get`.

### diff.swagger

The configuration of the differ for Swagger 2.0 documents. It accepts the same
keys as the `diff` block itself: `extends`, `rules`, the `request` and
`response` overlays and `overrides`. The properties are those of the Swagger
node types:

| Node type | Properties |
| --------- | ---------- |
| `swagger` | `swagger`, `info`, `host`, `basePath`, `schemes`, `consumes`, `produces`, `paths`, `definitions`, `parameters`, `responses`, `securityDefinitions`, `security`, `tags`, `externalDocs` |
| `info` | `title`, `description`, `termsOfService`, `contact`, `license`, `version` |
| `pathItem` | `$ref`, `parameters`, `operations` |
| `operation` | `tags`, `summary`, `description`, `externalDocs`, `operationId`, `consumes`, `produces`, `parameters`, `responses`, `schemes`, `deprecated`, `security` |
| `parameter` | `name`, `in`, `description`, `required`, `schema`, `type`, `format`, `allowEmptyValue`, `items`, `collectionFormat`, and the validation keywords |
| `response` | `description`, `schema`, `headers`, `examples` |
| `header`, `items` | `type`, `format`, `items`, `collectionFormat`, the validation keywords, and `description` for headers |
| `schema` | `title`, `description`, `type`, `format`, the validation keywords, `items`, `allOf`, `properties`, `required`, `maxProperties`, `minProperties`, `additionalProperties`, `discriminator`, `readOnly`, `xml`, `externalDocs`, `example` |
| `securityScheme` | `type`, `description`, `name`, `in`, `flow`, `authorizationUrl`, `tokenUrl`, `scopes` |
| `securityRequirement` | `scopes` |
| `contact`, `license`, `tag`, `externalDocumentation`, `xml` | their fields |

The validation keywords are `default`, `multipleOf`, `maximum`,
`exclusiveMaximum`, `minimum`, `exclusiveMinimum`, `maxLength`, `minLength`,
`pattern`, `maxItems`, `minItems`, `uniqueItems` and `enum`. For
`additionalProperties`, `add` applies when additional properties become
allowed, `remove` when they become forbidden, and `modify` to changes of their
schema. The OpenAPI settings of the `diff` block do not apply to Swagger
documents; without this block, Swagger documents are compared with the
`recommended` rules.

```yaml
version: '1.0'
commands:
  diff:
    extends: [recommended]
    swagger:
      extends: [recommended]
      rules:
        swagger:
          host: non_breaking
        pathItem:
          operations:
            # deprecated operations may be removed
            remove_item:
              if: deprecated
              then: non_breaking
              else: breaking
      overrides:
        - where:
            location: /paths/~1internal~1*/**
          rules:
            operation:
              parameters: non_functional
```

The locations of `overrides` follow the structure of the document, with
parameters by their keys and security requirements by the names of their
schemes, e.g. `/paths/~1pets/get/parameters/query:limit/maximum`,
`/paths/~1pets/post/parameters/body/schema/properties/name` or
`/definitions/Pet/**`.

### diff.graphql

The configuration of the differ for GraphQL schemas. It accepts `extends`
(`recommended`), `rules` and `overrides`, like `diff.protobuf`. The OpenAPI
settings of the `diff` block do not apply to GraphQL files; without this
block, GraphQL files are compared with the `recommended` rules. The node
types and their properties are:

| Node type | Properties |
| --------- | ---------- |
| `schema` | `query`, `mutation`, `subscription`, `types`, `directiveDefinitions`, `description` |
| `type` | `kind` (of types of all kinds) |
| `scalar` | `description` |
| `object` | `interfaces`, `fields`, `description` |
| `interface` | `interfaces`, `fields`, `description` |
| `union` | `members`, `description` |
| `enum` | `values`, `description` |
| `enumValue` | `description` |
| `inputObject` | `fields`, `description` |
| `field` | `type`, `arguments`, `description` |
| `argument` | `type`, `defaultValue`, `description` |
| `inputField` | `type`, `defaultValue`, `description` |
| `directive` | `arguments`, `repeatable`, `locations`, `description` |

The arguments of directive definitions are configured as `argument`. A
severity can depend on a condition:

| Condition | Holds when |
| --------- | ---------- |
| `required` | the added or removed argument or input field has a non-null type and no default value; for a default value, the type of the argument or input field is non-null |
| `compatible` | the old and the new type of a field, argument or input field are compatible (see above) |
| `deprecated` | the added or removed field, argument, input field or enum value has `@deprecated` |
| `true`, `false` | the new value of `repeatable`, or of the only argument of an applied directive when it is a boolean (the old value for a removed directive) |

Applied directives are configured by their names, without `@`, in the
`directives` property of `schema`, `scalar`, `object`, `interface`, `union`,
`enum`, `enumValue`, `inputObject`, `field`, `argument` and `inputField`;
`*` stands for the directives that are not listed, and `defaults.directives`
applies to all node types. Directives are compared with their arguments,
whatever the order of the arguments and the form of the strings; the uses of
a repeatable directive are compared together.

```yaml
version: '1.0'
commands:
  diff:
    graphql:
      extends: [recommended]
      rules:
        defaults:
          directives: non_functional
        field:
          directives:
            # authorization changes matter to clients
            auth: breaking
        enum:
          values:
            # deprecated values can be removed
            remove_item:
              if: deprecated
              then: non_breaking
              else: breaking
      overrides:
        - where:
            location: /types/Internal*/**
          rules:
            object:
              fields: none
```

The locations of `overrides` follow the structure of the schema, with
definitions by their names, e.g. `/types/User/fields/email/type`,
`/types/Query/fields/users/arguments/first/defaultValue`,
`/types/Status/values/*`, `/directiveDefinitions/auth/locations/*` or
`/query`.

### diff.asyncapi

The configuration of the differ for AsyncAPI documents. It accepts the same
keys as the `diff` block itself: `extends`, `rules`, `overrides`, and the
`receive` and `send` overlays in place of `request` and `response` (also as
the `direction` of an override). The
properties are those of the AsyncAPI node types, e.g. `channel.address` or
`message.payload`. The OpenAPI settings of the `diff` block do not apply to
AsyncAPI documents; without this block, AsyncAPI documents are compared with
the `recommended` rules.

```yaml
version: '1.0'
commands:
  diff:
    extends: [recommended]
    asyncapi:
      extends: [recommended]
      rules:
        channel:
          address: non_breaking
      send:
        schema:
          properties:
            # receivers reject unknown properties
            add_item: breaking
      overrides:
        - where:
            location: /channels/internal/**
          rules:
            message:
              payload: non_functional
```
