Skip to content

Commit 124aee1

Browse files
Cleanup And Support Disabling
1 parent 6b11970 commit 124aee1

7 files changed

Lines changed: 41 additions & 621 deletions

File tree

config/config.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ type ValidationOptions struct {
3131
AllowScalarCoercion bool // Enable string->boolean/number coercion
3232
Formats map[string]func(v any) error
3333
SchemaCache cache.SchemaCache // Optional cache for compiled schemas
34-
PathLookup radix.PathLookup // O(k) path lookup via radix tree (built automatically)
34+
PathTree radix.PathLookup // O(k) path lookup via radix tree (built automatically)
35+
pathTreeSet bool // Internal: true if PathTree was explicitly set via WithPathTree
3536
Logger *slog.Logger // Logger for debug/error output (nil = silent)
3637

3738
// strict mode options - detect undeclared properties even when additionalProperties: true
@@ -76,7 +77,8 @@ func WithExistingOpts(options *ValidationOptions) Option {
7677
o.AllowScalarCoercion = options.AllowScalarCoercion
7778
o.Formats = options.Formats
7879
o.SchemaCache = options.SchemaCache
79-
o.PathLookup = options.PathLookup
80+
o.PathTree = options.PathTree
81+
o.pathTreeSet = options.pathTreeSet
8082
o.Logger = options.Logger
8183
o.StrictMode = options.StrictMode
8284
o.StrictIgnorePaths = options.StrictIgnorePaths
@@ -173,11 +175,13 @@ func WithSchemaCache(schemaCache cache.SchemaCache) Option {
173175
}
174176
}
175177

176-
// WithPathLookup sets a custom path lookup implementation.
177-
// The default is a radix tree built from the OpenAPI specification.
178-
func WithPathLookup(pathLookup radix.PathLookup) Option {
178+
// WithPathTree sets a custom radix tree for path matching.
179+
// The default is built automatically from the OpenAPI specification.
180+
// Pass nil to disable the radix tree and use regex-based matching only.
181+
func WithPathTree(pathTree radix.PathLookup) Option {
179182
return func(o *ValidationOptions) {
180-
o.PathLookup = pathLookup
183+
o.PathTree = pathTree
184+
o.pathTreeSet = true
181185
}
182186
}
183187

@@ -244,6 +248,11 @@ var defaultIgnoredHeaders = []string{
244248
"request-start-time", // Added by some API clients for timing
245249
}
246250

251+
// IsPathTreeSet returns true if PathTree was explicitly configured via WithPathTree.
252+
func (o *ValidationOptions) IsPathTreeSet() bool {
253+
return o.pathTreeSet
254+
}
255+
247256
// GetEffectiveStrictIgnoredHeaders returns the list of headers to ignore
248257
// based on configuration. Returns defaults if not configured, merged list
249258
// if extra headers were added, or replaced list if headers were fully replaced.

parameters/path_parameters_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/pb33f/libopenapi-validator/config"
1717
"github.com/pb33f/libopenapi-validator/helpers"
1818
"github.com/pb33f/libopenapi-validator/paths"
19+
"github.com/pb33f/libopenapi-validator/radix"
1920
)
2021

2122
func TestNewValidator_SimpleArrayEncodedPath(t *testing.T) {
@@ -2321,7 +2322,7 @@ paths:
23212322
m, _ := doc.BuildV3Model()
23222323

23232324
cache := &regexCacheWatcher{inner: &sync.Map{}}
2324-
opts := &config.ValidationOptions{RegexCache: cache}
2325+
opts := &config.ValidationOptions{RegexCache: cache, PathTree: radix.BuildPathTree(&m.Model)}
23252326

23262327
// Simple path - should NOT use regex cache (handled by radix tree)
23272328
simpleRequest, _ := http.NewRequest(http.MethodGet, "https://things.com/simple/123", nil)

paths/paths.go

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/pb33f/libopenapi-validator/config"
1919
"github.com/pb33f/libopenapi-validator/errors"
2020
"github.com/pb33f/libopenapi-validator/helpers"
21-
"github.com/pb33f/libopenapi-validator/radix"
2221
)
2322

2423
// FindPath will find the path in the document that matches the request path. If a successful match was found, then
@@ -37,26 +36,13 @@ func FindPath(request *http.Request, document *v3.Document, options *config.Vali
3736
stripped := StripRequestPath(request, document)
3837

3938
// Fast path: try radix tree first (O(k) where k = path depth)
40-
tree := pathLookupFrom(options, document)
41-
if tree != nil {
42-
if pathItem, matchedPath, found := tree.Lookup(stripped); found {
43-
// Verify the path has the requested method
39+
// If no path lookup is provided, we will fall back to regex-based matching.
40+
if options != nil && options.PathTree != nil {
41+
if pathItem, matchedPath, found := options.PathTree.Lookup(stripped); found {
4442
if pathHasMethod(pathItem, request.Method) {
4543
return pathItem, nil, matchedPath
4644
}
47-
// Path found but method doesn't exist
48-
validationErrors := []*errors.ValidationError{{
49-
ValidationType: helpers.ParameterValidationPath,
50-
ValidationSubType: "missingOperation",
51-
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
52-
Reason: fmt.Sprintf("The %s method for that path does not exist in the specification",
53-
request.Method),
54-
SpecLine: -1,
55-
SpecCol: -1,
56-
HowToFix: errors.HowToFixPath,
57-
}}
58-
errors.PopulateValidationErrors(validationErrors, request, matchedPath)
59-
return pathItem, validationErrors, matchedPath
45+
return pathItem, missingOperationError(request, matchedPath), matchedPath
6046
}
6147
}
6248

@@ -128,18 +114,7 @@ func FindPath(request *http.Request, document *v3.Document, options *config.Vali
128114
}
129115

130116
// path matches exist but none have the required method
131-
validationErrors := []*errors.ValidationError{{
132-
ValidationType: helpers.PathValidation,
133-
ValidationSubType: helpers.ValidationMissingOperation,
134-
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
135-
Reason: fmt.Sprintf("The %s method for that path does not exist in the specification",
136-
request.Method),
137-
SpecLine: -1,
138-
SpecCol: -1,
139-
HowToFix: errors.HowToFixPath,
140-
}}
141-
errors.PopulateValidationErrors(validationErrors, request, bestOverall.path)
142-
return bestOverall.pathItem, validationErrors, bestOverall.path
117+
return bestOverall.pathItem, missingOperationError(request, bestOverall.path), bestOverall.path
143118
}
144119

145120
// normalizePathForMatching removes the fragment from a path template unless
@@ -257,13 +232,18 @@ func comparePaths(mapped, requested, basePaths []string, regexCache config.Regex
257232
return checkPathAgainstBase(l, r, basePaths)
258233
}
259234

260-
// pathLookupFrom returns the PathLookup from options, or builds one from the document.
261-
func pathLookupFrom(options *config.ValidationOptions, document *v3.Document) radix.PathLookup {
262-
if options != nil && options.PathLookup != nil {
263-
return options.PathLookup
264-
}
265-
if document != nil && document.Paths != nil {
266-
return radix.BuildPathTree(document)
267-
}
268-
return nil
235+
// missingOperationError returns a validation error for when a path was found but the HTTP method doesn't exist.
236+
func missingOperationError(request *http.Request, matchedPath string) []*errors.ValidationError {
237+
validationErrors := []*errors.ValidationError{{
238+
ValidationType: helpers.PathValidation,
239+
ValidationSubType: helpers.ValidationMissingOperation,
240+
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
241+
Reason: fmt.Sprintf("The %s method for that path does not exist in the specification",
242+
request.Method),
243+
SpecLine: -1,
244+
SpecCol: -1,
245+
HowToFix: errors.HowToFixPath,
246+
}}
247+
errors.PopulateValidationErrors(validationErrors, request, matchedPath)
248+
return validationErrors
269249
}

paths/radix_tree.go

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)