Skip to content

[TT-14863] Replace github.com/TykTechnologies/kin-openapi with github.com/getkin/kin-openapi #7041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 39 commits into
base: master
Choose a base branch
from

Conversation

edsonmichaque
Copy link
Contributor

@edsonmichaque edsonmichaque commented May 5, 2025

User description

TT-10853
Summary [SPIKE] PoC for updating kin-openapi dependency
Type Story Story
Status In Dev
Points N/A
Labels -

Description

This PR replaces the internal fork github.com/TykTechnologies/kin-openapi with the official upstream module github.com/getkin/kin-openapi. Migrating to the maintained upstream version reduces technical debt, ensures long-term maintainability, and gives us access to the latest features, bug fixes, and security updates from the community.

Related Issue

https://tyktech.atlassian.net/browse/TT-14863

Motivation and Context

How This Has Been Tested

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

PR Type

enhancement


Description

  • Upgrade kin-openapi dependency to v0.132.0

  • Refactor codebase for new kin-openapi API usage

  • Update all imports from old to new kin-openapi module path

  • Adjust OpenAPI Paths/Responses handling for new API


Changes walkthrough 📝

Relevant files
Dependencies
2 files
go.mod
Update kin-openapi and related dependencies to latest versions
+6/-7     
go.sum
Update dependency hashes for new kin-openapi and others   
+12/-25 
Tests
20 files
operation_test.go
Refactor tests for new kin-openapi Paths/Responses API     
+155/-138
default_test.go
Refactor tests for new kin-openapi Paths/Responses API     
+49/-43 
oas_test.go
Refactor OAS tests for new Paths/Responses API                     
+30/-16 
fixtures_test.go
Update kin-openapi import path in fixtures test                   
+1/-1     
import_test.go
Update import test for new Paths API                                         
+2/-2     
security_test.go
Refactor security tests for new Paths API                               
+4/-4     
validator_test.go
Refactor validator tests for new kin-openapi API                 
+15/-10 
example_test.go
Update kin-openapi import path in example tests                   
+1/-1     
api_definition_test.go
Update kin-openapi import path in API definition tests     
+1/-1     
api_test.go
Refactor API tests for new kin-openapi Paths API                 
+46/-56 
looping_test.go
Update kin-openapi import path in looping tests                   
+2/-2     
mock_response_test.go
Refactor mock response tests for new kin-openapi API         
+76/-64 
mw_oas_validate_request_test.go
Update kin-openapi import path in middleware tests             
+1/-1     
mw_streaming_test.go
Update kin-openapi import path in streaming tests               
+1/-1     
testutil.go
Update kin-openapi import path in test utilities                 
+2/-2     
mw_go_plugin_test.go
Update kin-openapi import path in Go plugin tests               
+1/-1     
root_test.go
Update kin-openapi import path in root tests                         
+1/-1     
validator_test.go
Update kin-openapi import path in streams validator tests
+2/-2     
ctx_test.go
Update kin-openapi import path in context tests                   
+1/-1     
paths_test.go
Update Paths test for new kin-openapi API                               
+3/-2     
Enhancement
15 files
operation.go
Refactor OAS operation logic for new kin-openapi API         
+15/-13 
default.go
Update middleware import logic for new Paths API                 
+2/-2     
example.go
Update example extraction for new Schema type API               
+7/-7     
oas.go
Update OAS struct for new kin-openapi import path               
+1/-1     
security.go
Update kin-openapi import path in security logic                 
+1/-1     
authentication.go
Update kin-openapi import path in authentication logic     
+1/-1     
old_oas.go
Update kin-openapi import path for migration logic             
+2/-2     
api.go
Update kin-openapi import path in API logic                           
+1/-1     
api_definition.go
Update kin-openapi and router imports in API definition   
+2/-2     
mock_response.go
Refactor mock response logic for new Responses API             
+4/-2     
model_apispec.go
Update kin-openapi router import path                                       
+1/-1     
mw_oas_validate_request.go
Update kin-openapi and filter imports in middleware           
+2/-2     
mw_version_check.go
Update kin-openapi router import path in version check     
+1/-1     
openapi.go
Update kin-openapi import path and Paths iteration             
+2/-2     
paths.go
Refactor Paths utilities for new kin-openapi API                 
+5/-3     

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @buger
    Copy link
    Member

    buger commented May 5, 2025

    Let's make that PR title a 💯 shall we? 💪

    Your PR title and story title look slightly different. Just checking in to know if it was intentional!

    Story Title [SPIKE] PoC for updating kin-openapi dependency
    PR Title [TT-10853] Upgrade kin-openapi to v0.132.0

    Check out this guide to learn more about PR best-practices.

    Copy link
    Contributor

    github-actions bot commented May 5, 2025

    API Changes

    no api changes detected

    Copy link
    Contributor

    github-actions bot commented May 5, 2025

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    API Surface Change

    The PR updates usage of the openapi3.Paths type throughout the codebase to use the new NewPaths() constructor and .Map() accessor methods, reflecting a breaking change in the kin-openapi library. This impacts how paths and responses are accessed and mutated. The reviewer should validate that all usages of paths, operations, and responses are correctly migrated, especially in edge cases or dynamic path manipulations.

    	// Regardless if `ep` is a zero value, we need a non-nil paths
    	// to produce a valid OAS document
    	if s.Paths == nil {
    		s.Paths = openapi3.NewPaths()
    	}
    
    	s.fillAllowance(ep.WhiteList, allow)
    	s.fillAllowance(ep.BlackList, block)
    	s.fillAllowance(ep.Ignored, ignoreAuthentication)
    	s.fillTransformRequestMethod(ep.MethodTransforms)
    	s.fillTransformRequestBody(ep.Transform)
    	s.fillTransformResponseBody(ep.TransformResponse)
    	s.fillTransformRequestHeaders(ep.TransformHeader)
    	s.fillTransformResponseHeaders(ep.TransformResponseHeader)
    	s.fillURLRewrite(ep.URLRewrite)
    	s.fillInternal(ep.Internal)
    	s.fillCache(ep.AdvanceCacheConfig)
    	s.fillEnforceTimeout(ep.HardTimeouts)
    	s.fillOASValidateRequest(ep.ValidateJSON)
    	s.fillVirtualEndpoint(ep.Virtual)
    	s.fillEndpointPostPlugins(ep.GoPlugin)
    	s.fillCircuitBreaker(ep.CircuitBreaker)
    	s.fillTrackEndpoint(ep.TrackEndpoints)
    	s.fillDoNotTrackEndpoint(ep.DoNotTrackEndpoints)
    	s.fillRequestSizeLimit(ep.SizeLimit)
    	s.fillRateLimitEndpoints(ep.RateLimit)
    	s.fillMockResponsePaths(*s.Paths, ep)
    }
    
    // fillMockResponsePaths converts classic API mock responses to OAS format.
    // This method only handles direct mock response conversions, as other middleware
    // configurations (like allow lists, block lists, etc.) are converted to classic
    // API mock responses in an earlier step of the process.
    //
    // For each mock response, it:
    // 1. Creates an OAS operation with a unique ID (if it doesn't exist)
    // 2. Sets up the mock response with content type detection and example values
    // 3. Configures the operation to ignore authentication for this endpoint
    //
    // The content type is determined by:
    // - Checking the Content-Type header if present
    // - Attempting to parse the body as JSON
    // - Defaulting to text/plain if neither above applies
    func (s *OAS) fillMockResponsePaths(paths openapi3.Paths, ep apidef.ExtendedPathsSet) {
    	for _, mock := range ep.MockResponse {
    		operationID := s.getOperationID(mock.Path, mock.Method)
    
    		var operation *openapi3.Operation
    
    		for _, item := range paths.Map() {
    			if op := item.GetOperation(mock.Method); op != nil && op.OperationID == operationID {
    				operation = op
    				break
    			}
    		}
    
    		if operation.Responses == nil {
    			operation.Responses = openapi3.NewResponses()
    		}
    
    		// Response description is required by the OAS spec, but we don't have it in Tyk classic.
    		// So we're using a dummy value to satisfy the spec.
    		var oasDesc string
    
    		response := &openapi3.Response{
    			Description: &oasDesc,
    		}
    
    		operation.Responses.Set(strconv.Itoa(mock.Code), &openapi3.ResponseRef{
    			Value: response,
    		})
    
    		operation.Responses.Delete("default")
    
    		tykOperation := s.GetTykExtension().getOperation(operation.OperationID)
    
    		if tykOperation.MockResponse == nil {
    			tykOperation.MockResponse = &MockResponse{}
    		}
    
    		tykOperation.MockResponse.Fill(mock)
    
    		if tykOperation.IgnoreAuthentication == nil && tykOperation.MockResponse.FromOASExamples == nil {
    			// We need to to add ignoreAuthentication middleware to the operation
    			// to stay consistent to the way mock responses work for classic APIs
    			tykOperation.IgnoreAuthentication = &Allowance{Enabled: true}
    		}
    
    		if ShouldOmit(tykOperation.MockResponse) {
    			tykOperation.MockResponse = &MockResponse{
    				FromOASExamples: &FromOASExamples{},
    			}
    		}
    	}
    }
    
    func (s *OAS) extractPathsAndOperations(ep *apidef.ExtendedPathsSet) {
    	ep.Clear()
    
    	tykOperations := s.getTykOperations()
    	if len(tykOperations) == 0 {
    		return
    	}
    
    	for _, pathItem := range oasutil.SortByPathLength(*s.Paths) {
    		for id, tykOp := range tykOperations {
    			path := pathItem.Path
    			for method, operation := range pathItem.Operations() {
    				if id == operation.OperationID {
    					tykOp.extractAllowanceTo(ep, path, method, allow)
    					tykOp.extractAllowanceTo(ep, path, method, block)
    					tykOp.extractAllowanceTo(ep, path, method, ignoreAuthentication)
    					tykOp.extractInternalTo(ep, path, method)
    					tykOp.extractTransformRequestMethodTo(ep, path, method)
    					tykOp.extractTransformRequestBodyTo(ep, path, method)
    					tykOp.extractTransformResponseBodyTo(ep, path, method)
    					tykOp.extractTransformRequestHeadersTo(ep, path, method)
    					tykOp.extractTransformResponseHeadersTo(ep, path, method)
    					tykOp.extractURLRewriteTo(ep, path, method)
    					tykOp.extractCacheTo(ep, path, method)
    					tykOp.extractEnforceTimeoutTo(ep, path, method)
    					tykOp.extractVirtualEndpointTo(ep, path, method)
    					tykOp.extractEndpointPostPluginTo(ep, path, method)
    					tykOp.extractCircuitBreakerTo(ep, path, method)
    					tykOp.extractTrackEndpointTo(ep, path, method)
    					tykOp.extractDoNotTrackEndpointTo(ep, path, method)
    					tykOp.extractRequestSizeLimitTo(ep, path, method)
    					tykOp.extractRateLimitEndpointTo(ep, path, method)
    					break
    				}
    			}
    		}
    	}
    
    	sortMockResponseAllowList(ep)
    }
    
    func (s *OAS) fillAllowance(endpointMetas []apidef.EndPointMeta, typ AllowanceType) {
    	for _, em := range endpointMetas {
    		operationID := s.getOperationID(em.Path, em.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		var allowance *Allowance
    
    		switch typ {
    		case block:
    			allowance = newAllowance(&operation.Block)
    		case ignoreAuthentication:
    			allowance = newAllowance(&operation.IgnoreAuthentication)
    		default:
    			// Skip endpoints that have mock responses configured via method actions, we should avoid
    			// creating allowance for them.
    			if hasMockResponse(em.MethodActions) {
    				continue
    			}
    
    			allowance = newAllowance(&operation.Allow)
    		}
    
    		allowance.Fill(em)
    		if ShouldOmit(allowance) {
    			allowance = nil
    		}
    	}
    }
    
    func newAllowance(prev **Allowance) *Allowance {
    	if *prev == nil {
    		*prev = &Allowance{}
    	}
    
    	return *prev
    }
    
    func (s *OAS) fillTransformRequestMethod(metas []apidef.MethodTransformMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    		if operation.TransformRequestMethod == nil {
    			operation.TransformRequestMethod = &TransformRequestMethod{}
    		}
    
    		operation.TransformRequestMethod.Fill(meta)
    		if ShouldOmit(operation.TransformRequestMethod) {
    			operation.TransformRequestMethod = nil
    		}
    	}
    }
    
    func (s *OAS) fillTransformRequestBody(metas []apidef.TemplateMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		if operation.TransformRequestBody == nil {
    			operation.TransformRequestBody = &TransformBody{}
    		}
    
    		operation.TransformRequestBody.Fill(meta)
    		if ShouldOmit(operation.TransformRequestBody) {
    			operation.TransformRequestBody = nil
    		}
    	}
    }
    
    func (s *OAS) fillTransformResponseBody(metas []apidef.TemplateMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		if operation.TransformResponseBody == nil {
    			operation.TransformResponseBody = &TransformBody{}
    		}
    
    		operation.TransformResponseBody.Fill(meta)
    		if ShouldOmit(operation.TransformResponseBody) {
    			operation.TransformResponseBody = nil
    		}
    	}
    }
    
    func (s *OAS) fillTransformRequestHeaders(metas []apidef.HeaderInjectionMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		if operation.TransformRequestHeaders == nil {
    			operation.TransformRequestHeaders = &TransformHeaders{}
    		}
    
    		operation.TransformRequestHeaders.Fill(meta)
    		if ShouldOmit(operation.TransformRequestHeaders) {
    			operation.TransformRequestHeaders = nil
    		}
    	}
    }
    
    func (s *OAS) fillTransformResponseHeaders(metas []apidef.HeaderInjectionMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		if operation.TransformResponseHeaders == nil {
    			operation.TransformResponseHeaders = &TransformHeaders{}
    		}
    
    		operation.TransformResponseHeaders.Fill(meta)
    		if ShouldOmit(operation.TransformResponseHeaders) {
    			operation.TransformResponseHeaders = nil
    		}
    	}
    }
    
    func (s *OAS) fillCache(metas []apidef.CacheMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    		if operation.Cache == nil {
    			operation.Cache = &CachePlugin{}
    		}
    
    		operation.Cache.Fill(meta)
    		if ShouldOmit(operation.Cache) {
    			operation.Cache = nil
    		}
    	}
    }
    
    func (s *OAS) fillEnforceTimeout(metas []apidef.HardTimeoutMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    		if operation.EnforceTimeout == nil {
    			operation.EnforceTimeout = &EnforceTimeout{}
    		}
    
    		operation.EnforceTimeout.Fill(meta)
    		if ShouldOmit(operation.EnforceTimeout) {
    			operation.EnforceTimeout = nil
    		}
    	}
    }
    
    func (s *OAS) fillRequestSizeLimit(metas []apidef.RequestSizeMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    		operation := s.GetTykExtension().getOperation(operationID)
    		if operation.RequestSizeLimit == nil {
    			operation.RequestSizeLimit = &RequestSizeLimit{}
    		}
    
    		operation.RequestSizeLimit.Fill(meta)
    		if ShouldOmit(operation.RequestSizeLimit) {
    			operation.RequestSizeLimit = nil
    		}
    	}
    }
    
    func (o *Operation) extractAllowanceTo(ep *apidef.ExtendedPathsSet, path string, method string, typ AllowanceType) {
    	allowance := o.Allow
    	endpointMetas := &ep.WhiteList
    
    	switch typ {
    	case block:
    		allowance = o.Block
    		endpointMetas = &ep.BlackList
    	case ignoreAuthentication:
    		allowance = o.IgnoreAuthentication
    		endpointMetas = &ep.Ignored
    	}
    
    	if allowance == nil {
    		return
    	}
    
    	endpointMeta := apidef.EndPointMeta{Path: path, Method: method}
    	allowance.ExtractTo(&endpointMeta)
    	*endpointMetas = append(*endpointMetas, endpointMeta)
    }
    
    func (o *Operation) extractTransformRequestMethodTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.TransformRequestMethod == nil {
    		return
    	}
    
    	meta := apidef.MethodTransformMeta{Path: path, Method: method}
    	o.TransformRequestMethod.ExtractTo(&meta)
    	ep.MethodTransforms = append(ep.MethodTransforms, meta)
    }
    
    func (o *Operation) extractTransformRequestBodyTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.TransformRequestBody == nil {
    		return
    	}
    
    	meta := apidef.TemplateMeta{Path: path, Method: method}
    	o.TransformRequestBody.ExtractTo(&meta)
    	ep.Transform = append(ep.Transform, meta)
    }
    
    func (o *Operation) extractTransformResponseBodyTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.TransformResponseBody == nil {
    		return
    	}
    
    	meta := apidef.TemplateMeta{Path: path, Method: method}
    	o.TransformResponseBody.ExtractTo(&meta)
    	ep.TransformResponse = append(ep.TransformResponse, meta)
    }
    
    func (o *Operation) extractTransformRequestHeadersTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.TransformRequestHeaders == nil {
    		return
    	}
    
    	meta := apidef.HeaderInjectionMeta{Path: path, Method: method}
    	o.TransformRequestHeaders.ExtractTo(&meta)
    	ep.TransformHeader = append(ep.TransformHeader, meta)
    }
    
    func (o *Operation) extractTransformResponseHeadersTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.TransformResponseHeaders == nil {
    		return
    	}
    
    	meta := apidef.HeaderInjectionMeta{Path: path, Method: method}
    	o.TransformResponseHeaders.ExtractTo(&meta)
    	ep.TransformResponseHeader = append(ep.TransformResponseHeader, meta)
    }
    
    func (o *Operation) extractCacheTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.Cache == nil {
    		return
    	}
    
    	newCacheMeta := apidef.CacheMeta{
    		Method: method,
    		Path:   path,
    	}
    	o.Cache.ExtractTo(&newCacheMeta)
    	ep.AdvanceCacheConfig = append(ep.AdvanceCacheConfig, newCacheMeta)
    }
    
    func (o *Operation) extractEnforceTimeoutTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.EnforceTimeout == nil {
    		return
    	}
    
    	meta := apidef.HardTimeoutMeta{Path: path, Method: method}
    	o.EnforceTimeout.ExtractTo(&meta)
    	ep.HardTimeouts = append(ep.HardTimeouts, meta)
    }
    
    func (o *Operation) extractRequestSizeLimitTo(ep *apidef.ExtendedPathsSet, path string, method string) {
    	if o.RequestSizeLimit == nil {
    		return
    	}
    
    	meta := apidef.RequestSizeMeta{Path: path, Method: method}
    	o.RequestSizeLimit.ExtractTo(&meta)
    	ep.SizeLimit = append(ep.SizeLimit, meta)
    }
    
    // detect possible regex pattern:
    // - character match ([a-z])
    // - greedy match (.*)
    // - ungreedy match (.+)
    // - end of string ($).
    var regexPatterns = []string{
    	".+", ".*", "[", "]", "$",
    }
    
    type pathPart struct {
    	name    string
    	value   string
    	isRegex bool
    }
    
    func (p pathPart) String() string {
    	if p.isRegex {
    		return "{" + p.name + "}"
    	}
    
    	return p.value
    }
    
    // isRegex checks if value has expected regular expression patterns.
    func isRegex(value string) bool {
    	for _, pattern := range regexPatterns {
    		if strings.Contains(value, pattern) {
    			return true
    		}
    	}
    	return false
    }
    
    // splitPath splits URL into folder parts, detecting regex patterns.
    func splitPath(inPath string) ([]pathPart, bool) {
    	trimmedPath := strings.Trim(inPath, "/")
    
    	if trimmedPath == "" {
    		return []pathPart{}, false
    	}
    
    	parts := strings.Split(trimmedPath, "/")
    	result := make([]pathPart, len(parts))
    
    	regexCount := 0
    	hasRegex := false
    
    	for i, segment := range parts {
    		var part pathPart
    		part, regexCount, hasRegex = parsePathSegment(segment, regexCount, hasRegex)
    		result[i] = part
    	}
    
    	return result, hasRegex
    }
    
    // buildPath converts the URL paths with regex to named parameters
    // e.g. ["a", ".*"] becomes /a/{customRegex1}.
    func buildPath(parts []pathPart, appendSlash bool) string {
    	newPath := ""
    
    	for _, part := range parts {
    		newPath += "/" + part.String()
    	}
    
    	if appendSlash {
    		return newPath + "/"
    	}
    
    	return newPath
    }
    
    func (s *OAS) getOperationID(inPath, method string) string {
    	operationID := strings.TrimPrefix(inPath, "/") + method
    
    	paths := s.Paths.Map()
    
    	createOrGetPathItem := func(item string) *openapi3.PathItem {
    		if paths[item] == nil {
    			paths[item] = &openapi3.PathItem{}
    		}
    
    		return paths[item]
    	}
    
    	createOrUpdateOperation := func(p *openapi3.PathItem) *openapi3.Operation {
    		operation := p.GetOperation(method)
    
    		if operation == nil {
    			operation = &openapi3.Operation{
    				Responses: openapi3.NewResponses(),
    			}
    			p.SetOperation(method, operation)
    		}
    
    		if operation.OperationID == "" {
    			operation.OperationID = operationID
    		}
    
    		return operation
    	}
    
    	var p *openapi3.PathItem
    	parts, hasRegex := splitPath(inPath)
    
    	if hasRegex {
    		newPath := buildPath(parts, strings.HasSuffix(inPath, "/"))
    
    		p = createOrGetPathItem(newPath)
    
    		// We should check if the parameters are already set before initializing it.
    		if p.Parameters == nil {
    			p.Parameters = []*openapi3.ParameterRef{}
    		}
    
    		existingParams := make(map[string]bool)
    		for _, existingParam := range p.Parameters {
    			existingParams[existingParam.Value.Name] = true
    		}
    
    		for _, part := range parts {
    			// Skip adding the parameter if it already exists so that we don't override it.
    			if existingParams[part.name] {
    				continue
    			}
    
    			if part.isRegex {
    				schema := &openapi3.SchemaRef{
    					Value: &openapi3.Schema{
    						Type:    &openapi3.Types{openapi3.TypeString},
    						Pattern: part.value,
    					},
    				}
    				param := &openapi3.ParameterRef{
    					Value: &openapi3.Parameter{
    						Name:     part.name,
    						In:       "path",
    						Required: true,
    						Schema:   schema,
    					},
    				}
    				p.Parameters = append(p.Parameters, param)
    			}
    		}
    	} else {
    		p = createOrGetPathItem(inPath)
    	}
    
    	operation := createOrUpdateOperation(p)
    	return operation.OperationID
    }
    
    func (x *XTykAPIGateway) getOperation(operationID string) *Operation {
    	if x.Middleware == nil {
    		x.Middleware = &Middleware{}
    	}
    
    	middleware := x.Middleware
    
    	if middleware.Operations == nil {
    		middleware.Operations = make(Operations)
    	}
    
    	operations := middleware.Operations
    
    	if operations[operationID] == nil {
    		operations[operationID] = &Operation{}
    	}
    
    	return operations[operationID]
    }
    
    // ValidateRequest holds configuration required for validating requests.
    type ValidateRequest struct {
    	// Enabled is a boolean flag, if set to `true`, it enables request validation.
    	Enabled bool `bson:"enabled" json:"enabled"`
    
    	// ErrorResponseCode is the error code emitted when the request fails validation.
    	// If unset or zero, the response will returned with http status 422 Unprocessable Entity.
    	ErrorResponseCode int `bson:"errorResponseCode,omitempty" json:"errorResponseCode,omitempty"`
    }
    
    // Fill fills *ValidateRequest receiver from apidef.ValidateRequestMeta.
    func (v *ValidateRequest) Fill(meta apidef.ValidatePathMeta) {
    	v.Enabled = !meta.Disabled
    	v.ErrorResponseCode = meta.ErrorResponseCode
    }
    
    func (*ValidateRequest) shouldImport(operation *openapi3.Operation) bool {
    	if len(operation.Parameters) > 0 {
    		return true
    	}
    
    	reqBody := operation.RequestBody
    	if reqBody == nil {
    		return false
    	}
    
    	reqBodyVal := reqBody.Value
    	if reqBodyVal == nil {
    		return false
    	}
    
    	media := reqBodyVal.Content.Get("application/json")
    
    	return media != nil
    }
    
    // Import populates *ValidateRequest with enabled argument and a default error response code.
    func (v *ValidateRequest) Import(enabled bool) {
    	v.Enabled = enabled
    	v.ErrorResponseCode = http.StatusUnprocessableEntity
    }
    
    func convertSchema(mapSchema map[string]interface{}) (*openapi3.Schema, error) {
    	bytes, err := json.Marshal(mapSchema)
    	if err != nil {
    		return nil, err
    	}
    
    	schema := openapi3.NewSchema()
    	err = schema.UnmarshalJSON(bytes)
    	if err != nil {
    		return nil, err
    	}
    
    	return schema, nil
    }
    
    func (s *OAS) fillOASValidateRequest(metas []apidef.ValidatePathMeta) {
    	for _, meta := range metas {
    		operationID := s.getOperationID(meta.Path, meta.Method)
    
    		operation := s.Paths.Find(meta.Path).GetOperation(meta.Method)
    		requestBodyRef := operation.RequestBody
    		if operation.RequestBody == nil {
    			requestBodyRef = &openapi3.RequestBodyRef{}
    			operation.RequestBody = requestBodyRef
    		}
    
    		if requestBodyRef.Value == nil {
    			requestBodyRef.Value = openapi3.NewRequestBody()
    		}
    
    		schema, err := convertSchema(meta.Schema)
    		if err != nil {
    			log.WithError(err).Error("Couldn't convert classic API validate JSON schema to OAS")
    		} else {
    			requestBodyRef.Value.WithJSONSchema(schema)
    		}
    
    		tykOp := s.GetTykExtension().getOperation(operationID)
    
    		if tykOp.ValidateRequest == nil {
    			tykOp.ValidateRequest = &ValidateRequest{}
    		}
    
    		tykOp.ValidateRequest.Fill(meta)
    
    		if ShouldOmit(tykOp.ValidateRequest) {
    			tykOp.ValidateRequest = nil
    		}
    	}
    }
    
    // MockResponse configures the mock responses.
    type MockResponse struct {
    	// Enabled activates the mock response middleware.
    	Enabled bool `bson:"enabled" json:"enabled"`
    	// Code is the HTTP response code that will be returned.
    	Code int `bson:"code,omitempty" json:"code,omitempty"`
    	// Body is the HTTP response body that will be returned.
    	Body string `bson:"body,omitempty" json:"body,omitempty"`
    	// Headers are the HTTP response headers that will be returned.
    	Headers Headers `bson:"headers,omitempty" json:"headers,omitempty"`
    	// FromOASExamples is the configuration to extract a mock response from OAS documentation.
    	FromOASExamples *FromOASExamples `bson:"fromOASExamples,omitempty" json:"fromOASExamples,omitempty"`
    }
    
    // Fill populates the MockResponse fields from a classic API MockResponseMeta.
    func (m *MockResponse) Fill(op apidef.MockResponseMeta) {
    	headers := make([]Header, 0)
    	for k, v := range op.Headers {
    		headers = append(headers, Header{
    			Name:  http.CanonicalHeaderKey(k),
    			Value: v,
    		})
    	}
    
    	// Sort headers by name so that the order is deterministic
    	sort.Slice(headers, func(i, j int) bool {
    		return headers[i].Name < headers[j].Name
    	})
    
    	m.Enabled = !op.Disabled
    	m.Code = op.Code
    	m.Body = op.Body
    	m.Headers = headers
    }
    
    func (m *MockResponse) ExtractTo(meta *apidef.MockResponseMeta) {
    	meta.Disabled = !m.Enabled
    	meta.Code = m.Code
    	meta.Body = m.Body
    
    	// Initialize headers map even when empty
    	meta.Headers = make(map[string]string)
    
    	for _, h := range m.Headers {
    		meta.Headers[h.Name] = h.Value
    	}
    
    	if len(meta.Headers) == 0 {
    		meta.Headers = nil
    	}
    }
    
    // FromOASExamples configures mock responses that should be returned from OAS example responses.
    type FromOASExamples struct {
    	// Enabled activates getting a mock response from OAS examples or schemas documented in OAS.
    	Enabled bool `bson:"enabled" json:"enabled"`
    	// Code is the default HTTP response code that the gateway reads from the path responses documented in OAS.
    	Code int `bson:"code,omitempty" json:"code,omitempty"`
    	// ContentType is the default HTTP response body type that the gateway reads from the path responses documented in OAS.
    	ContentType string `bson:"contentType,omitempty" json:"contentType,omitempty"`
    	// ExampleName is the default example name among multiple path response examples documented in OAS.
    	ExampleName string `bson:"exampleName,omitempty" json:"exampleName,omitempty"`
    }
    Type Handling Logic

    The logic for determining schema types in ExampleExtractor and emptyExampleVal has been updated to use the new Types API from kin-openapi. The reviewer should ensure that all type checks (object, array, string, integer, number, boolean) are correctly migrated and that no regressions are introduced in example extraction or default value generation.

    	}
    
    	switch {
    	case val.Type.Is(openapi3.TypeObject):
    		obj := make(map[string]interface{})
    		for name, prop := range schema.Value.Properties {
    			obj[name] = ExampleExtractor(prop)
    		}
    
    		return obj
    	case val.Type.Is(openapi3.TypeArray):
    		items := ExampleExtractor(val.Items)
    		return []interface{}{items}
    	default:
    		if len(val.Enum) > 0 {
    			return val.Enum[0]
    		}
    		return emptyExampleVal(val)
    	}
    }
    
    func emptyExampleVal(schema *openapi3.Schema) interface{} {
    	switch {
    	case schema.Type.Is(openapi3.TypeString):
    		return "string"
    	case schema.Type.Is(openapi3.TypeInteger), schema.Type.Is(openapi3.TypeNumber):
    		return 0
    	case schema.Type.Is(openapi3.TypeBoolean):
    		return true
    	default:
    Test Coverage for Path/Response Migration

    Numerous test cases have been updated to use the new Paths and Responses APIs. The reviewer should ensure that the test logic remains valid and that all assertions accurately reflect the intended behavior, especially where map access and mutation semantics have changed.

    	const existingOperationId = "userPOST"
    
    	var oas OAS
    	paths := openapi3.NewPaths()
    	paths.Set("/user", &openapi3.PathItem{
    		Get: &openapi3.Operation{
    			OperationID: operationId,
    		},
    	})
    	oas.Paths = paths
    
    	var operation Operation
    	Fill(t, &operation, 0)
    	operation.TrackEndpoint = nil                     // This one also fills native part, let's skip it for this test.
    	operation.DoNotTrackEndpoint = nil                // This one also fills native part, let's skip it for this test.
    	operation.ValidateRequest = nil                   // This one also fills native part, let's skip it for this test.
    	operation.MockResponse = nil                      // This one also fills native part, let's skip it for this test.
    	operation.URLRewrite = nil                        // This one also fills native part, let's skip it for this test.
    	operation.Internal = nil                          // This one also fills native part, let's skip it for this test.
    	operation.TransformRequestBody.Path = ""          // if `path` and `body` are present, `body` would take precedence, detailed tests can be found in middleware_test.go
    	operation.TransformResponseBody.Path = ""         // if `path` and `body` are present, `body` would take precedence, detailed tests can be found in middleware_test.go
    	operation.VirtualEndpoint.Path = ""               // if `path` and `body` are present, `body` would take precedence, detailed tests can be found in middleware_test.go
    	operation.VirtualEndpoint.Name = ""               // Name is deprecated.
    	operation.PostPlugins = operation.PostPlugins[:1] // only 1 post plugin is considered at this point, ignore others.
    	operation.PostPlugins[0].Name = ""                // Name is deprecated.
    
    	operation.RateLimit.Per = ReadableDuration(time.Minute)
    
    	xTykAPIGateway := &XTykAPIGateway{
    		Middleware: &Middleware{
    			Operations: Operations{
    				operationId: &operation,
    			},
    		},
    	}
    
    	oas.SetTykExtension(xTykAPIGateway)
    
    	var ep apidef.ExtendedPathsSet
    	oas.extractPathsAndOperations(&ep)
    
    	convertedOAS := minimumValidOAS()
    	convertedPaths := openapi3.NewPaths()
    
    	convertedPaths.Set("/user", &openapi3.PathItem{
    		Post: &openapi3.Operation{
    			OperationID: existingOperationId,
    			Responses:   openapi3.NewResponses(),
    		},
    	})
    	convertedOAS.Paths = paths
    	convertedOAS.SetTykExtension(&XTykAPIGateway{Middleware: &Middleware{Operations: Operations{}}})
    	convertedOAS.fillPathsAndOperations(ep)
    
    	assert.Equal(t, oas.getTykOperations(), convertedOAS.getTykOperations())
    
    	expCombinedPaths := openapi3.NewPaths()
    	expCombinedPaths.Set("/user", &openapi3.PathItem{
    		Post: &openapi3.Operation{
    			OperationID: existingOperationId,
    			Responses:   openapi3.NewResponses(),
    		},
    		Get: &openapi3.Operation{
    			OperationID: operationId,
    			Responses:   openapi3.NewResponses(),
    		},
    	})
    
    	assert.Equal(t, expCombinedPaths, convertedOAS.Paths)
    
    	t.Run("oas validation", func(t *testing.T) {
    		err := convertedOAS.Validate(context.Background())
    		assert.NoError(t, err)
    	})
    }
    
    func TestOAS_MockResponse_extractPathsAndOperations(t *testing.T) {
    	t.Parallel()
    
    	type testCase struct {
    		name string
    		spec OAS
    		want func(t *testing.T, ep *apidef.ExtendedPathsSet)
    	}
    
    	tests := []testCase{
    		{
    			name: "basic mock response",
    			spec: OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/test", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "testGET",
    							},
    						})
    						return paths
    					}(),
    					Extensions: map[string]interface{}{
    						"x-tyk-api-gateway": &XTykAPIGateway{
    							Middleware: &Middleware{
    								Operations: Operations{
    									"testGET": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: true,
    											Code:    200,
    											Body:    `{"message": "success"}`,
    											Headers: []Header{
    												{Name: "Content-Type", Value: "application/json"},
    											},
    										},
    									},
    								},
    							},
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, ep *apidef.ExtendedPathsSet) {
    				t.Helper()
    
    				// Verify mock responses
    				mockResponses := ep.MockResponse
    				require.Len(t, mockResponses, 0)
    			},
    		},
    		{
    			name: "multiple methods on same path",
    			spec: OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/test", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "testGET",
    							},
    							Post: &openapi3.Operation{
    								OperationID: "testPOST",
    							},
    						})
    						return paths
    					}(),
    					Extensions: map[string]interface{}{
    						"x-tyk-api-gateway": &XTykAPIGateway{
    							Middleware: &Middleware{
    								Operations: Operations{
    									"testGET": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: true,
    											Code:    200,
    											Body:    `{"status": "ok"}`,
    											Headers: []Header{
    												{Name: "Content-Type", Value: "application/json"},
    											},
    										},
    									},
    									"testPOST": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: true,
    											Code:    201,
    											Body:    `{"id": "123"}`,
    											Headers: []Header{
    												{Name: "Content-Type", Value: "application/json"},
    												{Name: "Location", Value: "/test/123"},
    											},
    										},
    									},
    								},
    							},
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, ep *apidef.ExtendedPathsSet) {
    				t.Helper()
    
    				// Verify mock responses
    				mockResponses := ep.MockResponse
    				require.Len(t, mockResponses, 0)
    			},
    		},
    		{
    			name: "disabled mock response",
    			spec: OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/test", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "testGET",
    							},
    						})
    						return paths
    					}(),
    					Extensions: map[string]interface{}{
    						"x-tyk-api-gateway": &XTykAPIGateway{
    							Middleware: &Middleware{
    								Operations: Operations{
    									"testGET": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: false,
    											Code:    404,
    											Body:    `{"error": "not found"}`,
    										},
    									},
    								},
    							},
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, ep *apidef.ExtendedPathsSet) {
    				t.Helper()
    
    				// Verify mock responses
    				mockResponses := ep.MockResponse
    				require.Len(t, mockResponses, 0)
    			},
    		},
    		{
    			name: "no mock responses",
    			spec: OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/test", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "testGET",
    							},
    						})
    						return paths
    					}(),
    					Extensions: map[string]interface{}{
    						"x-tyk-api-gateway": &XTykAPIGateway{
    							Middleware: &Middleware{
    								Operations: Operations{
    									"testGET": &Operation{},
    								},
    							},
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, ep *apidef.ExtendedPathsSet) {
    				t.Helper()
    
    				assert.Empty(t, ep.MockResponse)
    			},
    		},
    		{
    			name: "multiple paths with mock responses",
    			spec: OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/users", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "usersGET",
    							},
    						})
    						paths.Set("/items", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								OperationID: "itemsGET",
    							},
    						})
    						return paths
    					}(),
    					Extensions: map[string]interface{}{
    						"x-tyk-api-gateway": &XTykAPIGateway{
    							Middleware: &Middleware{
    								Operations: Operations{
    									"usersGET": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: true,
    											Code:    200,
    											Body:    `["user1", "user2"]`,
    											Headers: []Header{{Name: "Content-Type", Value: "application/json"}},
    										},
    									},
    									"itemsGET": &Operation{
    										MockResponse: &MockResponse{
    											Enabled: true,
    											Code:    200,
    											Body:    `["item1", "item2"]`,
    											Headers: []Header{{Name: "Content-Type", Value: "application/json"}},
    										},
    									},
    								},
    							},
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, ep *apidef.ExtendedPathsSet) {
    				t.Helper()
    
    				// Verify mock responses
    				mockResponses := ep.MockResponse
    				require.Len(t, mockResponses, 0)
    			},
    		},
    	}
    
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    			var ep apidef.ExtendedPathsSet
    			tt.spec.extractPathsAndOperations(&ep)
    
    			// We should ensure no AllowList is created
    			require.Len(t, ep.WhiteList, 0)
    
    			tt.want(t, &ep)
    		})
    	}
    }
    
    func TestOAS_PathsAndOperationsRegex(t *testing.T) {
    	t.Parallel()
    
    	expectedOperationID := "users/[a-z]+/[0-9]+$GET"
    	expectedPath := "/users/{customRegex1}/{customRegex2}"
    
    	var oas OAS
    	oas.Paths = openapi3.NewPaths()
    
    	_ = oas.getOperationID("/users/[a-z]+/[0-9]+$", "GET")
    
    	expectedPathItems := openapi3.NewPaths()
    	expectedPathItems.Set(expectedPath, &openapi3.PathItem{
    		Get: &openapi3.Operation{
    			OperationID: expectedOperationID,
    			Responses:   openapi3.NewResponses(),
    		},
    		Parameters: []*openapi3.ParameterRef{
    			{
    				Value: &openapi3.Parameter{
    					Schema: &openapi3.SchemaRef{
    						Value: &openapi3.Schema{
    							Type:    &openapi3.Types{openapi3.TypeString},
    							Pattern: "[a-z]+",
    						},
    					},
    					Name:     "customRegex1",
    					In:       "path",
    					Required: true,
    				},
    			},
    			{
    				Value: &openapi3.Parameter{
    					Schema: &openapi3.SchemaRef{
    						Value: &openapi3.Schema{
    							Type:    &openapi3.Types{openapi3.TypeString},
    							Pattern: "[0-9]+$",
    						},
    					},
    					Name:     "customRegex2",
    					In:       "path",
    					Required: true,
    				},
    			},
    		},
    	})
    
    	assert.Equal(t, expectedPathItems, oas.Paths, "expected path item differs")
    }
    
    func TestOAS_RegexOperationIDs(t *testing.T) {
    	t.Parallel()
    
    	type test struct {
    		input  string
    		method string
    		want   string
    	}
    
    	tests := []test{
    		{"/.+", "GET", ".+GET"},
    		{"/.*", "GET", ".*GET"},
    		{"/[^a]*", "GET", "[^a]*GET"},
    		{"/foo$", "GET", "foo$GET"},
    		{"/group/.+", "GET", "group/.+GET"},
    		{"/group/.*", "GET", "group/.*GET"},
    		{"/group/[^a]*", "GET", "group/[^a]*GET"},
    		{"/group/foo$", "GET", "group/foo$GET"},
    		{"/group/[^a]*/.*", "GET", "group/[^a]*/.*GET"},
    	}
    
    	for i, tc := range tests {
    		var oas OAS
    		oas.Paths = openapi3.NewPaths()
    		oas.Paths.Set(tc.input, &openapi3.PathItem{
    			Get: &openapi3.Operation{},
    		})
    		got := oas.getOperationID(tc.input, tc.method)
    		assert.Equalf(t, tc.want, got, "test %d: expected operationID %v, got %v", i, tc.want, got)
    	}
    }
    
    func TestOAS_RegexPaths(t *testing.T) {
    	t.Parallel()
    
    	type test struct {
    		input  string
    		want   string
    		params int
    	}
    
    	tests := []test{
    		{"/v1.Service", "/v1.Service", 0},
    		{"/v1.Service/stats.Service", "/v1.Service/stats.Service", 0},
    		{"/.+", "/{customRegex1}", 1},
    		{"/.*", "/{customRegex1}", 1},
    		{"/[^a]*", "/{customRegex1}", 1},
    		{"/foo$", "/{customRegex1}", 1},
    		{"/group/.+", "/group/{customRegex1}", 1},
    		{"/group/.*", "/group/{customRegex1}", 1},
    		{"/group/[^a]*", "/group/{customRegex1}", 1},
    		{"/group/foo$", "/group/{customRegex1}", 1},
    		{"/group/[^a]*/.*", "/group/{customRegex1}/{customRegex2}", 2},
    	}
    
    	for i, tc := range tests {
    		var oas OAS
    		oas.Paths = openapi3.NewPaths()
    		oas.Paths.Set(tc.input, &openapi3.PathItem{
    			Get: &openapi3.Operation{},
    		})
    		got := oas.getOperationID(tc.input, "GET")
    
    		pathKeys := make([]string, 0, len(oas.Paths.Map()))
    		for k := range oas.Paths.Map() {
    			pathKeys = append(pathKeys, k)
    		}
    
    		assert.Lenf(t, oas.Paths, 1, "Expected one path key being created, got %#v", pathKeys)
    		_, ok := oas.Paths.Map()[tc.want]
    		assert.True(t, ok)
    
    		p, ok := oas.Paths.Map()[tc.want]
    		assert.Truef(t, ok, "test %d: path doesn't exist in OAS: %v", i, tc.want)
    		assert.Lenf(t, p.Parameters, tc.params, "test %d: expected %d parameters, got %d", i, tc.params, len(p.Parameters))
    
    		// rebuild original link
    		got = tc.want
    		for _, param := range p.Parameters {
    			assert.NotNilf(t, param.Value, "test %d: missing value", i)
    			assert.NotNilf(t, param.Value.Schema, "test %d: missing schema", i)
    			assert.NotNilf(t, param.Value.Schema.Value, "test %d: missing schema value", i)
    
    			assert.Truef(t, strings.HasPrefix(param.Value.Name, "customRegex"), "test %d: invalid name %v", i, param.Value.Name)
    
    			got = strings.ReplaceAll(got, "{"+param.Value.Name+"}", param.Value.Schema.Value.Pattern)
    		}
    
    		assert.Equalf(t, tc.input, got, "test %d: rebuilt link, expected %v, got %v", i, tc.input, got)
    	}
    }
    
    // Map HTTP methods to their corresponding PathItem field setters
    var methodSetters = map[string]func(*openapi3.PathItem, *openapi3.Operation){
    	"GET":     func(p *openapi3.PathItem, op *openapi3.Operation) { p.Get = op },
    	"POST":    func(p *openapi3.PathItem, op *openapi3.Operation) { p.Post = op },
    	"PUT":     func(p *openapi3.PathItem, op *openapi3.Operation) { p.Put = op },
    	"PATCH":   func(p *openapi3.PathItem, op *openapi3.Operation) { p.Patch = op },
    	"DELETE":  func(p *openapi3.PathItem, op *openapi3.Operation) { p.Delete = op },
    	"HEAD":    func(p *openapi3.PathItem, op *openapi3.Operation) { p.Head = op },
    	"OPTIONS": func(p *openapi3.PathItem, op *openapi3.Operation) { p.Options = op },
    }
    
    func TestOAS_MockResponse_fillMockResponsePaths(t *testing.T) {
    	t.Parallel()
    
    	type testCase struct {
    		name string
    		ep   apidef.ExtendedPathsSet
    		spec *OAS
    		want func(t *testing.T, spec *OAS)
    	}
    
    	tests := []testCase{
    		{
    			name: "basic mock response",
    			spec: &OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Paths: func() *openapi3.Paths {
    						paths := openapi3.NewPaths()
    						paths.Set("/test", &openapi3.PathItem{
    							Get: &openapi3.Operation{
    								Summary:     "Existing summary",
    								OperationID: "testGET",
    							},
    						})
    						return paths
    					}(),
    				},
    			},
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{{
    					Path:   "/test",
    					Method: "GET",
    					Code:   200,
    					Body:   `{"message": "success"}`,
    					Headers: map[string]string{
    						"Content-Type": "application/json",
    					},
    				}},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				require.Len(t, spec.Paths, 1)
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    				tykOperation := spec.GetTykExtension().getOperation(pathItem.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				// Verify operation
    				require.NotNil(t, pathItem.Get)
    				require.Equal(t, "testGET", pathItem.Get.OperationID)
    				require.Equal(t, "Existing summary", pathItem.Get.Summary)
    				require.Nil(t, pathItem.Post)
    				require.Nil(t, pathItem.Put)
    				require.Nil(t, pathItem.Patch)
    				require.Nil(t, pathItem.Delete)
    
    				// Verify response
    				response200Ref := pathItem.Get.Responses.Map()["200"]
    				require.NotNil(t, response200Ref, "Response ref for 200 should not be nil")
    
    				response200 := response200Ref.Value
    				require.NotNil(t, response200, "Response value for 200 should not be nil")
    				require.NotNil(t, response200.Description)
    
    				// Verify headers
    				require.Nil(t, response200.Headers)
    			},
    		},
    		{
    			name: "multiple methods on same path",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{
    					{
    						Path:   "/test",
    						Method: "GET",
    						Code:   200,
    						Body:   `{"status": "ok"}`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    						},
    					},
    					{
    						Path:   "/test",
    						Method: "POST",
    						Code:   201,
    						Body:   `{"id": "123"}`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    							"Location":     "/test/123",
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				assert.Len(t, spec.Paths, 1)
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    
    				// Verify GET operation
    				require.NotNil(t, pathItem.Get)
    				require.Equal(t, "testGET", pathItem.Get.OperationID)
    				tykOperation := spec.GetTykExtension().getOperation(pathItem.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				response200Ref := pathItem.Get.Responses.Map()["200"]
    				require.NotNil(t, response200Ref, "Response ref for 200 should not be nil")
    
    				response200 := response200Ref.Value
    				require.NotNil(t, response200, "Response value should not be nil")
    				require.NotNil(t, response200.Description)
    
    				// Verify POST operation
    				require.NotNil(t, pathItem.Post)
    				require.Equal(t, "testPOST", pathItem.Post.OperationID)
    				tykOperation = spec.GetTykExtension().getOperation(pathItem.Post.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				postResponse := pathItem.Post.Responses.Map()["201"].Value
    				require.NotNil(t, postResponse)
    				require.NotNil(t, postResponse.Description)
    			},
    		},
    		{
    			name: "no content type header defaults to text/plain",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{{
    					Path:   "/test",
    					Method: "GET",
    					Code:   204,
    					Body:   "",
    				}},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    				require.Equal(t, "testGET", pathItem.Get.OperationID)
    				tykOperation := spec.GetTykExtension().getOperation(pathItem.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				response204Ref := pathItem.Get.Responses.Map()["204"]
    				require.NotNil(t, response204Ref, "Response ref for 204 should not be nil")
    
    				response204 := response204Ref.Value
    				require.NotNil(t, response204, "Response value for 204 should not be nil")
    				require.Nil(t, response204.Content["text/plain"])
    				require.Empty(t, response204.Headers)
    			},
    		},
    		{
    			name: "multiple paths",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{
    					{
    						Path:   "/users",
    						Method: "GET",
    						Code:   200,
    						Body:   `["user1", "user2"]`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    						},
    					},
    					{
    						Path:   "/items",
    						Method: "GET",
    						Code:   200,
    						Body:   `["item1", "item2"]`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    				assert.Len(t, spec.Paths, 2)
    
    				// Verify /users path
    				usersPath := spec.Paths.Map()["/users"]
    				require.NotNil(t, usersPath)
    				require.NotNil(t, usersPath.Get)
    				require.Equal(t, "usersGET", usersPath.Get.OperationID)
    				tykOperation := spec.GetTykExtension().getOperation(usersPath.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				usersResponse := usersPath.Get.Responses.Map()["200"].Value
    				require.NotNil(t, usersResponse)
    				require.NotNil(t, usersResponse.Description)
    
    				// Verify /items path
    				itemsPath := spec.Paths.Map()["/items"]
    				require.NotNil(t, itemsPath)
    				require.NotNil(t, itemsPath.Get)
    				require.Equal(t, "itemsGET", itemsPath.Get.OperationID)
    				tykOperation = spec.GetTykExtension().getOperation(itemsPath.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				itemsResponse := itemsPath.Get.Responses.Map()["200"].Value
    				require.NotNil(t, itemsResponse)
    				require.NotNil(t, itemsResponse.Description)
    			},
    		},
    		{
    			name: "empty mock response list",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				assert.Empty(t, spec.Paths)
    			},
    		},
    		{
    			name: "multiple response codes for same path",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{
    					{
    						Path:   "/test",
    						Method: "GET",
    						Code:   200,
    						Body:   `{"status": "success"}`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    				require.NotNil(t, pathItem.Get)
    				require.Equal(t, "testGET", pathItem.Get.OperationID)
    
    				// Verify responses exist
    				require.NotNil(t, pathItem.Get.Responses)
    
    				// Verify 200 response
    				response200 := pathItem.Get.Responses.Map()["200"]
    				require.NotNil(t, response200, "Response for 200 should not be nil")
    				require.NotNil(t, response200.Value)
    				require.NotNil(t, response200.Value.Description)
    				tykOperation := spec.GetTykExtension().getOperation(pathItem.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    			},
    		},
    		{
    			name: "different content types",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{
    					{
    						Path:   "/test",
    						Method: "GET",
    						Code:   200,
    						Body:   `{"data": "json"}`,
    						Headers: map[string]string{
    							"Content-Type": "application/json",
    						},
    					},
    					{
    						Path:   "/test.xml",
    						Method: "GET",
    						Code:   200,
    						Body:   `<data>xml</data>`,
    						Headers: map[string]string{
    							"Content-Type": "application/xml",
    						},
    					},
    					{
    						Path:   "/test.txt",
    						Method: "GET",
    						Code:   200,
    						Body:   `plain text`,
    						Headers: map[string]string{
    							"Content-Type": "text/plain",
    						},
    					},
    				},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				// JSON endpoint
    				jsonPath := spec.Paths.Map()["/test"]
    				require.NotNil(t, jsonPath)
    				jsonResponse := jsonPath.Get.Responses.Map()["200"].Value
    				require.NotNil(t, jsonResponse)
    				require.NotNil(t, jsonResponse.Description)
    				tykOperation := spec.GetTykExtension().getOperation(jsonPath.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				// XML endpoint
    				xmlPath := spec.Paths.Map()["/test.xml"]
    				require.NotNil(t, xmlPath)
    				xmlResponse := xmlPath.Get.Responses.Map()["200"].Value
    				require.NotNil(t, xmlResponse)
    				require.NotNil(t, xmlResponse.Description)
    				tykOperation = spec.GetTykExtension().getOperation(xmlPath.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    
    				// Text endpoint
    				txtPath := spec.Paths.Map()["/test.txt"]
    				require.NotNil(t, txtPath)
    				txtResponse := txtPath.Get.Responses.Map()["200"].Value
    				require.NotNil(t, txtResponse)
    				require.NotNil(t, txtResponse.Description)
    				tykOperation = spec.GetTykExtension().getOperation(txtPath.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    			},
    		},
    		{
    			name: "custom headers",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{{
    					Path:   "/test",
    					Method: "GET",
    					Code:   200,
    					Body:   `{"data": "test"}`,
    					Headers: map[string]string{
    						"Content-Type":      "application/json",
    						"X-Custom-Header":   "custom-value",
    						"X-Request-ID":      "123",
    						"X-Correlation-ID":  "abc",
    						"Cache-Control":     "no-cache",
    						"X-RateLimit-Limit": "100",
    					},
    				}},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    				response := pathItem.Get.Responses.Map()["200"].Value
    				require.NotNil(t, response)
    				require.NotNil(t, response.Description)
    
    				tykOperation := spec.GetTykExtension().getOperation(pathItem.Get.OperationID)
    				require.NotNil(t, tykOperation)
    				require.Nil(t, tykOperation.Allow)
    			},
    		},
    		{
    			name: "all HTTP methods",
    			ep: apidef.ExtendedPathsSet{
    				MockResponse: []apidef.MockResponseMeta{
    					{Path: "/test", Method: "GET", Code: 200, Body: `{"method":"get"}`},
    					{Path: "/test", Method: "POST", Code: 201, Body: `{"method":"post"}`},
    					{Path: "/test", Method: "PUT", Code: 200, Body: `{"method":"put"}`},
    					{Path: "/test", Method: "PATCH", Code: 200, Body: `{"method":"patch"}`},
    					{Path: "/test", Method: "DELETE", Code: 204, Body: ``},
    					{Path: "/test", Method: "HEAD", Code: 200, Body: ``},
    					{Path: "/test", Method: "OPTIONS", Code: 200, Body: ``},
    				},
    			},
    			want: func(t *testing.T, spec *OAS) {
    				t.Helper()
    
    				pathItem := spec.Paths.Map()["/test"]
    				require.NotNil(t, pathItem)
    
    				verifyOASOperation(t, spec, pathItem.Get, "GET", 200)
    				verifyOASOperation(t, spec, pathItem.Post, "POST", 201)
    				verifyOASOperation(t, spec, pathItem.Put, "PUT", 200)
    				verifyOASOperation(t, spec, pathItem.Patch, "PATCH", 200)
    				verifyOASOperation(t, spec, pathItem.Delete, "DELETE", 204)
    			},
    		},
    	}
    
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    			spec := &OAS{
    				T: openapi3.T{
    					OpenAPI: DefaultOpenAPI,
    					Info: &openapi3.Info{
    						Title:   "Test API",
    						Version: "1.0.0",
    					},
    					Paths: openapi3.NewPaths(),
    				},
    			}
    
    			if tt.spec != nil && tt.spec.Paths != nil {
    				spec.Paths = tt.spec.Paths
    			}
    
    			// Initialize the middleware extension with proper mock response setup
    			middleware := &Middleware{
    				Operations: make(Operations),
    			}
    
    			// Pre-initialize operations with mock responses
    			for _, mockResp := range tt.ep.MockResponse {
    				operationID := spec.getOperationID(mockResp.Path, mockResp.Method)
    
    				operation := &Operation{
    					MockResponse: &MockResponse{
    						Code:    mockResp.Code,
    						Body:    mockResp.Body,
    						Headers: make([]Header, 0),
    					},
    				}
    
    				// Convert headers to the expected format
    				for name, value := range mockResp.Headers {
    					operation.MockResponse.Headers = append(operation.MockResponse.Headers, Header{
    						Name:  name,
    						Value: value,
    					})
    				}
    
    				middleware.Operations[operationID] = operation
    			}
    
    			spec.SetTykExtension(&XTykAPIGateway{
    				Middleware: middleware,
    			})
    
    			// Initialize paths
    			for _, mockResp := range tt.ep.MockResponse {
    				operationID := spec.getOperationID(mockResp.Path, mockResp.Method)
    
    				// Initialize operation with responses
    				op := &openapi3.Operation{
    					OperationID: operationID,
    					Responses:   openapi3.NewResponses(),
    				}
    
    				// Preserve existing operation properties if they exist
    				if pathItem := spec.Paths.Find(mockResp.Path); pathItem != nil {
    					if existingOp := pathItem.GetOperation(mockResp.Method); existingOp != nil {
    						op.Summary = existingOp.Summary
    						op.Description = existingOp.Description
    						// Copy other relevant fields as needed
    					}
    				}
    
    				if tt.spec != nil {
    					if spec.Paths == nil {
    						spec.Paths = openapi3.NewPaths()
    					}
    					for k, v := range tt.spec.Paths.Map() {
    						spec.Paths.Map()[k] = v
    					}
    				}
    
    				// Set the operation based on method
    				if setter, ok := methodSetters[mockResp.Method]; ok {
    					path := mockResp.Path // Use the mock response path directly
    					if spec.Paths.Map()[path] == nil {
    						spec.Paths.Map()[path] = &openapi3.PathItem{}
    					}
    					setter(spec.Paths.Map()[path], op)
    
    					var desc string
    
    					// Add response for the specific status code
    					statusCode := strconv.Itoa(mockResp.Code)
    
    					op.Responses.Set(statusCode, &openapi3.ResponseRef{
    						Value: &openapi3.Response{
    							Description: &desc,
    						},
    					})
    				}
    			}
    
    			spec.fillMockResponsePaths(*spec.Paths, tt.ep)
    			tt.want(t, spec)
    		})
    	}
    }
    
    // Helper function to verify OpenAPI operation responses
    func verifyOASOperation(t *testing.T, spec *OAS, op *openapi3.Operation, method string, code int) {
    	t.Helper()
    
    	require.NotNil(t, op, "Operation %s should exist", method)
    	require.NotNil(t, op.Responses, "Responses should not be nil")
    
    	statusCode := strconv.Itoa(code)
    	require.NotNil(t, op.Responses.Map()[statusCode], "Responses should not be nil for status code %s and method %s", statusCode, method)
    
    	response := op.Responses.Map()[statusCode].Value
    	require.NotNil(t, response)
    	require.NotNil(t, response.Description)
    
    	tykOperation := spec.GetTykExtension().getOperation(op.OperationID)
    	require.NotNil(t, tykOperation)
    	require.Nil(t, tykOperation.Allow)
    }
    
    func TestOAS_fillAllowance(t *testing.T) {
    	t.Run("should fill allow list correctly", func(t *testing.T) {
    		s := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		s.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		endpointMetas := []apidef.EndPointMeta{
    			{
    				Path:   "/test",
    				Method: http.MethodGet,
    				MethodActions: map[string]apidef.EndpointMethodMeta{
    					http.MethodGet: {
    						Action: apidef.NoAction,
    					},
    				},
    			},
    		}
    
    		s.fillAllowance(endpointMetas, allow)
    
    		operationID := s.getOperationID("/test", http.MethodGet)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		assert.NotNil(t, operation.Allow)
    		assert.True(t, operation.Allow.Enabled)
    		assert.Nil(t, operation.Block)
    		assert.Nil(t, operation.IgnoreAuthentication)
    	})
    
    	t.Run("should fill block list correctly", func(t *testing.T) {
    		s := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		s.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		endpointMetas := []apidef.EndPointMeta{
    			{
    				Path:   "/test",
    				Method: http.MethodGet,
    			},
    		}
    
    		s.fillAllowance(endpointMetas, block)
    
    		operationID := s.getOperationID("/test", http.MethodGet)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		assert.NotNil(t, operation.Block)
    		assert.True(t, operation.Block.Enabled)
    		assert.Nil(t, operation.Allow)
    		assert.Nil(t, operation.IgnoreAuthentication)
    	})
    
    	t.Run("should fill ignore authentication correctly", func(t *testing.T) {
    		s := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		s.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		endpointMetas := []apidef.EndPointMeta{
    			{
    				Path:   "/test",
    				Method: http.MethodGet,
    			},
    		}
    
    		s.fillAllowance(endpointMetas, ignoreAuthentication)
    
    		operationID := s.getOperationID("/test", http.MethodGet)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		assert.NotNil(t, operation.IgnoreAuthentication)
    		assert.True(t, operation.IgnoreAuthentication.Enabled)
    		assert.Nil(t, operation.Allow)
    		assert.Nil(t, operation.Block)
    	})
    
    	t.Run("should skip Reply actions for allow list", func(t *testing.T) {
    		spec := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		spec.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		endpointMetas := []apidef.EndPointMeta{
    			{
    				Path:   "/test",
    				Method: http.MethodGet,
    				MethodActions: map[string]apidef.EndpointMethodMeta{
    					http.MethodGet: {
    						Action: apidef.Reply,
    					},
    				},
    			},
    		}
    
    		spec.fillAllowance(endpointMetas, allow)
    
    		operationID := spec.getOperationID("/test", http.MethodGet)
    		operation := spec.GetTykExtension().getOperation(operationID)
    
    		assert.Nil(t, operation.Allow, "Allow should be nil for Reply actions")
    	})
    
    	t.Run("should handle empty endpoint metas", func(t *testing.T) {
    		s := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		s.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		var endpointMetas []apidef.EndPointMeta
    
    		s.fillAllowance(endpointMetas, allow)
    
    		assert.Empty(t, s.Paths)
    	})
    
    	t.Run("should set allowance disabled when ShouldOmit returns true", func(t *testing.T) {
    		s := &OAS{
    			T: openapi3.T{
    				Paths: openapi3.NewPaths(),
    			},
    		}
    
    		s.SetTykExtension(&XTykAPIGateway{
    			Middleware: &Middleware{
    				Operations: make(Operations),
    			},
    		})
    
    		endpointMetas := []apidef.EndPointMeta{
    			{
    				Path:     "/test",
    				Method:   http.MethodGet,
    				Disabled: true,
    			},
    		}
    
    		s.fillAllowance(endpointMetas, allow)
    
    		operationID := s.getOperationID("/test", http.MethodGet)
    		operation := s.GetTykExtension().getOperation(operationID)
    
    		assert.NotNil(t, operation.Allow)
    		assert.False(t, operation.Allow.Enabled)
    	})
    }
    
    func TestSplitPath(t *testing.T) {
    	tests := map[string]struct {
    		input     string
    		wantParts []pathPart
    		wantRegex bool
    	}{
    		"simple path": {
    			input: "/test/path",
    			wantParts: []pathPart{
    				{name: "test", value: "test", isRegex: false},
    				{name: "path", value: "path", isRegex: false},
    			},
    			wantRegex: false,
    		},
    		"path with regex": {
    			input: "/test/.*/end",
    			wantParts: []pathPart{
    				{name: "test", value: "test", isRegex: false},
    				{name: "customRegex1", value: ".*", isRegex: true},
    				{name: "end", value: "end", isRegex: false},
    			},
    			wantRegex: true,
    		},
    		"path with curly braces": {
    			input: "/users/{id}/profile",
    			wantParts: []pathPart{
    				{name: "users", value: "users", isRegex: false},
    				{name: "id", isRegex: true},
    				{name: "profile", value: "profile", isRegex: false},
    			},
    			wantRegex: true,
    		},
    		"path with named regex": {
    			input: "/users/{userId:[0-9]+}/posts",
    			wantParts: []pathPart{
    				{name: "users", value: "users", isRegex: false},
    				{name: "userId", isRegex: true},
    				{name: "posts", value: "posts", isRegex: false},
    			},
    			wantRegex: true,
    		},
    		"path with named direct regex": {
    			input: "/users/[0-9]+/posts",
    			wantParts: []pathPart{
    				{name: "users", value: "users", isRegex: false},
    				{name: "customRegex1", value: "[0-9]+", isRegex: true},
    				{name: "posts", value: "posts", isRegex: false},
    			},
    			wantRegex: true,
    		},
    		"empty path": {
    			input:     "",
    			wantParts: []pathPart{},
    			wantRegex: false,
    		},
    		"root path": {
    			input:     "/",
    			wantParts: []pathPart{},
    			wantRegex: false,
    		},
    		"path with multiple regexes": {
    			input: "/users/{userId:[0-9]+}/posts/{postId:[a-z]+}/[a-z]+/{[0-9]{2}}/[a-z]{10}/abc/{id}/def/[0-9]+",
    			wantParts: []pathPart{
    				{name: "users", value: "users", isRegex: false},
    				{name: "userId", isRegex: true},
    				{name: "posts", value: "posts", isRegex: false},
    				{name: "postId", isRegex: true},
    				{name: "customRegex1", value: "[a-z]+", isRegex: true},
    				{name: "customRegex2", isRegex: true},
    				{name: "customRegex3", value: "[a-z]{10}", isRegex: true},
    				{name: "abc", value: "abc", isRegex: false},
    				{name: "id", isRegex: true},
    				{name: "def", value: "def", isRegex: false},
    				{name: "customRegex4", value: "[0-9]+", isRegex: true},
    			},
    			wantRegex: true,
    		},
    	}
    
    	for name, tc := range tests {
    		t.Run(name, func(t *testing.T) {
    			gotParts, gotRegex := splitPath(tc.input)
    
    			assert.Equal(t, tc.wantRegex, gotRegex, "regex detection mismatch")
    			assert.Equal(t, tc.wantParts, gotParts, "parts mismatch")
    		})
    	}
    }
    
    func TestGetOperationID(t *testing.T) {
    	type expectedParam struct {
    		pattern   string
    		paramType string
    	}
    
    	tests := map[string]struct {
    		inPath         string
    		method         string
    		expectedID     string
    		expectedPath   string
    		existingParams map[string]expectedParam
    		expectedParams map[string]expectedParam
    	}{
    		"simple path": {
    			inPath:         "/simple",
    			method:         "GET",
    			expectedID:     "simpleGET",
    			expectedPath:   "/simple",
    			existingParams: nil,
    			expectedParams: nil,
    		},
    		"path with regex": {
    			inPath:         "/items/{id}",
    			method:         "GET",
    			expectedID:     "items/{id}GET",
    			expectedPath:   "/items/{id}",
    			existingParams: nil,
    			expectedParams: map[string]expectedParam{
    				"id": {pattern: "", paramType: "string"},
    			},
    		},
    		"path with trailing slash": {
    			inPath:         "/trailing/",
    			method:         "POST",
    			expectedID:     "trailing/POST",
    			expectedPath:   "/trailing/",
    			existingParams: nil,
    			expectedParams: nil,
    		},
    		"complex regex path": {
    			inPath:       "/complex/{id}",
    			method:       "PUT",
    			expectedID:   "complex/{id}PUT",
    			expectedPath: "/complex/{id}",
    			existingParams: map[string]expectedParam{
    				"id": {pattern: "", paramType: "integer"},
    			},
    			expectedParams: map[string]expectedParam{
    				"id": {pattern: "", paramType: "integer"},
    			},
    		},
    		"path with existing parameter": {
    			inPath:       "/existing/{id}",
    			method:       "DELETE",
    			expectedID:   "existing/{id}DELETE",
    			expectedPath: "/existing/{id}",
    			existingParams: map[string]expectedParam{
    				"id": {pattern: "[0-9]+", paramType: "string"},
    			},
    			expectedParams: map[string]expectedParam{
    				"id": {pattern: "[0-9]+", paramType: "string"},
    			},
    		},
    	}
    
    	for name, tc := range tests {
    		t.Run(name, func(t *testing.T) {
    			oas := &OAS{
    				T: openapi3.T{
    					Paths: openapi3.NewPaths(),
    				},
    			}
    
    			// Prepopulate existing parameters if any
    			if tc.existingParams != nil {
    				pathItem := &openapi3.PathItem{}
    				for paramName, param := range tc.existingParams {
    					pathItem.Parameters = append(pathItem.Parameters, &openapi3.ParameterRef{
    						Value: &openapi3.Parameter{
    							Name:     paramName,
    							In:       "path",
    							Required: true,
    							Schema: &openapi3.SchemaRef{
    								Value: &openapi3.Schema{
    									Type:    &openapi3.Types{openapi3.TypeString},
    									Pattern: param.pattern,
    								},
    							},
    						},
    					})
    				}
    				oas.Paths.Map()[tc.expectedPath] = pathItem
    			}
    
    			operationID := oas.getOperationID(tc.inPath, tc.method)
    
    			if operationID != tc.expectedID {
    				t.Errorf("expected operation ID %s, got %s", tc.expectedID, operationID)
    			}
    
    			pathItem, ok := oas.Paths.Map()[tc.expectedPath]
    			if !ok {
    				t.Errorf("expected path %s to be created", tc.expectedPath)
    				return
    			}
    
    			if tc.expectedParams != nil {
    				if pathItem.Parameters == nil {
    					t.Errorf("expected parameters for path %s, but got none", tc.expectedPath)
    					return
    				}
    
    				for _, paramRef := range pathItem.Parameters {
    					param := paramRef.Value
    					expected, exists := tc.expectedParams[param.Name]
    					if !exists {
    						t.Errorf("unexpected parameter %s found", param.Name)
    						continue
    					}
    
    					if param.Schema.Value.Pattern != expected.pattern {
    						t.Errorf("expected pattern %s for parameter %s, got %s", expected.pattern, param.Name, param.Schema.Value.Pattern)
    					}
    
    					if param.Schema.Value.Type == nil || len(*param.Schema.Value.Type) == 0 {
    						t.Errorf("parameter %s has nil or empty type", param.Name)
    						continue
    					}
    					if (*param.Schema.Value.Type)[0] != expected.paramType {
    						t.Errorf("expected type %s for parameter %s, got %s", expected.paramType, param.Name, (*param.Schema.Value.Type)[0])
    					}

    Copy link
    Contributor

    github-actions bot commented May 5, 2025

    PR Code Suggestions ✨

    No code suggestions found for the PR.

    @edsonmichaque edsonmichaque changed the title [TT-10853] Upgrade kin-openapi to v0.132.0 [TT-10853] Replace github.com/TykTechnologies/kin-openapi with github.com/getkin/kin-openapi May 7, 2025
    edsonmichaque and others added 13 commits May 7, 2025 20:10
    …7061)
    
    ### **User description**
    <details open>
    <summary><a href="https://tyktech.atlassian.net/browse/TT-14868"
    title="TT-14868" target="_blank">TT-14868</a></summary>
      <br />
      <table>
        <tr>
          <th>Summary</th>
          <td>Request Body Not Recorded When Transfer-Encoding: chunked</td>
        </tr>
        <tr>
          <th>Type</th>
          <td>
    <img alt="Bug"
    src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
    />
            Bug
          </td>
        </tr>
        <tr>
          <th>Status</th>
          <td>In Dev</td>
        </tr>
        <tr>
          <th>Points</th>
          <td>N/A</td>
        </tr>
        <tr>
          <th>Labels</th>
    <td><a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20codilime_refined%20ORDER%20BY%20created%20DESC"
    title="codilime_refined">codilime_refined</a>, <a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20customer_bug%20ORDER%20BY%20created%20DESC"
    title="customer_bug">customer_bug</a>, <a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20jira_escalated%20ORDER%20BY%20created%20DESC"
    title="jira_escalated">jira_escalated</a></td>
        </tr>
      </table>
    </details>
    <!--
      do not remove this marker as it will break jira-lint's functionality.
      added_by_jira_lint
    -->
    
    ---
    
    <!-- Provide a general summary of your changes in the Title above -->
    
    ## Description
    
    The request body is not captured in the analytics record if the request
    uses Transfer-Encoding: chunked. This affects both Classic and OAS APIs.
    
    ## Related Issue
    
    <!-- This project only accepts pull requests related to open issues. -->
    <!-- If suggesting a new feature or change, please discuss it in an
    issue first. -->
    <!-- If fixing a bug, there should be an issue describing it with steps
    to reproduce. -->
    <!-- OSS: Please link to the issue here. Tyk: please create/link the
    JIRA ticket. -->
    
    ## Motivation and Context
    
    <!-- Why is this change required? What problem does it solve? -->
    
    ## How This Has Been Tested
    
    <!-- Please describe in detail how you tested your changes -->
    <!-- Include details of your testing environment, and the tests -->
    <!-- you ran to see how your change affects other areas of the code,
    etc. -->
    <!-- This information is helpful for reviewers and QA. -->
    
    ## Screenshots (if appropriate)
    
    ## Types of changes
    
    <!-- What types of changes does your code introduce? Put an `x` in all
    the boxes that apply: -->
    
    - [ ] Bug fix (non-breaking change which fixes an issue)
    - [ ] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing
    functionality to change)
    - [ ] Refactoring or add test (improvements in base code or adds test
    coverage to functionality)
    
    ## Checklist
    
    <!-- Go over all the following points, and put an `x` in all the boxes
    that apply -->
    <!-- If there are no documentation updates required, mark the item as
    checked. -->
    <!-- Raise up any additional concerns not covered by the checklist. -->
    
    - [ ] I ensured that the documentation is up to date
    - [ ] I explained why this PR updates go.mod in detail with reasoning
    why it's required
    - [ ] I would like a code coverage CI quality gate exception and have
    explained why
    
    
    ___
    
    ### **PR Type**
    Bug fix, Tests
    
    
    ___
    
    ### **Description**
    - Fixes request body logging for chunked Transfer-Encoding requests
    
    - Updates deepCopyBody to handle streaming requests correctly
    
    - Expands unit tests for deepCopyBody with streaming scenarios
    
    - Ensures grpc and upgrade requests are excluded from body copying
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Bug
    fix</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>reverse_proxy.go</strong><dd><code>Fix deepCopyBody to
    handle streaming and chunked requests</code></dd></summary>
    <hr>
    
    gateway/reverse_proxy.go
    
    <li>Updates deepCopyBody to skip streaming requests using
    <br>httputil.IsStreamingRequest<br> <li> Prevents body copying for
    chunked, grpc, and upgrade requests
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7061/files#diff-e6e07722257f7e41691e471185ad6d84fd56dc9e5459526ea32e9a5e8fa1a01b">+1/-1</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    </table></td></tr><tr><td><strong>Tests</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>reverse_proxy_test.go</strong><dd><code>Expand
    deepCopyBody tests for streaming and grpc
    scenarios</code></dd></summary>
    <hr>
    
    gateway/reverse_proxy_test.go
    
    <li>Adds tests for grpc and upgrade request handling in deepCopyBody<br>
    <li> Verifies that target body is not updated for streaming requests<br>
    <li> Ensures correct behavior for non-streaming requests
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7061/files#diff-ce040f6555143f760fba6059744bc600b6954f0966dfb0fa2832b5eabf7a3c3f">+8/-2</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    olamilekan000 and others added 17 commits May 25, 2025 12:14
    ### **User description**
    <details open>
    <summary><a href="https://tyktech.atlassian.net/browse/TT-8176"
    title="TT-8176" target="_blank">TT-8176</a></summary>
      <br />
      <table>
        <tr>
          <th>Summary</th>
    <td>Add ability to authenticate based on multiple JWKS endpoints</td>
        </tr>
        <tr>
          <th>Type</th>
          <td>
    <img alt="Story"
    src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
    />
            Story
          </td>
        </tr>
        <tr>
          <th>Status</th>
          <td>In Dev</td>
        </tr>
        <tr>
          <th>Points</th>
          <td>N/A</td>
        </tr>
        <tr>
          <th>Labels</th>
    <td><a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20Release3Candidate%20ORDER%20BY%20created%20DESC"
    title="Release3Candidate">Release3Candidate</a>, <a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20customer_request%20ORDER%20BY%20created%20DESC"
    title="customer_request">customer_request</a>, <a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20jira_escalated%20ORDER%20BY%20created%20DESC"
    title="jira_escalated">jira_escalated</a>, <a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20rel3-2025-commercial-commitment%20ORDER%20BY%20created%20DESC"
    title="rel3-2025-commercial-commitment">rel3-2025-commercial-commitment</a></td>
        </tr>
      </table>
    </details>
    <!--
      do not remove this marker as it will break jira-lint's functionality.
      added_by_jira_lint
    -->
    
    ---
    
    <!-- Provide a general summary of your changes in the Title above -->
    
    ## Description
    ```
    change implements being able to authorise a user's request using multiple JWK endpoints
    ```
    
    <!-- Describe your changes in detail -->
    
    ## Related Issue
    https://tyktech.atlassian.net/browse/TT-8176
    
    <!-- This project only accepts pull requests related to open issues. -->
    <!-- If suggesting a new feature or change, please discuss it in an
    issue first. -->
    <!-- If fixing a bug, there should be an issue describing it with steps
    to reproduce. -->
    <!-- OSS: Please link to the issue here. Tyk: please create/link the
    JIRA ticket. -->
    
    ## Motivation and Context
    
    <!-- Why is this change required? What problem does it solve? -->
    
    ## How This Has Been Tested
    
    <!-- Please describe in detail how you tested your changes -->
    <!-- Include details of your testing environment, and the tests -->
    <!-- you ran to see how your change affects other areas of the code,
    etc. -->
    <!-- This information is helpful for reviewers and QA. -->
    
    ## Screenshots (if appropriate)
    
    ## Types of changes
    
    <!-- What types of changes does your code introduce? Put an `x` in all
    the boxes that apply: -->
    
    - [ ] Bug fix (non-breaking change which fixes an issue)
    - [x] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing
    functionality to change)
    - [ ] Refactoring or add test (improvements in base code or adds test
    coverage to functionality)
    
    ## Checklist
    
    <!-- Go over all the following points, and put an `x` in all the boxes
    that apply -->
    <!-- If there are no documentation updates required, mark the item as
    checked. -->
    <!-- Raise up any additional concerns not covered by the checklist. -->
    
    - [ ] I ensured that the documentation is up to date
    - [ ] I explained why this PR updates go.mod in detail with reasoning
    why it's required
    - [ ] I would like a code coverage CI quality gate exception and have
    explained why
    
    
    ___
    
    ### **PR Type**
    Enhancement, Tests
    
    
    ___
    
    ### **Description**
    - Add support for multiple JWK URIs in JWT authorization
    
    - Update API and OAS definitions to include JWK URI lists
    
    - Implement concurrent JWK URI secret retrieval in middleware
    
    - Extend JWT middleware tests for multiple JWK URIs
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>api_definitions.go</strong><dd><code>Add JWTJwkURIs
    field to API definition struct</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
    <hr>
    
    apidef/api_definitions.go
    
    <li>Add <code>JWTJwkURIs</code> field to <code>APIDefinition</code>
    struct for multiple JWK URIs
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7060/files#diff-9961ccc89a48d32db5b47ba3006315ef52f6e5007fb4b09f8c5d6d299c669d67">+1/-0</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    <tr>
      <td>
        <details>
    <summary><strong>security.go</strong><dd><code>Add and map JwkURIs field
    in OAS JWT struct</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
    <hr>
    
    apidef/oas/security.go
    
    <li>Add <code>JwkURIs</code> field to OAS JWT struct for multiple JWK
    endpoints<br> <li> Map new field in fillJWT and extractJWTTo
    functions<br> <li> Reset <code>JWTJwkURIs</code> in resetSecuritySchemes
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7060/files#diff-15e7d47137452ca4f3f6139aa8c007cdb426152c41846f712f8bf5dfb607afcc">+8/-0</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    <tr>
      <td>
        <details>
    <summary><strong>mw_jwt.go</strong><dd><code>Implement multi-JWK URI
    secret retrieval in middleware</code>&nbsp; &nbsp; &nbsp;
    </dd></summary>
    <hr>
    
    gateway/mw_jwt.go
    
    <li>Check and use multiple JWK URIs for JWT secret retrieval<br> <li>
    Implement concurrent fetching from multiple JWK URIs<br> <li> Integrate
    new logic into JWT middleware
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7060/files#diff-e8bce0f6790c8c56b30e24dbeebb0fc4aa0879ab5ea5f6ef6dbe68931410e237">+49/-0</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    </table></td></tr><tr><td><strong>Tests</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>mw_jwt_test.go</strong><dd><code>Add tests for multiple
    JWK URIs in JWT middleware</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; </dd></summary>
    <hr>
    
    gateway/mw_jwt_test.go
    
    <li>Add test for multiple JWK URIs in JWT middleware<br> <li> Ensure
    error handling for multiple JWK URIs scenario
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7060/files#diff-406bf8fdb6c0cc77f04c6245c70abfc592ddb1525aa843200d850e14d135ebfc">+6/-0</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    …est Plugin loaded (#7064)
    
    ### **User description**
    <!-- Provide a general summary of your changes in the Title above -->
    
    ## Description
    Couple of tests were added to meet acceptance criteria
    
    ## Related Issue
    
    <!-- This project only accepts pull requests related to open issues. -->
    <!-- If suggesting a new feature or change, please discuss it in an
    issue first. -->
    <!-- If fixing a bug, there should be an issue describing it with steps
    to reproduce. -->
    <!-- OSS: Please link to the issue here. Tyk: please create/link the
    JIRA ticket. -->
    
    ## Motivation and Context
    
    <!-- Why is this change required? What problem does it solve? -->
    
    ## How This Has Been Tested
    
    <!-- Please describe in detail how you tested your changes -->
    <!-- Include details of your testing environment, and the tests -->
    <!-- you ran to see how your change affects other areas of the code,
    etc. -->
    <!-- This information is helpful for reviewers and QA. -->
    
    ## Screenshots (if appropriate)
    
    ## Types of changes
    
    <!-- What types of changes does your code introduce? Put an `x` in all
    the boxes that apply: -->
    
    - [ ] Bug fix (non-breaking change which fixes an issue)
    - [ ] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing
    functionality to change)
    - [ ] Refactoring or add test (improvements in base code or adds test
    coverage to functionality)
    
    ## Checklist
    
    <!-- Go over all the following points, and put an `x` in all the boxes
    that apply -->
    <!-- If there are no documentation updates required, mark the item as
    checked. -->
    <!-- Raise up any additional concerns not covered by the checklist. -->
    
    - [ ] I ensured that the documentation is up to date
    - [ ] I explained why this PR updates go.mod in detail with reasoning
    why it's required
    - [ ] I would like a code coverage CI quality gate exception and have
    explained why
    
    
    ___
    
    ### **PR Type**
    Tests
    
    
    ___
    
    ### **Description**
    - Added acceptance tests for Go-plugin OAS API access in response
    plugins
    
    - Tested standalone, chained, and combined request/response plugin
    scenarios
    
    - Enhanced test plugins to set additional headers for OAS API info
    
    - Implemented new response plugin for chained middleware validation
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>mw_go_plugin_test.go</strong><dd><code>Add acceptance
    tests for Go-plugin OAS API in response plugins</code></dd></summary>
    <hr>
    
    goplugin/mw_go_plugin_test.go
    
    <li>Added three new subtests for Go-plugin OAS API access in response
    <br>plugins<br> <li> Tested standalone response plugin, chained response
    plugins, and <br>combined request/response plugins<br> <li> Validated
    correct OAS API info propagation via custom headers<br> <li> Updated
    test paths and header assertions for new scenarios
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7064/files#diff-0ad6c75c29b2656d9d5cfa9108d32a3b242c339c1688ce168516ed213a5f482b">+92/-17</a>&nbsp;
    </td>
    
    </tr>
    
    <tr>
      <td>
        <details>
    <summary><strong>test_goplugin.go</strong><dd><code>Enhance test plugins
    for OAS API header assertions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; </dd></summary>
    <hr>
    
    test/goplugins/test_goplugin.go
    
    <li>Removed unused XOASDocTitle constant<br> <li> Enhanced
    MyPluginAccessingOASAPI and MyResponsePluginAccessingOASAPI <br>to set
    extra headers<br> <li> Added new MyResponsePluginAccessingOASAPI2 for
    chained response plugin <br>testing
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7064/files#diff-6b57b162c0610abdd8c4edf02dab0718ef7daa1c986aeae2e13adf9904ec3459">+12/-7</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    …API import (#7067)
    
    ### **User description**
    <details open>
    <summary><a href="https://tyktech.atlassian.net/browse/TT-11387"
    title="TT-11387" target="_blank">TT-11387</a></summary>
      <br />
      <table>
        <tr>
          <th>Summary</th>
    <td>Unhelpful error messages in UI when creating APIs via OpenAPI
    import</td>
        </tr>
        <tr>
          <th>Type</th>
          <td>
    <img alt="Bug"
    src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
    />
            Bug
          </td>
        </tr>
        <tr>
          <th>Status</th>
          <td>In Dev</td>
        </tr>
        <tr>
          <th>Points</th>
          <td>N/A</td>
        </tr>
        <tr>
          <th>Labels</th>
    <td><a
    href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20codilime_refined%20ORDER%20BY%20created%20DESC"
    title="codilime_refined">codilime_refined</a></td>
        </tr>
      </table>
    </details>
    <!--
      do not remove this marker as it will break jira-lint's functionality.
      added_by_jira_lint
    -->
    
    ---
    
    <!-- Provide a general summary of your changes in the Title above -->
    
    ## Description
    
    When importing an OpenAPI Document into Tyk, users encounter cryptic
    error messages that don't provide clear guidance on how to resolve the
    issues. This is particularly problematic for the "servers object is
    empty in OAS" error, which occurs when importing an OpenAPI Document
    without a servers section. Users aren't informed that they can use the
    Custom Upstream URL option as an alternative to modifying their OpenAPI
    Document.
    
    ## Related Issue
    
    <!-- This project only accepts pull requests related to open issues. -->
    <!-- If suggesting a new feature or change, please discuss it in an
    issue first. -->
    <!-- If fixing a bug, there should be an issue describing it with steps
    to reproduce. -->
    <!-- OSS: Please link to the issue here. Tyk: please create/link the
    JIRA ticket. -->
    
    ## Motivation and Context
    
    <!-- Why is this change required? What problem does it solve? -->
    
    ## How This Has Been Tested
    
    <!-- Please describe in detail how you tested your changes -->
    <!-- Include details of your testing environment, and the tests -->
    <!-- you ran to see how your change affects other areas of the code,
    etc. -->
    <!-- This information is helpful for reviewers and QA. -->
    
    ## Screenshots (if appropriate)
    
    ## Types of changes
    
    <!-- What types of changes does your code introduce? Put an `x` in all
    the boxes that apply: -->
    
    - [x] Bug fix (non-breaking change which fixes an issue)
    - [ ] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing
    functionality to change)
    - [ ] Refactoring or add test (improvements in base code or adds test
    coverage to functionality)
    
    ## Checklist
    
    <!-- Go over all the following points, and put an `x` in all the boxes
    that apply -->
    <!-- If there are no documentation updates required, mark the item as
    checked. -->
    <!-- Raise up any additional concerns not covered by the checklist. -->
    
    - [ ] I ensured that the documentation is up to date
    - [ ] I explained why this PR updates go.mod in detail with reasoning
    why it's required
    - [ ] I would like a code coverage CI quality gate exception and have
    explained why
    
    
    ___
    
    ### **PR Type**
    Bug fix, Tests
    
    
    ___
    
    ### **Description**
    - Improved error messages for OpenAPI import failures.
    
    - Updated error constants to provide actionable guidance.
    
    - Enhanced test cases to check for new error messages.
    
    - Clarified user instructions for missing or invalid OAS fields.
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Bug
    fix</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>default.go</strong><dd><code>Enhanced and clarified OAS
    import error messages</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; </dd></summary>
    <hr>
    
    apidef/oas/default.go
    
    <li>Replaced vague error messages with detailed, user-friendly
    <br>explanations.<br> <li> Updated error constants to suggest actionable
    next steps.<br> <li> Clarified error context for missing or invalid OAS
    fields.<br> <li> Adjusted invalid server URL format message for clarity.
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7067/files#diff-83c3a85bdd05785178ee519b05b1fe2008435dc4ae9448d72b080b5f67c491ad">+5/-5</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    </table></td></tr><tr><td><strong>Tests</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>api_test.go</strong><dd><code>Updated tests for
    improved OAS error messages</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
    <hr>
    
    gateway/api_test.go
    
    <li>Updated test assertions to match new, more informative error
    messages.<br> <li> Ensured tests check for actionable guidance in error
    responses.<br> <li> Improved test coverage for OAS import error
    scenarios.
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7067/files#diff-10b4a3d7bdd8d98e48b288d27fd46d9ee436617806c46913fdf7942c0e4a992e">+7/-3</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    <details open>
    <summary><a href="https://tyktech.atlassian.net/browse/TT-14731"
    title="TT-14731" target="_blank">TT-14731</a></summary>
      <br />
      <table>
        <tr>
          <th>Summary</th>
          <td>Add a flag to enable all bento inputs/outputs</td>
        </tr>
        <tr>
          <th>Type</th>
          <td>
    <img alt="Story"
    src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
    />
            Story
          </td>
        </tr>
        <tr>
          <th>Status</th>
          <td>In Dev</td>
        </tr>
        <tr>
          <th>Points</th>
          <td>N/A</td>
        </tr>
        <tr>
          <th>Labels</th>
          <td>-</td>
        </tr>
      </table>
    </details>
    <!--
      do not remove this marker as it will break jira-lint's functionality.
      added_by_jira_lint
    -->
    
    ---
    
    Re-opened and cleaned version of this PR:
    #6903
    
    This was a required move to use the latest tyk-analytics code for
    api-tests.
    
    ___
    
    Enhancement, Tests
    
    ___
    
    - Add `EnableAll` flag to streaming config to disable Bento validator
    
    - Implement `EnableAllConfigValidator` to bypass Bento config validation
    
    - Update stream loading to support all Bento plugins via single import
    
    - Add and extend tests for validator disabling and config placeholder
    support
    
    ___
    
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>4
    files</summary><table>
    <tr>
    <td><strong>config.go</strong><dd><code>Add EnableAll flag to
    StreamingConfig struct</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-fe44f09c4d5977b5f5eaea29170b6a0748819c9d02271746a20d81a5f3efca17">+2/-0</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    <tr>
    <td><strong>validator.go</strong><dd><code>Implement
    EnableAllConfigValidator to skip validation</code>&nbsp; &nbsp; &nbsp;
    &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-462103c9f2f33bbe3bd4a21e46b5614fa0a4bfc3c3774f6c5f2ef858ae3fbb3f">+14/-0</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    
    <tr>
    <td><strong>validator.go</strong><dd><code>Add logic to use EnableAll
    validator and new validation entrypoint</code></dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-351cfcacb241ec2c3a3172d8c17e1217d6e942b5962081e7f9fd1582e801ca7f">+16/-0</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    
    <tr>
    <td><strong>stream.go</strong><dd><code>Import all Bento plugins via
    single import</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-12571ea9605d5a2dd5ab5aa36972649881f87a84a39b7074213d29d24fc396a8">+2/-8</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>3
    files</summary><table>
    <tr>
    <td><strong>validator_test.go</strong><dd><code>Add tests for EnableAll
    validator and config validation</code>&nbsp; &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-a2b6f11dd78fabd9764d82f19af456f7b2bb835951d49e5e79d3488240513d0a">+63/-2</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    
    <tr>
    <td><strong>mw_streaming_test.go</strong><dd><code>Minor test cleanup
    and reliability improvements</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-a0d1bd0196a741537a3c850e340225c8993e49d709c838af0f1b48b9893af1da">+10/-33</a>&nbsp;
    </td>
    
    </tr>
    
    <tr>
    <td><strong>mw_streaming_mqtt_test.go</strong><dd><code>Fix test cleanup
    and error handling for WebSocket clients</code></dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-09d30d4ab7dc29a32af2e23e40cf873770963e4ee54c4a0bff4e83200e9d4926">+4/-3</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    </table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>1
    files</summary><table>
    <tr>
    <td><strong>go.mod</strong><dd><code>Update gopsutil version and add
    indirect dependencies</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6">+234/-12</a></td>
    
    </tr>
    </table></details></td></tr><tr><td><strong>Additional
    files</strong></td><td><details><summary>2 files</summary><table>
    <tr>
      <td><strong>middleware.go</strong></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-0ce428c0f09dca65e3df6e72d01fee63b6f237785e41e6ecf0ce34a8b65c74a5">+0/-1</a>&nbsp;
    &nbsp; &nbsp; </td>
    
    </tr>
    
    <tr>
      <td><strong>go.sum</strong></td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7065/files#diff-3295df7234525439d778f1b282d146a4f1ff6b415248aaac074e8042d9f42d63">+2228/-22</a></td>
    
    </tr>
    </table></details></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    
    ---------
    
    Co-authored-by: Leonid Bugaev <[email protected]>
    ### **User description**
    <details open>
    <summary><a href="https://tyktech.atlassian.net/browse/TT-14470"
    title="TT-14470" target="_blank">TT-14470</a></summary>
      <br />
      <table>
        <tr>
          <th>Summary</th>
          <td>Streams SSE and WS must be usable by Browser Clients</td>
        </tr>
        <tr>
          <th>Type</th>
          <td>
    <img alt="Bug"
    src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
    />
            Bug
          </td>
        </tr>
        <tr>
          <th>Status</th>
          <td>In Dev</td>
        </tr>
        <tr>
          <th>Points</th>
          <td>N/A</td>
        </tr>
        <tr>
          <th>Labels</th>
          <td>-</td>
        </tr>
      </table>
    </details>
    <!--
      do not remove this marker as it will break jira-lint's functionality.
      added_by_jira_lint
    -->
    
    ---
    
    This PR updates bento to `v1.7.1` that includes a fix for SSE with
    browsers (See warpstreamlabs/bento#284)
    
    
    ___
    
    ### **PR Type**
    Bug fix, Enhancement
    
    
    ___
    
    ### **Description**
    - Update `github.com/warpstreamlabs/bento` to v1.7.1 for SSE browser fix
    
    - Upgrade multiple dependencies to latest patch/minor versions
    
    - Add new indirect dependencies required by updated libraries
    
    - Improve compatibility and security via dependency updates
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>go.mod</strong><dd><code>Update bento and refresh
    direct/indirect dependencies</code>&nbsp; &nbsp; &nbsp; &nbsp;
    </dd></summary>
    <hr>
    
    go.mod
    
    <li>Bump <code>github.com/warpstreamlabs/bento</code> from v1.4.0 to
    v1.7.1<br> <li> Update several direct dependencies (e.g., grpc,
    protobuf, x/net)<br> <li> Add new indirect dependency
    <code>github.com/clbanning/mxj/v2</code><br> <li> Update indirect
    dependencies for Kafka, Prometheus, and others
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7072/files#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6">+11/-10</a>&nbsp;
    </td>
    
    </tr>
    
    <tr>
      <td>
        <details>
    <summary><strong>go.sum</strong><dd><code>Update dependency checksums
    for new/updated libraries</code>&nbsp; &nbsp; &nbsp; &nbsp;
    </dd></summary>
    <hr>
    
    go.sum
    
    <li>Add checksums for new/updated dependencies and versions<br> <li>
    Remove checksums for old dependency versions<br> <li> Reflects all
    changes from go.mod updates
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7072/files#diff-3295df7234525439d778f1b282d146a4f1ff6b415248aaac074e8042d9f42d63">+32/-28</a>&nbsp;
    </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    ### **User description**
    Removed dialer when trying to load grpc plugins and used non-depcrecated
    methods that should make use of dns instead of forcing tcp
    
    <!-- Provide a general summary of your changes in the Title above -->
    
    ## Description
    
    <!-- Describe your changes in detail -->
    
    ## Related Issue
    
    <!-- This project only accepts pull requests related to open issues. -->
    <!-- If suggesting a new feature or change, please discuss it in an
    issue first. -->
    <!-- If fixing a bug, there should be an issue describing it with steps
    to reproduce. -->
    <!-- OSS: Please link to the issue here. Tyk: please create/link the
    JIRA ticket. -->
    
    ## Motivation and Context
    
    <!-- Why is this change required? What problem does it solve? -->
    
    ## How This Has Been Tested
    
    <!-- Please describe in detail how you tested your changes -->
    <!-- Include details of your testing environment, and the tests -->
    <!-- you ran to see how your change affects other areas of the code,
    etc. -->
    <!-- This information is helpful for reviewers and QA. -->
    
    ## Screenshots (if appropriate)
    
    ## Types of changes
    
    <!-- What types of changes does your code introduce? Put an `x` in all
    the boxes that apply: -->
    
    - [ ] Bug fix (non-breaking change which fixes an issue)
    - [ ] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing
    functionality to change)
    - [ ] Refactoring or add test (improvements in base code or adds test
    coverage to functionality)
    
    ## Checklist
    
    <!-- Go over all the following points, and put an `x` in all the boxes
    that apply -->
    <!-- If there are no documentation updates required, mark the item as
    checked. -->
    <!-- Raise up any additional concerns not covered by the checklist. -->
    
    - [ ] I ensured that the documentation is up to date
    - [ ] I explained why this PR updates go.mod in detail with reasoning
    why it's required
    - [ ] I would like a code coverage CI quality gate exception and have
    explained why
    
    
    ___
    
    ### **PR Type**
    Bug fix, Enhancement
    
    
    ___
    
    ### **Description**
    - Replaced deprecated gRPC dial method with updated approach
    
    - Removed custom dialer to allow DNS-based service resolution
    
    - Adopted `grpc.WithTransportCredentials(insecure.NewCredentials())` for
    security
    
    - Improved compatibility with service names in gRPC plugins
    
    
    ___
    
    
    
    ### **Changes walkthrough** 📝
    <table><thead><tr><th></th><th align="left">Relevant
    files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
    <tr>
      <td>
        <details>
    <summary><strong>coprocess_grpc.go</strong><dd><code>Modernize gRPC
    client connection and remove deprecated dialer</code></dd></summary>
    <hr>
    
    gateway/coprocess_grpc.go
    
    <li>Removed use of deprecated <code>grpc.Dial</code> with custom
    dialer<br> <li> Switched to <code>grpc.NewClient</code> and modern
    connection options<br> <li> Eliminated forced TCP, enabling DNS-based
    service resolution<br> <li> Updated to use
    <br><code>grpc.WithTransportCredentials(insecure.NewCredentials())</code>
    
    
    </details>
    
    
      </td>
    <td><a
    href="https://github.com/TykTechnologies/tyk/pull/7052/files#diff-53c2775617dfe21b5a271269cd737bcc2818e2f0243b9a6e6bf5a73b14180334">+11/-4</a>&nbsp;
    &nbsp; </td>
    
    </tr>
    </table></td></tr></tr></tbody></table>
    
    ___
    
    > <details> <summary> Need help?</summary><li>Type <code>/help how to
    ...</code> in the comments thread for any questions about PR-Agent
    usage.</li><li>Check out the <a
    href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
    for more information.</li></details>
    Copy link

    @edsonmichaque edsonmichaque changed the title [TT-10853] Replace github.com/TykTechnologies/kin-openapi with github.com/getkin/kin-openapi [TT-14863] Replace github.com/TykTechnologies/kin-openapi with github.com/getkin/kin-openapi May 26, 2025
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    8 participants