= API Backward Compatibility == Overview Apache Fineract enforces API backward compatibility using https://github.com/docktape/swagger-brake[swagger-brake], an automated tool that compares OpenAPI specifications between the base branch and a pull request to detect breaking changes. This ensures that existing API consumers are not broken when new changes are deployed. The check runs automatically on every pull request via the `verify-api-backward-compatibility.yml` GitHub Actions workflow. == How It Works The workflow follows these steps: . **Generate baseline spec** — Checks out the base branch (e.g. `develop`) and runs `./gradlew :fineract-provider:resolve` to generate the current OpenAPI specification. . **Generate PR spec** — Checks out the PR branch and generates its OpenAPI specification. . **Sanitize specs** — Patches known issues in the generated specs (e.g. missing `schema` entries in `requestBody` content) to prevent false positives. . **Compare** — Runs `checkBreakingChanges` via the swagger-brake Gradle plugin to compare old vs new specs. . **Report** — If breaking changes are found: ** A deduplicated summary table is written to the GitHub Actions Step Summary (visible on the workflow run page). ** A comment is posted on the PR (when token permissions allow). ** The full JSON report is archived as a build artifact. ** The workflow **fails**, blocking the PR. == Breaking Change Rules swagger-brake detects the following categories of breaking changes: === Endpoint Rules [cols="1,3", options="header"] |=== | Rule | Description | R001 | A stable (non-beta) API was changed to beta | R002 | An API path was deleted |=== === Request Rules [cols="1,3", options="header"] |=== | Rule | Description | R003 | A request media type (content type) was removed | R004 | A request parameter was deleted | R005 | An enum value was removed from a request parameter | R006 | A parameter location changed (e.g. `query` to `header`) | R007 | A parameter was made required | R008 | A parameter type was changed | R009 | An attribute was removed from a request body schema | R010 | A property type was changed in a request schema | R011 | An enum value was removed from a request body schema |=== === Response Rules [cols="1,3", options="header"] |=== | Rule | Description | R012 | A response code was deleted | R013 | A response media type was removed | R014 | An attribute was removed from a response schema | R015 | A property type was changed in a response schema | R016 | An enum value was removed from a response schema |=== === Constraint Rules [cols="1,3", options="header"] |=== | Rule | Description | R017 | A request parameter constraint was tightened (covers `maxLength`, `minLength`, `maximum`, `minimum`, `maxItems`, `minItems`, `uniqueItems`) |=== == Gradle Configuration The swagger-brake plugin is configured in `fineract-provider/build.gradle`: [source,groovy] ---- apply plugin: 'com.docktape.swagger-brake' swaggerBrake { newApi = "${project.buildDir}/resources/main/static/fineract.json" oldApi = findProperty('apiBaseline') ?: "${projectDir}/config/swagger/fineract-baseline.json" outputFormats = ['JSON'] outputFilePath = "${project.buildDir}/swagger-brake" deprecatedApiDeletionAllowed = true strictValidation = false } ---- === Configuration Options [cols="2,1,4", options="header"] |=== | Option | Default | Description | `newApi` | — | Path to the new (PR branch) OpenAPI spec. Generated by the `resolve` task. | `oldApi` | — | Path to the baseline OpenAPI spec. Provided via `-PapiBaseline` in CI, or falls back to a local file. | `outputFormats` | `['STDOUT', 'HTML']` | Report formats. We use `['JSON']` to avoid STDOUT spam and parse the report programmatically. | `outputFilePath` | `build/swagger-brake` | Directory for generated reports. | `deprecatedApiDeletionAllowed` | `true` | When `true`, removing a deprecated endpoint is NOT a breaking change. | `strictValidation` | `true` | When `false`, schemas without an explicit `type` field log a warning instead of failing. Set to `false` for Fineract because the generated spec has many type-less schemas. | `excludedPaths` | `[]` | List of path prefixes to skip (e.g. `['/v1/smscampaigns', '/v1/internal']`). Useful for excluding endpoints undergoing cleanup. | `ignoredBreakingChangeRules` | `[]` | List of rule codes to suppress entirely (e.g. `['R001']`). | `betaApiExtensionName` | `x-beta-api` | Vendor extension name for marking beta APIs. Beta endpoints can be freely modified without triggering violations. | `maxLogSerializationDepth` | `3` | Controls nested object serialization depth in logs (range 1-20). Increase if you see `StackOverflowError` from circular schema references. |=== == Running Locally To run the check locally, you need a baseline spec to compare against: [source,bash] ---- # 1. Generate the baseline from develop git stash git checkout develop ./gradlew :fineract-provider:resolve --no-daemon cp fineract-provider/build/resources/main/static/fineract.json /tmp/baseline.json git checkout - git stash pop # 2. Generate your current spec and compare ./gradlew :fineract-provider:checkBreakingChanges \ -PapiBaseline="/tmp/baseline.json" \ --no-daemon ---- The JSON report is written to `fineract-provider/build/swagger-brake/`. == Handling Breaking Changes === Intentional Breaking Changes If your PR intentionally introduces a breaking API change (e.g. removing a deprecated field): . The workflow will fail and report the violations. . Document the breaking change in the PR description with justification. . A committer will review and approve the PR with the understanding that the API contract is changing. === Excluding Paths Under Cleanup If you need to fix incorrect API annotations on endpoints that are not yet stable, use `excludedPaths` to temporarily exclude them from checking: [source,groovy] ---- swaggerBrake { excludedPaths = [ '/v1/smscampaigns', '/v1/email', ] } ---- Path exclusion is **prefix-based** — excluding `/v1/smscampaigns` will skip all paths starting with that prefix. Remove the exclusion once the cleanup is complete. === Marking Endpoints as Beta For endpoints that are experimental or under active development, mark them as beta in the Java code: [source,java] ---- @Operation( summary = "...", extensions = @Extension( properties = @ExtensionProperty(name = "x-beta-api", value = "true") ) ) ---- Beta endpoints can be freely modified, created, or removed without triggering violations. Promoting a beta endpoint to stable (removing the extension) is also non-breaking. However, demoting a stable endpoint to beta **is** a breaking change (R001). == Report Format When breaking changes are detected, the workflow produces a deduplicated summary table: [cols="1,2,1,3,1", options="header"] |=== | Rule | Description | Detail | Affected endpoints | Count | R014 | Response attribute removed | `totalOverpaid` | `GET /v1/loans`, `GET /v1/loans/{loanId}`, `GET /v1/loans/external-id/{loanExternalId}` | 3 |=== The deduplication groups violations by rule code and affected attribute, collapsing multiple endpoint occurrences into a single row. This is important because a single schema change (e.g. removing a field from a shared response type) can generate dozens of raw violations — one per endpoint that uses that schema. == Tool Reference * **Tool**: https://github.com/docktape/swagger-brake[swagger-brake] v2.7.0 * **Gradle plugin**: `com.docktape.swagger-brake` * **Documentation**: https://docktape.github.io/swagger-brake/ * **License**: Apache 2.0