Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions platform-api/src/internal/dto/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ type OpenAPIValidationResponse struct {
Errors []string `json:"errors,omitempty"`
API *API `json:"api,omitempty"`
}

// ImportOpenAPIRequest represents the request for importing an OpenAPI definition
type ImportOpenAPIRequest struct {
URL string `form:"url"` // Optional: URL to fetch OpenAPI definition
Definition *multipart.FileHeader `form:"definition"` // Optional: Uploaded OpenAPI file (JSON/YAML)
API API `form:"api"` // API details for the imported definition
}
150 changes: 150 additions & 0 deletions platform-api/src/internal/handler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package handler

import (
"encoding/json"
"errors"
"log"
"net/http"
Expand All @@ -26,6 +27,7 @@ import (
"platform-api/src/internal/middleware"
"platform-api/src/internal/service"
"platform-api/src/internal/utils"
"strings"

"github.com/gin-gonic/gin"
)
Expand Down Expand Up @@ -810,6 +812,153 @@ func (h *APIHandler) ValidateOpenAPI(c *gin.Context) {
c.JSON(http.StatusOK, response)
}

// ImportOpenAPI handles POST /import/open-api and imports an API from OpenAPI definition
func (h *APIHandler) ImportOpenAPI(c *gin.Context) {
orgId, exists := middleware.GetOrganizationFromContext(c)
if !exists {
c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
"Organization claim not found in token"))
return
}

// Parse multipart form
err := c.Request.ParseMultipartForm(10 << 20) // 10 MB max
if err != nil {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Failed to parse multipart form"))
return
}

var req dto.ImportOpenAPIRequest

// Get URL from form if provided
if url := c.PostForm("url"); url != "" {
req.URL = url
}

// Get definition file from form if provided
if file, header, err := c.Request.FormFile("definition"); err == nil {
req.Definition = header
defer file.Close()
}

// Validate that at least one input is provided
if req.URL == "" && req.Definition == nil {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Either URL or definition file must be provided"))
return
}

// Get API details from form data (JSON string in 'api' field)
apiJSON := c.PostForm("api")
if apiJSON == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"API details are required"))
return
}

// Parse API details from JSON string
if err := json.Unmarshal([]byte(apiJSON), &req.API); err != nil {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid API details: "+err.Error()))
return
}

if req.API.Name == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"API name is required"))
return
}
if req.API.Context == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"API context is required"))
return
}
if req.API.Version == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"API version is required"))
return
}
if req.API.ProjectID == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Project ID is required"))
return
}

// Import API from OpenAPI definition
api, err := h.apiService.ImportFromOpenAPI(&req, orgId)
if err != nil {
if errors.Is(err, constants.ErrAPIAlreadyExists) {
c.JSON(http.StatusConflict, utils.NewErrorResponse(409, "Conflict",
"API already exists in the project"))
return
}
if errors.Is(err, constants.ErrProjectNotFound) {
c.JSON(http.StatusNotFound, utils.NewErrorResponse(404, "Not Found",
"Project not found"))
return
}
if errors.Is(err, constants.ErrInvalidAPIName) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid API name format"))
return
}
if errors.Is(err, constants.ErrInvalidAPIContext) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid API context format"))
return
}
if errors.Is(err, constants.ErrInvalidAPIVersion) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid API version format"))
return
}
if errors.Is(err, constants.ErrInvalidLifecycleState) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid lifecycle status"))
return
}
if errors.Is(err, constants.ErrInvalidAPIType) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid API type"))
return
}
if errors.Is(err, constants.ErrInvalidTransport) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid transport protocol"))
return
}
// Handle OpenAPI-specific errors
if strings.Contains(err.Error(), "failed to fetch OpenAPI from URL") {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Failed to fetch OpenAPI definition from URL"))
return
}
if strings.Contains(err.Error(), "failed to open OpenAPI definition file") ||
strings.Contains(err.Error(), "failed to read OpenAPI definition file") {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Failed to fetch OpenAPI definition from file"))
return
}
if strings.Contains(err.Error(), "failed to validate and parse OpenAPI definition") {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Invalid OpenAPI definition"))
return
}
if strings.Contains(err.Error(), "failed to merge API details") {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Failed to create API from OpenAPI definition: incompatible details"))
return
}

c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error",
"Failed to import API from OpenAPI definition"))
return
}

c.JSON(http.StatusCreated, api)
}

// RegisterRoutes registers all API routes
func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
// API routes
Expand All @@ -830,6 +979,7 @@ func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
importGroup := r.Group("/api/v1/import")
{
importGroup.POST("/api-project", h.ImportAPIProject)
importGroup.POST("/open-api", h.ImportOpenAPI)
}
validateGroup := r.Group("/api/v1/validate")
{
Expand Down
73 changes: 73 additions & 0 deletions platform-api/src/internal/service/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,3 +1375,76 @@ func (s *APIService) ValidateOpenAPIDefinition(req *dto.ValidateOpenAPIRequest)

return response, nil
}

// ImportFromOpenAPI imports an API from an OpenAPI definition
func (s *APIService) ImportFromOpenAPI(req *dto.ImportOpenAPIRequest, orgId string) (*dto.API, error) {
var content []byte
var err error
var errorList []string

// If URL is provided, fetch content from URL
if req.URL != "" {
content, err = s.apiUtil.FetchOpenAPIFromURL(req.URL)
if err != nil {
content = make([]byte, 0)
errorList = append(errorList, fmt.Sprintf("failed to fetch OpenAPI from URL: %s", err.Error()))
}
}

// If definition file is provided, read from file
if req.Definition != nil {
file, err := req.Definition.Open()
if err != nil {
errorList = append(errorList, fmt.Sprintf("failed to open OpenAPI definition file: %s", err.Error()))
return nil, fmt.Errorf(strings.Join(errorList, "; "))
}
defer file.Close()

content, err = io.ReadAll(file)
if err != nil {
errorList = append(errorList, fmt.Sprintf("failed to read OpenAPI definition file: %s", err.Error()))
return nil, fmt.Errorf(strings.Join(errorList, "; "))
}
}

// If neither URL nor file is provided
if len(content) == 0 {
errorList = append(errorList, "either URL or definition file must be provided")
return nil, fmt.Errorf(strings.Join(errorList, "; "))
}

// Validate and parse the OpenAPI definition
apiDetails, err := s.apiUtil.ValidateAndParseOpenAPI(content)
if err != nil {
return nil, fmt.Errorf("failed to validate and parse OpenAPI definition: %w", err)
}

// Merge provided API details with extracted details from OpenAPI
mergedAPI := s.apiUtil.MergeAPIDetails(&req.API, apiDetails)
if mergedAPI == nil {
return nil, errors.New("failed to merge API details")
}

// Create API using existing CreateAPI logic
createReq := &CreateAPIRequest{
Name: mergedAPI.Name,
DisplayName: mergedAPI.DisplayName,
Description: mergedAPI.Description,
Context: mergedAPI.Context,
Version: mergedAPI.Version,
Provider: mergedAPI.Provider,
ProjectID: mergedAPI.ProjectID,
LifeCycleStatus: mergedAPI.LifeCycleStatus,
Type: mergedAPI.Type,
Transport: mergedAPI.Transport,
MTLS: mergedAPI.MTLS,
Security: mergedAPI.Security,
CORS: mergedAPI.CORS,
BackendServices: mergedAPI.BackendServices,
APIRateLimiting: mergedAPI.APIRateLimiting,
Operations: mergedAPI.Operations,
}

// Create the API
return s.CreateAPI(createReq, orgId)
}
Loading