Skip to content

Conversation

@thivindu
Copy link
Contributor

@thivindu thivindu commented Nov 19, 2025

Purpose

The Management Portal requires API creation via Contract Flow via URL and OpenAPI definition file upload. Therefore, the provided OpenAPI definition needs to be validated, parsed and a API needs to be created using the API metadata sent from the frontend and the provided OpenAPI definition.

Fixes: wso2/api-platform/issues#161

Goals

Validate, parse and create an API from the provided API definition and the API metadata.

Approach

This PR adds a new endpoint, request/response models, service logic, and OpenAPI documentation for this functionality. The changes ensure that user-provided API details are merged with those parsed from the OpenAPI definition, with robust error handling and validation throughout the process.

New OpenAPI Import Feature

  • Added a new ImportOpenAPIRequest struct in openapi.go to represent the import request, supporting both URL and file upload for the OpenAPI definition, along with required API details.
  • Implemented the ImportOpenAPI handler in api.go to process multipart form requests, validate inputs, parse API details from JSON, and handle error scenarios (such as missing fields, invalid formats, and known conflicts).
  • Registered the new /api/v1/import/open-api POST route in the API handler.

Service and Utility Enhancements

  • Added the ImportFromOpenAPI method to APIService in api.go, which fetches/parses the OpenAPI definition, validates it, merges user and extracted API details, and creates the API using existing logic.
  • Introduced utility methods in api.go for validating/parsing OpenAPI content (ValidateAndParseOpenAPI) and merging user-provided and extracted API details (MergeAPIDetails).

Documentation and OpenAPI Spec Updates

  • Documented the new /import/open-api endpoint in the OpenAPI spec, including request/response schemas, error responses, and usage details. [1] [2]

Summary by CodeRabbit

  • New Features

    • Import and create APIs from OpenAPI definitions via URL or file upload (multipart/form-data).
    • Import flow validates OpenAPI specs, parses operations, and merges extracted details with user-provided API configuration (user values take precedence where applicable).
  • Documentation

    • API docs updated with a new OpenAPI import endpoint, request/response schemas, and examples.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Adds OpenAPI import support: a new DTO for multipart requests, handler and service logic to accept URL or uploaded definition, utilities to parse/merge OpenAPI content, a new POST endpoint, and related gateway validation signature/test updates.

Changes

Cohort / File(s) Summary
DTO & OpenAPI resource
platform-api/src/internal/dto/openapi.go, platform-api/src/resources/openapi.yaml
Adds ImportOpenAPIRequest DTO and OpenAPI spec entries for POST /import/open-api with request/response schemas.
HTTP Handler & Route
platform-api/src/internal/handler/api.go
Adds ImportOpenAPI handler (multipart form parsing, validation, error mapping) and registers POST /api/v1/import/open-api.
Service: API import
platform-api/src/internal/service/api.go
Adds ImportFromOpenAPI(req *dto.ImportOpenAPIRequest, orgId string) to parse/merge OpenAPI content and delegate creation via existing CreateAPI flow.
Utilities: parse & merge
platform-api/src/internal/utils/api.go
Adds ValidateAndParseOpenAPI(content []byte) (*dto.API, error) and MergeAPIDetails(userAPI *dto.API, extractedAPI *dto.API) *dto.API.
Gateway validation & tests
platform-api/src/internal/service/gateway.go, platform-api/src/internal/service/gateway_test.go
Updates validateGatewayInput signature to accept functionalityType and extends tests to cover functionalityType variations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler
    participant Service
    participant Utils
    participant Storage

    Client->>Handler: POST /api/v1/import/open-api (multipart: url/definition + api)
    Handler->>Handler: Parse multipart form\nEnsure url or file present
    Handler->>Service: ImportFromOpenAPI(req, orgId)

    Service->>Utils: ValidateAndParseOpenAPI(content)
    Utils-->>Service: extractedAPI / error
    Service->>Utils: MergeAPIDetails(userAPI, extractedAPI)
    Utils-->>Service: mergedAPI

    Service->>Service: Re-validate mergedAPI
    Service->>Storage: CreateAPI(mergedAPI, orgId)
    Storage-->>Service: createdAPI

    Service-->>Handler: createdAPI
    Handler-->>Client: 201 Created (API)

    Note over Handler,Service: Errors -> mapped to 400/401/404/409/500
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to: multipart parsing and mutual-exclusivity of URL vs file, error mapping in handler, correctness of MergeAPIDetails precedence, and interaction between ImportFromOpenAPI and existing CreateAPI validation paths.

Possibly related PRs

Suggested reviewers

  • malinthaprasan
  • nimsara66

Poem

🐇 I found a spec upon a hill,

URL or file — import with skill.
Merge the fields, validate, then cheer,
New APIs sprout, the meadow's clear! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main addition: enabling API import and creation from OpenAPI definitions.
Description check ✅ Passed The PR description covers most required template sections: Purpose (with issue link), Goals, Approach with detailed implementation details, and Documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
platform-api/src/internal/dto/openapi.go (1)

39-44: Clarify binding semantics for ImportOpenAPIRequest.API

Currently ImportOpenAPIRequest.API is tagged with json:"api", but the handler fills it via:

apiJSON := c.PostForm("api")
json.Unmarshal([]byte(apiJSON), &req.API)

So JSON binding on the whole struct is not used, and the tag may be misleading. Two options:

  • If you intend to keep manual multipart handling, consider adding form:"api" and optionally dropping the json tag, or
  • If you later move to ShouldBind, ensure the handler and tags are aligned.

Not critical, but tightening this now will avoid confusion for future maintainers.

platform-api/src/internal/service/api.go (1)

1379-1452: ImportFromOpenAPI flow is sound; consider small cleanups and error normalization

The overall flow (fetch → validate+parse → merge → reuse CreateAPI) looks good and reuses existing validation and creation logic appropriately.

A couple of small points:

  1. Redundant validation
    You call validateCreateAPIRequest(createReq) here and then CreateAPI immediately calls the same validator again. You can safely drop the explicit call and let CreateAPI own request validation.

  2. Normalize fetch error text
    The accumulated error message for URL failures is:

    "failed to fetch OpenAPI from URL: %s"

    while the handler checks for "failed to fetch OpenAPI definition". Aligning these (or moving to a shared constant / sentinel error) will avoid future divergence and keep HTTP error mapping consistent with intent (see handler comment).

These are small changes but would reduce duplication and make error handling more robust.

platform-api/src/internal/utils/api.go (1)

1452-1531: Merging semantics in MergeAPIDetails look correct; consider a defensive nil check

The merge logic (user overrides extracted values; required fields always from user; operations always from OpenAPI) is consistent with the contract-flow use case and keeps OpenAPI as the source of truth for operations.

Given the function signature accepts pointers, you might consider a defensive nil check to guard future callers:

if userAPI == nil || extractedAPI == nil {
    return nil
}

(or return extractedAPI when userAPI is nil) to avoid panics if it’s ever reused outside the current controlled path.

Not urgent, but cheap insurance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd8c28a and 51fd307.

📒 Files selected for processing (6)
  • platform-api/src/internal/dto/openapi.go (1 hunks)
  • platform-api/src/internal/handler/api.go (4 hunks)
  • platform-api/src/internal/service/api.go (1 hunks)
  • platform-api/src/internal/service/gateway_test.go (2 hunks)
  • platform-api/src/internal/utils/api.go (1 hunks)
  • platform-api/src/resources/openapi.yaml (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: thivindu
Repo: wso2/api-platform PR: 142
File: platform-api/src/resources/openapi.yaml:905-969
Timestamp: 2025-11-12T08:52:52.909Z
Learning: In the wso2/api-platform repository, the team follows an API-first development approach where OpenAPI specs may document planned features before backend implementation is complete, allowing frontend development to proceed against the intended API contract without requiring changes later.
🧬 Code graph analysis (4)
platform-api/src/internal/utils/api.go (1)
platform-api/src/internal/dto/api.go (1)
  • API (25-51)
platform-api/src/internal/handler/api.go (5)
platform-api/src/internal/middleware/auth.go (1)
  • GetOrganizationFromContext (169-176)
platform-api/src/internal/utils/error.go (1)
  • NewErrorResponse (28-37)
platform-api/src/internal/dto/openapi.go (1)
  • ImportOpenAPIRequest (40-44)
platform-api/src/internal/dto/api.go (1)
  • API (25-51)
platform-api/src/internal/constants/error.go (8)
  • ErrAPIAlreadyExists (41-41)
  • ErrProjectNotFound (33-33)
  • ErrInvalidAPIName (44-44)
  • ErrInvalidAPIContext (42-42)
  • ErrInvalidAPIVersion (43-43)
  • ErrInvalidLifecycleState (45-45)
  • ErrInvalidAPIType (46-46)
  • ErrInvalidTransport (47-47)
platform-api/src/internal/service/api.go (3)
platform-api/src/internal/dto/openapi.go (1)
  • ImportOpenAPIRequest (40-44)
platform-api/src/internal/dto/api.go (1)
  • API (25-51)
platform-api/src/internal/model/api.go (2)
  • API (25-51)
  • API (54-56)
platform-api/src/internal/service/gateway_test.go (1)
platform-api/src/internal/constants/constants.go (1)
  • GatewayFunctionalityTypeRegular (53-53)
🔇 Additional comments (4)
platform-api/src/internal/service/gateway_test.go (2)

21-21: LGTM!

The import is necessary for accessing constants.GatewayFunctionalityTypeRegular used throughout the test cases.


30-37: LGTM!

The test struct correctly includes the new functionalityType field to match the updated validateGatewayInput signature.

platform-api/src/internal/handler/api.go (1)

973-977: New /api/v1/import/open-api route wiring looks consistent

The new route is correctly grouped under /api/v1/import and wired to ImportOpenAPI, consistent with the existing ImportAPIProject pattern.

No issues from the handler/route wiring perspective.

platform-api/src/internal/utils/api.go (1)

1436-1450: Good reuse via ValidateAndParseOpenAPI

ValidateAndParseOpenAPI cleanly composes the existing validation and parsing steps and wraps errors with clear context strings. This keeps the service layer thinner and error messages more readable.

No issues here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
platform-api/src/internal/utils/api.go (2)

1436-1450: Wrap helper is fine; consider avoiding double libopenapi parsing.

ValidateAndParseOpenAPI currently creates and inspects the libopenapi document twice via ValidateOpenAPIDefinition and ParseAPIDefinition. It’s correct but does extra work. If this becomes hot, consider refactoring the validation/parse helpers to share a single libopenapi.Document (e.g., a lower-level helper that builds the model once and returns both validation errors and a parsed DTO).


1452-1535: Preserve extracted config (MTLS/Security/CORS/RateLimiting) when user does not override.

The merge behavior for basic fields and operations looks good, but config structs are only taken from userAPI:

  • MTLS, Security, CORS, APIRateLimiting are set from userAPI if non‑nil; otherwise they remain nil even if extractedAPI has values.
  • The comment says “Use user-provided configuration if available”, which implies extracted values should be the default baseline.

To future‑proof this against later enhancements to ParseAPIDefinition, consider falling back to extracted config when the user hasn’t provided one:

-    // Use user-provided configuration if available
-    if userAPI.MTLS != nil {
-        merged.MTLS = userAPI.MTLS
-    }
-    if userAPI.Security != nil {
-        merged.Security = userAPI.Security
-    }
-    if userAPI.CORS != nil {
-        merged.CORS = userAPI.CORS
-    }
-    if userAPI.APIRateLimiting != nil {
-        merged.APIRateLimiting = userAPI.APIRateLimiting
-    }
+    // Use user-provided configuration if available, otherwise keep extracted values
+    if userAPI.MTLS != nil {
+        merged.MTLS = userAPI.MTLS
+    } else {
+        merged.MTLS = extractedAPI.MTLS
+    }
+    if userAPI.Security != nil {
+        merged.Security = userAPI.Security
+    } else {
+        merged.Security = extractedAPI.Security
+    }
+    if userAPI.CORS != nil {
+        merged.CORS = userAPI.CORS
+    } else {
+        merged.CORS = extractedAPI.CORS
+    }
+    if userAPI.APIRateLimiting != nil {
+        merged.APIRateLimiting = userAPI.APIRateLimiting
+    } else {
+        merged.APIRateLimiting = extractedAPI.APIRateLimiting
+    }

This keeps OpenAPI‑derived defaults while still letting the user override selectively.

platform-api/src/internal/service/gateway_test.go (1)

20-224: Functionality type coverage looks solid; consider constants for "ai" / "event".

The extended table-driven tests for functionalityType (empty, whitespace, invalid, valid "ai"/"event") give good coverage of validateGatewayInput. One minor improvement would be to use constants (e.g., constants.GatewayFunctionalityTypeAI / ...Event) rather than string literals "ai" and "event" so tests stay aligned if valid values change in constants.

platform-api/src/internal/service/api.go (1)

1379-1450: ImportFromOpenAPI flow is correct; refine error messaging for URL‑only failures.

The end‑to‑end flow (fetch content → ValidateAndParseOpenAPIMergeAPIDetailsCreateAPI) is sound and correctly reuses existing CreateAPI validation/creation logic.

Two small polish points:

  1. URL‑only failure message is slightly misleading.
    When only req.URL is set and FetchOpenAPIFromURL fails, the returned error combines:

    • "failed to fetch OpenAPI from URL: ..." and
    • "either URL or definition file must be provided"
      even though a URL was provided. Consider making the second part more explicit, e.g. “failed to fetch OpenAPI from URL and no definition file was provided”.
  2. Dropped URL error when a file is also provided.
    If the URL fetch fails but the file upload succeeds, errorList retains the URL error but it’s never surfaced. If that’s intentional (file takes precedence), a brief comment or a debug log at the failure site would help future readers understand why the error is ignored.

These are non‑blocking, but tightening them would make debugging import issues easier.

platform-api/src/internal/dto/openapi.go (1)

39-44: DTO aligns with service; optionally clarify api parsing in comments.

ImportOpenAPIRequest cleanly represents the multipart payload for the import flow and matches APIService.ImportFromOpenAPI’s expectations. Since api is received as a JSON string form field and then unmarshaled into this API struct by the handler, consider adding a short comment noting that behavior so readers don’t assume automatic form binding is doing a deep object bind here.

platform-api/src/resources/openapi.yaml (1)

2616-2659: ImportOpenAPIRequest schema matches payload; optionally document file‑over‑URL precedence.

The ImportOpenAPIRequest schema correctly models:

  • api as a required string containing JSON-encoded API details.
  • url and definition as alternative OpenAPI sources via anyOf (at least one required).
  • Multipart usage consistent with the path’s multipart/form-data requestBody.

Two minor polish suggestions:

  • The description currently says “Either url or definition must be provided…”. Since the backend allows both and prefers the file when both are present, consider adding a line like: “If both are provided, the uploaded definition file takes precedence over url.”
  • To help client authors, you might add format: json (custom format) or similar on api to hint that the string is JSON-encoded API metadata.

These tweaks keep the contract aligned with the actual behavior and make the intent clearer to tooling and consumers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51fd307 and 113731d.

📒 Files selected for processing (6)
  • platform-api/src/internal/dto/openapi.go (1 hunks)
  • platform-api/src/internal/handler/api.go (4 hunks)
  • platform-api/src/internal/service/api.go (1 hunks)
  • platform-api/src/internal/service/gateway_test.go (2 hunks)
  • platform-api/src/internal/utils/api.go (1 hunks)
  • platform-api/src/resources/openapi.yaml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • platform-api/src/internal/handler/api.go
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: thivindu
Repo: wso2/api-platform PR: 142
File: platform-api/src/resources/openapi.yaml:905-969
Timestamp: 2025-11-12T08:52:52.909Z
Learning: In the wso2/api-platform repository, the team follows an API-first development approach where OpenAPI specs may document planned features before backend implementation is complete, allowing frontend development to proceed against the intended API contract without requiring changes later.
🧬 Code graph analysis (3)
platform-api/src/internal/service/api.go (2)
platform-api/src/internal/dto/openapi.go (1)
  • ImportOpenAPIRequest (40-44)
platform-api/src/internal/dto/api.go (1)
  • API (25-51)
platform-api/src/internal/service/gateway_test.go (1)
platform-api/src/internal/constants/constants.go (1)
  • GatewayFunctionalityTypeRegular (53-53)
platform-api/src/internal/utils/api.go (2)
platform-api/src/internal/dto/api.go (1)
  • API (25-51)
platform-api/src/internal/model/api.go (2)
  • API (25-51)
  • API (54-56)

@thivindu thivindu merged commit e5f4864 into wso2:main Nov 20, 2025
2 checks passed
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.

Implement create API from OpenAPI definition

3 participants