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
37 changes: 37 additions & 0 deletions platform-api/src/internal/dto/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package dto

import "mime/multipart"

// ValidateOpenAPIRequest represents the multipart form request for OpenAPI validation from UI
// This handles file uploads from web UI where users can either:
// 1. Upload an OpenAPI definition file (JSON/YAML)
// 2. Provide a URL to fetch the OpenAPI definition
// 3. Provide both (service will prioritize file upload over URL)
type ValidateOpenAPIRequest struct {
URL string `form:"url"` // Optional: URL to fetch OpenAPI definition
Definition *multipart.FileHeader `form:"definition"` // Optional: Uploaded OpenAPI file (JSON/YAML)
}

// OpenAPIValidationResponse represents the response for OpenAPI validation
type OpenAPIValidationResponse struct {
IsAPIDefinitionValid bool `json:"isAPIDefinitionValid"`
Errors []string `json:"errors,omitempty"`
API *API `json:"api,omitempty"`
}
43 changes: 43 additions & 0 deletions platform-api/src/internal/handler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,48 @@ func (h *APIHandler) ValidateAPIProject(c *gin.Context) {
c.JSON(http.StatusOK, response)
}

// ValidateOpenAPI handles POST /validate/open-api
func (h *APIHandler) ValidateOpenAPI(c *gin.Context) {
// 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.ValidateOpenAPIRequest

// 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
}

// Validate OpenAPI definition
response, err := h.apiService.ValidateOpenAPIDefinition(&req)
if err != nil {
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error",
"Failed to validate OpenAPI definition"))
return
}

// Return validation response (200 OK even if validation fails - errors are in the response body)
c.JSON(http.StatusOK, response)
}

// RegisterRoutes registers all API routes
func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
// API routes
Expand All @@ -792,5 +834,6 @@ func (h *APIHandler) RegisterRoutes(r *gin.Engine) {
validateGroup := r.Group("/api/v1/validate")
{
validateGroup.POST("/api-project", h.ValidateAPIProject)
validateGroup.POST("/open-api", h.ValidateOpenAPI)
}
}
70 changes: 66 additions & 4 deletions platform-api/src/internal/service/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ package service
import (
"errors"
"fmt"
"io"
"log"
pathpkg "path"
"platform-api/src/internal/dto"
"platform-api/src/internal/model"
"platform-api/src/internal/repository"
"platform-api/src/internal/utils"
"regexp"
"strings"
"time"

"platform-api/src/internal/constants"
"platform-api/src/internal/dto"
"platform-api/src/internal/model"
"platform-api/src/internal/repository"
"platform-api/src/internal/utils"

"github.com/google/uuid"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -1313,3 +1314,64 @@ func (s *APIService) convertToAPIDevPortalResponse(dpd *model.APIDevPortalWithDe

return apiDevPortalResponse
}

// ValidateOpenAPIDefinition validates an OpenAPI definition from multipart form data
func (s *APIService) ValidateOpenAPIDefinition(req *dto.ValidateOpenAPIRequest) (*dto.OpenAPIValidationResponse, error) {
response := &dto.OpenAPIValidationResponse{
IsAPIDefinitionValid: false,
Errors: []string{},
}

var content []byte
var err error

// 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)
response.Errors = append(response.Errors, 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 {
response.Errors = append(response.Errors, fmt.Sprintf("failed to open definition file: %s", err.Error()))
return response, nil
}
defer file.Close()

content, err = io.ReadAll(file)
if err != nil {
response.Errors = append(response.Errors, fmt.Sprintf("failed to read definition file: %s", err.Error()))
return response, nil
}
}

// If neither URL nor file is provided
if len(content) == 0 {
response.Errors = append(response.Errors, "either URL or definition file must be provided")
return response, nil
}

// Validate the OpenAPI definition
if err := s.apiUtil.ValidateOpenAPIDefinition(content); err != nil {
response.Errors = append(response.Errors, fmt.Sprintf("invalid OpenAPI definition: %s", err.Error()))
return response, nil
}

// Parse API specification to extract metadata directly into API DTO using libopenapi
api, err := s.apiUtil.ParseAPIDefinition(content)
if err != nil {
response.Errors = append(response.Errors, fmt.Sprintf("failed to parse API specification: %s", err.Error()))
return response, nil
}

// Set the parsed API for response
response.IsAPIDefinitionValid = true
response.API = api

return response, nil
}
Loading