Skip to content

feat: add API definition PATCH for v4 APIs#15830

Draft
ankita-gupta21 wants to merge 4 commits intomasterfrom
v4-api-definition-patch
Draft

feat: add API definition PATCH for v4 APIs#15830
ankita-gupta21 wants to merge 4 commits intomasterfrom
v4-api-definition-patch

Conversation

@ankita-gupta21
Copy link
Copy Markdown
Contributor

@ankita-gupta21 ankita-gupta21 commented Mar 20, 2026

Issue`

`
https://gravitee.atlassian.net/browse/APIM-XXX

Description

Adds JSON Patch (RFC 6902) support for V4 HTTP Proxy APIs on
PATCH /management/v2/organizations/{orgId}/environments/{envId}/apis/{apiId}/definition, aligned with the same export shape as GET .../apis/{apiId}/_export/definition and the same update semantics as PUT .../apis/{apiId} for the api payload.

Additional context

  • Partial definition updates via JSON Patch on the full export document (envelope + api, etc.).
  • dryRun=true: apply patch in memory and return patched JSON without persisting.
  • If-Match: optional; must match current revision ETag (412 on mismatch), consistent with V4 PUT.
  • Scope: V4 + type=proxy only; other kinds return 400 with a clear message.
  • Persisted changes: only the api object is mapped to UpdateApiV4 and saved through the existing updateHttpApiV4 path (same validation/rules as PUT). Envelope fields (plans, members, pages, etc.) are not applied from the patch—dedicated endpoints still apply.
  • Permissions: API_DEFINITION[UPDATE] or API_GATEWAY_DEFINITION[UPDATE] (same OR rule as V4 PUT); listener handling unchanged for users without gateway-definition update (unless PO/admin).

Some Example:

Success – dry run (200, body is patched JSON string)

No persistence; If-Match optional.

curl -sS -X PATCH \
  "${BASE}/management/v2/environments/${ENV}/apis/${API_ID}/definition?dryRun=true" \
  -H 'Content-Type: application/json' \
  -u 'admin:admin' \
  --data '[{"jsonPath":"$.api.name","operation":"REPLACE","value":"test-patch-api-v4-dry-run"}]'

Success – persist (200, full export JSON + new ETag)

Use a current ETag from GET .../apis/{apiId} (or last PUT/PATCH response). Quotes around the tag value are common.
ETAG='"1700000000000"' # example: from GET /apis/{apiId} → ETag header

curl -sS -D - -o /tmp/patch-body.json -X PATCH \
  "${BASE}/management/v2/environments/${ENV}/apis/${API_ID}/definition?dryRun=false" \
  -H 'Content-Type: application/json' \
  -H "If-Match: ${ETAG}" \
  -u 'admin:admin' \
  --data '[{"jsonPath":"$.api.name","operation":"REPLACE","value":"test-patch-api-v4"}]'

Check ETag in response headers and body in /tmp/patch-body.json.

Failure – precondition failed (412)

Wrong or stale If-Match:

curl -sS -o /dev/null -w '%{http_code}\n' -X PATCH \
  "${BASE}/management/v2/environments/${ENV}/apis/${API_ID}/definition?dryRun=false" \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "0"' \
  -u 'admin:admin' \
  --data '[{"jsonPath":"$.api.name","operation":"REPLACE","value":"will-not-apply"}]'

Expected: 412.

Failure – unsupported API kind (400)

Use an API that is not V4 HTTP Proxy (e.g. Native V4). Same payload:

curl -sS -X PATCH \
  "${BASE}/management/v2/environments/${ENV}/apis/${NATIVE_OR_MESSAGE_API_ID}/definition" \
  -H 'Content-Type: application/json' \
  -u 'admin:admin' \
  --data '[{"jsonPath":"$.api.name","operation":"REPLACE","value":"x"}]'

Expected: 400 with message about V4 HTTP Proxy only.

“Failure” – JSON Patch test op mismatch (204, no body)

Export must contain api.name matching reality; TEST uses a wrong expected value:

curl -sS -o /dev/null -w '%{http_code}\n' -X PATCH \
  "${BASE}/management/v2/environments/${ENV}/apis/${API_ID}/definition" \
  -H 'Content-Type: application/json' \
  -u 'admin:admin' \
  --data '[{"jsonPath":"$.api.name","operation":"TEST","value":"not-the-real-name"}]'
Screen.Recording.2026-03-22.at.2.01.59.PM.mov

@ankita-gupta21 ankita-gupta21 force-pushed the v4-api-definition-patch branch from 2b75a6e to 83a2ce9 Compare March 20, 2026 09:51
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature by providing a PATCH endpoint for V4 API definitions. This allows for more efficient and granular updates to API configurations, aligning with modern API management practices. The implementation includes robust error handling, concurrency control, and permission checks, ensuring that API definitions can be modified safely and precisely, particularly for V4 HTTP Proxy APIs.

Highlights

  • New PATCH Endpoint for V4 API Definitions: Introduced a new REST endpoint /apis/{apiId}/definition that supports HTTP PATCH requests for partially updating V4 API definitions using JSON Patch (RFC 6902).
  • JSON Patch (RFC 6902) Support: The new endpoint allows programmatic updates to API definitions by applying a collection of JSON Patch operations, enabling granular modifications without requiring a full API definition payload.
  • Dry Run Functionality: Added a dryRun query parameter to the PATCH endpoint, allowing users to preview the result of applying JSON patches without persisting any changes to the API definition.
  • ETag-based Concurrency Control: Implemented support for the If-Match HTTP header to ensure optimistic concurrency control, preventing accidental overwrites of API definitions by requiring the client to provide the current ETag (revision timestamp).
  • V4 HTTP Proxy API Specificity: The PATCH operation is specifically designed for V4 HTTP Proxy APIs, returning a 400 Bad Request for other API types (e.g., Message, Native/TCP, Federated).
  • Permission Handling for Listeners: The update logic for V4 HTTP Proxy APIs now includes specific permission checks for modifying listeners. If the caller lacks API_GATEWAY_DEFINITION[UPDATE] permission and is not the primary owner or admin, listener changes from the patch are ignored.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new REST endpoint, PATCH /apis/{apiId}/definition, to enable partial updates of V4 HTTP Proxy API definitions using JSON Patch (RFC 6902). The changes include a new ApiDefinitionResource to handle these operations, a new mapping method in ApiMapper to convert patched API exports to UpdateApiV4 objects, and integration of this resource into the existing ApiResource. Comprehensive unit tests have been added for the new endpoint, covering scenarios such as dry runs, optimistic locking with If-Match headers, permission checks, and validation for V4 HTTP Proxy API types. Additionally, the test configuration has been updated to provide the JsonPatchService bean.

@ankita-gupta21 ankita-gupta21 force-pushed the v4-api-definition-patch branch from 83a2ce9 to 50f7c16 Compare March 20, 2026 12:33
Copy link
Copy Markdown
Contributor

@vikrantgravitee vikrantgravitee left a comment

Choose a reason for hiding this comment

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

We could have Patch method in ApiResource

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support in Management API v2 for partially updating V4 HTTP Proxy API definitions via a JSON-patch-like mechanism on the exported definition document, with optional persistence (dryRun) and revision control (If-Match).

Changes:

  • Add PATCH /apis/{apiId}/definition endpoint for V4 HTTP Proxy APIs, integrating export → patch → map-to-update → update → re-export flow.
  • Introduce REST integration tests covering dry-run, persistence, If-Match handling, permission failures, and unsupported API kinds.
  • Wire JsonPatchService into the Management v2 REST test Spring context.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
.../spring/ResourceContextConfiguration.java Adds a JsonPatchService bean for REST integration tests.
.../resource/api/ApiResource_PatchApiDefinitionTest.java New integration tests validating PATCH definition behavior (200/204/400/403/412).
.../resource/api/ApiResource.java Implements PATCH /definition for V4 HTTP Proxy API definitions, including dry-run and ETag support.
.../mapper/ApiMapper.java Adds mapping from ApiV4 to UpdateApiV4 to reuse existing V4 update path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ankita-gupta21 ankita-gupta21 force-pushed the v4-api-definition-patch branch from cdd6f8c to 76f775d Compare March 23, 2026 14:56
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +569 to +571
case PatchApiDefinitionUseCase.Result.DryRun(String json) -> Response.ok(
ImportExportApiMapper.INSTANCE.definitionToExportApiV4(json)
).build();
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The PR description/examples state that dryRun=true returns the patched JSON as a raw string, but the implementation returns an ExportApiV4 JSON object (and the OpenAPI spec also describes ExportApiV4). Please align the PR description/examples with the actual response shape (or adjust the implementation/spec if the raw string response is required).

Copilot uses AI. Check for mistakes.
Comment on lines +595 to +610
responses:
"200":
description: |-
Patched API definition in `ExportApiV4` form (same shape as `GET .../_export/definition`).

With `dryRun=true`, the body is a preview only and is not persisted; `ETag` and `Last-Modified` are omitted, like the export endpoint.

With `dryRun=false`, the API is updated and `ETag` / `Last-Modified` reflect the stored API after persist.
content:
application/json:
schema:
$ref: "#/components/schemas/ExportApiV4"
"204":
description: No change applied (for example JSON Patch `test` operation did not match)
default:
$ref: "#/components/responses/Error"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The description mentions If-Match and ETag/Last-Modified behavior, but the OpenAPI operation does not define the optional If-Match request header nor the ETag/Last-Modified response headers for the persisted (non-dry-run) case. Please add these headers to the operation so generated clients and API docs reflect the concurrency semantics and caching metadata.

Copilot uses AI. Check for mistakes.
}

var patchOutput = patchApiDefinitionUseCase.execute(new PatchApiDefinitionUseCase.Input(apiId, getAuditInfo(), patches, dryRun));

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

If-Match is evaluated against currentEntity.getUpdatedAt(), but the patch is applied to an export fetched separately inside PatchApiDefinitionUseCase. If the API is updated between these two reads, the request can pass the precondition check yet apply the patch on a newer (or different) revision than the one validated by the ETag, weakening concurrency control. Consider evaluating If-Match against the same revision used to build the export (e.g., export definition’s api.updatedAt) and/or asserting that currentEntity.updatedAt matches the exported definition’s updatedAt before applying/persisting the patch.

Suggested change
// Re-check concurrency after computing the patch result but before applying it
final GenericApiEntity latestEntity = getGenericApiEntityById(apiId, false, false, false, false);
if (!latestEntity.getUpdatedAt().equals(currentEntity.getUpdatedAt())) {
// The API has been modified since the initial If-Match validation; reject with 412
return Response
.status(Response.Status.PRECONDITION_FAILED)
.tag(Long.toString(latestEntity.getUpdatedAt().getTime()))
.lastModified(latestEntity.getUpdatedAt())
.build();
}

Copilot uses AI. Check for mistakes.
Comment on lines +3799 to +3805
description: Gravitee JSON Patch entry (JSON Pointer path and operation).
required:
- jsonPath
properties:
jsonPath:
type: string
description: JSON Pointer path within the exported definition document.
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The schema/documentation calls jsonPath a “JSON Pointer path”, but the implementation uses Jayway JsonPath expressions (e.g., $.api.name). This is likely to confuse API consumers expecting RFC 6901 pointers. Please update the description text to explicitly say JSONPath (Jayway) syntax and, if relevant, clarify that this is Gravitee’s custom patch format (not RFC 6902 path/op).

Suggested change
description: Gravitee JSON Patch entry (JSON Pointer path and operation).
required:
- jsonPath
properties:
jsonPath:
type: string
description: JSON Pointer path within the exported definition document.
description: Gravitee JSON Patch entry using JSONPath (Jayway) expressions for jsonPath (custom format, not RFC 6902/JSON Pointer).
required:
- jsonPath
properties:
jsonPath:
type: string
description: JSONPath (Jayway) expression within the exported definition document.

Copilot uses AI. Check for mistakes.
Comment on lines +572 to +577
description: |-
Apply JSON Patch operations to the exported API definition for a V4 HTTP Proxy API.

The document patched is the same shape as returned by `GET .../_export/definition`. When `dryRun` is false, the API is updated using the same rules as `PUT .../apis/{apiId}` for a V4 API (including gateway-definition permission constraints on listeners).

User must have API_DEFINITION[UPDATE] or API_GATEWAY_DEFINITION[UPDATE] permissions.
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The endpoint persists changes by mapping and saving only the api object (envelope fields like plans/members/pages are ignored), but the OpenAPI description currently reads as if the whole exported definition document is updated when dryRun=false. To avoid misleading clients, please document that only api is applied on persist and that patching other envelope sections is preview-only / ignored for persistence.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not right place to have usecase

@ankita-gupta21 ankita-gupta21 force-pushed the v4-api-definition-patch branch from 007006b to 795895b Compare March 28, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants