pax_global_header 0000666 0000000 0000000 00000000064 15205340424 0014511 g ustar 00root root 0000000 0000000 52 comment=a5d6833b6717a3acd491545ed03f987751f4d12c
libopenapi-validator-0.13.8/ 0000775 0000000 0000000 00000000000 15205340424 0015707 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/.github/ 0000775 0000000 0000000 00000000000 15205340424 0017247 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/.github/dependabot.yml 0000664 0000000 0000000 00000000155 15205340424 0022100 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
libopenapi-validator-0.13.8/.github/workflows/ 0000775 0000000 0000000 00000000000 15205340424 0021304 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/.github/workflows/build.yaml 0000664 0000000 0000000 00000002772 15205340424 0023277 0 ustar 00root root 0000000 0000000 name: Build
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout scm
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.8
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v3
with:
go-version: 1.25
id: go
- name: Checkout code
uses: actions/checkout@v3
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Test
run: go test ./...
- name: Coverage
run: |
go get github.com/axw/gocov/gocov
go get github.com/AlekSi/gocov-xml
go install github.com/axw/gocov/gocov
go install github.com/AlekSi/gocov-xml
- run: |
go test -v -coverprofile cover.out ./...
gocov convert cover.out | gocov-xml > coverage.xml
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: unittests
fail_ci_if_error: false
verbose: true
libopenapi-validator-0.13.8/.golangci.yml 0000664 0000000 0000000 00000000756 15205340424 0020303 0 ustar 00root root 0000000 0000000 version: "2"
linters:
default: none
enable:
- asciicheck
- bidichk
- errcheck
- govet
- ineffassign
- staticcheck
- unused
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
libopenapi-validator-0.13.8/.pre-commit-config.yaml 0000664 0000000 0000000 00000000676 15205340424 0022201 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
# Run golangci-lint as a pre-commit hook to catch issues before they are pushed
# See https://golangci-lint.run/ for more information
- repo: local
hooks:
- id: golangci-lint
name: Lint Go code
entry: go tool golangci-lint run
language: system
pass_filenames: false
types: [go] libopenapi-validator-0.13.8/LICENSE.md 0000664 0000000 0000000 00000002130 15205340424 0017307 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
libopenapi-validator-0.13.8/Makefile 0000664 0000000 0000000 00000000533 15205340424 0017350 0 ustar 00root root 0000000 0000000 all: gofumpt import lint
init:
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
go install github.com/daixiang0/gci@latest
lint:
golangci-lint run ./...
gofumpt:
gofumpt -l -w .
import:
gci write --skip-generated -s standard -s default -s localmodule -s blank -s dot -s alias .
libopenapi-validator-0.13.8/README.md 0000664 0000000 0000000 00000010301 15205340424 0017161 0 ustar 00root root 0000000 0000000 īģŋ
# Enterprise grade OpenAPI validation tools for golang.

[](https://codecov.io/gh/pb33f/libopenapi-validator)
[](https://discord.gg/x7VACVuEGP)
[](https://pkg.go.dev/github.com/pb33f/libopenapi-validator)
A validation module for [libopenapi](https://github.com/pb33f/libopenapi).
`libopenapi-validator` will validate the following elements against an OpenAPI 3+ specification
- *http.Request* - Validates the request against the OpenAPI specification
- *http.Response* - Validates the response against the OpenAPI specification
- *libopenapi.Document* - Validates the OpenAPI document against the OpenAPI specification
- *base.Schema* - Validates a schema against a JSON or YAML blob / unmarshalled object
đđ [Check out the full documentation](https://pb33f.io/libopenapi/validation/) đđ
---
## Installation
```bash
go get github.com/pb33f/libopenapi-validator
```
## Validate OpenAPI Document
```bash
go run github.com/pb33f/libopenapi-validator/cmd/validate@latest [--regexengine] [--yaml2json]
```
## Install pre-commit hook
To install the pre-commit hook, run the following command in your terminal:
```bash
pre-commit install
```
### Options
#### --regexengine
đ Example: Use a custom regex engine/flag (e.g., ecmascript)
```bash
go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --regexengine=ecmascript
```
đ§ Supported **--regexengine** flags/values (âšī¸ Default: re2)
- none
- ignorecase
- multiline
- explicitcapture
- compiled
- singleline
- ignorepatternwhitespace
- righttoleft
- debug
- ecmascript
- re2
- unicode
#### --yaml2json
đ Convert YAML files to JSON before validation (âšī¸ Default: false)
[libopenapi](https://github.com/pb33f/libopenapi/blob/main/datamodel/spec_info.go#L115) passes `map[interface{}]interface{}` structures for deeply nested objects or complex mappings in the OpenAPI specification, which are not allowed in JSON.
These structures cannot be properly converted to JSON by libopenapi and cannot be validated by jsonschema, resulting in ambiguous errors.
This flag allows pre-converting from YAML to JSON to bypass this limitation of the libopenapi.
**When does this happen?**
- OpenAPI specs with deeply nested schema definitions
- Complex `allOf`, `oneOf`, or `anyOf` structures with multiple levels
- Specifications with intricate object mappings in examples or schema properties
Enabling this flag pre-converts the YAML document from YAML to JSON, ensuring a clean JSON structure before validation.
Example:
```bash
go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --yaml2json
```
## Documentation
- [The structure of the validator](https://pb33f.io/libopenapi/validation/#the-structure-of-the-validator)
- [Validation errors](https://pb33f.io/libopenapi/validation/#validation-errors)
- [Schema errors](https://pb33f.io/libopenapi/validation/#schema-errors)
- [High-level validation](https://pb33f.io/libopenapi/validation/#high-level-validation)
- [Validating http.Request](https://pb33f.io/libopenapi/validation/#validating-httprequest)
- [Validating http.Request and http.Response](https://pb33f.io/libopenapi/validation/#validating-httprequest-and-httpresponse)
- [Validating just http.Response](https://pb33f.io/libopenapi/validation/#validating-just-httpresponse)
- [Validating HTTP Parameters](https://pb33f.io/libopenapi/validation/#validating-http-parameters)
- [Validating an OpenAPI document](https://pb33f.io/libopenapi/validation/#validating-an-openapi-document)
- [Validating Schemas](https://pb33f.io/libopenapi/validation/#validating-schemas)
[libopenapi](https://github.com/pb33f/libopenapi) and [libopenapi-validator](https://github.com/pb33f/libopenapi-validator) are
products of Princess Beef Heavy Industries, LLC
libopenapi-validator-0.13.8/cache/ 0000775 0000000 0000000 00000000000 15205340424 0016752 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/cache/cache.go 0000664 0000000 0000000 00000001717 15205340424 0020352 0 ustar 00root root 0000000 0000000 // Copyright 2025 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package cache
import (
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/santhosh-tekuri/jsonschema/v6"
"go.yaml.in/yaml/v4"
)
// SchemaCacheEntry holds a compiled schema and its intermediate representations.
// This is stored in the cache to avoid re-rendering and re-compiling schemas on each request.
type SchemaCacheEntry struct {
Schema *base.Schema
RenderedInline []byte
ReferenceSchema string // String version of RenderedInline
RenderedJSON []byte
CompiledSchema *jsonschema.Schema
RenderedNode *yaml.Node
}
// SchemaCache defines the interface for schema caching implementations.
// The key is a uint64 hash of the schema (from schema.GoLow().Hash()).
type SchemaCache interface {
Load(key uint64) (*SchemaCacheEntry, bool)
Store(key uint64, value *SchemaCacheEntry)
Range(f func(key uint64, value *SchemaCacheEntry) bool)
}
libopenapi-validator-0.13.8/cache/cache_test.go 0000664 0000000 0000000 00000016126 15205340424 0021411 0 ustar 00root root 0000000 0000000 // Copyright 2025 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package cache
import (
"testing"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDefaultCache(t *testing.T) {
cache := NewDefaultCache()
assert.NotNil(t, cache)
assert.NotNil(t, cache.m)
}
func TestDefaultCache_StoreAndLoad(t *testing.T) {
cache := NewDefaultCache()
// Create a test schema cache entry
testSchema := &SchemaCacheEntry{
Schema: &base.Schema{},
RenderedInline: []byte("rendered"),
RenderedJSON: []byte(`{"type":"object"}`),
CompiledSchema: &jsonschema.Schema{},
}
// Create a test key (uint64 hash)
key := uint64(0x123456789abcdef0)
// Store the schema
cache.Store(key, testSchema)
// Load the schema back
loaded, ok := cache.Load(key)
assert.True(t, ok, "Should find the cached schema")
require.NotNil(t, loaded)
assert.Equal(t, testSchema.RenderedInline, loaded.RenderedInline)
assert.Equal(t, testSchema.RenderedJSON, loaded.RenderedJSON)
assert.NotNil(t, loaded.CompiledSchema)
}
func TestDefaultCache_LoadMissing(t *testing.T) {
cache := NewDefaultCache()
// Try to load a key that doesn't exist
key := uint64(0xdeadbeef)
loaded, ok := cache.Load(key)
assert.False(t, ok, "Should not find non-existent key")
assert.Nil(t, loaded)
}
func TestDefaultCache_LoadNilCache(t *testing.T) {
var cache *DefaultCache
key := uint64(0)
loaded, ok := cache.Load(key)
assert.False(t, ok)
assert.Nil(t, loaded)
}
func TestDefaultCache_StoreNilCache(t *testing.T) {
var cache *DefaultCache
// Should not panic
key := uint64(0)
cache.Store(key, &SchemaCacheEntry{})
// Verify nothing was stored (cache is nil)
assert.Nil(t, cache)
}
func TestDefaultCache_Range(t *testing.T) {
cache := NewDefaultCache()
// Store multiple entries
entries := make(map[uint64]*SchemaCacheEntry)
for i := 0; i < 5; i++ {
key := uint64(i)
entry := &SchemaCacheEntry{
RenderedInline: []byte{byte(i)},
RenderedJSON: []byte{byte(i)},
}
entries[key] = entry
cache.Store(key, entry)
}
// Range over all entries
count := 0
foundKeys := make(map[uint64]bool)
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
foundKeys[key] = true
// Verify the value matches what we stored
expected, exists := entries[key]
assert.True(t, exists, "Key should exist in original entries")
assert.Equal(t, expected.RenderedInline, value.RenderedInline)
return true
})
assert.Equal(t, 5, count, "Should iterate over all 5 entries")
assert.Equal(t, 5, len(foundKeys), "Should find all 5 unique keys")
}
func TestDefaultCache_RangeEarlyTermination(t *testing.T) {
cache := NewDefaultCache()
// Store multiple entries
for i := 0; i < 10; i++ {
key := uint64(i)
cache.Store(key, &SchemaCacheEntry{})
}
// Range but stop after 3 iterations
count := 0
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
return count < 3 // Stop after 3
})
assert.Equal(t, 3, count, "Should stop after 3 iterations")
}
func TestDefaultCache_RangeNilCache(t *testing.T) {
var cache *DefaultCache
// Should not panic
called := false
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
called = true
return true
})
assert.False(t, called, "Callback should not be called on nil cache")
}
func TestDefaultCache_RangeEmpty(t *testing.T) {
cache := NewDefaultCache()
// Range over empty cache
count := 0
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
return true
})
assert.Equal(t, 0, count, "Should not iterate over empty cache")
}
func TestDefaultCache_Overwrite(t *testing.T) {
cache := NewDefaultCache()
key := uint64(0x12345678)
// Store first value
first := &SchemaCacheEntry{
RenderedInline: []byte("first"),
}
cache.Store(key, first)
// Store second value with same key
second := &SchemaCacheEntry{
RenderedInline: []byte("second"),
}
cache.Store(key, second)
// Load should return the second value
loaded, ok := cache.Load(key)
assert.True(t, ok)
require.NotNil(t, loaded)
assert.Equal(t, []byte("second"), loaded.RenderedInline)
}
func TestDefaultCache_MultipleKeys(t *testing.T) {
cache := NewDefaultCache()
// Store with different keys
key1 := uint64(1)
key2 := uint64(2)
key3 := uint64(3)
cache.Store(key1, &SchemaCacheEntry{RenderedInline: []byte("value1")})
cache.Store(key2, &SchemaCacheEntry{RenderedInline: []byte("value2")})
cache.Store(key3, &SchemaCacheEntry{RenderedInline: []byte("value3")})
// Load each one
val1, ok1 := cache.Load(key1)
val2, ok2 := cache.Load(key2)
val3, ok3 := cache.Load(key3)
assert.True(t, ok1)
assert.True(t, ok2)
assert.True(t, ok3)
assert.Equal(t, []byte("value1"), val1.RenderedInline)
assert.Equal(t, []byte("value2"), val2.RenderedInline)
assert.Equal(t, []byte("value3"), val3.RenderedInline)
}
func TestDefaultCache_ThreadSafety(t *testing.T) {
cache := NewDefaultCache()
// Concurrent writes
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(val int) {
key := uint64(val)
cache.Store(key, &SchemaCacheEntry{
RenderedInline: []byte{byte(val)},
})
done <- true
}(i)
}
// Wait for all writes
for i := 0; i < 10; i++ {
<-done
}
// Concurrent reads
for i := 0; i < 10; i++ {
go func(val int) {
key := uint64(val)
loaded, ok := cache.Load(key)
assert.True(t, ok)
assert.NotNil(t, loaded)
done <- true
}(i)
}
// Wait for all reads
for i := 0; i < 10; i++ {
<-done
}
// Verify all entries exist
count := 0
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
return true
})
assert.Equal(t, 10, count, "All entries should be present")
}
func TestSchemaCache_Fields(t *testing.T) {
// Test that SchemaCache properly holds all fields
schema := &base.Schema{}
compiled := &jsonschema.Schema{}
sc := &SchemaCacheEntry{
Schema: schema,
RenderedInline: []byte("rendered"),
RenderedJSON: []byte(`{"type":"object"}`),
CompiledSchema: compiled,
}
assert.Equal(t, schema, sc.Schema)
assert.Equal(t, []byte("rendered"), sc.RenderedInline)
assert.Equal(t, []byte(`{"type":"object"}`), sc.RenderedJSON)
assert.Equal(t, compiled, sc.CompiledSchema)
}
func TestDefaultCache_RangeWithInvalidTypes(t *testing.T) {
cache := NewDefaultCache()
// Manually insert invalid types into the underlying sync.Map to test defensive programming
// Store an entry with wrong key type
cache.m.Store("invalid-key-type", &SchemaCacheEntry{})
// Store an entry with wrong value type
validKey := uint64(1)
cache.m.Store(validKey, "invalid-value-type")
// Store a valid entry
validKey2 := uint64(2)
validEntry := &SchemaCacheEntry{RenderedInline: []byte("valid")}
cache.Store(validKey2, validEntry)
// Range should skip invalid entries and only process valid ones
count := 0
var seenEntry *SchemaCacheEntry
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
seenEntry = value
return true
})
assert.Equal(t, 1, count, "Should only process valid entry")
assert.Equal(t, validEntry, seenEntry, "Should see the valid entry")
}
libopenapi-validator-0.13.8/cache/default_cache.go 0000664 0000000 0000000 00000002276 15205340424 0022057 0 ustar 00root root 0000000 0000000 package cache
import "sync"
// DefaultCache is the default cache implementation using sync.Map for thread-safe concurrent access.
type DefaultCache struct {
m *sync.Map
}
var _ SchemaCache = &DefaultCache{}
// NewDefaultCache creates a new DefaultCache with an initialized sync.Map.
func NewDefaultCache() *DefaultCache {
return &DefaultCache{m: &sync.Map{}}
}
// Load retrieves a schema from the cache.
func (c *DefaultCache) Load(key uint64) (*SchemaCacheEntry, bool) {
if c == nil || c.m == nil {
return nil, false
}
val, ok := c.m.Load(key)
if !ok {
return nil, false
}
schemaCache, ok := val.(*SchemaCacheEntry)
return schemaCache, ok
}
// Store saves a schema to the cache.
func (c *DefaultCache) Store(key uint64, value *SchemaCacheEntry) {
if c == nil || c.m == nil {
return
}
c.m.Store(key, value)
}
// Range calls f for each entry in the cache (for testing/inspection).
func (c *DefaultCache) Range(f func(key uint64, value *SchemaCacheEntry) bool) {
if c == nil || c.m == nil {
return
}
c.m.Range(func(k, v interface{}) bool {
key, ok := k.(uint64)
if !ok {
return true
}
val, ok := v.(*SchemaCacheEntry)
if !ok {
return true
}
return f(key, val)
})
}
libopenapi-validator-0.13.8/cmd/ 0000775 0000000 0000000 00000000000 15205340424 0016452 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/cmd/validate/ 0000775 0000000 0000000 00000000000 15205340424 0020243 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/cmd/validate/main.go 0000664 0000000 0000000 00000016006 15205340424 0021521 0 ustar 00root root 0000000 0000000 package main
import (
"errors"
"flag"
"fmt"
"log/slog"
"os"
"github.com/dlclark/regexp2"
"github.com/goccy/go-yaml"
"github.com/pb33f/libopenapi"
"github.com/santhosh-tekuri/jsonschema/v6"
validator "github.com/pb33f/libopenapi-validator"
"github.com/pb33f/libopenapi-validator/config"
)
type customRegexp regexp2.Regexp
func (re *customRegexp) MatchString(s string) bool {
matched, err := (*regexp2.Regexp)(re).MatchString(s)
return err == nil && matched
}
func (re *customRegexp) String() string {
return (*regexp2.Regexp)(re).String()
}
type regexEngine struct {
runtimeOption regexp2.RegexOptions
}
func (e *regexEngine) run(s string) (jsonschema.Regexp, error) {
re, err := regexp2.Compile(s, e.runtimeOption)
if err != nil {
return nil, err
}
return (*customRegexp)(re), nil
}
var regexParsingOptionsMap = map[string]regexp2.RegexOptions{
"none": regexp2.None,
"ignorecase": regexp2.IgnoreCase,
"multiline": regexp2.Multiline,
"explicitcapture": regexp2.ExplicitCapture,
"compiled": regexp2.Compiled,
"singleline": regexp2.Singleline,
"ignorepatternwhitespace": regexp2.IgnorePatternWhitespace,
"righttoleft": regexp2.RightToLeft,
"debug": regexp2.Debug,
"ecmascript": regexp2.ECMAScript,
"re2": regexp2.RE2,
"unicode": regexp2.Unicode,
}
var (
defaultRegexEngine = ""
regexParsingOptions = flag.String("regexengine", defaultRegexEngine, `Specify the regex parsing option to use.
Supported values are:
Engines: re2 (default), ecmascript
Flags: ignorecase, multiline, explicitcapture, compiled,
singleline, ignorepatternwhitespace, righttoleft,
debug, unicode
If not specified, the default libopenapi option is "re2".
If not specified, the default libopenapi regex engine is "re2"".`)
convertYAMLToJSON = flag.Bool("yaml2json", false, `Convert YAML files to JSON before validation.
libopenapi passes map[interface{}]interface{} structures for deeply nested objects
or complex mappings, which are not allowed in JSON and cannot be validated by jsonschema.
This flag allows pre-converting from YAML to JSON to bypass this limitation of the libopenapi.
Default is false.`)
)
// main is the entry point for validating an OpenAPI Specification (OAS) document.
// It uses the libopenapi-validator library to check if the provided OAS document
// conforms to the OpenAPI specification.
//
// This tool accepts a single input file (YAML or JSON) and provides optional flags:
//
// `--regexengine` flag to customize the regex engine used during validation.
// This is useful for cases where the spec uses regex patterns that require engines
// like ECMAScript or RE2.
//
// Supported regex options include:
// - Engines: re2 (default), ecmascript
// - Flags: ignorecase, multiline, explicitcapture, compiled, singleline,
// ignorepatternwhitespace, righttoleft, debug, unicode
//
// `--yaml2json` flag to convert YAML files to JSON before validation.
// libopenapi passes map[interface{}]interface{} structures for deeply nested
// objects or complex mappings, which are not allowed in JSON and cannot be
// validated by jsonschema. This flag allows pre-converting from YAML to JSON
// to bypass this limitation of the libopenapi. Default is false.
//
// Example usage:
//
// go run main.go --regexengine=ecmascript ./my-api-spec.yaml
// go run main.go --yaml2json ./my-api-spec.yaml
//
// If validation passes, the tool logs a success message.
// If the document is invalid or there is a processing error, it logs details and exits non-zero.
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: validate [OPTIONS]
Validates an OpenAPI document using libopenapi-validator.
Options:
--regexengine string Specify the regex parsing option to use.
Supported values are:
Engines: re2 (default), ecmascript
Flags: ignorecase, multiline, explicitcapture, compiled,
singleline, ignorepatternwhitespace, righttoleft,
debug, unicode
If not specified, the default libopenapi option is "re2".
--yaml2json Convert YAML files to JSON before validation.
libopenapi passes map[interface{}]interface{}
structures for deeply nested objects or complex mappings, which
are not allowed in JSON and cannot be validated by jsonschema.
This flag allows pre-converting from YAML to JSON to bypass this
limitation of the libopenapi.
(default: false)
-h, --help Show this help message and exit.
`)
}
for _, arg := range os.Args[1:] {
if arg == "--help" || arg == "-h" {
flag.Usage()
os.Exit(0)
}
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
flag.Parse()
filename := flag.Arg(0)
if len(flag.Args()) != 1 || filename == "" {
logger.Error("missing file argument", slog.Any("args", os.Args))
flag.Usage()
os.Exit(1)
}
validationOpts := []config.Option{}
if *regexParsingOptions != "" {
regexEngineOption, ok := regexParsingOptionsMap[*regexParsingOptions]
if !ok {
logger.Error("unsupported regex option provided",
slog.String("provided", *regexParsingOptions),
slog.Any("supported", []string{
"none",
"ignorecase",
"multiline",
"explicitcapture",
"compiled",
"singleline",
"ignorepatternwhitespace",
"righttoleft",
"debug",
"ecmascript",
"re2",
"unicode",
}),
)
os.Exit(1)
}
reEngine := ®exEngine{
runtimeOption: regexEngineOption,
}
validationOpts = append(validationOpts, config.WithRegexEngine(reEngine.run))
}
data, err := os.ReadFile(filename)
if err != nil {
logger.Error("error reading file", slog.String("provided", filename), slog.Any("error", err))
os.Exit(1)
}
if *convertYAMLToJSON {
var v interface{}
if err := yaml.Unmarshal(data, &v); err == nil {
data, err = yaml.YAMLToJSON(data)
if err != nil {
logger.Error("invalid api spec: error converting yaml to json", slog.Any("error", err))
os.Exit(1)
}
}
}
doc, err := libopenapi.NewDocument(data)
if err != nil {
logger.Error("error creating new libopenapi document", slog.Any("error", err))
os.Exit(1)
}
docValidator, validatorErrs := validator.NewValidator(doc, validationOpts...)
if len(validatorErrs) > 0 {
logger.Error("error creating a new validator", slog.Any("errors", errors.Join(validatorErrs...)))
os.Exit(1)
}
valid, validationErrs := docValidator.ValidateDocument()
if !valid {
logger.Error("validation errors", slog.Any("errors", validationErrs))
os.Exit(1)
}
logger.Info("document passes all validations", slog.String("filename", filename))
}
libopenapi-validator-0.13.8/config/ 0000775 0000000 0000000 00000000000 15205340424 0017154 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/config/config.go 0000664 0000000 0000000 00000031142 15205340424 0020751 0 ustar 00root root 0000000 0000000 package config
import (
"context"
"log/slog"
"net/http"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/pb33f/libopenapi-validator/cache"
"github.com/pb33f/libopenapi-validator/radix"
)
// RegexCache can be set to enable compiled regex caching.
// It can be just a sync.Map, or a custom implementation with possible cleanup.
//
// Be aware that the cache should be thread safe
type RegexCache interface {
Load(key any) (value any, ok bool) // Get a compiled regex from the cache
Store(key, value any) // Set a compiled regex to the cache
}
// AuthenticationFunc validates a security scheme for an HTTP request.
// Return nil when the scheme is satisfied; return an error to fail the current security requirement.
type AuthenticationFunc func(context.Context, *AuthenticationInput) error
// AuthenticationInput contains the request and OpenAPI security scheme details passed to an AuthenticationFunc.
type AuthenticationInput struct {
Request *http.Request
SecuritySchemeName string
SecurityScheme *v3.SecurityScheme
Scopes []string
}
// ValidationOptions A container for validation configuration.
//
// Generally fluent With... style functions are used to establish the desired behavior.
type ValidationOptions struct {
RegexEngine jsonschema.RegexpEngine
RegexCache RegexCache // Enable compiled regex caching
FormatAssertions bool
ContentAssertions bool
SecurityValidation bool
AuthenticationFunc AuthenticationFunc
OpenAPIMode bool // Enable OpenAPI-specific vocabulary validation
AllowScalarCoercion bool // Enable string->boolean/number coercion
Formats map[string]func(v any) error
SchemaCache cache.SchemaCache // Optional cache for compiled schemas
PathTree radix.PathLookup // O(k) path lookup via radix tree (built automatically)
pathTreeDisabled bool // Internal: true if radix tree auto-build was disabled via DisablePathTree
Logger *slog.Logger // Logger for debug/error output (nil = silent)
AllowXMLBodyValidation bool // Allows to convert XML to JSON for validating a request/response body.
AllowURLEncodedBodyValidation bool // Allows to convert URL Encoded to JSON for validating a request/response body.
// strict mode options - detect undeclared properties even when additionalProperties: true
StrictMode bool // Enable strict property validation
StrictIgnorePaths []string // Instance JSONPath patterns to exclude from strict checks
StrictIgnoredHeaders []string // Headers to always ignore in strict mode (nil = use defaults)
strictIgnoredHeadersMerge bool // Internal: true if merging with defaults
StrictRejectReadOnly bool // Reject readOnly properties in requests
StrictRejectWriteOnly bool // Reject writeOnly properties in responses
}
// Option Enables an 'Options pattern' approach
type Option func(*ValidationOptions)
// NewValidationOptions creates a new ValidationOptions instance with default values.
func NewValidationOptions(opts ...Option) *ValidationOptions {
// create the set of default values
o := &ValidationOptions{
FormatAssertions: false,
ContentAssertions: false,
SecurityValidation: true,
OpenAPIMode: true, // Enable OpenAPI vocabulary by default
SchemaCache: cache.NewDefaultCache(), // Enable caching by default
}
for _, opt := range opts {
if opt != nil {
opt(o)
}
}
return o
}
// WithExistingOpts returns an Option that will copy the values from the supplied ValidationOptions instance
func WithExistingOpts(options *ValidationOptions) Option {
return func(o *ValidationOptions) {
if options != nil {
o.RegexEngine = options.RegexEngine
o.RegexCache = options.RegexCache
o.FormatAssertions = options.FormatAssertions
o.ContentAssertions = options.ContentAssertions
o.SecurityValidation = options.SecurityValidation
o.AuthenticationFunc = options.AuthenticationFunc
o.OpenAPIMode = options.OpenAPIMode
o.AllowScalarCoercion = options.AllowScalarCoercion
o.Formats = options.Formats
o.SchemaCache = options.SchemaCache
o.PathTree = options.PathTree
o.pathTreeDisabled = options.pathTreeDisabled
o.Logger = options.Logger
o.AllowXMLBodyValidation = options.AllowXMLBodyValidation
o.AllowURLEncodedBodyValidation = options.AllowURLEncodedBodyValidation
o.StrictMode = options.StrictMode
o.StrictIgnorePaths = options.StrictIgnorePaths
o.StrictIgnoredHeaders = options.StrictIgnoredHeaders
o.strictIgnoredHeadersMerge = options.strictIgnoredHeadersMerge
o.StrictRejectReadOnly = options.StrictRejectReadOnly
o.StrictRejectWriteOnly = options.StrictRejectWriteOnly
}
}
}
// WithLogger sets the logger for validation debug/error output.
// If not set, logging is silent (nil logger is handled gracefully).
func WithLogger(logger *slog.Logger) Option {
return func(o *ValidationOptions) {
o.Logger = logger
}
}
// WithRegexEngine Assigns a custom regular-expression engine to be used during validation.
func WithRegexEngine(engine jsonschema.RegexpEngine) Option {
return func(o *ValidationOptions) {
o.RegexEngine = engine
}
}
// WithRegexCache assigns a cache for compiled regular expressions.
// A sync.Map should be sufficient for most use cases. It does not implement any cleanup
func WithRegexCache(regexCache RegexCache) Option {
return func(o *ValidationOptions) {
o.RegexCache = regexCache
}
}
// WithFormatAssertions enables checks for 'format' assertions (such as date, date-time, uuid, etc)
func WithFormatAssertions() Option {
return func(o *ValidationOptions) {
o.FormatAssertions = true
}
}
// WithContentAssertions enables checks for contentType, contentEncoding, etc
func WithContentAssertions() Option {
return func(o *ValidationOptions) {
o.ContentAssertions = true
}
}
// WithoutSecurityValidation disables security validation for request validation
func WithoutSecurityValidation() Option {
return func(o *ValidationOptions) {
o.SecurityValidation = false
}
}
// WithAuthenticationFunc sets a custom function for validating security requirements.
// When set, the function is authoritative for all security scheme types, including oauth2 and openIdConnect.
func WithAuthenticationFunc(fn AuthenticationFunc) Option {
return func(o *ValidationOptions) {
o.AuthenticationFunc = fn
}
}
// WithCustomFormat adds custom formats and their validators that checks for custom 'format' assertions
// When you add different validators with the same name, they will be overridden,
// and only the last registration will take effect.
func WithCustomFormat(name string, validator func(v any) error) Option {
return func(o *ValidationOptions) {
if o.Formats == nil {
o.Formats = make(map[string]func(v any) error)
}
o.Formats[name] = validator
}
}
// WithOpenAPIMode enables OpenAPI-specific keyword validation (default: true)
func WithOpenAPIMode() Option {
return func(o *ValidationOptions) {
o.OpenAPIMode = true
}
}
// WithoutOpenAPIMode disables OpenAPI-specific keyword validation
func WithoutOpenAPIMode() Option {
return func(o *ValidationOptions) {
o.OpenAPIMode = false
}
}
// WithScalarCoercion enables string to boolean/number coercion (Jackson-style)
func WithScalarCoercion() Option {
return func(o *ValidationOptions) {
o.AllowScalarCoercion = true
}
}
// WithXmlBodyValidation enables converting an XML body to a JSON when validating the schema from a request and response body
// The default option is set to false
func WithXmlBodyValidation() Option {
return func(o *ValidationOptions) {
o.AllowXMLBodyValidation = true
}
}
// WithURLEncodedBodyValidation enables converting an URL Encoded body to a JSON when validating the schema from a request and response body
// The default option is set to false
func WithURLEncodedBodyValidation() Option {
return func(o *ValidationOptions) {
o.AllowURLEncodedBodyValidation = true
}
}
// WithSchemaCache sets a custom cache implementation or disables caching if nil.
// Pass nil to disable schema caching and skip cache warming during validator initialization.
// The default cache is a thread-safe sync.Map wrapper.
func WithSchemaCache(schemaCache cache.SchemaCache) Option {
return func(o *ValidationOptions) {
o.SchemaCache = schemaCache
}
}
// WithPathTree sets a custom radix tree for path matching.
// The default is built automatically from the OpenAPI specification.
func WithPathTree(pathTree radix.PathLookup) Option {
return func(o *ValidationOptions) {
o.PathTree = pathTree
}
}
// DisablePathTree prevents automatic radix tree construction.
// Use this to fall back to regex-based path matching only.
func DisablePathTree() Option {
return func(o *ValidationOptions) {
o.pathTreeDisabled = true
}
}
// WithStrictMode enables strict property validation.
// In strict mode, undeclared properties are reported as errors even when
// additionalProperties: true would normally allow them.
//
// This is useful for API governance scenarios where you want to ensure
// clients only send properties that are explicitly documented in the
// OpenAPI specification.
func WithStrictMode() Option {
return func(o *ValidationOptions) {
o.StrictMode = true
}
}
// WithStrictIgnorePaths sets JSONPath patterns for paths to exclude from strict validation.
// Patterns use glob syntax:
// - * matches a single path segment
// - ** matches any depth (zero or more segments)
// - [*] matches any array index
// - \* escapes a literal asterisk
//
// Examples:
// - "$.body.metadata.*" - any property under metadata
// - "$.body.**.x-*" - any x-* property at any depth
// - "$.headers.X-*" - any header starting with X-
func WithStrictIgnorePaths(paths ...string) Option {
return func(o *ValidationOptions) {
o.StrictIgnorePaths = paths
}
}
// WithStrictRejectReadOnly enables rejection of readOnly properties in requests.
// When enabled, readOnly properties present in request bodies are reported as
// validation errors instead of being silently skipped.
func WithStrictRejectReadOnly() Option {
return func(o *ValidationOptions) {
o.StrictRejectReadOnly = true
}
}
// WithStrictRejectWriteOnly enables rejection of writeOnly properties in responses.
// When enabled, writeOnly properties present in response bodies are reported as
// validation errors instead of being silently skipped.
func WithStrictRejectWriteOnly() Option {
return func(o *ValidationOptions) {
o.StrictRejectWriteOnly = true
}
}
// WithStrictIgnoredHeaders replaces the default ignored headers list entirely.
// Use this to fully control which headers are ignored in strict mode.
// For the default list, see the strict package's DefaultIgnoredHeaders.
func WithStrictIgnoredHeaders(headers ...string) Option {
return func(o *ValidationOptions) {
o.StrictIgnoredHeaders = headers
o.strictIgnoredHeadersMerge = false
}
}
// WithStrictIgnoredHeadersExtra adds headers to the default ignored list.
// Unlike WithStrictIgnoredHeaders, this merges with the defaults rather
// than replacing them.
func WithStrictIgnoredHeadersExtra(headers ...string) Option {
return func(o *ValidationOptions) {
o.StrictIgnoredHeaders = headers
o.strictIgnoredHeadersMerge = true
}
}
// defaultIgnoredHeaders contains standard HTTP headers ignored by default.
// This is the fallback list used when no custom headers are configured.
var defaultIgnoredHeaders = []string{
"content-type", "content-length", "accept", "authorization",
"user-agent", "host", "connection", "accept-encoding",
"accept-language", "cache-control", "pragma", "origin",
"referer", "cookie", "date", "etag", "expires",
"if-match", "if-none-match", "if-modified-since",
"last-modified", "transfer-encoding", "vary", "x-forwarded-for",
"x-forwarded-proto", "x-real-ip", "x-request-id",
"request-start-time", // Added by some API clients for timing
}
// IsPathTreeDisabled returns true if radix tree auto-build was disabled via DisablePathTree.
func (o *ValidationOptions) IsPathTreeDisabled() bool {
return o.pathTreeDisabled
}
// GetEffectiveStrictIgnoredHeaders returns the list of headers to ignore
// based on configuration. Returns defaults if not configured, merged list
// if extra headers were added, or replaced list if headers were fully replaced.
func (o *ValidationOptions) GetEffectiveStrictIgnoredHeaders() []string {
if o.StrictIgnoredHeaders == nil {
return defaultIgnoredHeaders
}
if o.strictIgnoredHeadersMerge {
return append(defaultIgnoredHeaders, o.StrictIgnoredHeaders...)
}
return o.StrictIgnoredHeaders
}
libopenapi-validator-0.13.8/config/config_test.go 0000664 0000000 0000000 00000044142 15205340424 0022014 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package config
import (
"context"
"log/slog"
"sync"
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/stretchr/testify/assert"
)
func TestNewValidationOptions_Defaults(t *testing.T) {
opts := NewValidationOptions()
assert.NotNil(t, opts)
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.False(t, opts.AllowURLEncodedBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestNewValidationOptions_WithNilOption(t *testing.T) {
opts := NewValidationOptions(nil)
assert.NotNil(t, opts)
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithFormatAssertions(t *testing.T) {
opts := NewValidationOptions(WithFormatAssertions())
assert.True(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithContentAssertions(t *testing.T) {
opts := NewValidationOptions(WithContentAssertions())
assert.False(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithoutSecurityValidation(t *testing.T) {
opts := NewValidationOptions(WithoutSecurityValidation())
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.False(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithAuthenticationFunc(t *testing.T) {
called := false
authFn := func(ctx context.Context, input *AuthenticationInput) error {
called = true
assert.NotNil(t, ctx)
assert.Equal(t, "ApiKeyAuth", input.SecuritySchemeName)
return nil
}
opts := NewValidationOptions(WithAuthenticationFunc(authFn))
assert.True(t, opts.SecurityValidation)
assert.NotNil(t, opts.AuthenticationFunc)
assert.NoError(t, opts.AuthenticationFunc(context.Background(), &AuthenticationInput{
SecuritySchemeName: "ApiKeyAuth",
}))
assert.True(t, called)
}
func TestWithRegexEngine(t *testing.T) {
// Test with nil regex engine (valid)
var mockEngine jsonschema.RegexpEngine = nil
opts := NewValidationOptions(WithRegexEngine(mockEngine))
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithExistingOpts(t *testing.T) {
// Create original options with all settings enabled
var testEngine jsonschema.RegexpEngine = nil
original := &ValidationOptions{
RegexEngine: testEngine,
RegexCache: &sync.Map{},
FormatAssertions: true,
AllowXMLBodyValidation: true,
AllowURLEncodedBodyValidation: true,
ContentAssertions: true,
SecurityValidation: false,
}
// Create new options using existing options
opts := NewValidationOptions(WithExistingOpts(original))
assert.Nil(t, opts.RegexEngine) // Both should be nil
assert.NotNil(t, opts.RegexCache)
assert.Equal(t, original.AllowXMLBodyValidation, opts.AllowXMLBodyValidation)
assert.Equal(t, original.AllowURLEncodedBodyValidation, opts.AllowURLEncodedBodyValidation)
assert.Equal(t, original.FormatAssertions, opts.FormatAssertions)
assert.Equal(t, original.ContentAssertions, opts.ContentAssertions)
assert.Equal(t, original.SecurityValidation, opts.SecurityValidation)
}
func TestWithExistingOpts_NilSource(t *testing.T) {
// Test with nil source options
opts := NewValidationOptions(WithExistingOpts(nil))
assert.NotNil(t, opts)
// Should not panic and should have default values
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestMultipleOptions(t *testing.T) {
opts := NewValidationOptions(
WithFormatAssertions(),
WithContentAssertions(),
WithXmlBodyValidation(),
)
assert.True(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.AllowXMLBodyValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestOptionOverride(t *testing.T) {
// Test that later options override earlier ones
// First set format assertions, then turn them off by not setting them again
opts := NewValidationOptions(
WithFormatAssertions(),
WithContentAssertions(),
)
assert.True(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithExistingOpts_PartialOverride(t *testing.T) {
// Create original options
var testEngine jsonschema.RegexpEngine = nil
original := &ValidationOptions{
RegexEngine: testEngine,
FormatAssertions: true,
ContentAssertions: true,
SecurityValidation: false,
}
// Create new options using existing options, then override one setting
opts := NewValidationOptions(
WithExistingOpts(original),
WithContentAssertions(), // This should still be true (no change)
)
assert.Nil(t, opts.RegexEngine) // Both should be nil
assert.Nil(t, opts.RegexCache)
assert.True(t, opts.FormatAssertions) // From original
assert.True(t, opts.ContentAssertions) // Reapplied, but same value
assert.False(t, opts.SecurityValidation) // From original
}
func TestWithUrlEncodedBodyValidation(t *testing.T) {
opts := NewValidationOptions(
WithURLEncodedBodyValidation(),
)
assert.True(t, opts.AllowURLEncodedBodyValidation)
}
func TestComplexScenario(t *testing.T) {
// Test a complex real-world scenario
var mockEngine jsonschema.RegexpEngine = nil
// Start with some base options
baseOpts := &ValidationOptions{
FormatAssertions: true,
SecurityValidation: false,
// RegexEngine and ContentAssertions are defaults (nil/false)
}
// Create new options building on the base
opts := NewValidationOptions(
WithExistingOpts(baseOpts),
WithContentAssertions(),
WithRegexEngine(mockEngine),
)
// Verify all settings are as expected
assert.True(t, opts.FormatAssertions) // From base
assert.True(t, opts.ContentAssertions) // Added
assert.False(t, opts.SecurityValidation) // From base
assert.Nil(t, opts.RegexEngine) // Should be nil
assert.Nil(t, opts.RegexCache)
}
func TestMultipleOptionsWithSecurityDisabled(t *testing.T) {
opts := NewValidationOptions(
WithFormatAssertions(),
WithContentAssertions(),
WithoutSecurityValidation(),
)
assert.True(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.False(t, opts.SecurityValidation)
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithExistingOpts_SecurityValidationCopied(t *testing.T) {
// Test that SecurityValidation is properly copied
original := &ValidationOptions{
SecurityValidation: false,
}
opts := NewValidationOptions(WithExistingOpts(original))
assert.False(t, opts.SecurityValidation)
// Test the opposite
original2 := &ValidationOptions{
SecurityValidation: true,
}
opts2 := NewValidationOptions(WithExistingOpts(original2))
assert.True(t, opts2.SecurityValidation)
}
func TestWithExistingOpts_AuthenticationFuncCopied(t *testing.T) {
called := false
authFn := func(context.Context, *AuthenticationInput) error {
called = true
return nil
}
original := &ValidationOptions{
AuthenticationFunc: authFn,
}
opts := NewValidationOptions(WithExistingOpts(original))
assert.NotNil(t, opts.AuthenticationFunc)
assert.NoError(t, opts.AuthenticationFunc(context.Background(), &AuthenticationInput{}))
assert.True(t, called)
}
// Tests for new OpenAPI and scalar coercion configuration options
func TestWithOpenAPIMode(t *testing.T) {
opts := NewValidationOptions(WithOpenAPIMode())
assert.True(t, opts.OpenAPIMode)
assert.False(t, opts.AllowScalarCoercion) // Should be default false
assert.False(t, opts.FormatAssertions) // Should be default false
assert.False(t, opts.ContentAssertions) // Should be default false
assert.True(t, opts.SecurityValidation) // Should be default true
}
func TestWithoutOpenAPIMode(t *testing.T) {
opts := NewValidationOptions(WithoutOpenAPIMode())
assert.False(t, opts.OpenAPIMode)
assert.False(t, opts.AllowScalarCoercion) // Should be default false
assert.False(t, opts.FormatAssertions) // Should be default false
assert.False(t, opts.ContentAssertions) // Should be default false
assert.True(t, opts.SecurityValidation) // Should be default true
}
func TestWithScalarCoercion(t *testing.T) {
opts := NewValidationOptions(WithScalarCoercion())
assert.True(t, opts.AllowScalarCoercion)
assert.True(t, opts.OpenAPIMode) // Should be default true
assert.False(t, opts.FormatAssertions) // Should be default false
assert.False(t, opts.ContentAssertions) // Should be default false
assert.True(t, opts.SecurityValidation) // Should be default true
}
func TestWithOpenAPIModeAndScalarCoercion(t *testing.T) {
opts := NewValidationOptions(
WithOpenAPIMode(),
WithScalarCoercion(),
)
assert.True(t, opts.OpenAPIMode)
assert.True(t, opts.AllowScalarCoercion)
assert.False(t, opts.FormatAssertions) // Should be default false
assert.False(t, opts.ContentAssertions) // Should be default false
assert.True(t, opts.SecurityValidation) // Should be default true
}
func TestWithOpenAPIModeOverride(t *testing.T) {
// Test that WithoutOpenAPIMode can override WithOpenAPIMode
opts := NewValidationOptions(
WithOpenAPIMode(),
WithoutOpenAPIMode(),
)
assert.False(t, opts.OpenAPIMode) // Should be false (last option wins)
assert.False(t, opts.AllowScalarCoercion)
}
func TestComplexOpenAPIScenario(t *testing.T) {
// Test a complex scenario with OpenAPI mode and other options
opts := NewValidationOptions(
WithFormatAssertions(),
WithOpenAPIMode(),
WithScalarCoercion(),
WithContentAssertions(),
WithoutSecurityValidation(),
)
assert.True(t, opts.OpenAPIMode)
assert.True(t, opts.AllowScalarCoercion)
assert.True(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.False(t, opts.SecurityValidation)
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
func TestWithExistingOpts_OpenAPIFields(t *testing.T) {
// Test that OpenAPI fields are properly copied from existing options
original := &ValidationOptions{
OpenAPIMode: true,
AllowScalarCoercion: true,
FormatAssertions: false,
ContentAssertions: false,
SecurityValidation: true,
}
opts := NewValidationOptions(WithExistingOpts(original))
assert.True(t, opts.OpenAPIMode)
assert.True(t, opts.AllowScalarCoercion)
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
}
func TestWithCustomFormat(t *testing.T) {
// Test WithCustomFormat option
testFormatFunc := func(v any) error {
return nil // Simple test format function
}
opts := NewValidationOptions(WithCustomFormat("test-format", testFormatFunc))
assert.NotNil(t, opts.Formats)
assert.Contains(t, opts.Formats, "test-format")
assert.NotNil(t, opts.Formats["test-format"])
}
func TestWithSchemaCache(t *testing.T) {
// Test with nil cache (disables caching)
opts := NewValidationOptions(WithSchemaCache(nil))
assert.Nil(t, opts.SchemaCache)
// Test with default cache by creating a new options object
optsDefault := NewValidationOptions()
assert.NotNil(t, optsDefault.SchemaCache, "Default options should have a cache")
// Test setting a custom cache
customCache := optsDefault.SchemaCache // Use default cache as custom
optsCustom := NewValidationOptions(WithSchemaCache(customCache))
assert.Equal(t, customCache, optsCustom.SchemaCache)
}
func TestWithRegexpCache(t *testing.T) {
syncMap := &sync.Map{}
opts := NewValidationOptions(WithRegexCache(syncMap))
assert.NotNil(t, opts.RegexCache)
}
// Tests for strict mode configuration options
func TestWithStrictMode(t *testing.T) {
opts := NewValidationOptions(WithStrictMode())
assert.True(t, opts.StrictMode)
assert.Nil(t, opts.StrictIgnorePaths)
assert.Nil(t, opts.StrictIgnoredHeaders)
}
func TestWithStrictIgnorePaths(t *testing.T) {
paths := []string{"$.body.metadata.*", "$.headers.X-*"}
opts := NewValidationOptions(WithStrictIgnorePaths(paths...))
assert.Equal(t, paths, opts.StrictIgnorePaths)
assert.False(t, opts.StrictMode) // Not enabled by default
}
func TestWithStrictIgnoredHeaders(t *testing.T) {
headers := []string{"x-custom-header", "x-another-header"}
opts := NewValidationOptions(WithStrictIgnoredHeaders(headers...))
assert.Equal(t, headers, opts.StrictIgnoredHeaders)
assert.False(t, opts.strictIgnoredHeadersMerge)
}
func TestWithStrictIgnoredHeadersExtra(t *testing.T) {
headers := []string{"x-extra-header"}
opts := NewValidationOptions(WithStrictIgnoredHeadersExtra(headers...))
assert.Equal(t, headers, opts.StrictIgnoredHeaders)
assert.True(t, opts.strictIgnoredHeadersMerge)
}
func TestGetEffectiveStrictIgnoredHeaders_Default(t *testing.T) {
opts := NewValidationOptions()
headers := opts.GetEffectiveStrictIgnoredHeaders()
assert.NotNil(t, headers)
assert.Contains(t, headers, "content-type")
assert.Contains(t, headers, "authorization")
}
func TestGetEffectiveStrictIgnoredHeaders_Replace(t *testing.T) {
customHeaders := []string{"x-only-this"}
opts := NewValidationOptions(WithStrictIgnoredHeaders(customHeaders...))
headers := opts.GetEffectiveStrictIgnoredHeaders()
assert.Equal(t, customHeaders, headers)
assert.NotContains(t, headers, "content-type") // Default headers are replaced
}
func TestGetEffectiveStrictIgnoredHeaders_Merge(t *testing.T) {
extraHeaders := []string{"x-extra-header"}
opts := NewValidationOptions(WithStrictIgnoredHeadersExtra(extraHeaders...))
headers := opts.GetEffectiveStrictIgnoredHeaders()
// Should have both defaults and extras
assert.Contains(t, headers, "content-type") // From defaults
assert.Contains(t, headers, "x-extra-header") // From extras
assert.Contains(t, headers, "authorization") // From defaults
}
func TestWithLogger(t *testing.T) {
logger := slog.New(slog.NewTextHandler(nil, nil))
opts := NewValidationOptions(WithLogger(logger))
assert.Equal(t, logger, opts.Logger)
}
func TestWithExistingOpts_StrictFields(t *testing.T) {
original := &ValidationOptions{
StrictMode: true,
StrictRejectReadOnly: true,
StrictRejectWriteOnly: true,
StrictIgnorePaths: []string{"$.body.*"},
StrictIgnoredHeaders: []string{"x-custom"},
strictIgnoredHeadersMerge: true,
Logger: slog.New(slog.NewTextHandler(nil, nil)),
}
opts := NewValidationOptions(WithExistingOpts(original))
assert.True(t, opts.StrictMode)
assert.True(t, opts.StrictRejectReadOnly)
assert.True(t, opts.StrictRejectWriteOnly)
assert.Equal(t, original.StrictIgnorePaths, opts.StrictIgnorePaths)
assert.Equal(t, original.StrictIgnoredHeaders, opts.StrictIgnoredHeaders)
assert.True(t, opts.strictIgnoredHeadersMerge)
assert.Equal(t, original.Logger, opts.Logger)
}
func TestWithStrictRejectReadOnly(t *testing.T) {
opts := NewValidationOptions(WithStrictRejectReadOnly())
assert.True(t, opts.StrictRejectReadOnly)
}
func TestWithStrictRejectWriteOnly(t *testing.T) {
opts := NewValidationOptions(WithStrictRejectWriteOnly())
assert.True(t, opts.StrictRejectWriteOnly)
}
func TestStrictModeWithIgnorePaths(t *testing.T) {
paths := []string{"$.body.metadata.*"}
opts := NewValidationOptions(
WithStrictMode(),
WithStrictIgnorePaths(paths...),
)
assert.True(t, opts.StrictMode)
assert.Equal(t, paths, opts.StrictIgnorePaths)
}
func TestWithPathTree(t *testing.T) {
// Use a mock/nil path tree â WithPathTree just sets the field
opts := NewValidationOptions(WithPathTree(nil))
assert.Nil(t, opts.PathTree)
// TestWithPathTree with a real value â use a custom implementation
// We can verify the field is set using a simple check
opts2 := &ValidationOptions{}
WithPathTree(nil)(opts2)
assert.Nil(t, opts2.PathTree)
}
func TestDisablePathTree(t *testing.T) {
opts := NewValidationOptions(DisablePathTree())
assert.True(t, opts.IsPathTreeDisabled())
}
func TestIsPathTreeDisabled_Default(t *testing.T) {
opts := NewValidationOptions()
assert.False(t, opts.IsPathTreeDisabled())
}
func TestWithExistingOpts_PathTreeFields(t *testing.T) {
original := NewValidationOptions(DisablePathTree())
opts := NewValidationOptions(WithExistingOpts(original))
assert.True(t, opts.IsPathTreeDisabled())
assert.Nil(t, opts.PathTree)
}
libopenapi-validator-0.13.8/errors/ 0000775 0000000 0000000 00000000000 15205340424 0017223 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/errors/error_utilities.go 0000664 0000000 0000000 00000001162 15205340424 0022776 0 ustar 00root root 0000000 0000000 package errors
import (
"net/http"
)
// PopulateValidationErrors mutates the provided validation errors with additional useful error information, that is
// not necessarily available when the ValidationError was created and are standard for all errors.
// Specifically, the RequestPath, SpecPath and RequestMethod are populated.
func PopulateValidationErrors(validationErrors []*ValidationError, request *http.Request, path string) {
for _, validationError := range validationErrors {
validationError.SpecPath = path
validationError.RequestMethod = request.Method
validationError.RequestPath = request.URL.Path
}
}
libopenapi-validator-0.13.8/errors/error_utilities_test.go 0000664 0000000 0000000 00000001741 15205340424 0024040 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
// Helper function to create a mock ValidationError
func createMockValidationError() *ValidationError {
return &ValidationError{
Message: "Test validation error",
}
}
func TestPopulateValidationErrors(t *testing.T) {
// Create a mock request
req, _ := http.NewRequest(http.MethodGet, "/test/path", nil)
// Create mock validation errors
validationErrors := []*ValidationError{
createMockValidationError(),
createMockValidationError(),
}
// Call the function
PopulateValidationErrors(validationErrors, req, "/spec/path")
// Validate the results
for _, validationError := range validationErrors {
require.Equal(t, "/spec/path", validationError.SpecPath)
require.Equal(t, http.MethodGet, validationError.RequestMethod)
require.Equal(t, "/test/path", validationError.RequestPath)
}
}
libopenapi-validator-0.13.8/errors/package.go 0000664 0000000 0000000 00000000265 15205340424 0021150 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
// Package errors contains all the error types used by the validator
package errors
libopenapi-validator-0.13.8/errors/parameter_errors.go 0000664 0000000 0000000 00000144570 15205340424 0023141 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"fmt"
"net/url"
"strings"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
func IncorrectFormEncoding(param *v3.Parameter, qp *helpers.QueryParam, i int) *ValidationError {
specLine, specCol := paramExplodeLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not exploded correctly", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has a default or 'form' encoding defined, "+
"however the value '%s' is encoded as an object or an array using commas. The contract defines "+
"the explode value to set to 'true'", param.Name, qp.Values[i]),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: param,
HowToFix: fmt.Sprintf(HowToFixParamInvalidFormEncode,
helpers.CollapseCSVIntoFormStyle(param.Name, qp.Values[i])),
}
}
func IncorrectSpaceDelimiting(param *v3.Parameter, qp *helpers.QueryParam) *ValidationError {
specLine, specCol := paramStyleLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' delimited incorrectly", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has 'spaceDelimited' style defined, "+
"and explode is defined as false. There are multiple values (%d) supplied, instead of a single"+
" space delimited value", param.Name, len(qp.Values)),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: param,
HowToFix: fmt.Sprintf(HowToFixParamInvalidSpaceDelimitedObjectExplode,
helpers.CollapseCSVIntoSpaceDelimitedStyle(param.Name, qp.Values)),
}
}
func IncorrectPipeDelimiting(param *v3.Parameter, qp *helpers.QueryParam) *ValidationError {
specLine, specCol := paramStyleLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' delimited incorrectly", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has 'pipeDelimited' style defined, "+
"and explode is defined as false. There are multiple values (%d) supplied, instead of a single"+
" space delimited value", param.Name, len(qp.Values)),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: param,
HowToFix: fmt.Sprintf(HowToFixParamInvalidPipeDelimitedObjectExplode,
helpers.CollapseCSVIntoPipeDelimitedStyle(param.Name, qp.Values)),
}
}
func InvalidDeepObject(param *v3.Parameter, qp *helpers.QueryParam) *ValidationError {
specLine, specCol := paramStyleLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not a valid deepObject", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has the 'deepObject' style defined, "+
"There are multiple values (%d) supplied, instead of a single "+
"value", param.Name, len(qp.Values)),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: param,
HowToFix: fmt.Sprintf(HowToFixParamInvalidDeepObjectMultipleValues,
helpers.CollapseCSVIntoPipeDelimitedStyle(param.Name, qp.Values)),
}
}
func deepObjectPathForError(qp *helpers.QueryParam) string {
if qp == nil {
return ""
}
if len(qp.PropertyPath) > 0 {
return strings.Join(qp.PropertyPath, ".")
}
return qp.Property
}
func deepObjectBracketPathForError(qp *helpers.QueryParam) string {
if qp == nil {
return ""
}
if len(qp.PropertyPath) > 0 {
return strings.Join(qp.PropertyPath, "][")
}
return qp.Property
}
func InvalidDeepObjectPathConflict(param *v3.Parameter, prefixParam, nestedParam *helpers.QueryParam) *ValidationError {
specLine, specCol := paramStyleLineCol(param)
prefixPath := deepObjectPathForError(prefixParam)
nestedPath := deepObjectPathForError(nestedParam)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not a valid deepObject", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has the 'deepObject' style defined, "+
"but the property path '%s' is also used as a nested object prefix for '%s'",
param.Name, prefixPath, nestedPath),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: param,
HowToFix: fmt.Sprintf(HowToFixParamInvalidDeepObjectPathConflict,
param.Name, deepObjectBracketPathForError(prefixParam),
param.Name, deepObjectBracketPathForError(nestedParam)),
}
}
func QueryParameterMissing(param *v3.Parameter, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "required")
specLine, specCol := paramRequiredLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is missing", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' is defined as being required, "+
"however it's missing from the requests", param.Name),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
HowToFix: HowToFixMissingValue,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Required query parameter '%s' is missing", param.Name),
FieldName: param.Name,
FieldPath: "",
InstancePath: []string{},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func HeaderParameterMissing(param *v3.Parameter, pathTemplate string, operation string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
escapedPath = strings.TrimPrefix(escapedPath, "~1")
keywordLocation := fmt.Sprintf("/paths/%s/%s/parameters/%s/required", escapedPath, strings.ToLower(operation), param.Name)
specLine, specCol := paramRequiredLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' is missing", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' is defined as being required, "+
"however it's missing from the requests", param.Name),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
HowToFix: HowToFixMissingValue,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Required header parameter '%s' is missing", param.Name),
FieldName: param.Name,
FieldPath: "",
InstancePath: []string{},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func CookieParameterMissing(param *v3.Parameter, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "required")
specLine, specCol := paramRequiredLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie parameter '%s' is missing", param.Name),
Reason: fmt.Sprintf("The cookie parameter '%s' is defined as being required, "+
"however it's missing from the request", param.Name),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
HowToFix: HowToFixMissingValue,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Required cookie parameter '%s' is missing", param.Name),
FieldName: param.Name,
FieldPath: "",
InstancePath: []string{},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func HeaderParameterCannotBeDecoded(param *v3.Parameter, val string, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaTypeLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' cannot be decoded", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' cannot be "+
"extracted into an object, '%s' is malformed", param.Name, val),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
HowToFix: HowToFixInvalidEncoding,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Header value '%s' cannot be decoded as object (malformed encoding)", val),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectHeaderParamEnum(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
var enums []string
for i := range sch.Enum {
enums = append(enums, fmt.Sprint(sch.Enum[i].Value))
}
validEnums := strings.Join(enums, ", ")
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "enum")
specLine, specCol := paramSchemaEnumLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' does not match allowed values", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' has pre-defined "+
"values set via an enum. The value '%s' is not one of those values.", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidEnum, ef, validEnums),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' does not match any enum values: [%s]", ef, validEnums),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectQueryParamArrayBoolean(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid true/false value", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid boolean", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectParamArrayMaxNumItems(param *v3.Parameter, sch *base.Schema, expected, actual int64, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "maxItems")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' has too many items", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' has a maximum item length of %d, "+
"however the request provided %d items", param.Name, expected, actual),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixInvalidMaxItems, expected),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array has %d items, but maximum is %d", actual, expected),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectParamArrayMinNumItems(param *v3.Parameter, sch *base.Schema, expected, actual int64, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "minItems")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' does not have enough items", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' has a minimum items length of %d, "+
"however the request provided %d items", param.Name, expected, actual),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixInvalidMinItems, expected),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array has %d items, but minimum is %d", actual, expected),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectParamArrayUniqueItems(param *v3.Parameter, sch *base.Schema, duplicates string, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "uniqueItems")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' contains non-unique items", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' contains the following duplicates: '%s'", param.Name, duplicates),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: "Ensure the array values are all unique",
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array contains duplicate values: %s", duplicates),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectCookieParamArrayBoolean(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie array parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The cookie parameter (which is an array) '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid true/false value", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid boolean", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectQueryParamArrayInteger(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid integer", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectQueryParamArrayNumber(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid number", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectCookieParamArrayNumber(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie array parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The cookie parameter (which is an array) '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid number", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectParamEncodingJSON(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
escapedPath = strings.TrimPrefix(escapedPath, "~1")
keywordLocation := fmt.Sprintf("/paths/%s/%s/parameters/%s/content/application~1json/schema", escapedPath, strings.ToLower(operation), param.Name)
specLine, specCol := paramContentLineCol(param, helpers.JSONContentType)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not valid JSON", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' is defined as being a JSON object, "+
"however the value '%s' is not valid JSON", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: HowToFixInvalidJSON,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not valid JSON", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectQueryParamBool(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid boolean", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid boolean", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidQueryParamInteger(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid integer", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidQueryParamNumber(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid number", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectQueryParamEnum(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
var enums []string
for i := range sch.Enum {
enums = append(enums, fmt.Sprint(sch.Enum[i].Value))
}
validEnums := strings.Join(enums, ", ")
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "enum")
specLine, specCol := paramSchemaEnumLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' does not match allowed values", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has pre-defined "+
"values set via an enum. The value '%s' is not one of those values.", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidEnum, ef, validEnums),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' does not match any enum values: [%s]", ef, validEnums),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectQueryParamEnumArray(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedItemsSchema string) *ValidationError {
var enums []string
if low := param.GoLow(); low != nil {
if sv := low.Schema.Value; sv != nil {
if s := sv.Schema(); s != nil {
if iv := s.Items.Value; iv != nil {
if as := iv.A.Schema(); as != nil {
for i := range as.Enum.Value {
enums = append(enums, fmt.Sprint(as.Enum.Value[i].Value.Value))
}
}
}
}
}
}
validEnums := strings.Join(enums, ", ")
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/enum")
specLine, specCol := 1, 0
if low := param.GoLow(); low != nil {
if sv := low.Schema.Value; sv != nil {
if s := sv.Schema(); s != nil {
if iv := s.Items.Value; iv != nil {
if as := iv.A.Schema(); as != nil && as.Enum.KeyNode != nil {
specLine = as.Enum.KeyNode.Line
specCol = as.Enum.KeyNode.Column
}
}
}
}
}
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query array parameter '%s' does not match allowed values", param.Name),
Reason: fmt.Sprintf("The query array parameter '%s' has pre-defined "+
"values set via an enum. The value '%s' is not one of those values.", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidEnum, ef, validEnums),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' does not match any enum values: [%s]", ef, validEnums),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectReservedValues(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
escapedPath = strings.TrimPrefix(escapedPath, "~1")
keywordLocation := fmt.Sprintf("/paths/%s/%s/parameters/%s/allowReserved", escapedPath, strings.ToLower(operation), param.Name)
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationQuery,
Message: fmt.Sprintf("Query parameter '%s' value contains reserved values", param.Name),
Reason: fmt.Sprintf("The query parameter '%s' has 'allowReserved' set to false, "+
"however the value '%s' contains one of the following characters: :/?#[]@!$&'()*+,;=", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixReservedValues, url.QueryEscape(ef)),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' contains reserved characters but allowReserved is false", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidHeaderParamInteger(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid integer", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidHeaderParamNumber(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid number", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidCookieParamInteger(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The cookie parameter '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid integer", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func InvalidCookieParamNumber(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The cookie parameter '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid number", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectHeaderParamBool(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The header parameter '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid boolean", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid boolean", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectCookieParamBool(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type")
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The cookie parameter '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid boolean", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, ef),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid boolean", ef),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectCookieParamEnum(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError {
var enums []string
for i := range sch.Enum {
enums = append(enums, fmt.Sprint(sch.Enum[i].Value))
}
validEnums := strings.Join(enums, ", ")
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "enum")
specLine, specCol := paramSchemaEnumLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationCookie,
Message: fmt.Sprintf("Cookie parameter '%s' does not match allowed values", param.Name),
Reason: fmt.Sprintf("The cookie parameter '%s' has pre-defined "+
"values set via an enum. The value '%s' is not one of those values.", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidEnum, ef, validEnums),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' does not match any enum values: [%s]", ef, validEnums),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectHeaderParamArrayBoolean(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header array parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The header parameter (which is an array) '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid true/false value", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid boolean", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectHeaderParamArrayNumber(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, operation string, renderedItemsSchema string,
) *ValidationError {
keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "items/type")
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationHeader,
Message: fmt.Sprintf("Header array parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The header parameter (which is an array) '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid number", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedItemsSchema,
}},
}
}
func IncorrectPathParamBool(param *v3.Parameter, item string, sch *base.Schema, pathTemplate string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/type", escapedPath, param.Name)
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The path parameter '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid boolean", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid boolean", item),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamEnum(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/enum", escapedPath, param.Name)
var enums []string
for i := range sch.Enum {
enums = append(enums, fmt.Sprint(sch.Enum[i].Value))
}
validEnums := strings.Join(enums, ", ")
specLine, specCol := paramSchemaEnumLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
ParameterName: param.Name,
Message: fmt.Sprintf("Path parameter '%s' does not match allowed values", param.Name),
Reason: fmt.Sprintf("The path parameter '%s' has pre-defined "+
"values set via an enum. The value '%s' is not one of those values.", param.Name, ef),
SpecLine: specLine,
SpecCol: specCol,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidEnum, ef, validEnums),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' does not match any enum values: [%s]", ef, validEnums),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamInteger(param *v3.Parameter, item string, sch *base.Schema, pathTemplate string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/type", escapedPath, param.Name)
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The path parameter '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid integer", item),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamNumber(param *v3.Parameter, item string, sch *base.Schema, pathTemplate string, renderedSchema string) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/type", escapedPath, param.Name)
specLine, specCol := paramSchemaKeyLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The path parameter '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: sch,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Value '%s' is not a valid number", item),
FieldName: param.Name,
InstancePath: []string{param.Name},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamArrayNumber(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, renderedSchema string,
) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/items/type", escapedPath, param.Name)
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path array parameter '%s' is not a valid number", param.Name),
Reason: fmt.Sprintf("The path parameter (which is an array) '%s' is defined as being a number, "+
"however the value '%s' is not a valid number", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid number", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamArrayInteger(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, renderedSchema string,
) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/items/type", escapedPath, param.Name)
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path array parameter '%s' is not a valid integer", param.Name),
Reason: fmt.Sprintf("The path parameter (which is an array) '%s' is defined as being an integer, "+
"however the value '%s' is not a valid integer", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidNumber, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid integer", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func IncorrectPathParamArrayBoolean(
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema, pathTemplate string, renderedSchema string,
) *ValidationError {
escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
escapedPath = strings.ReplaceAll(escapedPath, "/", "~1")
keywordLocation := fmt.Sprintf("/paths/%s/parameters/%s/schema/items/type", escapedPath, param.Name)
specLine, specCol := schemaItemsTypeLineCol(sch)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path array parameter '%s' is not a valid boolean", param.Name),
Reason: fmt.Sprintf("The path parameter (which is an array) '%s' is defined as being a boolean, "+
"however the value '%s' is not a valid boolean", param.Name, item),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
Context: itemsSchema,
HowToFix: fmt.Sprintf(HowToFixParamInvalidBoolean, item),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Array item '%s' is not a valid boolean", item),
FieldName: param.Name,
InstancePath: []string{param.Name, "[item]"},
KeywordLocation: keywordLocation,
ReferenceSchema: renderedSchema,
}},
}
}
func PathParameterMissing(param *v3.Parameter, pathTemplate string, actualPath string) *ValidationError {
actualSegments := strings.Split(strings.Trim(actualPath, "/"), "/")
encodedPath := strings.ReplaceAll(pathTemplate, "~", "~0")
encodedPath = strings.ReplaceAll(encodedPath, "/", "~1")
encodedPath = strings.TrimPrefix(encodedPath, "~1")
keywordLoc := fmt.Sprintf("/paths/%s/parameters/%s/required", encodedPath, param.Name)
specLine, specCol := paramRequiredLineCol(param)
return &ValidationError{
ValidationType: helpers.ParameterValidation,
ValidationSubType: helpers.ParameterValidationPath,
Message: fmt.Sprintf("Path parameter '%s' is missing", param.Name),
Reason: fmt.Sprintf("The path parameter '%s' is defined as being required, "+
"however it's missing from the requests", param.Name),
SpecLine: specLine,
SpecCol: specCol,
ParameterName: param.Name,
HowToFix: HowToFixMissingValue,
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: fmt.Sprintf("Required path parameter '%s' is missing from path '%s'", param.Name, actualPath),
FieldName: param.Name,
FieldPath: "",
InstancePath: actualSegments,
KeywordLocation: keywordLoc,
}},
}
}
libopenapi-validator-0.13.8/errors/parameter_errors_test.go 0000664 0000000 0000000 00000167672 15205340424 0024210 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
// Helper to create a mock v3.Parameter object with a schema
func createMockParameterWithSchema() *v3.Parameter {
schemaProxy := &lowbase.SchemaProxy{}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
schemaProxy.Schema().Enum = low.NodeReference[[]low.ValueReference[*yaml.Node]]{
KeyNode: &yaml.Node{Line: 10, Column: 20},
ValueNode: &yaml.Node{},
Value: []low.ValueReference[*yaml.Node]{
{Value: &yaml.Node{Value: "enum1"}},
{Value: &yaml.Node{Value: "enum2"}},
},
}
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testParam"},
Schema: low.NodeReference[*lowbase.SchemaProxy]{Value: schemaProxy},
Style: low.NodeReference[string]{Value: "form", KeyNode: &yaml.Node{Line: 15, Column: 25}, ValueNode: &yaml.Node{}},
Explode: low.NodeReference[bool]{Value: false, ValueNode: &yaml.Node{Line: 18, Column: 30}, KeyNode: &yaml.Node{}},
Required: low.NodeReference[bool]{
KeyNode: &yaml.Node{Line: 22, Column: 32},
ValueNode: &yaml.Node{},
},
KeyNode: &yaml.Node{},
}
return v3.NewParameter(param)
}
func TestIncorrectFormEncoding(t *testing.T) {
param := createMockParameterWithSchema()
qp := &helpers.QueryParam{
Key: "testParam",
Values: []string{"incorrect,value"},
}
// Call the function
err := IncorrectFormEncoding(param, qp, 0)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' is not exploded correctly")
require.Contains(t, err.Reason, "'testParam' has a default or 'form' encoding defined")
require.Equal(t, 18, err.SpecLine)
require.Equal(t, 30, err.SpecCol)
require.Contains(t, err.HowToFix, "&testParam=incorrect&testParam=value'")
}
func TestIncorrectSpaceDelimiting(t *testing.T) {
param := createMockParameterWithSchema()
qp := &helpers.QueryParam{
Key: "testParam",
Values: []string{"value1", "value2"},
}
// create a low level query parameter
// Call the function
err := IncorrectSpaceDelimiting(param, qp)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' delimited incorrectly")
require.Contains(t, err.Reason, "'spaceDelimited' style defined")
require.Contains(t, err.HowToFix, "testParam=value1%20value2")
}
func TestIncorrectPipeDelimiting(t *testing.T) {
param := createMockParameterWithSchema()
qp := &helpers.QueryParam{
Key: "testParam",
Values: []string{"value1", "value2"},
}
// Call the function
err := IncorrectPipeDelimiting(param, qp)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' delimited incorrectly")
require.Contains(t, err.Reason, "'pipeDelimited' style defined")
require.Contains(t, err.HowToFix, "testParam=value1|value2")
}
func TestQueryParameterMissing(t *testing.T) {
param := createMockParameterWithSchema()
// Call the function
err := QueryParameterMissing(param, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' is missing")
require.Contains(t, err.Reason, "'testParam' is defined as being required")
require.Equal(t, HowToFixMissingValue, err.HowToFix)
}
func TestHeaderParameterMissing(t *testing.T) {
param := createMockParameterWithSchema()
// Call the function
err := HeaderParameterMissing(param, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'testParam' is missing")
require.Contains(t, err.Reason, "'testParam' is defined as being required")
require.Equal(t, HowToFixMissingValue, err.HowToFix)
}
func TestCookieParameterMissing(t *testing.T) {
param := createMockParameterWithSchema()
// Call the function
err := CookieParameterMissing(param, "/test", "get", "")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Cookie parameter 'testParam' is missing")
require.Contains(t, err.Reason, "'testParam' is defined as being required")
require.Equal(t, HowToFixMissingValue, err.HowToFix)
}
func TestHeaderParameterCannotBeDecoded(t *testing.T) {
param := createMockParameterWithSchema()
val := "malformed_header_value"
// Call the function
err := HeaderParameterCannotBeDecoded(param, val, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'testParam' cannot be decoded")
require.Contains(t, err.Reason, "'malformed_header_value' is malformed")
require.Equal(t, HowToFixInvalidEncoding, err.HowToFix)
}
func TestIncorrectHeaderParamEnum(t *testing.T) {
param := createMockParameterWithSchema()
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil))
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
schemaProxy.Schema().Enum = low.NodeReference[[]low.ValueReference[*yaml.Node]]{
KeyNode: &yaml.Node{Line: 10, Column: 20},
ValueNode: &yaml.Node{},
Value: []low.ValueReference[*yaml.Node]{
{Value: &yaml.Node{Value: "enum1"}},
{Value: &yaml.Node{Value: "enum2"}},
},
}
s := schemaProxy.Schema()
// build a high level schema from the low level one
schema := base.NewSchema(s)
// Call the function with an invalid enum value
err := IncorrectHeaderParamEnum(param, "invalidEnum", schema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'testParam' does not match allowed values")
require.Contains(t, err.Reason, "'invalidEnum' is not one of those values")
require.Equal(t, 10, err.SpecLine)
require.Equal(t, 20, err.SpecCol)
require.Contains(t, err.HowToFix, "enum1, enum2")
}
func TestIncorrectQueryParamArrayBoolean(t *testing.T) {
param := createMockParameterWithSchema()
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil))
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
schemaProxy.Schema().Items = low.NodeReference[*lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]]{
KeyNode: &yaml.Node{Line: 30, Column: 40},
ValueNode: &yaml.Node{},
Value: &lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]{A: schemaProxy},
}
s := schemaProxy.Schema()
// build a high level schema from the low level one
schema := base.NewSchema(s)
// Call the function with an invalid boolean value in the array
err := IncorrectQueryParamArrayBoolean(param, "notBoolean", schema, schema.Items.A.Schema(), "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testParam' is not a valid boolean")
require.Contains(t, err.Reason, "the value 'notBoolean' is not a valid true/false value")
require.Contains(t, err.HowToFix, "true/false")
}
// Helper function to create a mock v3.Parameter with deepObject style
func createMockParameterWithDeepObjectStyle() *v3.Parameter {
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testParam"},
Style: low.NodeReference[string]{Value: "deepObject", KeyNode: &yaml.Node{Line: 12, Column: 22}, ValueNode: &yaml.Node{}}, // Correct ValueNode set
Explode: low.NodeReference[bool]{Value: false},
}
return v3.NewParameter(param)
}
func TestInvalidDeepObject(t *testing.T) {
param := createMockParameterWithDeepObjectStyle()
// Create a mock query parameter with multiple values
qp := &helpers.QueryParam{
Key: "testParam",
Values: []string{"value1", "value2"},
}
// Call the function
err := InvalidDeepObject(param, qp)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' is not a valid deepObject")
require.Contains(t, err.Reason, "'testParam' has the 'deepObject' style defined")
require.Contains(t, err.HowToFix, "testParam=value1|value2")
}
func TestInvalidDeepObjectPathConflict(t *testing.T) {
param := createMockParameterWithDeepObjectStyle()
prefixParam := &helpers.QueryParam{
Key: "testParam",
Values: []string{"bad"},
Property: "nested",
PropertyPath: []string{"nested"},
}
nestedParam := &helpers.QueryParam{
Key: "testParam",
Values: []string{"ok"},
Property: "nested",
PropertyPath: []string{"nested", "child"},
}
err := InvalidDeepObjectPathConflict(param, prefixParam, nestedParam)
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testParam' is not a valid deepObject")
require.Contains(t, err.Reason, "property path 'nested'")
require.Contains(t, err.Reason, "'nested.child'")
require.Contains(t, err.HowToFix, "testParam[nested]")
require.Contains(t, err.HowToFix, "testParam[nested][child]")
}
func TestInvalidDeepObjectPathConflict_NilPaths(t *testing.T) {
param := createMockParameterWithDeepObjectStyle()
err := InvalidDeepObjectPathConflict(param, nil, nil)
require.NotNil(t, err)
require.Contains(t, err.Reason, "property path ''")
require.Contains(t, err.HowToFix, "testParam[]")
}
func TestInvalidDeepObjectPathConflict_PropertyFallback(t *testing.T) {
param := createMockParameterWithDeepObjectStyle()
prefixParam := &helpers.QueryParam{
Key: "testParam",
Values: []string{"bad"},
Property: "nested",
}
nestedParam := &helpers.QueryParam{
Key: "testParam",
Values: []string{"ok"},
Property: "nested.child",
}
err := InvalidDeepObjectPathConflict(param, prefixParam, nestedParam)
require.NotNil(t, err)
require.Contains(t, err.Reason, "property path 'nested'")
require.Contains(t, err.Reason, "'nested.child'")
require.Contains(t, err.HowToFix, "testParam[nested]")
require.Contains(t, err.HowToFix, "testParam[nested.child]")
}
func createMockParameterForBooleanArray() *v3.Parameter {
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testCookieParam"},
}
return v3.NewParameter(param)
}
// Helper function to create a mock base.Schema with boolean items schema
func createMockLowBaseSchemaForBooleanArray() *lowbase.Schema {
itemsSchema := &lowbase.Schema{
Type: low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "boolean",
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
schemaProxy := &lowbase.SchemaProxy{}
itemsSchema.Items = low.NodeReference[*lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]]{
Value: &lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]{
A: schemaProxy,
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
return itemsSchema
}
func TestIncorrectCookieParamArrayBoolean(t *testing.T) {
// Create mock parameter and schemas
param := createMockParameterForBooleanArray()
baseSchema := createMockLowBaseSchemaForBooleanArray()
s := base.NewSchema(baseSchema)
itemsSchema := base.NewSchema(baseSchema.Items.Value.A.Schema())
// Call the function with an invalid boolean value in the array
err := IncorrectCookieParamArrayBoolean(param, "notBoolean", s, itemsSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "testCookieParam", err.ParameterName)
require.Contains(t, err.Message, "Cookie array parameter 'testCookieParam' is not a valid boolean")
require.Contains(t, err.Reason, "the value 'notBoolean' is not a valid true/false value")
require.Contains(t, err.HowToFix, "true/false")
}
// Helper function to create a mock v3.Parameter for number array validation
func createMockParameterForNumberArray() *v3.Parameter {
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testQueryParam"},
}
return v3.NewParameter(param)
}
// Helper function to create a mock base.Schema with number items schema
func createMockLowBaseSchemaForNumberArray() *lowbase.Schema {
itemsSchema := &lowbase.Schema{
Type: low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "number",
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
schemaProxy := &lowbase.SchemaProxy{}
itemsSchema.Items = low.NodeReference[*lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]]{
Value: &lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]{
A: schemaProxy,
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
return itemsSchema
}
func TestIncorrectQueryParamArrayInteger(t *testing.T) {
// Create mock parameter and schemas
param := createMockParameterForNumberArray()
baseSchema := createMockLowBaseSchemaForNumberArray()
s := base.NewSchema(baseSchema)
itemsSchema := base.NewSchema(baseSchema.Items.Value.A.Schema())
// Call the function with an invalid number value in the array
err := IncorrectQueryParamArrayInteger(param, "notNumber", s, itemsSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' is not a valid integer")
require.Contains(t, err.Reason, "the value 'notNumber' is not a valid integer")
require.Contains(t, err.HowToFix, "notNumber")
}
func TestIncorrectQueryParamArrayNumber(t *testing.T) {
// Create mock parameter and schemas
param := createMockParameterForNumberArray()
baseSchema := createMockLowBaseSchemaForNumberArray()
s := base.NewSchema(baseSchema)
itemsSchema := base.NewSchema(baseSchema.Items.Value.A.Schema())
// Call the function with an invalid number value in the array
err := IncorrectQueryParamArrayNumber(param, "notNumber", s, itemsSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' is not a valid number")
require.Contains(t, err.Reason, "the value 'notNumber' is not a valid number")
require.Contains(t, err.HowToFix, "notNumber")
}
// Helper function to create a mock v3.Parameter for cookie number array validation
func createMockParameterForCookieNumberArray() *v3.Parameter {
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testCookieParam"},
}
return v3.NewParameter(param)
}
// Helper function to create a mock base.Schema with number items schema
func createMockLowBaseSchemaForCookieNumberArray() *lowbase.Schema {
itemsSchema := &lowbase.Schema{
Type: low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "number",
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
schemaProxy := &lowbase.SchemaProxy{}
itemsSchema.Items = low.NodeReference[*lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]]{
Value: &lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]{
A: schemaProxy,
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
return itemsSchema
}
func TestIncorrectCookieParamArrayNumber(t *testing.T) {
// Create mock parameter and schemas
param := createMockParameterForCookieNumberArray()
baseSchema := createMockLowBaseSchemaForCookieNumberArray()
s := base.NewSchema(baseSchema)
itemsSchema := base.NewSchema(baseSchema.Items.Value.A.Schema())
// Call the function with an invalid number value in the cookie array
err := IncorrectCookieParamArrayNumber(param, "notNumber", s, itemsSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "testCookieParam", err.ParameterName)
require.Contains(t, err.Message, "Cookie array parameter 'testCookieParam' is not a valid number")
require.Contains(t, err.Reason, "the value 'notNumber' is not a valid number")
require.Contains(t, err.HowToFix, "notNumber")
}
// Helper function to create a mock v3.Parameter
func createMockParameter() *v3.Parameter {
schemaProxy := &lowbase.SchemaProxy{}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
m := orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]()
m.Set(low.KeyReference[string]{Value: "application/json"}, low.ValueReference[*lowv3.MediaType]{ValueNode: &yaml.Node{}, Value: &lowv3.MediaType{}})
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "testQueryParam"},
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: m,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
Schema: low.NodeReference[*lowbase.SchemaProxy]{
Value: schemaProxy,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
Required: low.NodeReference[bool]{
KeyNode: &yaml.Node{},
},
}
return v3.NewParameter(param)
}
// Helper function to create a mock base.Schema
func createMockLowBaseSchema() *lowbase.Schema {
itemsSchema := &lowbase.Schema{
Type: low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "boolean",
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
schemaProxy := &lowbase.SchemaProxy{}
itemsSchema.Items = low.NodeReference[*lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]]{
Value: &lowbase.SchemaDynamicValue[*lowbase.SchemaProxy, bool]{
A: schemaProxy,
},
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
}
_ = schemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil)
schemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
Value: lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]{
A: "string",
},
}
return itemsSchema
}
func TestIncorrectParamEncodingJSON(t *testing.T) {
param := createMockParameter()
baseSchema := createMockLowBaseSchema()
// Call the function with an invalid JSON value
err := IncorrectParamEncodingJSON(param, "invalidJSON", base.NewSchema(baseSchema), "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testQueryParam' is not valid JSON")
require.Contains(t, err.Reason, "the value 'invalidJSON' is not valid JSON")
require.Equal(t, HowToFixInvalidJSON, err.HowToFix)
}
func TestIncorrectQueryParamBool(t *testing.T) {
param := createMockParameter()
baseSchema := createMockLowBaseSchema()
lschemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, lschemaProxy.Build(context.Background(), &yaml.Node{}, &yaml.Node{}, nil))
lschemaProxy.Schema().Type = low.NodeReference[lowbase.SchemaDynamicValue[string, []low.ValueReference[string]]]{
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
}
param.GoLow().Schema.KeyNode = &yaml.Node{}
param.Schema = base.NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{
Value: lschemaProxy,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
})
// Call the function with an invalid boolean value
err := IncorrectQueryParamBool(param, "notBoolean", base.NewSchema(baseSchema), "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testQueryParam' is not a valid boolean")
require.Contains(t, err.Reason, "the value 'notBoolean' is not a valid boolean")
require.Contains(t, err.HowToFix, "true/false")
}
func TestInvalidQueryParamNumber(t *testing.T) {
param := createMockParameter()
baseSchema := createMockLowBaseSchema()
// Call the function with an invalid number value
err := InvalidQueryParamNumber(param, "notNumber", base.NewSchema(baseSchema), "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testQueryParam' is not a valid number")
require.Contains(t, err.Reason, "the value 'notNumber' is not a valid number")
require.Contains(t, err.HowToFix, "notNumber")
}
func TestInvalidQueryParamInteger(t *testing.T) {
param := createMockParameter()
baseSchema := createMockLowBaseSchema()
// Call the function with an invalid number value
err := InvalidQueryParamInteger(param, "notNumber", base.NewSchema(baseSchema), "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testQueryParam' is not a valid integer")
require.Contains(t, err.Reason, "the value 'notNumber' is not a valid integer")
require.Contains(t, err.HowToFix, "notNumber")
}
func TestIncorrectQueryParamEnum(t *testing.T) {
enum := `enum: [fish, crab, lobster]`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.Value.Schema().Enum.KeyNode = &yaml.Node{}
// Call the function with an invalid enum value
err := IncorrectQueryParamEnum(param, "invalidEnum", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'testQueryParam' does not match allowed values")
require.Contains(t, err.Reason, "'invalidEnum' is not one of those values")
require.Contains(t, err.HowToFix, "fish, crab, lobster")
}
func TestIncorrectQueryParamEnumArray(t *testing.T) {
enum := `items:
enum: [fish, crab, lobster]`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.Value = schemaProxy
param.GoLow().Schema.Value.Schema().Items.Value.A.Schema().Enum.Value = []low.ValueReference[*yaml.Node]{
{Value: &yaml.Node{Value: "fish, crab, lobster"}},
}
// Call the function with an invalid enum value
err := IncorrectQueryParamEnumArray(param, "invalidEnum", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' does not match allowed values")
require.Contains(t, err.Reason, "'invalidEnum' is not one of those values")
require.Contains(t, err.HowToFix, "fish, crab, lobster")
}
func TestIncorrectReservedValues(t *testing.T) {
enum := `name: bork`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "borked::?^&*"
err := IncorrectReservedValues(param, "borked::?^&*", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "borked::?^&*", err.ParameterName)
require.Contains(t, err.Message, "Query parameter 'borked::?^&*' value contains reserved values")
require.Contains(t, err.Reason, "The query parameter 'borked::?^&*' has 'allowReserved' set to false")
require.Contains(t, err.HowToFix, "borked%3A%3A%3F%5E%26%2A")
}
func TestInvalidHeaderParamInteger(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "bunny"
err := InvalidHeaderParamInteger(param, "bunmy", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "bunny", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'bunny' is not a valid integer")
require.Contains(t, err.Reason, "The header parameter 'bunny' is defined as being an integer")
require.Contains(t, err.HowToFix, "bunmy")
}
func TestInvalidHeaderParamNumber(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "bunny"
err := InvalidHeaderParamNumber(param, "bunmy", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "bunny", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'bunny' is not a valid number")
require.Contains(t, err.Reason, "The header parameter 'bunny' is defined as being a number")
require.Contains(t, err.HowToFix, "bunmy")
}
func TestInvalidCookieParamNumber(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "cookies"
err := InvalidCookieParamNumber(param, "milky", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "cookies", err.ParameterName)
require.Contains(t, err.Message, "Cookie parameter 'cookies' is not a valid number")
require.Contains(t, err.Reason, "The cookie parameter 'cookies' is defined as being a number")
require.Contains(t, err.HowToFix, "milky")
}
func TestInvalidCookieParamInteger(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "cookies"
err := InvalidCookieParamInteger(param, "milky", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "cookies", err.ParameterName)
require.Contains(t, err.Message, "Cookie parameter 'cookies' is not a valid integer")
require.Contains(t, err.Reason, "The cookie parameter 'cookies' is defined as being an integer")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectHeaderParamBool(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "cookies"
err := IncorrectHeaderParamBool(param, "milky", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "cookies", err.ParameterName)
require.Contains(t, err.Message, "Header parameter 'cookies' is not a valid boolean")
require.Contains(t, err.Reason, "The header parameter 'cookies' is defined as being a boolean")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectCookieParamBool(t *testing.T) {
enum := `name: blip`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Name = "cookies"
err := IncorrectCookieParamBool(param, "milky", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "cookies", err.ParameterName)
require.Contains(t, err.Message, "Cookie parameter 'cookies' is not a valid boolean")
require.Contains(t, err.Reason, "The cookie parameter 'cookies' is defined as being a boolean")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectCookieParamEnum(t *testing.T) {
enum := `enum: [fish, crab, lobster]
items:
enum: [fish, crab, lobster]`
var n yaml.Node
_ = yaml.Unmarshal([]byte(enum), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.Value = schemaProxy
param.GoLow().Schema.Value.Schema().Enum.Value = []low.ValueReference[*yaml.Node]{
{Value: &yaml.Node{Value: "fish, crab, lobster"}},
}
param.GoLow().Schema.Value.Schema().Enum.KeyNode = &yaml.Node{}
err := IncorrectCookieParamEnum(param, "milky", highSchema, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationCookie, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Cookie parameter 'testQueryParam' does not match allowed values")
require.Contains(t, err.Reason, "The cookie parameter 'testQueryParam' has pre-defined values set via an enum")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectHeaderParamArrayBoolean(t *testing.T) {
items := `items:
type: boolean`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
highSchema.GoLow().Items.Value.A.Schema()
param := createMockParameter()
param.Name = "bubbles"
err := IncorrectHeaderParamArrayBoolean(param, "milky", highSchema, nil, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "bubbles", err.ParameterName)
require.Contains(t, err.Message, "Header array parameter 'bubbles' is not a valid boolean")
require.Contains(t, err.Reason, "The header parameter (which is an array) 'bubbles' is defined as being a boolean")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectHeaderParamArrayNumber(t *testing.T) {
items := `items:
type: number`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
highSchema.GoLow().Items.Value.A.Schema()
param := createMockParameter()
param.Name = "bubbles"
err := IncorrectHeaderParamArrayNumber(param, "milky", highSchema, nil, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationHeader, err.ValidationSubType)
require.Equal(t, "bubbles", err.ParameterName)
require.Contains(t, err.Message, "Header array parameter 'bubbles' is not a valid number")
require.Contains(t, err.Reason, "The header parameter (which is an array) 'bubbles' is defined as being a number")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamBool(t *testing.T) {
items := `items:
type: number`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectPathParamBool(param, "milky", highSchema, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Path parameter 'testQueryParam' is not a valid boolean")
require.Contains(t, err.Reason, "The path parameter 'testQueryParam' is defined as being a boolean")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamEnum(t *testing.T) {
items := `enum: [fish, crab, lobster]
items:
enum: [fish, crab, lobster]`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.Value = schemaProxy
param.GoLow().Schema.Value.Schema().Enum.Value = []low.ValueReference[*yaml.Node]{
{Value: &yaml.Node{Value: "fish, crab, lobster"}},
}
param.GoLow().Schema.Value.Schema().Enum.KeyNode = &yaml.Node{}
err := IncorrectPathParamEnum(param, "milky", highSchema, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Path parameter 'testQueryParam' does not match allowed values")
require.Contains(t, err.Reason, "The path parameter 'testQueryParam' has pre-defined values set via an enum")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamNumber(t *testing.T) {
items := `items:
type: number`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectPathParamNumber(param, "milky", highSchema, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Path parameter 'testQueryParam' is not a valid number")
require.Contains(t, err.Reason, "The path parameter 'testQueryParam' is defined as being a number")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamInteger(t *testing.T) {
items := `items:
type: integer`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectPathParamInteger(param, "milky", highSchema, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Path parameter 'testQueryParam' is not a valid integer")
require.Contains(t, err.Reason, "The path parameter 'testQueryParam' is defined as being an integer")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamArrayNumber(t *testing.T) {
items := `items:
type: number`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
highSchema.GoLow().Items.Value.A.Schema()
param := createMockParameter()
param.Name = "bubbles"
err := IncorrectPathParamArrayNumber(param, "milky", highSchema, nil, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "bubbles", err.ParameterName)
require.Contains(t, err.Message, "Path array parameter 'bubbles' is not a valid number")
require.Contains(t, err.Reason, "The path parameter (which is an array) 'bubbles' is defined as being a number")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamArrayInteger(t *testing.T) {
items := `items:
type: integer`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
highSchema.GoLow().Items.Value.A.Schema()
param := createMockParameter()
param.Name = "bubbles"
err := IncorrectPathParamArrayInteger(param, "milky", highSchema, nil, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "bubbles", err.ParameterName)
require.Contains(t, err.Message, "Path array parameter 'bubbles' is not a valid integer")
require.Contains(t, err.Reason, "The path parameter (which is an array) 'bubbles' is defined as being an integer")
require.Contains(t, err.HowToFix, "milky")
}
func TestIncorrectPathParamArrayBoolean(t *testing.T) {
items := `items:
type: number`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
highSchema.GoLow().Items.Value.A.Schema()
param := createMockParameter()
param.Name = "bubbles"
err := IncorrectPathParamArrayBoolean(param, "milky", highSchema, nil, "/test-path", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "bubbles", err.ParameterName)
require.Contains(t, err.Message, "Path array parameter 'bubbles' is not a valid boolean")
require.Contains(t, err.Reason, "The path parameter (which is an array) 'bubbles' is defined as being a boolean")
require.Contains(t, err.HowToFix, "milky")
}
func TestPathParameterMissing(t *testing.T) {
items := `required:
- testQueryParam`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := PathParameterMissing(param, "/test/{testQueryParam}", "/test/")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationPath, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Path parameter 'testQueryParam' is missing")
require.Contains(t, err.Reason, "The path parameter 'testQueryParam' is defined as being required")
require.Contains(t, err.HowToFix, "Ensure the value has been set")
}
func TestPathParameterMaxItems(t *testing.T) {
items := `maxItems: 5
items:
type: string`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectParamArrayMaxNumItems(param, param.Schema.Schema(), 10, 25, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' has too many items")
require.Contains(t, err.Reason, "The query parameter (which is an array) 'testQueryParam' has a maximum item length of 10, however the request provided 25 items")
require.Contains(t, err.HowToFix, "Reduce the number of items in the array to 10 or less")
}
func TestPathParameterMinItems(t *testing.T) {
items := `minItems: 5
items:
type: string`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectParamArrayMinNumItems(param, param.Schema.Schema(), 10, 5, "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' does not have enough items")
require.Contains(t, err.Reason, "The query parameter (which is an array) 'testQueryParam' has a minimum items length of 10, however the request provided 5 items")
require.Contains(t, err.HowToFix, "Increase the number of items in the array to 10 or more")
}
func TestPathParameterUniqueItems(t *testing.T) {
items := `uniqueItems: true
items:
type: string`
var n yaml.Node
_ = yaml.Unmarshal([]byte(items), &n)
schemaProxy := &lowbase.SchemaProxy{}
require.NoError(t, schemaProxy.Build(context.Background(), n.Content[0], n.Content[0], nil))
highSchema := base.NewSchema(schemaProxy.Schema())
param := createMockParameter()
param.Schema = base.CreateSchemaProxy(highSchema)
param.GoLow().Schema.KeyNode = &yaml.Node{}
err := IncorrectParamArrayUniqueItems(param, param.Schema.Schema(), "fish, cake", "/test-path", "get", "{}")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ParameterValidation, err.ValidationType)
require.Equal(t, helpers.ParameterValidationQuery, err.ValidationSubType)
require.Equal(t, "testQueryParam", err.ParameterName)
require.Contains(t, err.Message, "Query array parameter 'testQueryParam' contains non-unique items")
require.Contains(t, err.Reason, "The query parameter (which is an array) 'testQueryParam' contains the following duplicates: 'fish, cake'")
require.Contains(t, err.HowToFix, "Ensure the array values are all unique")
}
// createMinimalParameter creates a parameter with nil GoLow nodes to test nil safety.
func createMinimalParameter() *v3.Parameter {
param := &lowv3.Parameter{
Name: low.NodeReference[string]{Value: "minParam"},
// All node references intentionally left with nil KeyNode/ValueNode
}
return v3.NewParameter(param)
}
func TestParameterErrors_NilGoLowNodes(t *testing.T) {
// Tests that all parameter error constructors handle nil GoLow nodes
// without panicking. This covers the crash scenario from wiretap #134.
param := createMinimalParameter()
qp := &helpers.QueryParam{
Key: "minParam",
Values: []string{"value"},
}
sch := &base.Schema{}
t.Run("IncorrectFormEncoding", func(t *testing.T) {
err := IncorrectFormEncoding(param, qp, 0)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectSpaceDelimiting", func(t *testing.T) {
err := IncorrectSpaceDelimiting(param, qp)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPipeDelimiting", func(t *testing.T) {
err := IncorrectPipeDelimiting(param, qp)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidDeepObject", func(t *testing.T) {
err := InvalidDeepObject(param, qp)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidDeepObjectPathConflict", func(t *testing.T) {
err := InvalidDeepObjectPathConflict(param, qp, &helpers.QueryParam{
Key: "test",
Values: []string{"ok"},
Property: "foo",
PropertyPath: []string{"foo", "bar"},
})
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("QueryParameterMissing", func(t *testing.T) {
err := QueryParameterMissing(param, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("HeaderParameterMissing", func(t *testing.T) {
err := HeaderParameterMissing(param, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("CookieParameterMissing", func(t *testing.T) {
err := CookieParameterMissing(param, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("HeaderParameterCannotBeDecoded", func(t *testing.T) {
err := HeaderParameterCannotBeDecoded(param, "bad", "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectHeaderParamEnum", func(t *testing.T) {
err := IncorrectHeaderParamEnum(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectQueryParamEnum", func(t *testing.T) {
err := IncorrectQueryParamEnum(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectCookieParamEnum", func(t *testing.T) {
err := IncorrectCookieParamEnum(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamEnum", func(t *testing.T) {
err := IncorrectPathParamEnum(param, "bad", sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectQueryParamBool", func(t *testing.T) {
err := IncorrectQueryParamBool(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidQueryParamInteger", func(t *testing.T) {
err := InvalidQueryParamInteger(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidQueryParamNumber", func(t *testing.T) {
err := InvalidQueryParamNumber(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectReservedValues", func(t *testing.T) {
err := IncorrectReservedValues(param, "a:b", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidHeaderParamInteger", func(t *testing.T) {
err := InvalidHeaderParamInteger(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidHeaderParamNumber", func(t *testing.T) {
err := InvalidHeaderParamNumber(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidCookieParamInteger", func(t *testing.T) {
err := InvalidCookieParamInteger(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("InvalidCookieParamNumber", func(t *testing.T) {
err := InvalidCookieParamNumber(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectHeaderParamBool", func(t *testing.T) {
err := IncorrectHeaderParamBool(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectCookieParamBool", func(t *testing.T) {
err := IncorrectCookieParamBool(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamBool", func(t *testing.T) {
err := IncorrectPathParamBool(param, "bad", sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamInteger", func(t *testing.T) {
err := IncorrectPathParamInteger(param, "bad", sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamNumber", func(t *testing.T) {
err := IncorrectPathParamNumber(param, "bad", sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectParamEncodingJSON", func(t *testing.T) {
err := IncorrectParamEncodingJSON(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectQueryParamEnumArray", func(t *testing.T) {
err := IncorrectQueryParamEnumArray(param, "bad", sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("PathParameterMissing", func(t *testing.T) {
err := PathParameterMissing(param, "/test/{id}", "/test/123")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
}
func TestParameterErrors_NilSchemaItems(t *testing.T) {
// Tests array parameter error constructors with nil Items in schema.
param := createMinimalParameter()
sch := &base.Schema{} // no Items set
t.Run("IncorrectQueryParamArrayBoolean", func(t *testing.T) {
err := IncorrectQueryParamArrayBoolean(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectParamArrayMaxNumItems", func(t *testing.T) {
err := IncorrectParamArrayMaxNumItems(param, sch, 5, 10, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectParamArrayMinNumItems", func(t *testing.T) {
err := IncorrectParamArrayMinNumItems(param, sch, 5, 2, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectParamArrayUniqueItems", func(t *testing.T) {
err := IncorrectParamArrayUniqueItems(param, sch, "dup", "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectCookieParamArrayBoolean", func(t *testing.T) {
err := IncorrectCookieParamArrayBoolean(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectQueryParamArrayInteger", func(t *testing.T) {
err := IncorrectQueryParamArrayInteger(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectQueryParamArrayNumber", func(t *testing.T) {
err := IncorrectQueryParamArrayNumber(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectCookieParamArrayNumber", func(t *testing.T) {
err := IncorrectCookieParamArrayNumber(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectHeaderParamArrayBoolean", func(t *testing.T) {
err := IncorrectHeaderParamArrayBoolean(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectHeaderParamArrayNumber", func(t *testing.T) {
err := IncorrectHeaderParamArrayNumber(param, "bad", sch, sch, "/test", "get", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamArrayNumber", func(t *testing.T) {
err := IncorrectPathParamArrayNumber(param, "bad", sch, sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamArrayInteger", func(t *testing.T) {
err := IncorrectPathParamArrayInteger(param, "bad", sch, sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
t.Run("IncorrectPathParamArrayBoolean", func(t *testing.T) {
err := IncorrectPathParamArrayBoolean(param, "bad", sch, sch, "/test", "{}")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
})
}
libopenapi-validator-0.13.8/errors/parameters_howtofix.go 0000664 0000000 0000000 00000010312 15205340424 0023641 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
const (
HowToFixReservedValues string = "parameter values need to URL Encoded to ensure reserved " +
"values are correctly encoded, for example: '%s'"
HowToFixParamInvalidInteger string = "Convert the value '%s' into an integer"
HowToFixParamInvalidNumber string = "Convert the value '%s' into a number"
HowToFixParamInvalidString string = "Convert the value '%s' into a string (cannot start with a number, or be a floating point)"
HowToFixParamInvalidBoolean string = "Convert the value '%s' into a true/false value"
HowToFixParamInvalidEnum string = "Instead of '%s', use one of the allowed values: '%s'"
HowToFixParamInvalidFormEncode string = "Use a form style encoding for parameter values, for example: '%s'"
HowToFixInvalidXml string = "Ensure xml is well-formed and matches schema structure"
HowToFixXmlPrefix string = "Make sure to prepend the correct prefix '%s' to the declared fields"
HowToFixXmlNamespace string = "Make sure to declare the 'xmlns:%s' with the correct namespace URI"
HowToFixFormDataReservedCharacters string = "Make sure to correcly encode specials characters to percent encoding, or set allowReserved to true"
HowToFixInvalidSchema string = "Ensure that the object being submitted, matches the schema correctly"
HowToFixInvalidTypeEncoding string = "Ensure that the object being submitted matches the property encoding Content-Type"
HowToFixParamInvalidSpaceDelimitedObjectExplode string = "When using 'explode' with space delimited parameters, " +
"they should be separated by spaces. For example: '%s'"
HowToFixParamInvalidPipeDelimitedObjectExplode string = "When using 'explode' with pipe delimited parameters, " +
"they should be separated by pipes '|'. For example: '%s'"
HowToFixParamInvalidDeepObjectMultipleValues string = "There can only be a single value per property name, " +
"deepObject parameters should contain the property key in square brackets next to the parameter name. For example: '%s'"
HowToFixParamInvalidDeepObjectPathConflict string = "Use either '%s[%s]' or nested properties like '%s[%s]', not both"
HowToFixInvalidJSON string = "The JSON submitted is invalid, please check the syntax"
HowToFixInvalidUrlEncoded string = "Ensure URL Encoded submitted is well-formed and matches schema structure"
HowToFixDecodingError string = "The object can't be decoded, so make sure it's being encoded correctly according to the spec."
HowToFixInvalidContentType string = "The content type is invalid, Use one of the %d supported types for this operation: %s"
HowToFixInvalidResponseCode string = "The service is responding with a code that is not defined in the spec, fix the service or add the code to the specification"
HowToFixInvalidEncoding string = "Ensure the correct encoding has been used on the object"
HowToFixMissingValue string = "Ensure the value has been set"
HowToFixPath string = "Check the path is correct, and check that the correct HTTP method has been used (e.g. GET, POST, PUT, DELETE)"
HowToFixPathMethod string = "Add the missing operation to the contract for the path"
HowToFixInvalidMaxItems string = "Reduce the number of items in the array to %d or less"
HowToFixInvalidMinItems string = "Increase the number of items in the array to %d or more"
HowToFixMissingHeader string = "Make sure the service responding sets the required headers with this response code"
HowToFixInvalidRenderedSchema string = "Check the request schema for circular references or invalid structures"
HowToFixInvalidJsonSchema string = "Check the request schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs"
)
libopenapi-validator-0.13.8/errors/request_errors.go 0000664 0000000 0000000 00000004631 15205340424 0022642 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"fmt"
"net/http"
"strings"
"github.com/pb33f/libopenapi/orderedmap"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
func RequestContentTypeNotFound(op *v3.Operation, request *http.Request, specPath string) *ValidationError {
ct := request.Header.Get(helpers.ContentTypeHeader)
var ctypes []string
var contentMap *orderedmap.Map[string, *v3.MediaType]
specLine, specCol := 1, 0
if op.RequestBody != nil {
contentMap = op.RequestBody.Content
for pair := orderedmap.First(op.RequestBody.Content); pair != nil; pair = pair.Next() {
ctypes = append(ctypes, pair.Key())
}
if low := op.RequestBody.GoLow(); low != nil && low.Content.KeyNode != nil {
specLine = low.Content.KeyNode.Line
specCol = low.Content.KeyNode.Column
}
}
return &ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.RequestBodyContentType,
Message: fmt.Sprintf("%s operation request content type '%s' does not exist",
request.Method, ct),
Reason: fmt.Sprintf("The content type '%s' of the %s request submitted has not "+
"been defined, it's an unknown type", ct, request.Method),
SpecLine: specLine,
SpecCol: specCol,
Context: op,
HowToFix: fmt.Sprintf(HowToFixInvalidContentType, orderedmap.Len(contentMap), strings.Join(ctypes, ", ")),
RequestPath: request.URL.Path,
RequestMethod: request.Method,
SpecPath: specPath,
}
}
func OperationNotFound(pathItem *v3.PathItem, request *http.Request, method string, specPath string) *ValidationError {
specLine, specCol := 1, 0
if low := pathItem.GoLow(); low != nil && low.KeyNode != nil {
specLine = low.KeyNode.Line
specCol = low.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.RequestValidation,
ValidationSubType: helpers.ValidationMissingOperation,
Message: fmt.Sprintf("%s operation request content type '%s' does not exist",
request.Method, method),
Reason: fmt.Sprintf("The path was found, but there was no '%s' method found in the spec", request.Method),
SpecLine: specLine,
SpecCol: specCol,
Context: pathItem,
HowToFix: HowToFixPathMethod,
RequestPath: request.URL.Path,
RequestMethod: request.Method,
SpecPath: specPath,
}
}
libopenapi-validator-0.13.8/errors/request_errors_test.go 0000664 0000000 0000000 00000010064 15205340424 0023676 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"net/http"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
// Helper to create a mock v3.Operation object with a RequestBody
func createMockOperationWithRequestBody() *v3.Operation {
content := orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]()
content.Set(low.KeyReference[string]{
Value: "application/json",
}, low.ValueReference[*lowv3.MediaType]{
Value: &lowv3.MediaType{},
})
reqBody := &lowv3.RequestBody{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: content,
KeyNode: &yaml.Node{Line: 10, Column: 20},
ValueNode: &yaml.Node{},
},
}
// Create a lowv3.Operation object
op := &lowv3.Operation{
RequestBody: low.NodeReference[*lowv3.RequestBody]{
Value: reqBody,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
// Create a new v3.Operation object from the low
return v3.NewOperation(op)
}
// Helper to create a mock v3.PathItem object
func createMockPathItem() *v3.PathItem {
pathItem := &lowv3.PathItem{
KeyNode: &yaml.Node{Line: 15, Column: 25},
}
return v3.NewPathItem(pathItem)
}
func TestRequestContentTypeNotFound(t *testing.T) {
// Create a mock operation with request body content types
op := createMockOperationWithRequestBody()
// Create a mock request with an invalid content type
request, _ := http.NewRequest(http.MethodPost, "/test", nil)
request.Header.Set(helpers.ContentTypeHeader, "application/xml")
// Call the function
err := RequestContentTypeNotFound(op, request, "/test")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.RequestBodyValidation, err.ValidationType)
require.Equal(t, helpers.RequestBodyContentType, err.ValidationSubType)
require.Contains(t, err.Message, "'application/xml' does not exist")
require.Contains(t, err.Reason, "The content type 'application/xml' of the POST request submitted has not been defined")
require.Equal(t, 10, err.SpecLine)
require.Equal(t, 20, err.SpecCol)
require.Contains(t, err.HowToFix, "application/json")
}
func TestOperationNotFound(t *testing.T) {
// Create a mock path item
pathItem := createMockPathItem()
// Create a mock request
request, _ := http.NewRequest(http.MethodPatch, "/test", nil)
// Call the function
err := OperationNotFound(pathItem, request, http.MethodPatch, "/test")
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.RequestValidation, err.ValidationType)
require.Equal(t, helpers.ValidationMissingOperation, err.ValidationSubType)
require.Contains(t, err.Message, "'PATCH' does not exist")
require.Contains(t, err.Reason, "there was no 'PATCH' method found in the spec")
require.Equal(t, 15, err.SpecLine)
require.Equal(t, 25, err.SpecCol)
require.Equal(t, HowToFixPathMethod, err.HowToFix)
}
func TestRequestContentTypeNotFound_NilContentKeyNode(t *testing.T) {
// RequestBody exists but has no content KeyNode â should not panic
reqBody := &lowv3.RequestBody{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]](),
// KeyNode intentionally nil
},
}
op := &lowv3.Operation{
RequestBody: low.NodeReference[*lowv3.RequestBody]{
Value: reqBody,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
highOp := v3.NewOperation(op)
request, _ := http.NewRequest(http.MethodPost, "/test", nil)
request.Header.Set(helpers.ContentTypeHeader, "application/xml")
// Should not panic
err := RequestContentTypeNotFound(highOp, request, "/test")
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
}
libopenapi-validator-0.13.8/errors/response_errors.go 0000664 0000000 0000000 00000005551 15205340424 0023012 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"fmt"
"net/http"
"strings"
"github.com/pb33f/libopenapi/orderedmap"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
func ResponseContentTypeNotFound(op *v3.Operation,
request *http.Request,
response *http.Response,
code string,
isDefault bool,
) *ValidationError {
ct := response.Header.Get(helpers.ContentTypeHeader)
mediaTypeString, _, _ := helpers.ExtractContentType(ct)
var ctypes []string
specLine, specCol := 1, 0
var contentMap *orderedmap.Map[string, *v3.MediaType]
// check for a default type (applies to all codes without a match)
if !isDefault {
resp := op.Responses.Codes.GetOrZero(code)
if resp != nil {
for pair := orderedmap.First(resp.Content); pair != nil; pair = pair.Next() {
ctypes = append(ctypes, pair.Key())
}
contentMap = resp.Content
if low := resp.GoLow(); low != nil && low.Content.KeyNode != nil {
specLine = low.Content.KeyNode.Line
specCol = low.Content.KeyNode.Column
}
}
} else {
if op.Responses.Default != nil {
for pair := orderedmap.First(op.Responses.Default.Content); pair != nil; pair = pair.Next() {
ctypes = append(ctypes, pair.Key())
}
contentMap = op.Responses.Default.Content
if low := op.Responses.Default.GoLow(); low != nil && low.Content.KeyNode != nil {
specLine = low.Content.KeyNode.Line
specCol = low.Content.KeyNode.Column
}
}
}
return &ValidationError{
ValidationType: helpers.ResponseBodyValidation,
ValidationSubType: helpers.RequestBodyContentType,
Message: fmt.Sprintf("%s / %s operation response content type '%s' does not exist",
request.Method, code, mediaTypeString),
Reason: fmt.Sprintf("The content type '%s' of the %s response received has not "+
"been defined, it's an unknown type", mediaTypeString, request.Method),
SpecLine: specLine,
SpecCol: specCol,
Context: op,
HowToFix: fmt.Sprintf(HowToFixInvalidContentType,
orderedmap.Len(contentMap), strings.Join(ctypes, ", ")),
}
}
func ResponseCodeNotFound(op *v3.Operation, request *http.Request, code int) *ValidationError {
specLine, specCol := 1, 0
if low := op.GoLow(); low != nil && low.Responses.KeyNode != nil {
specLine = low.Responses.KeyNode.Line
specCol = low.Responses.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.ResponseBodyValidation,
ValidationSubType: helpers.ResponseBodyResponseCode,
Message: fmt.Sprintf("%s operation request response code '%d' does not exist",
request.Method, code),
Reason: fmt.Sprintf("The response code '%d' of the %s request submitted has not "+
"been defined, it's an unknown type", code, request.Method),
SpecLine: specLine,
SpecCol: specCol,
Context: op,
HowToFix: HowToFixInvalidResponseCode,
}
}
libopenapi-validator-0.13.8/errors/response_errors_test.go 0000664 0000000 0000000 00000020657 15205340424 0024055 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"net/http"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi-validator/helpers"
)
// Helper to create a mock v3.Operation object
func createMockOperation() *v3.Operation {
content := orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]()
content.Set(low.KeyReference[string]{
Value: "application/json",
}, low.ValueReference[*lowv3.MediaType]{
Value: &lowv3.MediaType{},
})
r := &lowv3.Response{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: content,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
// resp := v3.NewResponse(r)
// create a lowv3.Responses object
responses := &lowv3.Responses{
Default: low.NodeReference[*lowv3.Response]{
Value: r,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
Codes: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.Response]](),
KeyNode: &yaml.Node{},
}
// create a lowv3.Operation object
op := &lowv3.Operation{
Responses: low.NodeReference[*lowv3.Responses]{
Value: responses,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
// create a new v3.Operation object from the low
highOp := v3.NewOperation(op)
return highOp
}
func TestResponseContentTypeNotFound_Default(t *testing.T) {
// Create a mock operation with a default response and content type
op := createMockOperation()
op.Responses.Default.Content.Set("application/json", &v3.MediaType{})
op.Responses.Default.GoLow().Content.KeyNode.Line = 12
op.Responses.Default.GoLow().Content.KeyNode.Column = 34
// Create a mock request and response
request, _ := http.NewRequest(http.MethodGet, "/test", nil)
response := &http.Response{
Header: http.Header{
helpers.ContentTypeHeader: {"application/xml"},
},
}
// Call the function
err := ResponseContentTypeNotFound(op, request, response, "200", true)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ResponseBodyValidation, err.ValidationType)
require.Equal(t, helpers.RequestBodyContentType, err.ValidationSubType)
require.Contains(t, err.Message, "'application/xml' does not exist")
require.Contains(t, err.Reason, "The content type 'application/xml' of the GET response received has not been defined")
require.Equal(t, 12, err.SpecLine)
require.Equal(t, 34, err.SpecCol)
require.Contains(t, err.HowToFix, "application/json")
}
func TestResponseContentTypeNotFound_SpecificCode(t *testing.T) {
// Create a mock operation with a response code and content type
op := createMockOperation()
responseContent := orderedmap.New[string, *v3.MediaType]()
responseContent.Set("application/json", &v3.MediaType{})
content := orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]()
content.Set(low.KeyReference[string]{
Value: "application/json",
}, low.ValueReference[*lowv3.MediaType]{
Value: &lowv3.MediaType{},
})
r := &lowv3.Response{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: content,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
resp := v3.NewResponse(r)
op.Responses.Codes.Set("200", resp)
op.Responses.Codes.GetOrZero("200").GoLow().Content.KeyNode.Line = 15
op.Responses.Codes.GetOrZero("200").GoLow().Content.KeyNode.Column = 42
// Create a mock request and response
request, _ := http.NewRequest(http.MethodPost, "/test", nil)
response := &http.Response{
Header: http.Header{
helpers.ContentTypeHeader: {"application/xml"},
},
}
// Call the function
err := ResponseContentTypeNotFound(op, request, response, "200", false)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ResponseBodyValidation, err.ValidationType)
require.Equal(t, helpers.RequestBodyContentType, err.ValidationSubType)
require.Contains(t, err.Message, "'application/xml' does not exist")
require.Contains(t, err.Reason, "The content type 'application/xml' of the POST response received has not been defined")
require.Equal(t, 15, err.SpecLine)
require.Equal(t, 42, err.SpecCol)
require.Contains(t, err.HowToFix, "application/json")
}
func TestResponseCodeNotFound(t *testing.T) {
// Create a mock operation with responses
op := createMockOperation()
op.GoLow().Responses.KeyNode.Line = 22
op.GoLow().Responses.KeyNode.Column = 56
// Create a mock request
request, _ := http.NewRequest(http.MethodDelete, "/test", nil)
// Call the function with a response code that doesn't exist
err := ResponseCodeNotFound(op, request, 404)
// Validate the error
require.NotNil(t, err)
require.Equal(t, helpers.ResponseBodyValidation, err.ValidationType)
require.Equal(t, helpers.ResponseBodyResponseCode, err.ValidationSubType)
require.Contains(t, err.Message, "response code '404' does not exist")
require.Contains(t, err.Reason, "The response code '404' of the DELETE request submitted has not been defined")
require.Equal(t, 22, err.SpecLine)
require.Equal(t, 56, err.SpecCol)
require.Equal(t, HowToFixInvalidResponseCode, err.HowToFix)
}
func TestResponseContentTypeNotFound_NilContentKeyNode(t *testing.T) {
// Response code exists but has no content KeyNode â should not panic
r := &lowv3.Response{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]](),
// KeyNode intentionally nil
},
}
resp := v3.NewResponse(r)
responses := &lowv3.Responses{
Codes: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.Response]](),
KeyNode: &yaml.Node{},
}
op := &lowv3.Operation{
Responses: low.NodeReference[*lowv3.Responses]{
Value: responses,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
highOp := v3.NewOperation(op)
highOp.Responses.Codes.Set("200", resp)
request, _ := http.NewRequest(http.MethodGet, "/test", nil)
response := &http.Response{
Header: http.Header{
helpers.ContentTypeHeader: {"application/xml"},
},
}
// Should not panic
err := ResponseContentTypeNotFound(highOp, request, response, "200", false)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
}
func TestResponseContentTypeNotFound_NilDefaultContentKeyNode(t *testing.T) {
// Default response exists but has no content KeyNode â should not panic
r := &lowv3.Response{
Content: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]]]{
Value: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.MediaType]](),
// KeyNode intentionally nil
},
}
responses := &lowv3.Responses{
Default: low.NodeReference[*lowv3.Response]{
Value: r,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
Codes: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.Response]](),
KeyNode: &yaml.Node{},
}
op := &lowv3.Operation{
Responses: low.NodeReference[*lowv3.Responses]{
Value: responses,
KeyNode: &yaml.Node{},
ValueNode: &yaml.Node{},
},
}
highOp := v3.NewOperation(op)
request, _ := http.NewRequest(http.MethodGet, "/test", nil)
response := &http.Response{
Header: http.Header{
helpers.ContentTypeHeader: {"application/xml"},
},
}
// Should not panic
err := ResponseContentTypeNotFound(highOp, request, response, "200", true)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
}
func TestResponseCodeNotFound_NilResponsesKeyNode(t *testing.T) {
// Operation with nil Responses KeyNode â should not panic
responses := &lowv3.Responses{
Codes: orderedmap.New[low.KeyReference[string], low.ValueReference[*lowv3.Response]](),
// KeyNode intentionally nil
}
op := &lowv3.Operation{
Responses: low.NodeReference[*lowv3.Responses]{
Value: responses,
ValueNode: &yaml.Node{},
},
}
highOp := v3.NewOperation(op)
request, _ := http.NewRequest(http.MethodGet, "/test", nil)
// Should not panic
err := ResponseCodeNotFound(highOp, request, 404)
require.NotNil(t, err)
require.Equal(t, 1, err.SpecLine)
require.Equal(t, 0, err.SpecCol)
}
libopenapi-validator-0.13.8/errors/spec_line_col.go 0000664 0000000 0000000 00000006345 15205340424 0022360 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"go.yaml.in/yaml/v4"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi/datamodel/high/base"
)
// SafeNodeLineCol safely extracts line and column from a yaml.Node pointer.
// Returns (1, 0) if the node is nil.
func SafeNodeLineCol(node *yaml.Node) (int, int) {
if node == nil {
return 1, 0
}
return node.Line, node.Column
}
// paramExplodeLineCol safely extracts SpecLine/SpecCol from param.GoLow().Explode.ValueNode.
func paramExplodeLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil && low.Explode.ValueNode != nil {
return low.Explode.ValueNode.Line, low.Explode.ValueNode.Column
}
return 1, 0
}
// paramStyleLineCol safely extracts SpecLine/SpecCol from param.GoLow().Style.ValueNode.
func paramStyleLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil && low.Style.ValueNode != nil {
return low.Style.ValueNode.Line, low.Style.ValueNode.Column
}
return 1, 0
}
// paramRequiredLineCol safely extracts SpecLine/SpecCol from param.GoLow().Required.KeyNode.
func paramRequiredLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil && low.Required.KeyNode != nil {
return low.Required.KeyNode.Line, low.Required.KeyNode.Column
}
return 1, 0
}
// paramSchemaKeyLineCol safely extracts SpecLine/SpecCol from param.GoLow().Schema.KeyNode.
func paramSchemaKeyLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil && low.Schema.KeyNode != nil {
return low.Schema.KeyNode.Line, low.Schema.KeyNode.Column
}
return 1, 0
}
// paramSchemaTypeLineCol safely extracts SpecLine/SpecCol from
// param.GoLow().Schema.Value.Schema().Type.KeyNode.
func paramSchemaTypeLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil {
if sv := low.Schema.Value; sv != nil {
if s := sv.Schema(); s != nil && s.Type.KeyNode != nil {
return s.Type.KeyNode.Line, s.Type.KeyNode.Column
}
}
}
return 1, 0
}
// paramSchemaEnumLineCol safely extracts SpecLine/SpecCol from
// param.GoLow().Schema.Value.Schema().Enum.KeyNode.
func paramSchemaEnumLineCol(param *v3.Parameter) (int, int) {
if low := param.GoLow(); low != nil {
if sv := low.Schema.Value; sv != nil {
if s := sv.Schema(); s != nil && s.Enum.KeyNode != nil {
return s.Enum.KeyNode.Line, s.Enum.KeyNode.Column
}
}
}
return 1, 0
}
// paramContentLineCol safely extracts SpecLine/SpecCol from
// param.GoLow().FindContent(ct).ValueNode.
func paramContentLineCol(param *v3.Parameter, contentType string) (int, int) {
if low := param.GoLow(); low != nil {
if found := low.FindContent(contentType); found != nil && found.ValueNode != nil {
return found.ValueNode.Line, found.ValueNode.Column
}
}
return 1, 0
}
// schemaItemsTypeLineCol safely extracts SpecLine/SpecCol from
// sch.Items.A.GoLow().Schema().Type.KeyNode.
func schemaItemsTypeLineCol(sch *base.Schema) (int, int) {
if sch.Items != nil && sch.Items.A != nil {
if low := sch.Items.A.GoLow(); low != nil {
if s := low.Schema(); s != nil && s.Type.KeyNode != nil {
return s.Type.KeyNode.Line, s.Type.KeyNode.Column
}
}
}
return 1, 0
}
libopenapi-validator-0.13.8/errors/strict_errors.go 0000664 0000000 0000000 00000015055 15205340424 0022464 0 ustar 00root root 0000000 0000000 // Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"fmt"
"strings"
)
// StrictValidationType is the validation type for strict mode errors.
const StrictValidationType = "strict"
// StrictValidationSubTypes for different kinds of strict validation errors.
const (
StrictSubTypeProperty = "undeclared-property"
StrictSubTypeHeader = "undeclared-header"
StrictSubTypeQuery = "undeclared-query-param"
StrictSubTypeCookie = "undeclared-cookie"
StrictSubTypeReadOnlyProperty = "readonly-property"
StrictSubTypeWriteOnlyProperty = "writeonly-property"
)
// UndeclaredPropertyError creates a ValidationError for an undeclared property.
func UndeclaredPropertyError(
path string,
name string,
value any,
declaredProperties []string,
direction string,
requestPath string,
requestMethod string,
specLine int,
specCol int,
) *ValidationError {
dirStr := direction
if dirStr == "" {
dirStr = "request"
}
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeProperty,
Message: fmt.Sprintf("%s property '%s' at '%s' is not declared in schema",
dirStr, name, path),
Reason: fmt.Sprintf("Strict mode: found property not in schema. "+
"Declared properties: [%s]", strings.Join(declaredProperties, ", ")),
HowToFix: fmt.Sprintf("Add '%s' to the schema, remove it from the %s, "+
"or add '%s' to StrictIgnorePaths", name, dirStr, path),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
SpecLine: specLine,
SpecCol: specCol,
}
}
// UndeclaredHeaderError creates a ValidationError for an undeclared header.
func UndeclaredHeaderError(
name string,
value string,
declaredHeaders []string,
direction string,
requestPath string,
requestMethod string,
) *ValidationError {
dirStr := direction
if dirStr == "" {
dirStr = "request"
}
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeHeader,
Message: fmt.Sprintf("%s header '%s' is not declared in specification",
dirStr, name),
Reason: fmt.Sprintf("Strict mode: found header not in spec. "+
"Declared headers: [%s]", strings.Join(declaredHeaders, ", ")),
HowToFix: fmt.Sprintf("Add '%s' to the operation's parameters, remove it from the %s, "+
"or add it to StrictIgnoredHeaders", name, dirStr),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: value,
}
}
// UndeclaredQueryParamError creates a ValidationError for an undeclared query parameter.
func UndeclaredQueryParamError(
path string,
name string,
value any,
declaredParams []string,
requestPath string,
requestMethod string,
) *ValidationError {
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeQuery,
Message: fmt.Sprintf("query parameter '%s' at '%s' is not declared in specification", name, path),
Reason: fmt.Sprintf("Strict mode: found query parameter not in spec. "+
"Declared parameters: [%s]", strings.Join(declaredParams, ", ")),
HowToFix: fmt.Sprintf("Add '%s' to the operation's query parameters, remove it from the request, "+
"or add '%s' to StrictIgnorePaths", name, path),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
}
}
// UndeclaredCookieError creates a ValidationError for an undeclared cookie.
func UndeclaredCookieError(
path string,
name string,
value any,
declaredCookies []string,
requestPath string,
requestMethod string,
) *ValidationError {
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeCookie,
Message: fmt.Sprintf("cookie '%s' at '%s' is not declared in specification", name, path),
Reason: fmt.Sprintf("Strict mode: found cookie not in spec. "+
"Declared cookies: [%s]", strings.Join(declaredCookies, ", ")),
HowToFix: fmt.Sprintf("Add '%s' to the operation's cookie parameters, remove it from the request, "+
"or add '%s' to StrictIgnorePaths", name, path),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
}
}
// ReadOnlyPropertyError creates a ValidationError for a readOnly property in a request.
func ReadOnlyPropertyError(
path string,
name string,
value any,
requestPath string,
requestMethod string,
specLine int,
specCol int,
) *ValidationError {
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeReadOnlyProperty,
Message: fmt.Sprintf("request property '%s' at '%s' is readOnly and should not be sent in the request",
name, path),
Reason: fmt.Sprintf("Strict mode: property '%s' is marked readOnly in the schema",
name),
HowToFix: fmt.Sprintf("Remove the readOnly annotation from '%s' in the schema, "+
"remove it from the request, or add '%s' to StrictIgnorePaths", name, path),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
SpecLine: specLine,
SpecCol: specCol,
}
}
// WriteOnlyPropertyError creates a ValidationError for a writeOnly property in a response.
func WriteOnlyPropertyError(
path string,
name string,
value any,
requestPath string,
requestMethod string,
specLine int,
specCol int,
) *ValidationError {
return &ValidationError{
ValidationType: StrictValidationType,
ValidationSubType: StrictSubTypeWriteOnlyProperty,
Message: fmt.Sprintf("response property '%s' at '%s' is writeOnly and should not be returned in the response",
name, path),
Reason: fmt.Sprintf("Strict mode: property '%s' is marked writeOnly in the schema",
name),
HowToFix: fmt.Sprintf("Remove the writeOnly annotation from '%s' in the schema, "+
"remove it from the response, or add '%s' to StrictIgnorePaths", name, path),
RequestPath: requestPath,
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
SpecLine: specLine,
SpecCol: specCol,
}
}
// truncateForContext creates a truncated string representation for error context.
func truncateForContext(v any) string {
switch val := v.(type) {
case string:
if len(val) > 50 {
return val[:47] + "..."
}
return val
case map[string]any:
return "{...}"
case []any:
return "[...]"
default:
s := fmt.Sprintf("%v", v)
if len(s) > 50 {
return s[:47] + "..."
}
return s
}
}
libopenapi-validator-0.13.8/errors/strict_errors_test.go 0000664 0000000 0000000 00000015771 15205340424 0023530 0 ustar 00root root 0000000 0000000 // Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUndeclaredPropertyError(t *testing.T) {
err := UndeclaredPropertyError(
"$.body.user.extra",
"extra",
"some value",
[]string{"name", "email"},
"request",
"/users",
"POST",
42,
10,
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeProperty, err.ValidationSubType)
assert.Contains(t, err.Message, "request property 'extra' at '$.body.user.extra'")
assert.Contains(t, err.Reason, "name, email")
assert.Contains(t, err.HowToFix, "extra")
assert.Contains(t, err.HowToFix, "$.body.user.extra")
assert.Equal(t, "/users", err.RequestPath)
assert.Equal(t, "POST", err.RequestMethod)
assert.Equal(t, "extra", err.ParameterName)
assert.Equal(t, 42, err.SpecLine)
assert.Equal(t, 10, err.SpecCol)
}
func TestUndeclaredPropertyError_Response(t *testing.T) {
err := UndeclaredPropertyError(
"$.body.data.undeclared",
"undeclared",
map[string]any{"nested": "value"},
[]string{"id", "name"},
"response",
"/items/123",
"GET",
100,
5,
)
assert.NotNil(t, err)
assert.Contains(t, err.Message, "response property 'undeclared'")
assert.Contains(t, err.Reason, "id, name")
assert.Equal(t, "{...}", err.Context) // Map truncated
assert.Equal(t, 100, err.SpecLine)
assert.Equal(t, 5, err.SpecCol)
}
func TestUndeclaredPropertyError_EmptyDirection(t *testing.T) {
err := UndeclaredPropertyError(
"$.body.prop",
"prop",
"value",
nil,
"", // Empty direction defaults to "request"
"/test",
"POST",
0, // Zero values for unknown location
0,
)
assert.Contains(t, err.Message, "request property")
assert.Equal(t, 0, err.SpecLine)
assert.Equal(t, 0, err.SpecCol)
}
func TestUndeclaredHeaderError(t *testing.T) {
err := UndeclaredHeaderError(
"X-Custom-Header",
"header-value",
[]string{"Content-Type", "Authorization"},
"request",
"/api/endpoint",
"GET",
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeHeader, err.ValidationSubType)
assert.Contains(t, err.Message, "request header 'X-Custom-Header'")
assert.Contains(t, err.Reason, "Content-Type, Authorization")
assert.Contains(t, err.HowToFix, "X-Custom-Header")
assert.Equal(t, "/api/endpoint", err.RequestPath)
assert.Equal(t, "GET", err.RequestMethod)
assert.Equal(t, "X-Custom-Header", err.ParameterName)
assert.Equal(t, "header-value", err.Context)
}
func TestUndeclaredHeaderError_Response(t *testing.T) {
err := UndeclaredHeaderError(
"X-Response-Header",
"value",
nil,
"response",
"/test",
"POST",
)
assert.Contains(t, err.Message, "response header")
}
func TestUndeclaredHeaderError_EmptyDirection(t *testing.T) {
err := UndeclaredHeaderError(
"X-Header",
"value",
nil,
"",
"/test",
"GET",
)
assert.Contains(t, err.Message, "request header")
}
func TestUndeclaredQueryParamError(t *testing.T) {
err := UndeclaredQueryParamError(
"$.query.debug",
"debug",
"true",
[]string{"page", "limit"},
"/items",
"GET",
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeQuery, err.ValidationSubType)
assert.Contains(t, err.Message, "query parameter 'debug' at '$.query.debug'")
assert.Contains(t, err.Reason, "page, limit")
assert.Contains(t, err.HowToFix, "debug")
assert.Contains(t, err.HowToFix, "$.query.debug")
assert.Equal(t, "/items", err.RequestPath)
assert.Equal(t, "GET", err.RequestMethod)
assert.Equal(t, "debug", err.ParameterName)
}
func TestUndeclaredCookieError(t *testing.T) {
err := UndeclaredCookieError(
"$.cookies.tracking",
"tracking",
"abc123",
[]string{"session", "csrf"},
"/dashboard",
"GET",
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeCookie, err.ValidationSubType)
assert.Contains(t, err.Message, "cookie 'tracking' at '$.cookies.tracking'")
assert.Contains(t, err.Reason, "session, csrf")
assert.Contains(t, err.HowToFix, "tracking")
assert.Contains(t, err.HowToFix, "$.cookies.tracking")
assert.Equal(t, "/dashboard", err.RequestPath)
assert.Equal(t, "GET", err.RequestMethod)
assert.Equal(t, "tracking", err.ParameterName)
}
func TestReadOnlyPropertyError(t *testing.T) {
err := ReadOnlyPropertyError(
"$.body.id",
"id",
"user-123",
"/users",
"POST",
10,
5,
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeReadOnlyProperty, err.ValidationSubType)
assert.Contains(t, err.Message, "readOnly")
assert.Contains(t, err.Message, "'id'")
assert.Contains(t, err.Message, "'$.body.id'")
assert.Contains(t, err.Reason, "readOnly")
assert.Contains(t, err.HowToFix, "id")
assert.Equal(t, "/users", err.RequestPath)
assert.Equal(t, "POST", err.RequestMethod)
assert.Equal(t, "id", err.ParameterName)
assert.Equal(t, 10, err.SpecLine)
assert.Equal(t, 5, err.SpecCol)
}
func TestWriteOnlyPropertyError(t *testing.T) {
err := WriteOnlyPropertyError(
"$.body.password",
"password",
"secret",
"/users/123",
"GET",
20,
3,
)
assert.NotNil(t, err)
assert.Equal(t, StrictValidationType, err.ValidationType)
assert.Equal(t, StrictSubTypeWriteOnlyProperty, err.ValidationSubType)
assert.Contains(t, err.Message, "writeOnly")
assert.Contains(t, err.Message, "'password'")
assert.Contains(t, err.Message, "'$.body.password'")
assert.Contains(t, err.Reason, "writeOnly")
assert.Contains(t, err.HowToFix, "password")
assert.Equal(t, "/users/123", err.RequestPath)
assert.Equal(t, "GET", err.RequestMethod)
assert.Equal(t, "password", err.ParameterName)
assert.Equal(t, 20, err.SpecLine)
assert.Equal(t, 3, err.SpecCol)
}
func TestTruncateForContext_String(t *testing.T) {
// Short string should not be truncated
short := truncateForContext("short")
assert.Equal(t, "short", short)
// Long string should be truncated
long := truncateForContext("this is a very long string that exceeds fifty characters and should be truncated")
assert.Len(t, long, 50)
assert.True(t, len(long) <= 50)
assert.Contains(t, long, "...")
}
func TestTruncateForContext_Map(t *testing.T) {
m := map[string]any{"key": "value"}
result := truncateForContext(m)
assert.Equal(t, "{...}", result)
}
func TestTruncateForContext_Slice(t *testing.T) {
s := []any{1, 2, 3}
result := truncateForContext(s)
assert.Equal(t, "[...]", result)
}
func TestTruncateForContext_Other(t *testing.T) {
// Integer
i := truncateForContext(12345)
assert.Equal(t, "12345", i)
// Boolean
b := truncateForContext(true)
assert.Equal(t, "true", b)
// Long formatted value
type customType struct {
Field1 string
Field2 string
Field3 string
}
longValue := customType{
Field1: "this is a long value",
Field2: "that will exceed fifty",
Field3: "characters when formatted",
}
result := truncateForContext(longValue)
assert.True(t, len(result) <= 50)
assert.Contains(t, result, "...")
}
libopenapi-validator-0.13.8/errors/urlencoded_errors.go 0000664 0000000 0000000 00000004214 15205340424 0023273 0 ustar 00root root 0000000 0000000 package errors
import (
"fmt"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi/datamodel/high/base"
)
func InvalidURLEncodedParsing(reason, referenceObject string) *ValidationError {
return &ValidationError{
ValidationType: helpers.URLEncodedValidation,
ValidationSubType: helpers.Schema,
Message: "Unable to parse form-urlencoded body",
Reason: fmt.Sprintf("failed to parse form-urlencoded: %s", reason),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: reason,
ReferenceSchema: "",
ReferenceObject: referenceObject,
}},
HowToFix: HowToFixInvalidUrlEncoded,
}
}
func InvalidTypeEncoding(schema *base.Schema, name, contentType string) *ValidationError {
line := 1
col := 0
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.URLEncodedValidation,
ValidationSubType: helpers.InvalidTypeEncoding,
Message: fmt.Sprintf("The value '%s' could not be parsed to the defined encoding", name),
Reason: fmt.Sprintf("The value '%s' is encoded as '%s' in the schema, however the value could not be parsed", name, contentType),
SpecLine: line,
SpecCol: col,
Context: schema,
HowToFix: HowToFixInvalidTypeEncoding,
}
}
func ReservedURLEncodedValue(schema *base.Schema, name, value string) *ValidationError {
line := 1
col := 0
if schema != nil {
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
}
return &ValidationError{
ValidationType: helpers.URLEncodedValidation,
ValidationSubType: helpers.ReservedValues,
Message: fmt.Sprintf("Form value '%s' contains reserved characters", name),
Reason: fmt.Sprintf("The form value '%s' contains reserved characters but allowReserved is false. Value: '%s'", name, value),
SpecLine: line,
SpecCol: col,
Context: schema,
HowToFix: HowToFixFormDataReservedCharacters,
}
}
libopenapi-validator-0.13.8/errors/urlencoded_errors_test.go 0000664 0000000 0000000 00000003263 15205340424 0024335 0 ustar 00root root 0000000 0000000 package errors
import (
"testing"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/stretchr/testify/assert"
)
func getURLEncodingTestSchema() *base.Schema {
spec := `openapi: 3.0.0
paths:
/pet:
get:
responses:
'200':
content:
application/x-www-form-urlencoded:
encoding:
animal:
contentType: application/json
schema:
type: object
properties:
animal:
type: object`
doc, _ := libopenapi.NewDocument([]byte(spec))
v3Doc, _ := doc.BuildV3Model()
return v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
Content.GetOrZero("application/x-www-form-urlencoded").Schema.Schema()
}
func TestInvalidURLEncodedParsing(t *testing.T) {
err := InvalidURLEncodedParsing("no data sent", "invalid-formdata")
assert.NotNil(t, (*err))
assert.Equal(t, (*err).SchemaValidationErrors[0].Reason, "no data sent")
assert.Equal(t, (*err).SchemaValidationErrors[0].ReferenceObject, "invalid-formdata")
assert.Equal(t, helpers.Schema, (*err).ValidationSubType)
}
func TestInvalidTypeEncoding(t *testing.T) {
err := InvalidTypeEncoding(getURLEncodingTestSchema(), "animal", helpers.JSONContentType)
assert.NotNil(t, (*err))
assert.Equal(t, helpers.InvalidTypeEncoding, (*err).ValidationSubType)
}
func TestReservedURLEncodedValue(t *testing.T) {
err := ReservedURLEncodedValue(getURLEncodingTestSchema(), "animal", "!")
assert.NotNil(t, (*err))
assert.Equal(t, helpers.ReservedValues, (*err).ValidationSubType)
}
libopenapi-validator-0.13.8/errors/validation_error.go 0000664 0000000 0000000 00000014614 15205340424 0023123 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package errors
import (
"fmt"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/santhosh-tekuri/jsonschema/v6"
)
// SchemaValidationFailure describes any failure that occurs when validating data
// against either an OpenAPI or JSON Schema. It aims to be a more user-friendly
// representation of the error than what is provided by the jsonschema library.
type SchemaValidationFailure struct {
// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
// InstancePath is the raw path segments from the root to the failing field
InstancePath []string `json:"instancePath,omitempty" yaml:"instancePath,omitempty"`
// FieldName is the name of the specific field that failed validation (last segment of the path)
FieldName string `json:"fieldName,omitempty" yaml:"fieldName,omitempty"`
// FieldPath is the JSONPath representation of the field location that failed validation (e.g., "$.user.email")
FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
// KeywordLocation is the JSON Pointer (RFC 6901) path to the schema keyword that failed validation
// (e.g., "/properties/age/minimum")
KeywordLocation string `json:"keywordLocation,omitempty" yaml:"keywordLocation,omitempty"`
// Line is the line number where the violation occurred. This may a local line number
// if the validation is a schema (only schemas are validated locally, so the line number will be relative to
// the Context object held by the ValidationError object).
Line int `json:"line,omitempty" yaml:"line,omitempty"`
// Column is the column number where the violation occurred. This may a local column number
// if the validation is a schema (only schemas are validated locally, so the column number will be relative to
// the Context object held by the ValidationError object).
Column int `json:"column,omitempty" yaml:"column,omitempty"`
// ReferenceSchema is the schema that was referenced in the validation failure.
ReferenceSchema string `json:"referenceSchema,omitempty" yaml:"referenceSchema,omitempty"`
// ReferenceObject is the object that failed schema validation
ReferenceObject string `json:"referenceObject,omitempty" yaml:"referenceObject,omitempty"`
// ReferenceExample is an example object generated from the schema that was referenced in the validation failure.
ReferenceExample string `json:"referenceExample,omitempty" yaml:"referenceExample,omitempty"`
// The original jsonschema.ValidationError object, if the schema failure originated from the jsonschema library.
OriginalJsonSchemaError *jsonschema.ValidationError `json:"-" yaml:"-"`
// Context is the raw schema object that failed validation (for programmatic access)
Context interface{} `json:"-" yaml:"-"`
}
// Error returns a string representation of the error
func (s *SchemaValidationFailure) Error() string {
if s.FieldPath != "" {
return fmt.Sprintf("Reason: %s, FieldPath: %s", s.Reason, s.FieldPath)
}
return fmt.Sprintf("Reason: %s", s.Reason)
}
// ValidationError is a struct that contains all the information about a validation error.
type ValidationError struct {
// Message is a human-readable message describing the error.
Message string `json:"message" yaml:"message"`
// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason" yaml:"reason"`
// ValidationType is a string that describes the type of validation that failed.
ValidationType string `json:"validationType" yaml:"validationType"`
// ValidationSubType is a string that describes the subtype of validation that failed.
ValidationSubType string `json:"validationSubType" yaml:"validationSubType"`
// SpecLine is the line number in the spec where the error occurred.
SpecLine int `json:"specLine" yaml:"specLine"`
// SpecCol is the column number in the spec where the error occurred.
SpecCol int `json:"specColumn" yaml:"specColumn"`
// HowToFix is a human-readable message describing how to fix the error.
HowToFix string `json:"howToFix" yaml:"howToFix"`
// RequestPath is the path of the request
RequestPath string `json:"requestPath" yaml:"requestPath"`
// SpecPath is the path from the specification that corresponds to the request
SpecPath string `json:"specPath" yaml:"specPath"`
// RequestMethod is the HTTP method of the request
RequestMethod string `json:"requestMethod" yaml:"requestMethod"`
// ParameterName is the name of the parameter that failed validation (for parameter validation errors)
ParameterName string `json:"parameterName,omitempty" yaml:"parameterName,omitempty"`
// SchemaValidationErrors is a slice of SchemaValidationFailure objects that describe the validation errors
// This is only populated when the validation type is against a schema.
SchemaValidationErrors []*SchemaValidationFailure `json:"validationErrors,omitempty" yaml:"validationErrors,omitempty"`
// Context is the object that the validation error occurred on. This is usually a pointer to a schema
// or a parameter object.
Context interface{} `json:"-" yaml:"-"`
}
// Error returns a string representation of the error
func (v *ValidationError) Error() string {
if v.SchemaValidationErrors != nil {
if v.SpecLine > 0 && v.SpecCol > 0 {
return fmt.Sprintf("Error: %s, Reason: %s, Validation Errors: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SchemaValidationErrors, v.SpecLine, v.SpecCol)
} else {
return fmt.Sprintf("Error: %s, Reason: %s, Validation Errors: %s",
v.Message, v.Reason, v.SchemaValidationErrors)
}
} else {
if v.SpecLine > 0 && v.SpecCol > 0 {
return fmt.Sprintf("Error: %s, Reason: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SpecLine, v.SpecCol)
} else {
return fmt.Sprintf("Error: %s, Reason: %s",
v.Message, v.Reason)
}
}
}
// IsPathMissingError returns true if the error has a ValidationType of "path" and a ValidationSubType of "missing"
func (v *ValidationError) IsPathMissingError() bool {
return v.ValidationType == helpers.PathValidation && v.ValidationSubType == helpers.ValidationMissing
}
// IsOperationMissingError returns true if the error has a ValidationType of "request" and a ValidationSubType of "missingOperation"
func (v *ValidationError) IsOperationMissingError() bool {
return v.ValidationType == helpers.PathValidation && v.ValidationSubType == helpers.ValidationMissingOperation
}
libopenapi-validator-0.13.8/errors/validation_error_test.go 0000664 0000000 0000000 00000007677 15205340424 0024175 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"fmt"
"testing"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/stretchr/testify/require"
)
func TestSchemaValidationFailure_Error(t *testing.T) {
// Test the Error method of SchemaValidationFailure
s := &SchemaValidationFailure{
Reason: "Invalid type",
FieldPath: "$.path.to.property",
}
expectedError := "Reason: Invalid type, FieldPath: $.path.to.property"
require.Equal(t, expectedError, s.Error())
}
func TestValidationError_Error_NoSchemaValidationErrors(t *testing.T) {
// Test the Error method of ValidationError with no SchemaValidationErrors and no line/column info
v := &ValidationError{
Message: "Missing required field",
Reason: "The field 'id' is required but missing",
}
expectedError := "Error: Missing required field, Reason: The field 'id' is required but missing"
require.Equal(t, expectedError, v.Error())
}
func TestValidationError_Error_WithSpecLineAndColumn(t *testing.T) {
// Test the Error method of ValidationError with spec line and column
v := &ValidationError{
Message: "Invalid data type",
Reason: "Expected 'string', got 'integer'",
SpecLine: 10,
SpecCol: 15,
}
expectedError := "Error: Invalid data type, Reason: Expected 'string', got 'integer', Line: 10, Column: 15"
require.Equal(t, expectedError, v.Error())
}
func TestValidationError_Error_WithSchemaValidationErrors(t *testing.T) {
// Test the Error method of ValidationError with SchemaValidationErrors
schemaError := &SchemaValidationFailure{
Reason: "Invalid enum value",
FieldPath: "$.path.to.enum",
}
v := &ValidationError{
Message: "Enum validation failed",
Reason: "Invalid enum value",
SchemaValidationErrors: []*SchemaValidationFailure{schemaError},
}
expectedError := fmt.Sprintf("Error: Enum validation failed, Reason: Invalid enum value, Validation Errors: %s", []*SchemaValidationFailure{schemaError})
require.Equal(t, expectedError, v.Error())
}
func TestValidationError_Error_WithSchemaValidationErrors_AndSpecLineColumn(t *testing.T) {
// Test the Error method of ValidationError with SchemaValidationErrors and SpecLine and SpecCol
schemaError := &SchemaValidationFailure{
Reason: "Invalid enum value",
FieldPath: "$.path.to.enum",
}
v := &ValidationError{
Message: "Enum validation failed",
Reason: "Invalid enum value",
SchemaValidationErrors: []*SchemaValidationFailure{schemaError},
SpecLine: 12,
SpecCol: 5,
}
expectedError := fmt.Sprintf("Error: Enum validation failed, Reason: Invalid enum value, Validation Errors: %s, Line: 12, Column: 5", []*SchemaValidationFailure{schemaError})
require.Equal(t, expectedError, v.Error())
}
func TestValidationError_IsPathMissingError(t *testing.T) {
// Test the IsPathMissingError method
v := &ValidationError{
ValidationType: helpers.PathValidation,
ValidationSubType: helpers.ValidationMissing,
}
require.True(t, v.IsPathMissingError())
// Test with different ValidationSubType
v.ValidationSubType = "wrongType"
require.False(t, v.IsPathMissingError())
// Test with different ValidationType
v.ValidationType = helpers.RequestValidation
v.ValidationSubType = helpers.ValidationMissing
require.False(t, v.IsPathMissingError())
}
func TestValidationError_IsOperationMissingError(t *testing.T) {
// Test the IsOperationMissingError method
v := &ValidationError{
ValidationType: helpers.PathValidation,
ValidationSubType: helpers.ValidationMissingOperation,
}
require.True(t, v.IsOperationMissingError())
// Test with different ValidationSubType
v.ValidationSubType = "wrongOperation"
require.False(t, v.IsOperationMissingError())
// Test with different ValidationType
v.ValidationType = helpers.RequestValidation
v.ValidationSubType = helpers.ValidationMissingOperation
require.False(t, v.IsOperationMissingError())
}
libopenapi-validator-0.13.8/errors/xml_errors.go 0000664 0000000 0000000 00000007073 15205340424 0021755 0 ustar 00root root 0000000 0000000 package errors
import (
"fmt"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi/datamodel/high/base"
)
func MissingPrefix(schema *base.Schema, prefix string) *ValidationError {
line := 1
col := 0
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.XmlValidation,
ValidationSubType: helpers.XmlValidationPrefix,
Message: fmt.Sprintf("The prefix '%s' is defined in the schema, however it's missing from the xml", prefix),
Reason: fmt.Sprintf("The prefix '%s' is defined in the schema, however it's missing from the xml content", prefix),
SpecLine: line,
SpecCol: col,
Context: schema,
HowToFix: fmt.Sprintf(HowToFixXmlPrefix, prefix),
}
}
func InvalidPrefix(schema *base.Schema, prefix string) *ValidationError {
line := 1
col := 0
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.XmlValidation,
ValidationSubType: helpers.XmlValidationPrefix,
Message: fmt.Sprintf("The prefix '%s' defined in the schema differs from the xml", prefix),
Reason: fmt.Sprintf("The prefix '%s' is defined in the schema, however the xml sent and invalid prefix", prefix),
SpecCol: col,
SpecLine: line,
Context: schema,
HowToFix: fmt.Sprintf(HowToFixXmlPrefix, prefix),
}
}
func MissingNamespace(schema *base.Schema, namespace string) *ValidationError {
line := 1
col := 0
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.XmlValidation,
ValidationSubType: helpers.XmlValidationNamespace,
Message: fmt.Sprintf("The namespace '%s' is defined in the schema, however it's missing from the xml", namespace),
Reason: fmt.Sprintf("The namespace '%s' is defined in the schema, however it's missing from the xml content", namespace),
SpecLine: line,
SpecCol: col,
Context: schema,
HowToFix: fmt.Sprintf(HowToFixXmlNamespace, namespace),
}
}
func InvalidNamespace(schema *base.Schema, namespace, expectedNamespace, prefix string) *ValidationError {
line := 1
col := 0
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
line = low.Type.KeyNode.Line
col = low.Type.KeyNode.Column
}
return &ValidationError{
ValidationType: helpers.XmlValidation,
ValidationSubType: helpers.XmlValidationNamespace,
Message: fmt.Sprintf("The namespace from prefix '%s' differs from the xml", prefix),
Reason: fmt.Sprintf("The namespace from prefix '%s' is declared as '%s' in the schema, however in xml is declared as '%s'",
prefix, expectedNamespace, namespace),
SpecLine: line,
SpecCol: col,
Context: schema,
HowToFix: fmt.Sprintf(HowToFixXmlNamespace, namespace),
}
}
func InvalidXMLParsing(reason, referenceObject string) *ValidationError {
return &ValidationError{
ValidationType: helpers.XmlValidation,
ValidationSubType: helpers.Schema,
Message: "xml example is malformed",
Reason: fmt.Sprintf("failed to parse xml: %s", reason),
SchemaValidationErrors: []*SchemaValidationFailure{{
Reason: reason,
ReferenceSchema: "",
ReferenceObject: referenceObject,
}},
HowToFix: HowToFixInvalidXml,
}
}
libopenapi-validator-0.13.8/errors/xml_errors_test.go 0000664 0000000 0000000 00000004076 15205340424 0023014 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package errors
import (
"testing"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/stretchr/testify/assert"
)
func getTestSchema() *base.Schema {
spec := `openapi: 3.0.0
paths:
/pet:
get:
responses:
'200':
content:
application/xml:
schema:
type: object
properties:
age:
type: integer
xml:
name: Cat`
doc, _ := libopenapi.NewDocument([]byte(spec))
v3Doc, _ := doc.BuildV3Model()
return v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
Content.GetOrZero("application/xml").Schema.Schema()
}
func TestMissingPrefixError(t *testing.T) {
schema := getTestSchema()
err := MissingPrefix(schema, "prx")
assert.NotNil(t, *err)
assert.Equal(t, helpers.XmlValidationPrefix, (*err).ValidationSubType)
}
func TestMissingNamespaceError(t *testing.T) {
schema := getTestSchema()
err := MissingNamespace(schema, "http://ex.c")
assert.NotNil(t, *err)
assert.Equal(t, helpers.XmlValidationNamespace, (*err).ValidationSubType)
}
func TestInvalidPrefixError(t *testing.T) {
schema := getTestSchema()
err := InvalidPrefix(schema, "prx")
assert.NotNil(t, *err)
assert.Equal(t, helpers.XmlValidationPrefix, (*err).ValidationSubType)
}
func TestInvalidNamespaceError(t *testing.T) {
schema := getTestSchema()
err := InvalidNamespace(schema, "other", "http://ex.c", "prx")
assert.NotNil(t, *err)
assert.Equal(t, helpers.XmlValidationNamespace, (*err).ValidationSubType)
}
func TestInvalidParsing(t *testing.T) {
err := InvalidXMLParsing("no data sent", "invalid-xml")
assert.NotNil(t, (*err))
assert.Equal(t, (*err).SchemaValidationErrors[0].Reason, "no data sent")
assert.Equal(t, (*err).SchemaValidationErrors[0].ReferenceObject, "invalid-xml")
assert.Equal(t, helpers.Schema, (*err).ValidationSubType)
}
libopenapi-validator-0.13.8/go.mod 0000664 0000000 0000000 00000001772 15205340424 0017024 0 ustar 00root root 0000000 0000000 module github.com/pb33f/libopenapi-validator
go 1.25.0
require (
github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad
github.com/dlclark/regexp2 v1.12.0
github.com/go-openapi/jsonpointer v0.23.1
github.com/goccy/go-yaml v1.19.2
github.com/pb33f/jsonpath v0.8.2
github.com/pb33f/libopenapi v0.36.6
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/stretchr/testify v1.11.1
go.yaml.in/yaml/v4 v4.0.0-rc.4
golang.org/x/text v0.37.0
)
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.20.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
libopenapi-validator-0.13.8/go.sum 0000664 0000000 0000000 00000021104 15205340424 0017040 0 ustar 00root root 0000000 0000000 github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad h1:3swAvbzgfaI6nKuDDU7BiKfZRdF+h2ZwKgMHd8Ha4t8=
github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad/go.mod h1:9+nBLYNWkvPcq9ep0owWUsPTLgL9ZXTsZWcCSVGGLJ0=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=
github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=
github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4=
github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pb33f/jsonpath v0.8.2 h1:Ou4C7zjYClBm97dfZjDCjdZGusJoynv/vrtiEKNfj2Y=
github.com/pb33f/jsonpath v0.8.2/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo=
github.com/pb33f/libopenapi v0.36.6 h1:DRqWcgnMn8wiknMBKETIRZiY+GL05xEFtWwnK/Q+WOs=
github.com/pb33f/libopenapi v0.36.6/go.mod h1:MsDdUlQ1CdrIDO5v26JfgBxQs7kcaOUEpMP3EqU6bI4=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
libopenapi-validator-0.13.8/helpers/ 0000775 0000000 0000000 00000000000 15205340424 0017351 5 ustar 00root root 0000000 0000000 libopenapi-validator-0.13.8/helpers/constants.go 0000664 0000000 0000000 00000004702 15205340424 0021717 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
const (
ParameterValidation = "parameter"
ParameterValidationPath = "path"
ParameterValidationQuery = "query"
ParameterValidationHeader = "header"
ParameterValidationCookie = "cookie"
RequestValidation = "request"
RequestBodyValidation = "requestBody"
XmlValidation = "xmlValidation"
XmlValidationPrefix = "prefix"
XmlValidationNamespace = "namespace"
URLEncodedValidation = "urlEncodedValidation"
InvalidTypeEncoding = "invalidTypeEncoding"
ReservedValues = "reservedValues"
Schema = "schema"
ResponseBodyValidation = "response"
RequestBodyContentType = "contentType"
// Deprecated: use ValidationMissingOperation
RequestMissingOperation = "missingOperation"
PathValidation = "path"
ValidationMissing = "missing"
ValidationMissingOperation = "missingOperation"
ResponseBodyResponseCode = "statusCode"
SecurityValidation = "security"
DocumentValidation = "document"
SpaceDelimited = "spaceDelimited"
PipeDelimited = "pipeDelimited"
DefaultDelimited = "default"
MatrixStyle = "matrix"
LabelStyle = "label"
Pipe = "|"
Comma = ","
Space = " "
SemiColon = ";"
Asterisk = "*"
Period = "."
Equals = "="
Integer = "integer"
Number = "number"
Slash = "/"
Object = "object"
String = "string"
Array = "array"
Boolean = "boolean"
DeepObject = "deepObject"
Header = "header"
Cookie = "cookie"
Path = "path"
Form = "form"
Query = "query"
JSONContentType = "application/json"
URLEncodedContentType = "application/x-www-form-urlencoded"
JSONType = "json"
ContentTypeHeader = "Content-Type"
AuthorizationHeader = "Authorization"
Charset = "charset"
Boundary = "boundary"
Preferred = "preferred"
FailSegment = "**&&FAIL&&**"
)
libopenapi-validator-0.13.8/helpers/ignore_regex.go 0000664 0000000 0000000 00000001326 15205340424 0022357 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import "regexp"
var (
// Ignore generic poly errors that just say "none matched" since we get specific errors
// But keep errors that say which subschemas matched (for multiple match scenarios)
IgnorePattern = `^'?(anyOf|allOf|oneOf|validation)'? failed(, none matched)?$`
IgnorePolyPattern = `^'?(anyOf|allOf|oneOf)'? failed(, none matched)?$`
)
// IgnoreRegex is a regular expression that matches the IgnorePattern
var IgnoreRegex = regexp.MustCompile(IgnorePattern)
// IgnorePolyRegex is a regular expression that matches the IgnorePattern
var IgnorePolyRegex = regexp.MustCompile(IgnorePolyPattern)
libopenapi-validator-0.13.8/helpers/json_pointer.go 0000664 0000000 0000000 00000003440 15205340424 0022412 0 ustar 00root root 0000000 0000000 // Copyright 2026 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
import (
"fmt"
"strings"
"github.com/go-openapi/jsonpointer"
)
// EscapeJSONPointerSegment escapes a single segment for use in a JSON Pointer (RFC 6901).
// It replaces '~' with '~0' and '/' with '~1'.
func EscapeJSONPointerSegment(segment string) string {
return jsonpointer.Escape(segment)
}
// ConstructParameterJSONPointer constructs a full JSON Pointer path for a parameter
// in the OpenAPI specification.
// Format: /paths/{path}/{method}/parameters/{paramName}/schema/{keyword}
// The path segment is automatically escaped according to RFC 6901.
// The keyword can be a simple keyword like "type" or a nested path like "items/type".
func ConstructParameterJSONPointer(pathTemplate, method, paramName, keyword string) string {
escapedPath := EscapeJSONPointerSegment(pathTemplate)
escapedPath = strings.TrimPrefix(escapedPath, "~1") // Remove leading slash encoding
method = strings.ToLower(method)
return fmt.Sprintf("/paths/%s/%s/parameters/%s/schema/%s", escapedPath, method, paramName, keyword)
}
// ConstructResponseHeaderJSONPointer constructs a full JSON Pointer path for a response header
// in the OpenAPI specification.
// Format: /paths/{path}/{method}/responses/{statusCode}/headers/{headerName}/{keyword}
// The path segment is automatically escaped according to RFC 6901.
func ConstructResponseHeaderJSONPointer(pathTemplate, method, statusCode, headerName, keyword string) string {
escapedPath := EscapeJSONPointerSegment(pathTemplate)
escapedPath = strings.TrimPrefix(escapedPath, "~1") // Remove leading slash encoding
method = strings.ToLower(method)
return fmt.Sprintf("/paths/%s/%s/responses/%s/headers/%s/%s", escapedPath, method, statusCode, headerName, keyword)
}
libopenapi-validator-0.13.8/helpers/json_pointer_test.go 0000664 0000000 0000000 00000007247 15205340424 0023462 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEscapeJSONPointerSegment(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "no special characters",
input: "simple",
expected: "simple",
},
{
name: "tilde only",
input: "some~thing",
expected: "some~0thing",
},
{
name: "slash only",
input: "path/to/something",
expected: "path~1to~1something",
},
{
name: "both tilde and slash",
input: "path/with~special/chars~",
expected: "path~1with~0special~1chars~0",
},
{
name: "path template",
input: "/users/{id}",
expected: "~1users~1{id}",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := EscapeJSONPointerSegment(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestConstructParameterJSONPointer(t *testing.T) {
tests := []struct {
name string
pathTemplate string
method string
paramName string
keyword string
expected string
}{
{
name: "simple path with query parameter type",
pathTemplate: "/users",
method: "GET",
paramName: "limit",
keyword: "type",
expected: "/paths/users/get/parameters/limit/schema/type",
},
{
name: "path with parameter and enum keyword",
pathTemplate: "/users/{id}",
method: "POST",
paramName: "status",
keyword: "enum",
expected: "/paths/users~1{id}/post/parameters/status/schema/enum",
},
{
name: "path with tilde character",
pathTemplate: "/some~path",
method: "PUT",
paramName: "value",
keyword: "format",
expected: "/paths/some~0path/put/parameters/value/schema/format",
},
{
name: "path with multiple slashes",
pathTemplate: "/api/v1/users/{userId}/posts/{postId}",
method: "DELETE",
paramName: "filter",
keyword: "required",
expected: "/paths/api~1v1~1users~1{userId}~1posts~1{postId}/delete/parameters/filter/schema/required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstructParameterJSONPointer(tt.pathTemplate, tt.method, tt.paramName, tt.keyword)
assert.Equal(t, tt.expected, result)
})
}
}
func TestConstructResponseHeaderJSONPointer(t *testing.T) {
tests := []struct {
name string
pathTemplate string
method string
statusCode string
headerName string
keyword string
expected string
}{
{
name: "simple response header",
pathTemplate: "/health",
method: "GET",
statusCode: "200",
headerName: "X-Request-ID",
keyword: "required",
expected: "/paths/health/get/responses/200/headers/X-Request-ID/required",
},
{
name: "path with parameter",
pathTemplate: "/users/{id}",
method: "POST",
statusCode: "201",
headerName: "Location",
keyword: "schema",
expected: "/paths/users~1{id}/post/responses/201/headers/Location/schema",
},
{
name: "path with tilde and slash",
pathTemplate: "/some~path/to/resource",
method: "PUT",
statusCode: "204",
headerName: "ETag",
keyword: "type",
expected: "/paths/some~0path~1to~1resource/put/responses/204/headers/ETag/type",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstructResponseHeaderJSONPointer(tt.pathTemplate, tt.method, tt.statusCode, tt.headerName, tt.keyword)
assert.Equal(t, tt.expected, result)
})
}
}
libopenapi-validator-0.13.8/helpers/operation_utilities.go 0000664 0000000 0000000 00000002725 15205340424 0024001 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
import (
"mime"
"net/http"
"github.com/pb33f/libopenapi/datamodel/high/v3"
)
// ExtractOperation extracts the operation from the path item based on the request method. If there is no
// matching operation found, then nil is returned.
func ExtractOperation(request *http.Request, item *v3.PathItem) *v3.Operation {
switch request.Method {
case http.MethodGet:
return item.Get
case http.MethodPost:
return item.Post
case http.MethodPut:
return item.Put
case http.MethodDelete:
return item.Delete
case http.MethodOptions:
return item.Options
case http.MethodHead:
if item.Head != nil {
return item.Head
}
return item.Get
case http.MethodPatch:
return item.Patch
case http.MethodTrace:
return item.Trace
}
return nil
}
// ExtractContentType extracts the content type from the request header. First return argument is the content type
// of the request.The second (optional) argument is the charset of the request. The third (optional)
// argument is the boundary of the type (only used with forms really).
func ExtractContentType(contentType string) (string, string, string) {
// mime.ParseMediaType: "If there is an error parsing the optional parameter,
// the media type will be returned along with the error ErrInvalidMediaParameter."
ct, params, _ := mime.ParseMediaType(contentType)
return ct, params["charset"], params["boundary"]
}
libopenapi-validator-0.13.8/helpers/operation_utilities_test.go 0000664 0000000 0000000 00000011205 15205340424 0025031 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import (
"mime"
"net/http"
"testing"
"github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/stretchr/testify/require"
)
// Test ExtractOperation for each HTTP method
func TestExtractOperation(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{Summary: "GET operation"},
Post: &v3.Operation{Summary: "POST operation"},
Put: &v3.Operation{Summary: "PUT operation"},
Delete: &v3.Operation{Summary: "DELETE operation"},
Options: &v3.Operation{Summary: "OPTIONS operation"},
Head: &v3.Operation{Summary: "HEAD operation"},
Patch: &v3.Operation{Summary: "PATCH operation"},
Trace: &v3.Operation{Summary: "TRACE operation"},
}
// Test all HTTP methods
tests := []struct {
method string
want string
}{
{http.MethodGet, "GET operation"},
{http.MethodPost, "POST operation"},
{http.MethodPut, "PUT operation"},
{http.MethodDelete, "DELETE operation"},
{http.MethodOptions, "OPTIONS operation"},
{http.MethodHead, "HEAD operation"},
{http.MethodPatch, "PATCH operation"},
{http.MethodTrace, "TRACE operation"},
}
for _, tt := range tests {
req, _ := http.NewRequest(tt.method, "/", nil)
operation := ExtractOperation(req, pathItem)
require.NotNil(t, operation)
require.Equal(t, tt.want, operation.Summary)
}
// Test an unsupported HTTP method
req, _ := http.NewRequest("INVALID", "/", nil)
operation := ExtractOperation(req, pathItem)
require.Nil(t, operation)
}
// Test ExtractContentType for various input cases
func TestExtractContentType(t *testing.T) {
// Simple content type with no charset or boundary
contentType, charset, boundary := ExtractContentType("application/json")
require.Equal(t, "application/json", contentType)
require.Empty(t, charset)
require.Empty(t, boundary)
// Content type with charset
contentType, charset, boundary = ExtractContentType("text/html; charset=UTF-8")
require.Equal(t, "text/html", contentType)
require.Equal(t, "UTF-8", charset)
require.Empty(t, boundary)
// Content type with boundary
contentType, charset, boundary = ExtractContentType("multipart/form-data; boundary=----WebKitFormBoundary")
require.Equal(t, "multipart/form-data", contentType)
require.Empty(t, charset)
require.Equal(t, "----WebKitFormBoundary", boundary)
// Content type with both charset and boundary
contentType, charset, boundary = ExtractContentType("multipart/form-data; charset=UTF-8; boundary=----WebKitFormBoundary")
require.Equal(t, "multipart/form-data", contentType)
require.Equal(t, "UTF-8", charset)
require.Equal(t, "----WebKitFormBoundary", boundary)
// Content type with leading/trailing spaces
contentType, charset, boundary = ExtractContentType(" application/xml ; charset=ISO-8859-1 ; boundary=myBoundary ")
require.Equal(t, "application/xml", contentType)
require.Equal(t, "ISO-8859-1", charset)
require.Equal(t, "myBoundary", boundary)
// Invalid content type (no key-value pair for charset/boundary)
contentType, charset, boundary = ExtractContentType("application/xml; charset; boundary")
require.Equal(t, "application/xml", contentType)
require.Empty(t, charset)
require.Empty(t, boundary)
// Content type with custom parameter
contentType, charset, boundary = ExtractContentType("text/html; version=2")
require.Equal(t, "text/html", contentType)
require.Empty(t, charset)
require.Empty(t, boundary)
// Content type with custom parameter, charset, and boundary
contentType, charset, boundary = ExtractContentType("text/html; charset=UTF-8; version=2; boundary=myBoundary")
require.Equal(t, "text/html", contentType)
require.Equal(t, "UTF-8", charset)
require.Equal(t, "myBoundary", boundary)
// mime.ParseMediaType returns an error, but ExtractContentType still returns the content type.
const ct = "text/plain;;"
_, _, err := mime.ParseMediaType(ct)
require.ErrorIs(t, err, mime.ErrInvalidMediaParameter)
contentType, charset, boundary = ExtractContentType(ct)
require.Equal(t, "text/plain", contentType)
require.Empty(t, charset)
require.Empty(t, boundary)
}
func TestExtractOperationHeadFallback(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{Summary: "GET operation"},
Head: nil,
}
req, _ := http.NewRequest(http.MethodHead, "/", nil)
operation := ExtractOperation(req, pathItem)
require.NotNil(t, operation)
require.Equal(t, "GET operation", operation.Summary)
}
func TestExtractOperationHeadFallbackNoGet(t *testing.T) {
pathItem := &v3.PathItem{
Head: nil,
Get: nil,
}
req, _ := http.NewRequest(http.MethodHead, "/", nil)
operation := ExtractOperation(req, pathItem)
require.Nil(t, operation)
}
libopenapi-validator-0.13.8/helpers/package.go 0000664 0000000 0000000 00000000606 15205340424 0021275 0 ustar 00root root 0000000 0000000 // Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
// Package helpers contains helper and utility functions used by the validator. Trying to avoid using the package
// name utils anymore, as it's too generic and can cause conflicts with other packages - however I feel this pattern
// will suffer the exact same fate with time.
package helpers
libopenapi-validator-0.13.8/helpers/parameter_utilities.go 0000664 0000000 0000000 00000052576 15205340424 0023772 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
import (
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
)
// QueryParam is a struct that holds the key, values and property name for a query parameter
// it's used for complex query types that need to be parsed and tracked differently depending
// on the encoding styles used.
type QueryParam struct {
Key string
Values []string
Property string
PropertyPath []string
}
// ExtractParamsForOperation will extract the parameters for the operation based on the request method.
// Both the path level params and the method level params will be returned.
func ExtractParamsForOperation(request *http.Request, item *v3.PathItem) []*v3.Parameter {
params := item.Parameters
switch request.Method {
case http.MethodGet:
if item.Get != nil {
params = append(params, item.Get.Parameters...)
}
case http.MethodPost:
if item.Post != nil {
params = append(params, item.Post.Parameters...)
}
case http.MethodPut:
if item.Put != nil {
params = append(params, item.Put.Parameters...)
}
case http.MethodDelete:
if item.Delete != nil {
params = append(params, item.Delete.Parameters...)
}
case http.MethodOptions:
if item.Options != nil {
params = append(params, item.Options.Parameters...)
}
case http.MethodHead:
if item.Head != nil {
params = append(params, item.Head.Parameters...)
}
case http.MethodPatch:
if item.Patch != nil {
params = append(params, item.Patch.Parameters...)
}
case http.MethodTrace:
if item.Trace != nil {
params = append(params, item.Trace.Parameters...)
}
}
return params
}
// ExtractSecurityForOperation will extract the security requirements for the operation based on the request method.
//
// Deprecated: use EffectiveSecurityForOperation instead, which also handles
// document-level global security inheritance per the OpenAPI specification.
func ExtractSecurityForOperation(request *http.Request, item *v3.PathItem) []*base.SecurityRequirement {
var schemes []*base.SecurityRequirement
switch request.Method {
case http.MethodGet:
if item.Get != nil {
schemes = append(schemes, item.Get.Security...)
}
case http.MethodPost:
if item.Post != nil {
schemes = append(schemes, item.Post.Security...)
}
case http.MethodPut:
if item.Put != nil {
schemes = append(schemes, item.Put.Security...)
}
case http.MethodDelete:
if item.Delete != nil {
schemes = append(schemes, item.Delete.Security...)
}
case http.MethodOptions:
if item.Options != nil {
schemes = append(schemes, item.Options.Security...)
}
case http.MethodHead:
if item.Head != nil {
schemes = append(schemes, item.Head.Security...)
}
case http.MethodPatch:
if item.Patch != nil {
schemes = append(schemes, item.Patch.Security...)
}
case http.MethodTrace:
if item.Trace != nil {
schemes = append(schemes, item.Trace.Security...)
}
}
return schemes
}
// ExtractSecurityHeaderNames extracts header names from applicable security schemes.
// Returns header names from apiKey schemes with in:"header", plus "Authorization"
// for http/oauth2/openIdConnect schemes.
//
// This function is used by strict mode validation to recognize security headers
// as "declared" headers that should not trigger undeclared header errors.
func ExtractSecurityHeaderNames(
security []*base.SecurityRequirement,
securitySchemes map[string]*v3.SecurityScheme,
) []string {
if security == nil || securitySchemes == nil {
return nil
}
seen := make(map[string]bool)
var headers []string
for _, sec := range security {
if sec == nil || sec.ContainsEmptyRequirement {
continue // No security required for this option
}
if sec.Requirements == nil {
continue
}
for pair := sec.Requirements.First(); pair != nil; pair = pair.Next() {
schemeName := pair.Key()
scheme, ok := securitySchemes[schemeName]
if !ok || scheme == nil {
continue
}
var headerName string
switch strings.ToLower(scheme.Type) {
case "apikey":
if strings.ToLower(scheme.In) == Header {
headerName = scheme.Name
}
case "http", "oauth2", "openidconnect":
headerName = "Authorization"
}
if headerName != "" && !seen[strings.ToLower(headerName)] {
seen[strings.ToLower(headerName)] = true
headers = append(headers, headerName)
}
}
}
return headers
}
// EffectiveSecurityForOperation returns the security requirements that apply to the
// operation matched by request method. It implements OpenAPI's inheritance rule:
// - If the operation defines security (even an empty array), use that.
// - Otherwise, fall back to the document-level global security.
// - Returns nil only when neither level defines security.
func EffectiveSecurityForOperation(request *http.Request, item *v3.PathItem, docSecurity []*base.SecurityRequirement) []*base.SecurityRequirement {
op := ExtractOperation(request, item)
if op != nil && op.Security != nil {
return op.Security // operation-level (may be empty [] = "no security")
}
return docSecurity // nil when no global security either
}
func cast(v string) any {
if v == "true" || v == "false" {
b, _ := strconv.ParseBool(v)
return b
}
if i, err := strconv.ParseFloat(v, 64); err == nil {
// check if this is an int or not
if !strings.Contains(v, Period) {
iv, _ := strconv.ParseInt(v, 10, 64)
return iv
}
return i
}
return v
}
// getPropertySchema looks up a property's schema from an object schema's Properties map.
// Returns nil if objectSchema is nil, has no Properties, or the property is not found.
func getPropertySchema(objectSchema *base.Schema, propertyName string) *base.Schema {
if objectSchema == nil || objectSchema.Properties == nil {
return nil
}
proxy := objectSchema.Properties.GetOrZero(propertyName)
if proxy == nil {
return nil
}
return proxy.Schema()
}
func getAdditionalPropertiesSchema(objectSchema *base.Schema) *base.Schema {
if objectSchema == nil || objectSchema.AdditionalProperties == nil || !objectSchema.AdditionalProperties.IsA() ||
objectSchema.AdditionalProperties.A == nil {
return nil
}
return objectSchema.AdditionalProperties.A.Schema()
}
func getSchemaForObjectProperty(objectSchema *base.Schema, propertyName string) *base.Schema {
if propSchema := getPropertySchema(objectSchema, propertyName); propSchema != nil {
return propSchema
}
return getAdditionalPropertiesSchema(objectSchema)
}
// ParseDeepObjectKey splits a query-string key using qs-style deepObject bracket notation.
// It returns ok=false for non-deepObject keys or malformed bracket paths.
func ParseDeepObjectKey(key string) (baseName string, propertyPath []string, ok bool) {
open := strings.IndexRune(key, '[')
if open <= 0 {
return "", nil, false
}
baseName = key[:open]
rest := key[open:]
for len(rest) > 0 {
if rest[0] != '[' {
return "", nil, false
}
close := strings.IndexRune(rest, ']')
if close <= 1 {
return "", nil, false
}
propertyPath = append(propertyPath, rest[1:close])
rest = rest[close+1:]
}
return baseName, propertyPath, len(propertyPath) > 0
}
// castWithSchema casts a string value consulting the schema for the property's declared type.
// If the schema says the property, or array property item, is a string, the value is returned
// as-is (no numeric/bool guessing).
// For other declared types, it falls back to cast() which produces correct results for integer,
// number, and boolean values. The explicit string check prevents the most common miscast: numeric-
// looking strings like "10" being converted to int64 when the schema declares type: string.
func castWithSchema(v string, objectSchema *base.Schema, propertyName string) any {
propSchema := schemaForPropertyPath(objectSchema, []string{propertyName})
if schemaPreservesStringValue(propSchema) {
return v
}
if propSchema == nil && schemaPreservesStringValue(objectSchema) {
return v
}
return cast(v)
}
func queryParamPropertyPath(v *QueryParam) []string {
if len(v.PropertyPath) > 0 {
return v.PropertyPath
}
return []string{v.Property}
}
func queryParamDeepObjectPath(v *QueryParam) []string {
if v == nil {
return nil
}
if len(v.PropertyPath) > 0 {
return v.PropertyPath
}
if v.Property != "" {
return []string{v.Property}
}
return nil
}
func schemaForPropertyPath(objectSchema *base.Schema, propertyPath []string) *base.Schema {
current := objectSchema
for _, propertyName := range propertyPath {
current = getSchemaForObjectProperty(current, propertyName)
if current == nil {
return nil
}
}
return current
}
func schemaTypeIncludes(sch *base.Schema, schemaType string) bool {
return sch != nil && slices.Contains(sch.Type, schemaType)
}
func schemaArrayItems(sch *base.Schema) *base.Schema {
if sch == nil || sch.Items == nil || !sch.Items.IsA() || sch.Items.A == nil {
return nil
}
return sch.Items.A.Schema()
}
func schemaPreservesStringValue(sch *base.Schema) bool {
if schemaTypeIncludes(sch, String) {
return true
}
return schemaTypeIncludes(sch, Array) && schemaTypeIncludes(schemaArrayItems(sch), String)
}
// DeepObjectAllowsMultipleValues reports whether repeated values are allowed for a deepObject
// property. It preserves existing top-level array/additionalProperties behavior and adds support
// for nested properties declared as arrays.
func DeepObjectAllowsMultipleValues(objectSchema *base.Schema, qp *QueryParam) bool {
if objectSchema == nil {
return false
}
if schemaTypeIncludes(objectSchema, Array) {
return true
}
propertyPath := queryParamPropertyPath(qp)
if schemaTypeIncludes(schemaForPropertyPath(objectSchema, propertyPath), Array) {
return true
}
return schemaTypeIncludes(getAdditionalPropertiesSchema(objectSchema), Array)
}
func schemaForCastingPath(objectSchema *base.Schema, propertyPath []string) *base.Schema {
propSchema := schemaForPropertyPath(objectSchema, propertyPath)
if propSchema != nil {
return propSchema
}
if schemaTypeIncludes(objectSchema, Array) {
return objectSchema
}
return nil
}
func castWithSchemaPath(v string, objectSchema *base.Schema, propertyPath []string) any {
if schemaPreservesStringValue(schemaForCastingPath(objectSchema, propertyPath)) {
return v
}
if len(propertyPath) == 1 {
return castWithSchema(v, objectSchema, propertyPath[0])
}
return cast(v)
}
func deepObjectPathHasPrefix(prefix, path []string) bool {
if len(prefix) >= len(path) {
return false
}
for i := range prefix {
if prefix[i] != path[i] {
return false
}
}
return true
}
// DeepObjectPathConflict reports whether any deepObject property path is also used
// as a prefix for a nested path, such as obj[nested] and obj[nested][child].
func DeepObjectPathConflict(values []*QueryParam) (prefixParam, nestedParam *QueryParam, ok bool) {
for i := range values {
leftPath := queryParamDeepObjectPath(values[i])
if len(leftPath) == 0 {
continue
}
for j := i + 1; j < len(values); j++ {
rightPath := queryParamDeepObjectPath(values[j])
if len(rightPath) == 0 || slices.Equal(leftPath, rightPath) {
continue
}
if deepObjectPathHasPrefix(leftPath, rightPath) {
return values[i], values[j], true
}
if deepObjectPathHasPrefix(rightPath, leftPath) {
return values[j], values[i], true
}
}
}
return nil, nil, false
}
func setNestedDeepObjectValue(target map[string]interface{}, propertyPath []string, value any) bool {
if len(propertyPath) == 0 {
target[""] = value
return true
}
current := target
for _, propertyName := range propertyPath[:len(propertyPath)-1] {
next, ok := current[propertyName].(map[string]interface{})
if !ok {
if existing, exists := current[propertyName]; exists {
current[propertyName] = []interface{}{existing, value}
return false
}
next = make(map[string]interface{})
current[propertyName] = next
}
current = next
}
propertyName := propertyPath[len(propertyPath)-1]
if existing, exists := current[propertyName]; exists {
if _, existingIsMap := existing.(map[string]interface{}); existingIsMap {
if _, valueIsMap := value.(map[string]interface{}); !valueIsMap {
current[propertyName] = []interface{}{existing, value}
return false
}
}
}
current[propertyName] = value
return true
}
// constructKVFromDelimited is the shared implementation for constructing key=value maps
// from delimited strings (comma, period, semicolon). The delimiter determines how to split
// entries, and each entry is further split on '=' to extract key-value pairs.
func constructKVFromDelimited(values string, delimiter string, sch *base.Schema) map[string]interface{} {
props := make(map[string]interface{})
exploded := strings.Split(values, delimiter)
for i := range exploded {
obK := strings.Split(exploded[i], Equals)
if len(obK) == 2 {
props[obK[0]] = castWithSchema(obK[1], sch, obK[0])
}
}
return props
}
// constructParamMapFromDelimitedEncoding is the shared implementation for constructing
// parameter maps from pipe-delimited or space-delimited query parameter values.
// Entries alternate between keys and values (key|value|key|value or key value key value).
func constructParamMapFromDelimitedEncoding(values []*QueryParam, delimiter string, sch *base.Schema) map[string]interface{} {
decoded := make(map[string]interface{})
for _, v := range values {
props := make(map[string]interface{})
exploded := strings.Split(v.Values[0], delimiter)
for i := range exploded {
if i%2 == 0 && i+1 < len(exploded) {
props[exploded[i]] = castWithSchema(exploded[i+1], sch, exploded[i])
}
}
decoded[v.Key] = props
}
return decoded
}
// ConstructParamMapFromDeepObjectEncoding will construct a map from the query parameters that are encoded as
// deep objects. It's kind of a crazy way to do things, but hey, each to their own.
func ConstructParamMapFromDeepObjectEncoding(values []*QueryParam, sch *base.Schema) map[string]interface{} {
decoded := make(map[string]interface{})
for _, v := range values {
propertyPath := queryParamPropertyPath(v)
castForProp := func(val string) any {
return castWithSchemaPath(val, sch, propertyPath)
}
props, ok := decoded[v.Key].(map[string]interface{})
if !ok {
props = make(map[string]interface{})
decoded[v.Key] = props
}
rawValues := make([]interface{}, len(v.Values))
for i := range v.Values {
rawValues[i] = castForProp(v.Values[i])
}
if DeepObjectAllowsMultipleValues(sch, v) {
setNestedDeepObjectValue(props, propertyPath, rawValues)
continue
}
setNestedDeepObjectValue(props, propertyPath, castForProp(v.Values[0]))
}
return decoded
}
// ConstructParamMapFromQueryParamInput will construct a param map from an existing map of *QueryParam slices.
//
// Deprecated: use ConstructParamMapFromQueryParamInputWithSchema instead.
func ConstructParamMapFromQueryParamInput(values map[string][]*QueryParam) map[string]interface{} {
return ConstructParamMapFromQueryParamInputWithSchema(values, nil)
}
// ConstructParamMapFromPipeEncoding will construct a map from the query parameters that are encoded as
// pipe separated values.
//
// Deprecated: use ConstructParamMapFromPipeEncodingWithSchema instead.
func ConstructParamMapFromPipeEncoding(values []*QueryParam) map[string]interface{} {
return ConstructParamMapFromPipeEncodingWithSchema(values, nil)
}
// ConstructParamMapFromSpaceEncoding will construct a map from the query parameters that are encoded as
// space delimited values.
//
// Deprecated: use ConstructParamMapFromSpaceEncodingWithSchema instead.
func ConstructParamMapFromSpaceEncoding(values []*QueryParam) map[string]interface{} {
return ConstructParamMapFromSpaceEncodingWithSchema(values, nil)
}
// ConstructMapFromCSV will construct a map from a comma separated value string.
//
// Deprecated: use ConstructMapFromCSVWithSchema instead.
func ConstructMapFromCSV(csv string) map[string]interface{} {
return ConstructMapFromCSVWithSchema(csv, nil)
}
// ConstructKVFromCSV will construct a map from a comma separated value string that denotes key value pairs.
//
// Deprecated: use ConstructKVFromCSVWithSchema instead.
func ConstructKVFromCSV(values string) map[string]interface{} {
return ConstructKVFromCSVWithSchema(values, nil)
}
// ConstructKVFromLabelEncoding will construct a map from a period separated value string that denotes key value pairs.
//
// Deprecated: use ConstructKVFromLabelEncodingWithSchema instead.
func ConstructKVFromLabelEncoding(values string) map[string]interface{} {
return ConstructKVFromLabelEncodingWithSchema(values, nil)
}
// ConstructKVFromMatrixCSV will construct a map from a semicolon separated value string that denotes key value pairs.
//
// Deprecated: use ConstructKVFromMatrixCSVWithSchema instead.
func ConstructKVFromMatrixCSV(values string) map[string]interface{} {
return ConstructKVFromMatrixCSVWithSchema(values, nil)
}
// ConstructParamMapFromFormEncodingArray will construct a map from the query parameters that are encoded as
// form encoded values.
//
// Deprecated: use ConstructParamMapFromFormEncodingArrayWithSchema instead.
func ConstructParamMapFromFormEncodingArray(values []*QueryParam) map[string]interface{} {
return ConstructParamMapFromFormEncodingArrayWithSchema(values, nil)
}
// DoesFormParamContainDelimiter will determine if a form parameter contains a delimiter.
func DoesFormParamContainDelimiter(value, style string) bool {
if strings.Contains(value, Comma) && (style == "" || style == Form) {
return true
}
return false
}
// ExplodeQueryValue will explode a query value based on the style (space, pipe, or form/default).
func ExplodeQueryValue(value, style string) []string {
switch style {
case SpaceDelimited:
return strings.Split(value, Space)
case PipeDelimited:
return strings.Split(value, Pipe)
default:
return strings.Split(value, Comma)
}
}
func CollapseCSVIntoFormStyle(key string, value string) string {
return fmt.Sprintf("&%s=%s", key,
strings.Join(strings.Split(value, ","), fmt.Sprintf("&%s=", key)))
}
func CollapseCSVIntoSpaceDelimitedStyle(key string, values []string) string {
return fmt.Sprintf("%s=%s", key, strings.Join(values, "%20"))
}
func CollapseCSVIntoPipeDelimitedStyle(key string, values []string) string {
return fmt.Sprintf("%s=%s", key, strings.Join(values, Pipe))
}
// ConstructParamMapFromQueryParamInputWithSchema constructs a param map from an existing map of
// *QueryParam slices, using the object schema to determine property types before casting.
func ConstructParamMapFromQueryParamInputWithSchema(values map[string][]*QueryParam, sch *base.Schema) map[string]interface{} {
decoded := make(map[string]interface{})
for _, q := range values {
for _, v := range q {
decoded[v.Key] = castWithSchema(v.Values[0], sch, v.Key)
}
}
return decoded
}
// ConstructParamMapFromPipeEncodingWithSchema constructs a map from pipe-delimited query parameters,
// using the object schema to determine property types before casting.
func ConstructParamMapFromPipeEncodingWithSchema(values []*QueryParam, sch *base.Schema) map[string]interface{} {
return constructParamMapFromDelimitedEncoding(values, Pipe, sch)
}
// ConstructParamMapFromSpaceEncodingWithSchema constructs a map from space-delimited query parameters,
// using the object schema to determine property types before casting.
func ConstructParamMapFromSpaceEncodingWithSchema(values []*QueryParam, sch *base.Schema) map[string]interface{} {
return constructParamMapFromDelimitedEncoding(values, Space, sch)
}
// ConstructMapFromCSVWithSchema constructs a map from a comma separated value string,
// using the object schema to determine property types before casting.
func ConstructMapFromCSVWithSchema(csv string, sch *base.Schema) map[string]interface{} {
decoded := make(map[string]interface{})
exploded := strings.Split(csv, Comma)
for i := range exploded {
if i%2 == 0 {
if len(exploded) == i+1 {
break
}
decoded[exploded[i]] = castWithSchema(exploded[i+1], sch, exploded[i])
}
}
return decoded
}
// ConstructKVFromCSVWithSchema constructs a map from a comma-separated key=value string,
// using the object schema to determine property types before casting.
func ConstructKVFromCSVWithSchema(values string, sch *base.Schema) map[string]interface{} {
return constructKVFromDelimited(values, Comma, sch)
}
// ConstructKVFromLabelEncodingWithSchema constructs a map from a period-separated key=value string,
// using the object schema to determine property types before casting.
func ConstructKVFromLabelEncodingWithSchema(values string, sch *base.Schema) map[string]interface{} {
return constructKVFromDelimited(values, Period, sch)
}
// ConstructKVFromMatrixCSVWithSchema constructs a map from a semicolon-separated key=value string,
// using the object schema to determine property types before casting.
func ConstructKVFromMatrixCSVWithSchema(values string, sch *base.Schema) map[string]interface{} {
return constructKVFromDelimited(values, SemiColon, sch)
}
// ConstructParamMapFromFormEncodingArrayWithSchema constructs a map from form-encoded query parameters,
// using the object schema to determine property types before casting.
func ConstructParamMapFromFormEncodingArrayWithSchema(values []*QueryParam, sch *base.Schema) map[string]interface{} {
decoded := make(map[string]interface{})
for _, v := range values {
props := make(map[string]interface{})
exploded := strings.Split(v.Values[0], Comma)
for i := range exploded {
if i%2 == 0 {
if len(exploded) > i+1 {
props[exploded[i]] = castWithSchema(exploded[i+1], sch, exploded[i])
}
}
}
decoded[v.Key] = props
}
return decoded
}
libopenapi-validator-0.13.8/helpers/parameter_utilities_test.go 0000664 0000000 0000000 00000133655 15205340424 0025027 0 ustar 00root root 0000000 0000000 // Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT
package helpers
import (
"net/http"
"testing"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/require"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
)
// Test ExtractParamsForOperation with various HTTP methods
func TestExtractParamsForOperation(t *testing.T) {
pathItem := &v3.PathItem{
// Parameters: []*v3.Parameter{{Name: "param"}},
Get: &v3.Operation{Parameters: []*v3.Parameter{{Name: "getParam"}}},
Post: &v3.Operation{Parameters: []*v3.Parameter{{Name: "postParam"}}},
Put: &v3.Operation{Parameters: []*v3.Parameter{{Name: "putParam"}}},
Delete: &v3.Operation{Parameters: []*v3.Parameter{{Name: "deleteParam"}}},
Options: &v3.Operation{Parameters: []*v3.Parameter{{Name: "optionsParam"}}},
Head: &v3.Operation{Parameters: []*v3.Parameter{{Name: "headParam"}}},
Patch: &v3.Operation{Parameters: []*v3.Parameter{{Name: "patchParam"}}},
Trace: &v3.Operation{Parameters: []*v3.Parameter{{Name: "traceParam"}}},
}
// Test all HTTP methods
tests := []struct {
method string
expected []string // Expected parameter names
}{
{http.MethodGet, []string{"getParam"}},
{http.MethodPost, []string{"postParam"}},
{http.MethodPut, []string{"putParam"}},
{http.MethodDelete, []string{"deleteParam"}},
{http.MethodOptions, []string{"optionsParam"}},
{http.MethodHead, []string{"headParam"}},
{http.MethodPatch, []string{"patchParam"}},
{http.MethodTrace, []string{"traceParam"}},
}
for _, tt := range tests {
// Create a new request with the specified method
request, _ := http.NewRequest(tt.method, "/", nil)
// Extract the parameters for the current request method
params := ExtractParamsForOperation(request, pathItem)
// Check if the number of parameters matches the expected count
require.Len(t, params, len(tt.expected))
// Verify that the extracted parameter names match the expected ones
for i, param := range params {
require.Equal(t, tt.expected[i], param.Name)
}
}
}
// Test cast with different values (bool, int, float, string)
func TestCast(t *testing.T) {
require.Equal(t, true, cast("true"))
require.Equal(t, int64(123), cast("123"))
require.Equal(t, 123.45, cast("123.45"))
require.Equal(t, "test", cast("test"))
}
// Test ExtractSecurityForOperation with various HTTP methods
func TestExtractSecurityForOperation(t *testing.T) {
// Create a PathItem with security requirements for each method
pathItem := &v3.PathItem{
Get: &v3.Operation{Security: []*base.SecurityRequirement{{}}},
Post: &v3.Operation{Security: []*base.SecurityRequirement{{}}},
Put: &v3.Operation{Security: []*base.SecurityRequirement{{}}},
Delete: &v3.Operation{Security: []*base.SecurityRequirement{{}}},
Options: &v3.Operation{
Security: []*base.SecurityRequirement{{}},
},
Head: &v3.Operation{
Security: []*base.SecurityRequirement{{}},
},
Patch: &v3.Operation{
Security: []*base.SecurityRequirement{{}},
},
Trace: &v3.Operation{
Security: []*base.SecurityRequirement{{}},
},
}
// Test all HTTP methods
tests := []struct {
method string
}{
{http.MethodGet},
{http.MethodPost},
{http.MethodPut},
{http.MethodDelete},
{http.MethodOptions},
{http.MethodHead},
{http.MethodPatch},
{http.MethodTrace},
}
for _, tt := range tests {
// Create a new request with the specified method
request, _ := http.NewRequest(tt.method, "/", nil)
// Extract the security requirements for the current request method
security := ExtractSecurityForOperation(request, pathItem)
// Check if the number of security requirements matches the expected count (1 in all cases)
require.Len(t, security, 1, "Failed for method: "+tt.method)
}
}
// Test ExtractSecurityHeaderNames with various security scheme types
func TestExtractSecurityHeaderNames(t *testing.T) {
t.Run("nil inputs", func(t *testing.T) {
require.Nil(t, ExtractSecurityHeaderNames(nil, nil))
require.Nil(t, ExtractSecurityHeaderNames([]*base.SecurityRequirement{}, nil))
require.Nil(t, ExtractSecurityHeaderNames(nil, map[string]*v3.SecurityScheme{}))
})
t.Run("apiKey with in:header", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyAuth": {"read"},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"X-API-Key"}, headers)
})
t.Run("apiKey with in:query should not add header", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyQuery": {
Type: "apiKey",
In: "query",
Name: "api_key",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyQuery": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("apiKey with in:cookie should not add header", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyCookie": {
Type: "apiKey",
In: "cookie",
Name: "session_id",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyCookie": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("http bearer scheme", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"BearerAuth": {
Type: "http",
Scheme: "bearer",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"BearerAuth": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"Authorization"}, headers)
})
t.Run("http basic scheme", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"BasicAuth": {
Type: "http",
Scheme: "basic",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"BasicAuth": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"Authorization"}, headers)
})
t.Run("oauth2 scheme", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"OAuth2": {
Type: "oauth2",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"OAuth2": {"read:users"},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"Authorization"}, headers)
})
t.Run("openIdConnect scheme", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"OpenID": {
Type: "openIdConnect",
OpenIdConnectUrl: "https://example.com/.well-known/openid-configuration",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"OpenID": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"Authorization"}, headers)
})
t.Run("empty security requirement (ContainsEmptyRequirement)", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
}
security := []*base.SecurityRequirement{
{
ContainsEmptyRequirement: true,
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("nil security requirement in slice", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
}
security := []*base.SecurityRequirement{nil}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("security requirement with nil Requirements map", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
}
security := []*base.SecurityRequirement{
{
Requirements: nil,
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("multiple security options OR - different headers", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
"BearerAuth": {
Type: "http",
Scheme: "bearer",
},
}
// OR logic: separate security requirements
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyAuth": {},
}),
},
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"BearerAuth": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Len(t, headers, 2)
require.Contains(t, headers, "X-API-Key")
require.Contains(t, headers, "Authorization")
})
t.Run("combined requirements AND - both headers", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
"BearerAuth": {
Type: "http",
Scheme: "bearer",
},
}
// AND logic: multiple schemes in one requirement
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyAuth": {},
"BearerAuth": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Len(t, headers, 2)
require.Contains(t, headers, "X-API-Key")
require.Contains(t, headers, "Authorization")
})
t.Run("security scheme not found in schemes map", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"SomeOtherScheme": {
Type: "apiKey",
In: "header",
Name: "X-Other",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"NonExistent": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("nil scheme in schemes map", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"NilScheme": nil,
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"NilScheme": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
t.Run("deduplication of Authorization header", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"BearerAuth": {
Type: "http",
Scheme: "bearer",
},
"OAuth2": {
Type: "oauth2",
},
}
// Both use Authorization header
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"BearerAuth": {},
}),
},
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"OAuth2": {"read"},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"Authorization"}, headers)
})
t.Run("case insensitive type matching", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"ApiKeyAuth": {
Type: "APIKEY", // uppercase
In: "HEADER", // uppercase
Name: "X-API-Key",
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"ApiKeyAuth": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Equal(t, []string{"X-API-Key"}, headers)
})
t.Run("unknown security type is ignored", func(t *testing.T) {
schemes := map[string]*v3.SecurityScheme{
"Unknown": {
Type: "mutualTLS", // valid OpenAPI type but doesn't use headers
},
}
security := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"Unknown": {},
}),
},
}
headers := ExtractSecurityHeaderNames(security, schemes)
require.Nil(t, headers)
})
}
func TestConstructParamMapFromDeepObjectEncoding(t *testing.T) {
// Define mock values for testing
values := []*QueryParam{
{Key: "key1", Values: []string{"value1"}, Property: "prop1"},
{Key: "key2", Values: []string{"123"}, Property: "prop2"},
{Key: "key3", Values: []string{"456", "789"}, Property: "prop3"},
}
// Test case 1: Schema is nil
decoded := ConstructParamMapFromDeepObjectEncoding(values, nil)
require.NotNil(t, decoded)
require.Equal(t, "value1", decoded["key1"].(map[string]interface{})["prop1"])
require.Equal(t, int64(123), decoded["key2"].(map[string]interface{})["prop2"])
require.Equal(t, int64(456), decoded["key3"].(map[string]interface{})["prop3"])
// Test case 2: Schema type contains array for the first param (Array handling)
schema := &base.Schema{Type: []string{"array"}}
decoded = ConstructParamMapFromDeepObjectEncoding(values, schema)
require.NotNil(t, decoded)
require.Equal(t, []interface{}{"value1"}, decoded["key1"].(map[string]interface{})["prop1"])
require.Equal(t, []interface{}{int64(123)}, decoded["key2"].(map[string]interface{})["prop2"])
require.Equal(t, []interface{}{int64(456), int64(789)}, decoded["key3"].(map[string]interface{})["prop3"])
// Test case 3: Schema with additional properties that is an array
proxy := base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
})
schema = &base.Schema{
AdditionalProperties: &base.DynamicValue[*base.SchemaProxy, bool]{
A: proxy,
},
}
decoded = ConstructParamMapFromDeepObjectEncoding(values, schema)
require.NotNil(t, decoded)
require.Equal(t, []interface{}{"value1"}, decoded["key1"].(map[string]interface{})["prop1"])
require.Equal(t, []interface{}{int64(123)}, decoded["key2"].(map[string]interface{})["prop2"])
require.Equal(t, []interface{}{int64(456), int64(789)}, decoded["key3"].(map[string]interface{})["prop3"])
// Test case 4: Adding a value to an existing key in the decoded map
valuesWithDup := []*QueryParam{
{Key: "key1", Values: []string{"value2"}, Property: "prop1"},
{Key: "key2", Values: []string{"456"}, Property: "prop2"},
}
decoded = ConstructParamMapFromDeepObjectEncoding(valuesWithDup, nil)
require.NotNil(t, decoded)
require.Equal(t, "value2", decoded["key1"].(map[string]interface{})["prop1"])
require.Equal(t, int64(456), decoded["key2"].(map[string]interface{})["prop2"])
// Test case 5: Schema is not an array (standard object)
nonArraySchema := &base.Schema{Type: []string{"object"}}
decoded = ConstructParamMapFromDeepObjectEncoding(values, nonArraySchema)
require.NotNil(t, decoded)
require.Equal(t, "value1", decoded["key1"].(map[string]interface{})["prop1"])
require.Equal(t, int64(123), decoded["key2"].(map[string]interface{})["prop2"])
require.Equal(t, int64(456), decoded["key3"].(map[string]interface{})["prop3"])
}
func TestConstructParamMapFromDeepObjectEncoding_ElseCase(t *testing.T) {
arraySchema := &base.Schema{Type: []string{"array"}, AdditionalProperties: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{Type: []string{"array"}}),
}}
newValues := []*QueryParam{
{Key: "key1", Values: []string{"456", "789"}, Property: "prop3"},
{Key: "key1", Values: []string{"999", "888"}, Property: "prop3"},
}
decoded := ConstructParamMapFromDeepObjectEncoding(newValues, arraySchema)
require.Equal(t, []interface{}{int64(999), int64(888)}, decoded["key1"].(map[string]interface{})["prop3"])
arraySchema = &base.Schema{Type: []string{"integer"}, AdditionalProperties: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}}
newValues = []*QueryParam{
{Key: "key1", Values: []string{"456", "789"}, Property: "prop3"},
{Key: "key1", Values: []string{"999", "888"}, Property: "prop3"},
}
decoded = ConstructParamMapFromDeepObjectEncoding(newValues, arraySchema)
require.Equal(t, int64(999), decoded["key1"].(map[string]interface{})["prop3"])
}
func TestParseDeepObjectKey(t *testing.T) {
tests := []struct {
name string
key string
expectedBase string
expectedPath []string
expectedOK bool
}{
{
name: "flat",
key: "obj[root]",
expectedBase: "obj",
expectedPath: []string{"root"},
expectedOK: true,
},
{
name: "nested",
key: "obj[nested][child]",
expectedBase: "obj",
expectedPath: []string{"nested", "child"},
expectedOK: true,
},
{
name: "plain key",
key: "obj",
expectedOK: false,
},
{
name: "empty segment",
key: "obj[]",
expectedOK: false,
},
{
name: "trailing text",
key: "obj[root]extra",
expectedOK: false,
},
{
name: "missing closing bracket",
key: "obj[root",
expectedOK: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
baseName, propertyPath, ok := ParseDeepObjectKey(tc.key)
require.Equal(t, tc.expectedOK, ok)
require.Equal(t, tc.expectedBase, baseName)
require.Equal(t, tc.expectedPath, propertyPath)
})
}
}
func TestConstructParamMapFromDeepObjectEncoding_NestedObject(t *testing.T) {
sch := &base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"root": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"nested": base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"child": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"count": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}),
}),
}
values := []*QueryParam{
{Key: "obj", Values: []string{"test1"}, Property: "root", PropertyPath: []string{"root"}},
{Key: "obj", Values: []string{"10"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
{Key: "obj", Values: []string{"42"}, Property: "nested", PropertyPath: []string{"nested", "count"}},
}
decoded := ConstructParamMapFromDeepObjectEncoding(values, sch)
obj := decoded["obj"].(map[string]interface{})
nested := obj["nested"].(map[string]interface{})
require.Equal(t, "test1", obj["root"])
require.Equal(t, "10", nested["child"])
require.Equal(t, int64(42), nested["count"])
}
func TestConstructParamMapFromDeepObjectEncoding_NestedArray(t *testing.T) {
sch := &base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"nested": base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"tags": base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
},
}),
}),
}),
}),
}
values := []*QueryParam{
{Key: "obj", Values: []string{"123", "456"}, Property: "nested", PropertyPath: []string{"nested", "tags"}},
}
decoded := ConstructParamMapFromDeepObjectEncoding(values, sch)
obj := decoded["obj"].(map[string]interface{})
nested := obj["nested"].(map[string]interface{})
require.Equal(t, []interface{}{"123", "456"}, nested["tags"])
require.True(t, DeepObjectAllowsMultipleValues(sch, values[0]))
}
func TestConstructParamMapFromDeepObjectEncoding_NestedAdditionalPropertiesArray(t *testing.T) {
sch := &base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"filters": base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},
AdditionalProperties: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
},
}),
},
}),
}),
}
values := []*QueryParam{
{Key: "obj", Values: []string{"123", "456"}, Property: "filters", PropertyPath: []string{"filters", "tag"}},
}
decoded := ConstructParamMapFromDeepObjectEncoding(values, sch)
obj := decoded["obj"].(map[string]interface{})
filters := obj["filters"].(map[string]interface{})
require.Equal(t, []interface{}{"123", "456"}, filters["tag"])
require.True(t, DeepObjectAllowsMultipleValues(sch, values[0]))
}
func TestDeepObjectPathConflict(t *testing.T) {
tests := []struct {
name string
values []*QueryParam
expect bool
prefixPath []string
nestedPath []string
}{
{
name: "scalar before nested",
values: []*QueryParam{
{Key: "obj", Values: []string{"bad"}, Property: "nested", PropertyPath: []string{"nested"}},
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
},
expect: true,
prefixPath: []string{"nested"},
nestedPath: []string{"nested", "child"},
},
{
name: "nested before scalar",
values: []*QueryParam{
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
{Key: "obj", Values: []string{"bad"}, Property: "nested", PropertyPath: []string{"nested"}},
},
expect: true,
prefixPath: []string{"nested"},
nestedPath: []string{"nested", "child"},
},
{
name: "same array path",
values: []*QueryParam{
{Key: "obj", Values: []string{"alpha"}, Property: "nested", PropertyPath: []string{"nested", "tags"}},
{Key: "obj", Values: []string{"beta"}, Property: "nested", PropertyPath: []string{"nested", "tags"}},
},
},
{
name: "sibling nested paths",
values: []*QueryParam{
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "other"}},
},
},
{
name: "different roots",
values: []*QueryParam{
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested"}},
{Key: "obj", Values: []string{"ok"}, Property: "other", PropertyPath: []string{"other", "child"}},
},
},
{
name: "empty path is ignored",
values: []*QueryParam{
{Key: "obj", Values: []string{"ignored"}},
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
prefixParam, nestedParam, ok := DeepObjectPathConflict(tc.values)
require.Equal(t, tc.expect, ok)
if !tc.expect {
return
}
require.Equal(t, tc.prefixPath, prefixParam.PropertyPath)
require.Equal(t, tc.nestedPath, nestedParam.PropertyPath)
})
}
}
func TestQueryParamDeepObjectPath(t *testing.T) {
require.Nil(t, queryParamDeepObjectPath(nil))
require.Nil(t, queryParamDeepObjectPath(&QueryParam{}))
require.Equal(t, []string{"root"}, queryParamDeepObjectPath(&QueryParam{Property: "root"}))
require.Equal(t, []string{"root", "child"}, queryParamDeepObjectPath(&QueryParam{
Property: "root",
PropertyPath: []string{"root", "child"},
}))
}
func TestSetNestedDeepObjectValue_PreservesConflicts(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
target := make(map[string]interface{})
require.True(t, setNestedDeepObjectValue(target, nil, "root"))
require.Equal(t, "root", target[""])
})
t.Run("scalar before nested", func(t *testing.T) {
target := make(map[string]interface{})
require.True(t, setNestedDeepObjectValue(target, []string{"nested"}, "bad"))
require.False(t, setNestedDeepObjectValue(target, []string{"nested", "child"}, "ok"))
require.IsType(t, []interface{}{}, target["nested"])
})
t.Run("nested before scalar", func(t *testing.T) {
target := make(map[string]interface{})
require.True(t, setNestedDeepObjectValue(target, []string{"nested", "child"}, "ok"))
require.False(t, setNestedDeepObjectValue(target, []string{"nested"}, "bad"))
require.IsType(t, []interface{}{}, target["nested"])
})
}
func TestConstructParamMapFromDeepObjectEncoding_NestedPathConflict(t *testing.T) {
tests := []struct {
name string
values []*QueryParam
}{
{
name: "scalar before nested",
values: []*QueryParam{
{Key: "obj", Values: []string{"bad"}, Property: "nested", PropertyPath: []string{"nested"}},
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
},
},
{
name: "nested before scalar",
values: []*QueryParam{
{Key: "obj", Values: []string{"ok"}, Property: "nested", PropertyPath: []string{"nested", "child"}},
{Key: "obj", Values: []string{"bad"}, Property: "nested", PropertyPath: []string{"nested"}},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
decoded := ConstructParamMapFromDeepObjectEncoding(tc.values, nil)
obj := decoded["obj"].(map[string]interface{})
require.IsType(t, []interface{}{}, obj["nested"])
})
}
}
func TestConstructKVFromLabelEncoding(t *testing.T) {
// Test case 1: Empty input string
values := ""
props := ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Empty(t, props)
// Test case 2: Single valid key-value pair
values = "key1=value1"
props = ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
// Test case 3: Multiple valid key-value pairs
values = "key1=value1.key2=value2"
props = ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
require.Equal(t, "value2", props["key2"])
// Test case 4: Invalid key-value pair (missing equals)
values = "key1=value1.key2"
props = ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
require.NotContains(t, props, "key2") // key2 should be ignored due to invalid format
// Test case 5: Key-value pair where value needs to be cast to int and bool
values = "key1=123.key2=true"
props = ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Equal(t, int64(123), props["key1"]) // cast to int
require.Equal(t, true, props["key2"]) // cast to bool
// Test case 6: Handle multiple valid and invalid key-value pairs
values = "key1=value1.key2.key3=123.key4=true"
props = ConstructKVFromLabelEncoding(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"]) // valid
require.Equal(t, int64(123), props["key3"]) // valid
require.Equal(t, true, props["key4"]) // valid
require.NotContains(t, props, "key2") // invalid, missing value
}
func TestConstructParamMapFromQueryParamInput(t *testing.T) {
// Test case 1: Empty input map
values := map[string][]*QueryParam{}
decoded := ConstructParamMapFromQueryParamInput(values)
require.NotNil(t, decoded)
require.Empty(t, decoded)
// Test case 2: Single entry in the input map
values = map[string][]*QueryParam{
"param1": {
{Key: "param1", Values: []string{"value1"}},
},
}
decoded = ConstructParamMapFromQueryParamInput(values)
require.NotNil(t, decoded)
require.Equal(t, "value1", decoded["param1"])
// Test case 3: Multiple entries in the input map
values = map[string][]*QueryParam{
"param1": {
{Key: "param1", Values: []string{"value1"}},
},
"param2": {
{Key: "param2", Values: []string{"123"}},
},
"param3": {
{Key: "param3", Values: []string{"true"}},
},
}
decoded = ConstructParamMapFromQueryParamInput(values)
require.NotNil(t, decoded)
require.Equal(t, "value1", decoded["param1"])
require.Equal(t, int64(123), decoded["param2"]) // cast to int
require.Equal(t, true, decoded["param3"]) // cast to bool
// Test case 4: Handle multiple values but only the first value is used
values = map[string][]*QueryParam{
"param1": {
{Key: "param1", Values: []string{"first", "second"}},
},
}
decoded = ConstructParamMapFromQueryParamInput(values)
require.NotNil(t, decoded)
require.Equal(t, "first", decoded["param1"]) // Only the first value is used
// Test case 5: Handle different types of values
values = map[string][]*QueryParam{
"intParam": {
{Key: "intParam", Values: []string{"42"}},
},
"boolParam": {
{Key: "boolParam", Values: []string{"false"}},
},
"stringParam": {
{Key: "stringParam", Values: []string{"hello"}},
},
}
decoded = ConstructParamMapFromQueryParamInput(values)
require.NotNil(t, decoded)
require.Equal(t, int64(42), decoded["intParam"])
require.Equal(t, false, decoded["boolParam"])
require.Equal(t, "hello", decoded["stringParam"])
}
// Test ConstructParamMapFromPipeEncoding
func TestConstructParamMapFromPipeEncoding(t *testing.T) {
params := []*QueryParam{
{Key: "key1", Values: []string{"name|value"}},
}
result := ConstructParamMapFromPipeEncoding(params)
require.Equal(t, "value", result["key1"].(map[string]interface{})["name"])
}
// Test ConstructParamMapFromSpaceEncoding
func TestConstructParamMapFromSpaceEncoding(t *testing.T) {
params := []*QueryParam{
{Key: "key1", Values: []string{"name value"}},
}
result := ConstructParamMapFromSpaceEncoding(params)
require.Equal(t, "value", result["key1"].(map[string]interface{})["name"])
}
// Test ConstructMapFromCSV
func TestConstructMapFromCSV(t *testing.T) {
result := ConstructMapFromCSV("key1,value1,key2,value2")
require.Equal(t, "value1", result["key1"])
require.Equal(t, "value2", result["key2"])
// add odd number of keys/values
result = ConstructMapFromCSV("key1,value1,key2")
require.Equal(t, "value1", result["key1"])
}
// Test ConstructKVFromCSV
func TestConstructKVFromCSV(t *testing.T) {
result := ConstructKVFromCSV("key1=value1,key2=value2")
require.Equal(t, "value1", result["key1"])
require.Equal(t, "value2", result["key2"])
}
// Test CollapseCSVIntoFormStyle
func TestCollapseCSVIntoFormStyle(t *testing.T) {
result := CollapseCSVIntoFormStyle("key", "value1,value2")
require.Equal(t, "&key=value1&key=value2", result)
}
// Test CollapseCSVIntoSpaceDelimitedStyle
func TestCollapseCSVIntoSpaceDelimitedStyle(t *testing.T) {
result := CollapseCSVIntoSpaceDelimitedStyle("key", []string{"value1", "value2"})
require.Equal(t, "key=value1%20value2", result)
}
// Test CollapseCSVIntoPipeDelimitedStyle
func TestCollapseCSVIntoPipeDelimitedStyle(t *testing.T) {
result := CollapseCSVIntoPipeDelimitedStyle("key", []string{"value1", "value2"})
require.Equal(t, "key=value1|value2", result)
}
// Test DoesFormParamContainDelimiter
func TestDoesFormParamContainDelimiter(t *testing.T) {
require.True(t, DoesFormParamContainDelimiter("value1,value2", ""))
require.False(t, DoesFormParamContainDelimiter("value1 value2", ""))
}
// Test ExplodeQueryValue
func TestExplodeQueryValue(t *testing.T) {
require.Equal(t, []string{"value1", "value2"}, ExplodeQueryValue("value1,value2", ""))
require.Equal(t, []string{"value1", "value2"}, ExplodeQueryValue("value1 value2", "spaceDelimited"))
require.Equal(t, []string{"value1", "value2"}, ExplodeQueryValue("value1|value2", "pipeDelimited"))
}
func TestConstructKVFromMatrixCSV(t *testing.T) {
// Test case 1: Empty input string
values := ""
props := ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Empty(t, props)
// Test case 2: Single valid key-value pair
values = "key1=value1"
props = ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
// Test case 3: Multiple valid key-value pairs
values = "key1=value1;key2=value2"
props = ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
require.Equal(t, "value2", props["key2"])
// Test case 4: Invalid key-value pair (missing equals)
values = "key1=value1;key2"
props = ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"])
require.NotContains(t, props, "key2") // key2 should be ignored due to invalid format
// Test case 5: Key-value pair where value needs to be cast to int and bool
values = "key1=123;key2=true"
props = ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Equal(t, int64(123), props["key1"]) // cast to int
require.Equal(t, true, props["key2"]) // cast to bool
// Test case 6: Handle multiple valid and invalid key-value pairs
values = "key1=value1;key2;key3=456;key4=false"
props = ConstructKVFromMatrixCSV(values)
require.NotNil(t, props)
require.Equal(t, "value1", props["key1"]) // valid
require.Equal(t, int64(456), props["key3"]) // valid
require.Equal(t, false, props["key4"]) // valid
require.NotContains(t, props, "key2") // invalid, missing value
}
func TestConstructParamMapFromFormEncodingArray(t *testing.T) {
// Test case 1: Empty input
values := []*QueryParam{}
decoded := ConstructParamMapFromFormEncodingArray(values)
require.NotNil(t, decoded)
require.Empty(t, decoded)
// Test case 2: Single QueryParam with valid key-value pairs
values = []*QueryParam{
{
Key: "param1",
Values: []string{"key1,value1,key2,value2"},
},
}
decoded = ConstructParamMapFromFormEncodingArray(values)
require.NotNil(t, decoded)
require.Contains(t, decoded, "param1")
require.Equal(t, "value1", decoded["param1"].(map[string]interface{})["key1"])
require.Equal(t, "value2", decoded["param1"].(map[string]interface{})["key2"])
// Test case 3: Multiple QueryParam entries
values = []*QueryParam{
{
Key: "param1",
Values: []string{"key1,value1"},
},
{
Key: "param2",
Values: []string{"key3,value3,key4,value4"},
},
}
decoded = ConstructParamMapFromFormEncodingArray(values)
require.NotNil(t, decoded)
require.Contains(t, decoded, "param1")
require.Equal(t, "value1", decoded["param1"].(map[string]interface{})["key1"])
require.Equal(t, "value3", decoded["param2"].(map[string]interface{})["key3"])
require.Equal(t, "value4", decoded["param2"].(map[string]interface{})["key4"])
// Test case 4: Odd number of values (incomplete key-value pair)
values = []*QueryParam{
{
Key: "param1",
Values: []string{"key1,value1,key2"},
},
}
decoded = ConstructParamMapFromFormEncodingArray(values)
require.NotNil(t, decoded)
require.Contains(t, decoded, "param1")
require.Equal(t, "value1", decoded["param1"].(map[string]interface{})["key1"])
require.NotContains(t, decoded["param1"].(map[string]interface{}), "key2") // Invalid, no value for key2
// Test case 5: Casting different types (int, bool, string)
values = []*QueryParam{
{
Key: "param1",
Values: []string{"key1,123,key2,true,key3,hello"},
},
}
decoded = ConstructParamMapFromFormEncodingArray(values)
require.NotNil(t, decoded)
require.Contains(t, decoded, "param1")
require.Equal(t, int64(123), decoded["param1"].(map[string]interface{})["key1"]) // cast to int
require.Equal(t, true, decoded["param1"].(map[string]interface{})["key2"]) // cast to bool
require.Equal(t, "hello", decoded["param1"].(map[string]interface{})["key3"]) // string remains string
}
func TestCastWithSchema(t *testing.T) {
t.Run("returns string unchanged when schema property type is string", func(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"item_count": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
result := castWithSchema("10", sch, "item_count")
require.Equal(t, "10", result)
})
t.Run("casts to int64 when schema property type is integer", func(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"count": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}
result := castWithSchema("10", sch, "count")
require.Equal(t, int64(10), result)
})
t.Run("falls back to cast when no schema provided", func(t *testing.T) {
result := castWithSchema("10", nil, "anything")
require.Equal(t, int64(10), result)
})
t.Run("falls back to cast when property not found in schema", func(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"other": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
result := castWithSchema("10", sch, "missing")
require.Equal(t, int64(10), result)
})
t.Run("falls back to cast when schema has no properties", func(t *testing.T) {
sch := &base.Schema{Type: []string{"object"}}
result := castWithSchema("10", sch, "anything")
require.Equal(t, int64(10), result)
})
}
func TestConstructParamMapFromQueryParamInputWithSchema(t *testing.T) {
t.Run("preserves string '10' when schema says string", func(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"item_count": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"search_term": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
values := map[string][]*QueryParam{
"item_count": {
{Key: "item_count", Values: []string{"10"}},
},
"search_term": {
{Key: "search_term", Values: []string{"foo"}},
},
}
decoded := ConstructParamMapFromQueryParamInputWithSchema(values, sch)
require.Equal(t, "10", decoded["item_count"])
require.Equal(t, "foo", decoded["search_term"])
})
t.Run("casts numeric values when schema says integer", func(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"count": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}
values := map[string][]*QueryParam{
"count": {
{Key: "count", Values: []string{"42"}},
},
}
decoded := ConstructParamMapFromQueryParamInputWithSchema(values, sch)
require.Equal(t, int64(42), decoded["count"])
})
t.Run("falls back to heuristic when no schema", func(t *testing.T) {
values := map[string][]*QueryParam{
"count": {
{Key: "count", Values: []string{"42"}},
},
}
decoded := ConstructParamMapFromQueryParamInputWithSchema(values, nil)
require.Equal(t, int64(42), decoded["count"])
})
}
func TestConstructParamMapFromDeepObjectEncoding_WithSchema(t *testing.T) {
t.Run("preserves string values when schema property type is string", func(t *testing.T) {
sch := &base.Schema{
Type: []string{"object"},
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"prop1": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
values := []*QueryParam{
{Key: "key1", Values: []string{"123"}, Property: "prop1"},
}
decoded := ConstructParamMapFromDeepObjectEncoding(values, sch)
require.Equal(t, "123", decoded["key1"].(map[string]interface{})["prop1"])
})
t.Run("preserves string values when root array items are strings", func(t *testing.T) {
sch := &base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{
A: base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
},
}
require.Equal(t, "123", castWithSchema("123", sch, "unknown"))
})
}
func TestConstructParamMapFromPipeEncodingWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"name": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"count": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}
params := []*QueryParam{
{Key: "key1", Values: []string{"name|123|count|42"}},
}
result := ConstructParamMapFromPipeEncodingWithSchema(params, sch)
props := result["key1"].(map[string]interface{})
require.Equal(t, "123", props["name"]) // string because schema says string
require.Equal(t, int64(42), props["count"]) // int because schema says integer
}
func TestConstructParamMapFromSpaceEncodingWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"name": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
params := []*QueryParam{
{Key: "key1", Values: []string{"name 456"}},
}
result := ConstructParamMapFromSpaceEncodingWithSchema(params, sch)
props := result["key1"].(map[string]interface{})
require.Equal(t, "456", props["name"]) // string because schema says string
}
func TestConstructMapFromCSVWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"id": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"rank": base.CreateSchemaProxy(&base.Schema{Type: []string{"number"}}),
}),
}
result := ConstructMapFromCSVWithSchema("id,99,rank,3.5", sch)
require.Equal(t, "99", result["id"]) // string
require.Equal(t, 3.5, result["rank"]) // number
// odd number of values
result = ConstructMapFromCSVWithSchema("id,99,rank", sch)
require.Equal(t, "99", result["id"])
require.NotContains(t, result, "rank")
}
func TestConstructKVFromCSVWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"key1": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"key2": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}
result := ConstructKVFromCSVWithSchema("key1=100,key2=200", sch)
require.Equal(t, "100", result["key1"]) // string
require.Equal(t, int64(200), result["key2"]) // integer
}
func TestConstructKVFromLabelEncodingWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"key1": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"key2": base.CreateSchemaProxy(&base.Schema{Type: []string{"boolean"}}),
}),
}
result := ConstructKVFromLabelEncodingWithSchema("key1=true.key2=true", sch)
require.Equal(t, "true", result["key1"]) // string because schema says string
require.Equal(t, true, result["key2"]) // bool because schema says boolean
// invalid pair (missing equals) is ignored
result = ConstructKVFromLabelEncodingWithSchema("key1=val.key2", sch)
require.Equal(t, "val", result["key1"])
require.NotContains(t, result, "key2")
}
func TestConstructKVFromMatrixCSVWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"key1": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
}),
}
result := ConstructKVFromMatrixCSVWithSchema("key1=456;key2=789", sch)
require.Equal(t, "456", result["key1"]) // string
require.Equal(t, int64(789), result["key2"]) // no schema for key2, falls back to cast
// invalid pair
result = ConstructKVFromMatrixCSVWithSchema("key1=val;key2", sch)
require.Equal(t, "val", result["key1"])
require.NotContains(t, result, "key2")
}
func TestConstructParamMapFromFormEncodingArrayWithSchema(t *testing.T) {
sch := &base.Schema{
Properties: orderedmap.ToOrderedMap(map[string]*base.SchemaProxy{
"key1": base.CreateSchemaProxy(&base.Schema{Type: []string{"string"}}),
"key2": base.CreateSchemaProxy(&base.Schema{Type: []string{"integer"}}),
}),
}
values := []*QueryParam{
{Key: "param1", Values: []string{"key1,123,key2,456"}},
}
decoded := ConstructParamMapFromFormEncodingArrayWithSchema(values, sch)
props := decoded["param1"].(map[string]interface{})
require.Equal(t, "123", props["key1"]) // string
require.Equal(t, int64(456), props["key2"]) // integer
// odd number of values â incomplete pair ignored
values = []*QueryParam{
{Key: "param1", Values: []string{"key1,val,key2"}},
}
decoded = ConstructParamMapFromFormEncodingArrayWithSchema(values, sch)
props = decoded["param1"].(map[string]interface{})
require.Equal(t, "val", props["key1"])
require.NotContains(t, props, "key2")
}
func TestEffectiveSecurityForOperation(t *testing.T) {
globalSecurity := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"GlobalAuth": {},
}),
},
}
opSecurity := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"OpAuth": {},
}),
},
}
t.Run("operation-level security wins over global", func(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{Security: opSecurity},
}
request, _ := http.NewRequest(http.MethodGet, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.Equal(t, opSecurity, result)
})
t.Run("nil operation security falls back to global", func(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{}, // Security is nil
}
request, _ := http.NewRequest(http.MethodGet, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.Equal(t, globalSecurity, result)
})
t.Run("empty operation security means no security (opt-out)", func(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{Security: []*base.SecurityRequirement{}},
}
request, _ := http.NewRequest(http.MethodGet, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.NotNil(t, result)
require.Len(t, result, 0)
})
t.Run("both nil returns nil", func(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{},
}
request, _ := http.NewRequest(http.MethodGet, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, nil)
require.Nil(t, result)
})
t.Run("nil operation falls back to global", func(t *testing.T) {
pathItem := &v3.PathItem{} // no Get operation
request, _ := http.NewRequest(http.MethodGet, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.Equal(t, globalSecurity, result)
})
t.Run("HEAD falls back to GET operation security", func(t *testing.T) {
pathItem := &v3.PathItem{
Get: &v3.Operation{Security: opSecurity},
}
request, _ := http.NewRequest(http.MethodHead, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.Equal(t, opSecurity, result)
})
t.Run("HEAD with explicit Head security uses Head", func(t *testing.T) {
headSecurity := []*base.SecurityRequirement{
{
Requirements: orderedmap.ToOrderedMap(map[string][]string{
"HeadAuth": {},
}),
},
}
pathItem := &v3.PathItem{
Get: &v3.Operation{Security: opSecurity},
Head: &v3.Operation{Security: headSecurity},
}
request, _ := http.NewRequest(http.MethodHead, "/", nil)
result := EffectiveSecurityForOperation(request, pathItem, globalSecurity)
require.Equal(t, headSecurity, result)
})
}
libopenapi-validator-0.13.8/helpers/path_finder.go 0000664 0000000 0000000 00000010617 15205340424 0022170 0 ustar 00root root 0000000 0000000 // Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import (
"fmt"
"strings"
"unicode"
"github.com/santhosh-tekuri/jsonschema/v6"
)
// ExtractJSONPathFromValidationError traverses and processes a ValidationError to construct a JSONPath string representation of its instance location.
func ExtractJSONPathFromValidationError(e *jsonschema.ValidationError) string {
if len(e.Causes) > 0 {
for _, cause := range e.Causes {
ExtractJSONPathFromValidationError(cause)
}
}
if len(e.InstanceLocation) > 0 {
var b strings.Builder
b.WriteString("$")
for _, seg := range e.InstanceLocation {
switch {
case isNumeric(seg):
fmt.Fprintf(&b, "[%s]", seg)
case isSimpleIdentifier(seg):
b.WriteByte('.')
b.WriteString(seg)
default:
esc := escapeBracketString(seg)
b.WriteString("['")
b.WriteString(esc)
b.WriteString("']")
}
}
return b.String()
}
return ""
}
// isNumeric returns true if s is a nonâempty string of digits.
func isNumeric(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r < '0' || r > '9' {
return false
}
}
return true
}
// isSimpleIdentifier returns true if s matches [A-Za-z_][A-Za-z0-9_]*.
func isSimpleIdentifier(s string) bool {
for i, r := range s {
if i == 0 {
if !unicode.IsLetter(r) && r != '_' {
return false
}
} else {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' {
return false
}
}
}
return len(s) > 0
}
// escapeBracketString escapes backslashes and singleâquotes for inside ['...']
func escapeBracketString(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `'`, `\'`)
return s
}
// ExtractJSONPathsFromValidationErrors takes a slice of ValidationError pointers and returns a slice of JSONPath strings
func ExtractJSONPathsFromValidationErrors(errors []*jsonschema.ValidationError) []string {
var paths []string
for _, err := range errors {
path := ExtractJSONPathFromValidationError(err)
if path != "" {
paths = append(paths, path)
}
}
return paths
}
// ExtractFieldNameFromInstanceLocation returns the last segment of the instance location as the field name
func ExtractFieldNameFromInstanceLocation(instanceLocation []string) string {
if len(instanceLocation) == 0 {
return ""
}
return instanceLocation[len(instanceLocation)-1]
}
// ExtractFieldNameFromStringLocation returns the last segment of the instance location as the field name
// when the location is provided as a string path
func ExtractFieldNameFromStringLocation(instanceLocation string) string {
if instanceLocation == "" {
return ""
}
// Handle string format like "/properties/email" or "/0/name"
segments := strings.Split(strings.Trim(instanceLocation, "/"), "/")
if len(segments) == 0 || (len(segments) == 1 && segments[0] == "") {
return ""
}
return segments[len(segments)-1]
}
// ExtractJSONPathFromInstanceLocation creates a JSONPath string from instance location segments
func ExtractJSONPathFromInstanceLocation(instanceLocation []string) string {
if len(instanceLocation) == 0 {
return ""
}
var b strings.Builder
b.WriteString("$")
for _, seg := range instanceLocation {
switch {
case isNumeric(seg):
fmt.Fprintf(&b, "[%s]", seg)
case isSimpleIdentifier(seg):
b.WriteByte('.')
b.WriteString(seg)
default:
esc := escapeBracketString(seg)
b.WriteString("['")
b.WriteString(esc)
b.WriteString("']")
}
}
return b.String()
}
// ExtractJSONPathFromStringLocation creates a JSONPath string from string-based instance location
func ExtractJSONPathFromStringLocation(instanceLocation string) string {
if instanceLocation == "" {
return ""
}
// Convert string format like "/properties/email" to array format
segments := strings.Split(strings.Trim(instanceLocation, "/"), "/")
if len(segments) == 0 || (len(segments) == 1 && segments[0] == "") {
return ""
}
return ExtractJSONPathFromInstanceLocation(segments)
}
// ConvertStringLocationToPathSegments converts a string-based instance location to path segments array
// Handles edge cases like empty strings and root-only paths
func ConvertStringLocationToPathSegments(instanceLocation string) []string {
if instanceLocation == "" {
return []string{}
}
segments := strings.Split(strings.Trim(instanceLocation, "/"), "/")
if len(segments) == 1 && segments[0] == "" {
return []string{}
}
return segments
}
libopenapi-validator-0.13.8/helpers/path_finder_test.go 0000664 0000000 0000000 00000033460 15205340424 0023230 0 ustar 00root root 0000000 0000000 // Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import (
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/stretchr/testify/assert"
)
func TestDiveIntoValidationError(t *testing.T) {
tests := []struct {
name string
error *jsonschema.ValidationError
expected string
}{
{
name: "empty instance location",
error: &jsonschema.ValidationError{
InstanceLocation: []string{},
},
expected: "",
},
{
name: "numeric path segments",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"root", "array", "0", "1"},
},
expected: "$.root.array[0][1]",
},
{
name: "simple identifier path segments",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"user", "name", "first"},
},
expected: "$.user.name.first",
},
{
name: "complex path segments requiring escaping",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"user", "name-with-dash", "special'quote", "back\\slash"},
},
expected: "$.user['name-with-dash']['special\\'quote']['back\\\\slash']",
},
{
name: "mixed path segments",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"users", "0", "address", "street-name", "123"},
},
expected: "$.users[0].address['street-name'][123]",
},
{
name: "with nested causes",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"root"},
Causes: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"nested", "error"},
},
},
},
expected: "$.root",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ExtractJSONPathFromValidationError(tt.error)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsNumeric(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"123", true},
{"0", true},
{"01", true},
{"", false},
{"abc", false},
{"123abc", false},
{"12.3", false},
{"-123", false},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := isNumeric(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsSimpleIdentifier(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"abc", true},
{"a123", true},
{"_abc", true},
{"_123", true},
{"abc_123", true},
{"", false},
{"123abc", false},
{"abc-def", false},
{"abc.def", false},
{"abc def", false},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := isSimpleIdentifier(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestEscapeBracketString(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"normal", "normal"},
{"with'quote", "with\\'quote"},
{"with\\backslash", "with\\\\backslash"},
{"with'quote\\and\\backslash", "with\\'quote\\\\and\\\\backslash"},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := escapeBracketString(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestDiveIntoValidationErrorRecursion tests that the function properly handles
// recursive traversal through nested validation errors.
func TestDiveIntoValidationErrorRecursion(t *testing.T) {
childError1 := &jsonschema.ValidationError{
InstanceLocation: []string{"child1", "prop"},
}
childError2 := &jsonschema.ValidationError{
InstanceLocation: []string{"child2", "0", "name"},
}
parentError := &jsonschema.ValidationError{
InstanceLocation: []string{"parent"},
Causes: []*jsonschema.ValidationError{childError1, childError2},
}
// The parent error should return its own path
result := ExtractJSONPathFromValidationError(parentError)
assert.Equal(t, "$.parent", result)
// Verify the child errors return their paths correctly when called directly
assert.Equal(t, "$.child1.prop", ExtractJSONPathFromValidationError(childError1))
assert.Equal(t, "$.child2[0].name", ExtractJSONPathFromValidationError(childError2))
}
// TestDiveIntoValidationErrorEdgeCases tests edge cases including empty strings and unusual characters
func TestDiveIntoValidationErrorEdgeCases(t *testing.T) {
tests := []struct {
name string
error *jsonschema.ValidationError
expected string
}{
{
name: "empty strings as elements",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"", "property"},
},
expected: "$[''].property",
},
{
name: "Unicode characters",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"đ", "unicode_property"},
},
expected: "$['đ'].unicode_property",
},
{
name: "null causes",
error: &jsonschema.ValidationError{
InstanceLocation: []string{"root"},
Causes: nil,
},
expected: "$.root",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ExtractJSONPathFromValidationError(tt.error)
assert.Equal(t, tt.expected, result)
})
}
}
// TestExtractJSONPathsFromValidationErrors tests the ExtractJSONPathsFromValidationErrors function
func TestExtractJSONPathsFromValidationErrors(t *testing.T) {
tests := []struct {
name string
errors []*jsonschema.ValidationError
expected []string
}{
{
name: "nil errors",
errors: nil,
expected: nil,
},
{
name: "empty errors",
errors: []*jsonschema.ValidationError{},
expected: nil,
},
{
name: "single error with empty path",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{},
},
},
expected: nil,
},
{
name: "single error with path",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"root", "property"},
},
},
expected: []string{"$.root.property"},
},
{
name: "multiple errors with paths",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"users", "0", "name"},
},
{
InstanceLocation: []string{"users", "1", "address", "street"},
},
},
expected: []string{"$.users[0].name", "$.users[1].address.street"},
},
{
name: "mixed errors - some with empty paths",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{},
},
{
InstanceLocation: []string{"users", "0", "name"},
},
{
InstanceLocation: []string{},
},
},
expected: []string{"$.users[0].name"},
},
{
name: "complex paths with special characters",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"data", "special-field", "nested"},
},
{
InstanceLocation: []string{"data", "array", "0", "item's", "property"},
},
},
expected: []string{"$.data['special-field'].nested", "$.data.array[0]['item\\'s'].property"},
},
{
name: "with nested causes",
errors: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"parent"},
Causes: []*jsonschema.ValidationError{
{
InstanceLocation: []string{"child", "property"},
},
},
},
},
expected: []string{"$.parent"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ExtractJSONPathsFromValidationErrors(tt.errors)
assert.Equal(t, tt.expected, result)
})
}
}
func TestExtractFieldNameFromInstanceLocation(t *testing.T) {
testCases := []struct {
name string
instancePath []string
expected string
}{
{
name: "Empty path",
instancePath: []string{},
expected: "",
},
{
name: "Single field",
instancePath: []string{"name"},
expected: "name",
},
{
name: "Nested field",
instancePath: []string{"user", "profile", "email"},
expected: "email",
},
{
name: "Array index",
instancePath: []string{"users", "0", "name"},
expected: "name",
},
{
name: "Complex path",
instancePath: []string{"root", "nested", "array", "1", "field"},
expected: "field",
},
{
name: "Field with special characters",
instancePath: []string{"user", "email-address", "value"},
expected: "value",
},
{
name: "Numeric field name",
instancePath: []string{"data", "123"},
expected: "123",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ExtractFieldNameFromInstanceLocation(tc.instancePath)
assert.Equal(t, tc.expected, result)
})
}
}
func TestExtractJSONPathFromInstanceLocation(t *testing.T) {
testCases := []struct {
name string
instancePath []string
expected string
}{
{
name: "Empty path",
instancePath: []string{},
expected: "",
},
{
name: "Simple field",
instancePath: []string{"name"},
expected: "$.name",
},
{
name: "Nested object fields",
instancePath: []string{"user", "profile", "email"},
expected: "$.user.profile.email",
},
{
name: "Array access",
instancePath: []string{"users", "0", "name"},
expected: "$.users[0].name",
},
{
name: "Mixed array and object",
instancePath: []string{"data", "items", "1", "properties", "value"},
expected: "$.data.items[1].properties.value",
},
{
name: "Field with dashes",
instancePath: []string{"user", "email-address"},
expected: "$.user['email-address']",
},
{
name: "Field with spaces",
instancePath: []string{"user", "full name"},
expected: "$.user['full name']",
},
{
name: "Field with special characters",
instancePath: []string{"data", "field'with'quotes"},
expected: "$.data['field\\'with\\'quotes']",
},
{
name: "Field with backslash",
instancePath: []string{"data", "field\\with\\backslash"},
expected: "$.data['field\\\\with\\\\backslash']",
},
{
name: "Unicode field name",
instancePath: []string{"đ", "unicode_field"},
expected: "$['đ'].unicode_field",
},
{
name: "Numeric array indices",
instancePath: []string{"matrix", "0", "1", "value"},
expected: "$.matrix[0][1].value",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ExtractJSONPathFromInstanceLocation(tc.instancePath)
assert.Equal(t, tc.expected, result)
})
}
}
func TestExtractFieldNameFromStringLocation(t *testing.T) {
testCases := []struct {
name string
instancePath string
expected string
}{
{
name: "Empty path",
instancePath: "",
expected: "",
},
{
name: "Single field",
instancePath: "/name",
expected: "name",
},
{
name: "Nested field",
instancePath: "/user/profile/email",
expected: "email",
},
{
name: "Array index",
instancePath: "/users/0/name",
expected: "name",
},
{
name: "Complex path",
instancePath: "/root/nested/array/1/field",
expected: "field",
},
{
name: "Field with special characters",
instancePath: "/user/email-address/value",
expected: "value",
},
{
name: "Root path only",
instancePath: "/",
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ExtractFieldNameFromStringLocation(tc.instancePath)
assert.Equal(t, tc.expected, result)
})
}
}
func TestExtractJSONPathFromStringLocation(t *testing.T) {
testCases := []struct {
name string
instancePath string
expected string
}{
{
name: "Empty path",
instancePath: "",
expected: "",
},
{
name: "Simple field",
instancePath: "/name",
expected: "$.name",
},
{
name: "Nested object fields",
instancePath: "/user/profile/email",
expected: "$.user.profile.email",
},
{
name: "Array access",
instancePath: "/users/0/name",
expected: "$.users[0].name",
},
{
name: "Mixed array and object",
instancePath: "/data/items/1/properties/value",
expected: "$.data.items[1].properties.value",
},
{
name: "Root path only",
instancePath: "/",
expected: "",
},
{
name: "Complex nested path",
instancePath: "/matrix/0/1/value",
expected: "$.matrix[0][1].value",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ExtractJSONPathFromStringLocation(tc.instancePath)
assert.Equal(t, tc.expected, result)
})
}
}
func TestConvertStringLocationToPathSegments(t *testing.T) {
testCases := []struct {
name string
instancePath string
expected []string
}{
{
name: "Empty string",
instancePath: "",
expected: []string{},
},
{
name: "Root path only",
instancePath: "/",
expected: []string{},
},
{
name: "Single field",
instancePath: "/name",
expected: []string{"name"},
},
{
name: "Multiple fields",
instancePath: "/user/profile/email",
expected: []string{"user", "profile", "email"},
},
{
name: "Array index",
instancePath: "/users/0/name",
expected: []string{"users", "0", "name"},
},
{
name: "Multiple array indices",
instancePath: "/matrix/0/1/value",
expected: []string{"matrix", "0", "1", "value"},
},
{
name: "Field with special characters",
instancePath: "/user/email-address",
expected: []string{"user", "email-address"},
},
{
name: "Complex nested path",
instancePath: "/data/items/1/properties/field-name",
expected: []string{"data", "items", "1", "properties", "field-name"},
},
{
name: "Leading and trailing slashes",
instancePath: "///user/name///",
expected: []string{"user", "name"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ConvertStringLocationToPathSegments(tc.instancePath)
assert.Equal(t, tc.expected, result)
})
}
}
libopenapi-validator-0.13.8/helpers/regex_maker.go 0000664 0000000 0000000 00000007720 15205340424 0022177 0 ustar 00root root 0000000 0000000 package helpers
import (
"bytes"
"fmt"
"regexp"
"strings"
)
var (
baseDefaultPattern = "[^/]*"
DefaultPatternRegex = regexp.MustCompile("^([^/]*)$")
DefaultPatternRegexString = DefaultPatternRegex.String()
)
// GetRegexForPath returns a compiled regular expression for the given path template.
//
// This function takes a path template string `tpl` and generates a regular expression
// that matches the structure of the template. The template can include placeholders
// enclosed in braces `{}` with optional custom patterns.
//
// Placeholders in the template can be defined as:
// - `{name}`: Matches any sequence of characters except '/'
// - `{name:pattern}`: Matches the specified custom pattern
//
// The function ensures that the template is well-formed, with balanced and properly
// nested braces. If the template is invalid, an error is returned.
//
// Parameters:
// - tpl: The path template string to convert into a regular expression.
//
// Returns:
// - *regexp.Regexp: A compiled regular expression that matches the template.
// - error: An error if the template is invalid or the regular expression cannot be compiled.
//
// Example:
//
// regex, err := GetRegexForPath("/orders/{id:[0-9]+}/items/{itemId}")
// // regex: ^/orders/([0-9]+)/items/([^/]+)$
// // err: nil
func GetRegexForPath(tpl string) (*regexp.Regexp, error) {
// Check if it is well-formed.
idxs, errBraces := BraceIndices(tpl)
if errBraces != nil {
return nil, errBraces
}
// Backup the original.
template := tpl
pattern := bytes.NewBufferString("^")
var end int
for i := 0; i < len(idxs); i += 2 {
// Set all values we are interested in.
raw := tpl[end:idxs[i]]
end = idxs[i+1]
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
name := parts[0]
patt := baseDefaultPattern
if len(parts) == 2 {
patt = parts[1]
}
// Name or pattern can't be empty.
if name == "" || patt == "" {
return nil, fmt.Errorf("missing name or pattern in %q", tpl[idxs[i]:end])
}
// Build the regexp pattern.
_, err := fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
if err != nil {
return nil, err
}
}
// Add the remaining.
raw := tpl[end:]
pattern.WriteString(regexp.QuoteMeta(raw))
pattern.WriteByte('$')
patternString := pattern.String()
if patternString == DefaultPatternRegexString {
return DefaultPatternRegex, nil
}
// Compile full regexp.
reg, errCompile := regexp.Compile(patternString)
if errCompile != nil {
return nil, errCompile
}
// Check for capturing groups which used to work in older versions
if reg.NumSubexp() != len(idxs)/2 {
return nil, fmt.Errorf("route %s contains capture groups in its regexp. Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)", template)
}
// Done!
return reg, nil
}
// BraceIndices returns the indices of the opening and closing braces in a string.
//
// It scans the input string `s` and identifies the positions of matching pairs
// of braces ('{' and '}'). The function ensures that the braces are balanced
// and properly nested.
//
// If the braces are unbalanced or improperly nested, an error is returned.
//
// Parameters:
// - s: The input string to scan for braces.
//
// Returns:
// - []int: A slice of integers where each pair of indices represents the
// start and end positions of a matching pair of braces.
// - error: An error if the braces are unbalanced or improperly nested.
//
// Example:
//
// indices, err := BraceIndices("/orders/{id}/items/{itemId}")
// // indices: [8, 12, 19, 26]
// // err: nil
func BraceIndices(s string) ([]int, error) {
var level, idx int
var idxs []int
for i := 0; i < len(s); i++ {
switch s[i] {
case '{':
if level++; level == 1 {
idx = i
}
case '}':
if level--; level == 0 {
idxs = append(idxs, idx, i+1)
} else if level < 0 {
return nil, fmt.Errorf("unbalanced braces in %q", s)
}
}
}
if level != 0 {
return nil, fmt.Errorf("unbalanced braces in %q", s)
}
return idxs, nil
}
libopenapi-validator-0.13.8/helpers/regex_maker_test.go 0000664 0000000 0000000 00000006441 15205340424 0023235 0 ustar 00root root 0000000 0000000 package helpers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetRegexForPath(t *testing.T) {
tests := []struct {
name string
tpl string
wantErr bool
wantExpr string
}{
{
name: "well-formed template with default pattern",
tpl: "/orders/{id}",
wantErr: false,
wantExpr: "^/orders/([^/]*)$",
},
{
name: "well-formed template with custom pattern",
tpl: "/orders/{id:[0-9]+}",
wantErr: false,
wantExpr: "^/orders/([0-9]+)$",
},
{
name: "missing name in template",
tpl: "/orders/{:pattern}",
wantErr: true,
},
{
name: "missing pattern in template",
tpl: "/orders/{name:}",
wantErr: true,
},
{
name: "unbalanced braces in template",
tpl: "/orders/{id",
wantErr: true,
},
{
name: "unbalanced braces in template",
tpl: "/orders/id}",
wantErr: true,
},
{
name: "template with multiple variables",
tpl: "/orders/{id:[0-9]+}/items/{itemId}",
wantErr: false,
wantExpr: "^/orders/([0-9]+)/items/([^/]*)$",
},
{
name: "OData formatted URL with single quotes",
tpl: "/entities('{id}')",
wantErr: false,
wantExpr: "^/entities\\('([^/]*)'\\)$",
},
{
name: "OData formatted URL with custom pattern",
tpl: "/entities('{id:[0-9]+}')",
wantErr: false,
wantExpr: "^/entities\\('([0-9]+)'\\)$",
},
{
name: "get default pattern",
tpl: "/{param}",
wantErr: false,
wantExpr: "^/([^/]*)$",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetRegexForPath(tt.tpl)
if (err != nil) != tt.wantErr {
t.Errorf("GetRegexForPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got.String() != tt.wantExpr {
t.Errorf("GetRegexForPath() = %v, want %v", got.String(), tt.wantExpr)
}
})
}
}
func TestBraceIndices(t *testing.T) {
tests := []struct {
name string
s string
want []int
wantErr bool
}{
{
name: "well-formed braces",
s: "/orders/{id}/items/{itemId}",
want: []int{8, 12, 19, 27},
wantErr: false,
},
{
name: "unbalanced braces",
s: "/orders/{id/items/{itemId}",
wantErr: true,
},
{
name: "unbalanced braces",
s: "/orders/{id}/items/{itemId",
wantErr: true,
},
{
name: "no braces",
s: "/orders/id/items/itemId",
want: []int{},
wantErr: false,
},
{
name: "OData formatted URL with single quotes",
s: "/entities('{id}')",
want: []int{11, 15},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BraceIndices(tt.s)
if (err != nil) != tt.wantErr {
t.Errorf("BraceIndices() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !equal(got, tt.want) {
t.Errorf("BraceIndices() = %v, want %v", got, tt.want)
}
})
}
}
func TestDefaultPatternCompileCache(t *testing.T) {
res, err := GetRegexForPath("{param}")
assert.Nil(t, err)
assert.Equal(t, res, DefaultPatternRegex)
assert.Equal(t, res.String(), DefaultPatternRegexString)
}
func equal(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
libopenapi-validator-0.13.8/helpers/schema_compiler.go 0000664 0000000 0000000 00000023132 15205340424 0023033 0 ustar 00root root 0000000 0000000 package helpers
import (
"bytes"
"encoding/json"
"fmt"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/openapi_vocabulary"
)
// ConfigureCompiler configures a JSON Schema compiler with the desired behavior.
func ConfigureCompiler(c *jsonschema.Compiler, o *config.ValidationOptions) {
if o == nil {
// Sanity
return
}
// nil is the default so this is OK.
c.UseRegexpEngine(o.RegexEngine)
if o.FormatAssertions {
c.AssertFormat()
}
if o.ContentAssertions {
c.AssertContent()
}
for n, v := range o.Formats {
c.RegisterFormat(&jsonschema.Format{
Name: n,
Validate: v,
})
}
}
// NewCompilerWithOptions mints a new JSON schema compiler with custom configuration.
func NewCompilerWithOptions(o *config.ValidationOptions) *jsonschema.Compiler {
c := jsonschema.NewCompiler()
ConfigureCompiler(c, o)
return c
}
// NewCompiledSchema establishes a programmatic representation of a JSON Schema document that is used for validation.
// Defaults to OpenAPI 3.1+ behavior (strict JSON Schema compliance).
func NewCompiledSchema(name string, jsonSchema []byte, o *config.ValidationOptions) (*jsonschema.Schema, error) {
return NewCompiledSchemaWithVersion(name, jsonSchema, o, 3.1)
}
// NewCompiledSchemaWithVersion establishes a programmatic representation of a JSON Schema document that is used for validation.
// The version parameter determines which OpenAPI keywords are allowed:
// - version 3.0: Allows OpenAPI 3.0 keywords like 'nullable'
// - version 3.1+: Rejects OpenAPI 3.0 keywords like 'nullable' (strict JSON Schema compliance)
func NewCompiledSchemaWithVersion(name string, jsonSchema []byte, options *config.ValidationOptions, version float32) (*jsonschema.Schema, error) {
compiler := NewCompilerWithOptions(options)
compiler.UseLoader(NewCompilerLoader())
// register OpenAPI vocabulary with appropriate version and coercion settings
if options != nil && options.OpenAPIMode {
var vocabVersion openapi_vocabulary.VersionType
if version >= 3.15 { // use 3.15 to avoid floating point precision issues (3.2+)
vocabVersion = openapi_vocabulary.Version32
} else if version >= 3.05 { // use 3.05 to avoid floating point precision issues (3.1)
vocabVersion = openapi_vocabulary.Version31
} else {
vocabVersion = openapi_vocabulary.Version30
}
vocab := openapi_vocabulary.NewOpenAPIVocabularyWithCoercion(vocabVersion, options.AllowScalarCoercion)
compiler.RegisterVocabulary(vocab)
compiler.AssertVocabs()
if version < 3.05 {
jsonSchema = transformOpenAPI30Schema(jsonSchema)
}
if options.AllowScalarCoercion {
jsonSchema = transformSchemaForCoercion(jsonSchema)
}
}
decodedSchema, err := jsonschema.UnmarshalJSON(bytes.NewReader(jsonSchema))
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON schema: %w", err)
}
if err = compiler.AddResource(name, decodedSchema); err != nil {
return nil, fmt.Errorf("failed to add resource to schema compiler: %w", err)
}
jsch, err := compiler.Compile(name)
if err != nil {
return nil, fmt.Errorf("JSON schema compile failed: %s", err.Error())
}
return jsch, nil
}
// transformOpenAPI30Schema transforms OpenAPI 3.0 schemas to JSON Schema 2020-12 compatible format.
// Handles OAS 3.0-specific keywords:
// - nullable: true â type array with "null"
// - exclusiveMinimum/exclusiveMaximum: bool â numeric (draft-04 â 2020-12)
func transformOpenAPI30Schema(jsonSchema []byte) []byte {
var schema map[string]interface{}
if err := json.Unmarshal(jsonSchema, &schema); err != nil {
return jsonSchema
}
transformed := transformOAS30Keywords(schema)
result, err := json.Marshal(transformed)
if err != nil {
return jsonSchema
}
return result
}
// transformOAS30Keywords recursively transforms OAS 3.0-specific keywords in a schema object
func transformOAS30Keywords(schema interface{}) interface{} {
switch s := schema.(type) {
case map[string]interface{}:
result := make(map[string]interface{})
// copy all properties first, recursing into nested schemas
for key, value := range s {
result[key] = transformOAS30Keywords(value)
}
// handle nullable keyword
if nullable, ok := s["nullable"]; ok {
if nullableBool, ok := nullable.(bool); ok {
if nullableBool {
result = transformNullableSchema(result)
} else {
delete(result, "nullable")
}
}
}
// handle exclusiveMinimum: bool â numeric
transformExclusiveBound(result, "exclusiveMinimum", "minimum")
// handle exclusiveMaximum: bool â numeric
transformExclusiveBound(result, "exclusiveMaximum", "maximum")
return result
case []interface{}:
result := make([]interface{}, len(s))
for i, item := range s {
result[i] = transformOAS30Keywords(item)
}
return result
default:
return schema
}
}
// transformExclusiveBound converts OAS 3.0 boolean exclusiveMinimum/exclusiveMaximum
// to JSON Schema 2020-12 numeric form.
//
// OAS 3.0 (draft-04): minimum: 10, exclusiveMinimum: true â value must be > 10
// JSON Schema 2020-12: exclusiveMinimum: 10 â value must be > 10
func transformExclusiveBound(schema map[string]interface{}, exclusiveKey, boundKey string) {
exVal, ok := schema[exclusiveKey]
if !ok {
return
}
exBool, isBool := exVal.(bool)
if !isBool {
return // already numeric (3.1 style), leave as-is
}
if exBool {
// exclusiveMinimum: true + minimum: X â exclusiveMinimum: X (remove minimum)
if bound, hasBound := schema[boundKey]; hasBound {
schema[exclusiveKey] = bound
delete(schema, boundKey)
} else {
// boolean true without a corresponding bound is invalid, just remove it
delete(schema, exclusiveKey)
}
} else {
// exclusiveMinimum: false is a no-op, just remove the keyword
delete(schema, exclusiveKey)
}
}
// transformNullableSchema transforms a schema with nullable: true to JSON Schema compatible format
func transformNullableSchema(schema map[string]interface{}) map[string]interface{} {
delete(schema, "nullable")
// get the current type
currentType, hasType := schema["type"]
if hasType {
// if there's already a type, convert it to include null
switch t := currentType.(type) {
case string:
// convert "string" to ["string", "null"]
schema["type"] = []interface{}{t, "null"}
case []interface{}:
// if it's already an array, add null if not present
found := false
for _, item := range t {
if str, ok := item.(string); ok && str == "null" {
found = true
break
}
}
if !found {
newTypes := make([]interface{}, len(t)+1)
copy(newTypes, t)
newTypes[len(t)] = "null"
schema["type"] = newTypes
}
}
}
allOf, hasAllOf := schema["allOf"]
if hasAllOf {
delete(schema, "allOf")
oneOfAdditions := []interface{}{
map[string]interface{}{
"allOf": allOf,
},
map[string]interface{}{
"type": "null",
},
}
var oneOfSlice []interface{}
oneOf, hasOneOf := schema["oneOf"]
if hasOneOf {
oneOfSlice, _ = oneOf.([]interface{})
}
oneOfSlice = append(oneOfSlice, oneOfAdditions...)
schema["oneOf"] = oneOfSlice
}
// Handle enum values - add null if nullable but not already in enum
enum, hasEnum := schema["enum"]
if hasEnum {
if enumSlice, ok := enum.([]interface{}); ok {
// Check if null is already in enum
hasNull := false
for _, v := range enumSlice {
if v == nil {
hasNull = true
break
}
}
// Add null if not present
if !hasNull {
enumSlice = append(enumSlice, nil)
schema["enum"] = enumSlice
}
}
}
return schema
}
// transformSchemaForCoercion transforms schemas to allow scalar coercion (string->boolean/number)
func transformSchemaForCoercion(jsonSchema []byte) []byte {
var schema map[string]interface{}
if err := json.Unmarshal(jsonSchema, &schema); err != nil {
// If we can't parse it, return as-is
return jsonSchema
}
transformed := transformCoercionInSchema(schema)
result, err := json.Marshal(transformed)
if err != nil {
return jsonSchema
}
return result
}
// transformCoercionInSchema recursively transforms schemas to support scalar coercion
func transformCoercionInSchema(schema interface{}) interface{} {
switch s := schema.(type) {
case map[string]interface{}:
result := make(map[string]interface{})
// copy all properties first
for key, value := range s {
result[key] = transformCoercionInSchema(value)
}
// transform type to allow string coercion for coercible types
if schemaType, hasType := s["type"]; hasType {
result["type"] = transformTypeForCoercion(schemaType)
}
return result
case []interface{}:
result := make([]interface{}, len(s))
for i, item := range s {
result[i] = transformCoercionInSchema(item)
}
return result
default:
return schema
}
}
// transformTypeForCoercion transforms type fields to allow string coercion
func transformTypeForCoercion(schemaType interface{}) interface{} {
switch t := schemaType.(type) {
case string:
// transform scalar types to include string for coercion
if t == "boolean" || t == "number" || t == "integer" {
return []interface{}{t, "string"}
}
return t
case []interface{}:
// if already an array, add string if it contains coercible types and doesn't already have string
hasCoercibleType := false
hasString := false
for _, item := range t {
if str, ok := item.(string); ok {
if str == "boolean" || str == "number" || str == "integer" {
hasCoercibleType = true
}
if str == "string" {
hasString = true
}
}
}
if hasCoercibleType && !hasString {
newTypes := make([]interface{}, len(t)+1)
copy(newTypes, t)
newTypes[len(t)] = "string"
return newTypes
}
return t
default:
return schemaType
}
}
libopenapi-validator-0.13.8/helpers/schema_compiler_test.go 0000664 0000000 0000000 00000057023 15205340424 0024100 0 ustar 00root root 0000000 0000000 package helpers
import (
"encoding/json"
"fmt"
"testing"
"unicode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pb33f/libopenapi-validator/config"
)
// A few simple JSON Schemas
const stringSchema = `{
"type": "string",
"format": "date",
"minLength": 10
}`
const objectSchema = `{
"type": "object",
"title" : "Fish",
"properties" : {
"name" : {
"type": "string",
"description": "The given name of the fish"
},
"name" : {
"type": "string",
"format": "capital",
"description": "The given name of the fish"
},
"species" : {
"type" : "string",
"enum" : [ "OTHER", "GUPPY", "PIKE", "BASS" ]
}
}
}`
func Test_SchemaWithNilOptions(t *testing.T) {
jsch, err := NewCompiledSchema("test", []byte(stringSchema), nil)
require.NoError(t, err, "Failed to compile Schema")
require.NotNil(t, jsch, "Did not return a compiled schema")
}
func Test_SchemaWithDefaultOptions(t *testing.T) {
valOptions := config.NewValidationOptions()
jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions)
require.NoError(t, err, "Failed to compile Schema")
require.NotNil(t, jsch, "Did not return a compiled schema")
}
func Test_SchemaWithOptions(t *testing.T) {
valOptions := config.NewValidationOptions(
config.WithFormatAssertions(),
config.WithContentAssertions(),
config.WithCustomFormat("capital", func(v any) error {
s, ok := v.(string)
if !ok {
return fmt.Errorf("expected string")
}
if s == "" {
return nil
}
r := []rune(s)[0]
if !unicode.IsUpper(r) {
return fmt.Errorf("expected first latter to be uppercase")
}
return nil
}),
)
jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions)
require.NoError(t, err, "Failed to compile Schema")
require.NotNil(t, jsch, "Did not return a compiled schema")
}
func Test_ObjectSchema(t *testing.T) {
valOptions := config.NewValidationOptions()
jsch, err := NewCompiledSchema("test", []byte(objectSchema), valOptions)
require.NoError(t, err, "Failed to compile Schema")
require.NotNil(t, jsch, "Did not return a compiled schema")
}
func Test_ValidJSONSchemaWithInvalidContent(t *testing.T) {
// An example of a dubious JSON Schema
const badSchema = `{
"type": "you-dont-know-me",
"format": "date",
"minLength": 10
}`
jsch, err := NewCompiledSchema("test", []byte(badSchema), nil)
assert.Error(t, err, "Expected an error to be thrown")
assert.Nil(t, jsch, "invalid schema compiled!")
}
func Test_MalformedSONSchema(t *testing.T) {
// An example of a JSON schema with malformed JSON
const badSchema = `{
"type": "you-dont-know-me",
"format": "date"
"minLength": 10
}`
jsch, err := NewCompiledSchema("test", []byte(badSchema), nil)
assert.Error(t, err, "Expected an error to be thrown")
assert.Nil(t, jsch, "invalid schema compiled!")
}
func Test_ValidJSONSchemaWithIncompleteContent(t *testing.T) {
// An example of a dJSON schema with references to non-existent stuff
const incompleteSchema = `{
"type": "object",
"title" : "unresolvable",
"properties" : {
"name" : {
"type": "string",
},
"species" : {
"$ref": "#/$defs/speciesEnum"
}
}
}`
jsch, err := NewCompiledSchema("test", []byte(incompleteSchema), nil)
assert.Error(t, err, "Expected an error to be thrown")
assert.Nil(t, jsch, "invalid schema compiled!")
}
// Additional comprehensive tests for version-aware schema compilation
func TestNewCompiledSchemaWithVersion_OpenAPIMode_Version30(t *testing.T) {
schemaJSON := `{
"type": "string",
"nullable": true
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test version 3.0 (< 3.05)
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.0)
require.NoError(t, err, "Should compile OpenAPI 3.0 schema with nullable")
require.NotNil(t, jsch, "Should return compiled schema")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_Version31(t *testing.T) {
schemaJSON := `{
"type": "string"
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test version 3.1 (>= 3.05)
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.1)
require.NoError(t, err, "Should compile OpenAPI 3.1 schema")
require.NotNil(t, jsch, "Should return compiled schema")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_Version32(t *testing.T) {
schemaJSON := `{
"type": "string"
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test version 3.2 (>= 3.15)
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.2)
require.NoError(t, err, "Should compile OpenAPI 3.2 schema")
require.NotNil(t, jsch, "Should return compiled schema")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_Version32_NullableRejected(t *testing.T) {
schemaJSON := `{
"type": "string",
"nullable": true
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test version 3.2 (>= 3.15) with nullable should fail (same as 3.1+)
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.2)
assert.Error(t, err, "Should fail for nullable in OpenAPI 3.2")
assert.Nil(t, jsch, "Should not return compiled schema")
assert.Contains(t, err.Error(), "The `nullable` keyword is not supported in OpenAPI 3.1+")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_Version31_NullableRejected(t *testing.T) {
schemaJSON := `{
"type": "string",
"nullable": true
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test version 3.1 (>= 3.05) with nullable should fail
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.1)
assert.Error(t, err, "Should fail for nullable in OpenAPI 3.1")
assert.Nil(t, jsch, "Should not return compiled schema")
assert.Contains(t, err.Error(), "The `nullable` keyword is not supported in OpenAPI 3.1+")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_ScalarCoercion(t *testing.T) {
schemaJSON := `{
"type": "boolean"
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
config.WithScalarCoercion(),
)
// Test with scalar coercion enabled
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.0)
require.NoError(t, err, "Should compile with scalar coercion")
require.NotNil(t, jsch, "Should return compiled schema")
// Test that coercion works
err = jsch.Validate("true")
assert.NoError(t, err, "Should allow string 'true' for boolean with coercion")
err = jsch.Validate("invalid")
assert.Error(t, err, "Should reject invalid boolean string")
}
func TestNewCompiledSchemaWithVersion_OpenAPIMode_NoScalarCoercion(t *testing.T) {
schemaJSON := `{
"type": "boolean"
}`
options := config.NewValidationOptions(
config.WithOpenAPIMode(),
)
// Test with scalar coercion disabled (default)
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.0)
require.NoError(t, err, "Should compile without scalar coercion")
require.NotNil(t, jsch, "Should return compiled schema")
// Test that coercion doesn't work
err = jsch.Validate("true")
assert.Error(t, err, "Should reject string 'true' for boolean without coercion")
err = jsch.Validate(true)
assert.NoError(t, err, "Should accept actual boolean value")
}
func TestNewCompiledSchemaWithVersion_NonOpenAPIMode(t *testing.T) {
schemaJSON := `{
"type": "string",
"nullable": true
}`
options := config.NewValidationOptions()
// OpenAPIMode is false by default
// Test that OpenAPI keywords are ignored when not in OpenAPI mode
jsch, err := NewCompiledSchemaWithVersion("test", []byte(schemaJSON), options, 3.0)
require.NoError(t, err, "Should compile in non-OpenAPI mode")
require.NotNil(t, jsch, "Should return compiled schema")
// String values should pass when OpenAPI mode is disabled
err = jsch.Validate("test")
assert.NoError(t, err, "Should accept string values")
// When OpenAPI mode is disabled, nullable is ignored by JSON Schema
// The behavior with null depends on the JSON Schema version and mode
}
func TestTransformOpenAPI30Schema_ValidJSON(t *testing.T) {
input := []byte(`{
"type": "string",
"nullable": true
}`)
result := transformOpenAPI30Schema(input)
var schema map[string]interface{}
err := json.Unmarshal(result, &schema)
require.NoError(t, err, "Result should be valid JSON")
// Check that nullable was transformed
schemaType, ok := schema["type"]
assert.True(t, ok, "Should have type field")
typeArray, ok := schemaType.([]interface{})
assert.True(t, ok, "Type should be an array")
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")
_, hasNullable := schema["nullable"]
assert.False(t, hasNullable, "Should not have nullable field after transformation")
}
func TestTransformOpenAPI30Schema_InvalidJSON(t *testing.T) {
input := []byte(`{invalid json}`)
result := transformOpenAPI30Schema(input)
// Should return original when invalid
assert.Equal(t, input, result)
}
func TestTransformNullableInSchema_MapWithNullableTrue(t *testing.T) {
schema := map[string]interface{}{
"type": "string",
"nullable": true,
}
result := transformOAS30Keywords(schema)
resultMap, ok := result.(map[string]interface{})
require.True(t, ok)
schemaType, ok := resultMap["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")
_, hasNullable := resultMap["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableInSchema_MapWithNullableFalse(t *testing.T) {
schema := map[string]interface{}{
"type": "string",
"nullable": false,
}
result := transformOAS30Keywords(schema)
resultMap, ok := result.(map[string]interface{})
require.True(t, ok)
// nullable: false should just remove nullable, keep type as is
schemaType, ok := resultMap["type"]
require.True(t, ok)
assert.Equal(t, "string", schemaType)
_, hasNullable := resultMap["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableInSchema_Array(t *testing.T) {
schema := []interface{}{
map[string]interface{}{
"type": "string",
"nullable": true,
},
"other-item",
}
result := transformOAS30Keywords(schema)
resultArray, ok := result.([]interface{})
require.True(t, ok)
assert.Len(t, resultArray, 2)
firstItem, ok := resultArray[0].(map[string]interface{})
require.True(t, ok)
schemaType := firstItem["type"].([]interface{})
assert.Contains(t, schemaType, "string")
assert.Contains(t, schemaType, "null")
_, hasNullable := firstItem["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableInSchema_OtherTypes(t *testing.T) {
stringSchema := "string-value"
result := transformOAS30Keywords(stringSchema)
assert.Equal(t, stringSchema, result)
numberSchema := 123
result = transformOAS30Keywords(numberSchema)
assert.Equal(t, numberSchema, result)
var nilSchema interface{} = nil
result = transformOAS30Keywords(nilSchema)
assert.Equal(t, nilSchema, result)
}
func TestTransformExclusiveBound_TrueWithBound(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"minimum": float64(10),
"exclusiveMinimum": true,
}
transformExclusiveBound(schema, "exclusiveMinimum", "minimum")
assert.Equal(t, float64(10), schema["exclusiveMinimum"])
_, hasMin := schema["minimum"]
assert.False(t, hasMin)
}
func TestTransformExclusiveBound_TrueWithoutBound(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"exclusiveMinimum": true,
}
transformExclusiveBound(schema, "exclusiveMinimum", "minimum")
_, hasExMin := schema["exclusiveMinimum"]
assert.False(t, hasExMin)
}
func TestTransformExclusiveBound_False(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"minimum": float64(10),
"exclusiveMinimum": false,
}
transformExclusiveBound(schema, "exclusiveMinimum", "minimum")
_, hasExMin := schema["exclusiveMinimum"]
assert.False(t, hasExMin)
assert.Equal(t, float64(10), schema["minimum"])
}
func TestTransformExclusiveBound_NotPresent(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"minimum": float64(10),
}
transformExclusiveBound(schema, "exclusiveMinimum", "minimum")
assert.Equal(t, float64(10), schema["minimum"])
}
func TestTransformExclusiveBound_AlreadyNumeric(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"exclusiveMinimum": float64(10),
}
transformExclusiveBound(schema, "exclusiveMinimum", "minimum")
assert.Equal(t, float64(10), schema["exclusiveMinimum"])
}
func TestTransformExclusiveBound_Maximum(t *testing.T) {
schema := map[string]interface{}{
"type": "number",
"maximum": float64(100),
"exclusiveMaximum": true,
}
transformExclusiveBound(schema, "exclusiveMaximum", "maximum")
assert.Equal(t, float64(100), schema["exclusiveMaximum"])
_, hasMax := schema["maximum"]
assert.False(t, hasMax)
}
func TestTransformOAS30Keywords_ExclusiveMinMaxRecursive(t *testing.T) {
schema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"price": map[string]interface{}{
"type": "number",
"minimum": float64(0),
"exclusiveMinimum": true,
"maximum": float64(1000),
"exclusiveMaximum": true,
},
},
}
result := transformOAS30Keywords(schema)
resultMap := result.(map[string]interface{})
props := resultMap["properties"].(map[string]interface{})
price := props["price"].(map[string]interface{})
assert.Equal(t, float64(0), price["exclusiveMinimum"])
assert.Equal(t, float64(1000), price["exclusiveMaximum"])
_, hasMin := price["minimum"]
assert.False(t, hasMin)
_, hasMax := price["maximum"]
assert.False(t, hasMax)
}
func TestTransformNullableSchema_ArrayType(t *testing.T) {
schema := map[string]interface{}{
"type": []interface{}{"string", "number"},
"nullable": true,
}
result := transformNullableSchema(schema)
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "number")
assert.Contains(t, typeArray, "null")
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableSchema_ArrayTypeWithNull(t *testing.T) {
schema := map[string]interface{}{
"type": []interface{}{"string", "null"},
"nullable": true,
}
result := transformNullableSchema(schema)
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")
// Should not have duplicate "null"
nullCount := 0
for _, item := range typeArray {
if item == "null" {
nullCount++
}
}
assert.Equal(t, 1, nullCount)
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableSchema_NullableAllOf(t *testing.T) {
schema := map[string]interface{}{
"type": []interface{}{"object"},
"allOf": []interface{}{
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
},
},
},
},
"nullable": true,
}
result := transformNullableSchema(schema)
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "object")
assert.Contains(t, typeArray, "null")
oneOf, ok := result["oneOf"]
require.True(t, ok)
oneOfSlice, ok := oneOf.([]interface{})
require.True(t, ok)
assert.Len(t, oneOfSlice, 2)
assert.Contains(t, oneOfSlice, map[string]interface{}{
"allOf": []interface{}{
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
},
},
},
},
})
assert.Contains(t, oneOfSlice, map[string]interface{}{
"type": "null",
})
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
}
func TestTransformNullableSchema_NullableAllOfWithExistingOneOf(t *testing.T) {
schema := map[string]interface{}{
"type": []interface{}{"object"},
"allOf": []interface{}{
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
},
},
},
},
"oneOf": []interface{}{
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
"const": []any{"val"},
},
},
},
},
"nullable": true,
}
result := transformNullableSchema(schema)
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "object")
assert.Contains(t, typeArray, "null")
oneOf, ok := result["oneOf"]
require.True(t, ok)
oneOfSlice, ok := oneOf.([]interface{})
require.True(t, ok)
assert.Len(t, oneOfSlice, 3)
assert.Contains(t, oneOfSlice, map[string]interface{}{
"allOf": []interface{}{
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
},
},
},
},
})
assert.Contains(t, oneOfSlice, map[string]interface{}{
"type": "null",
})
assert.Contains(t, oneOfSlice, map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
"const": []any{"val"},
},
},
})
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
}
func TestTransformSchemaForCoercion_ValidJSON(t *testing.T) {
input := []byte(`{
"type": "boolean"
}`)
result := transformSchemaForCoercion(input)
var schema map[string]interface{}
err := json.Unmarshal(result, &schema)
require.NoError(t, err, "Result should be valid JSON")
// Check that type was transformed to include string
schemaType, ok := schema["type"]
assert.True(t, ok, "Should have type field")
typeArray, ok := schemaType.([]interface{})
assert.True(t, ok, "Type should be an array")
assert.Contains(t, typeArray, "boolean")
assert.Contains(t, typeArray, "string")
}
func TestTransformSchemaForCoercion_InvalidJSON(t *testing.T) {
input := []byte(`{invalid json}`)
result := transformSchemaForCoercion(input)
// Should return original when invalid
assert.Equal(t, input, result)
}
func TestTransformOpenAPI30Schema_MarshalError(t *testing.T) {
// Create a transformation that could potentially cause marshal issues
// This is hard to test because Go's json.Marshal rarely fails
// The error path exists for defensive programming
input := []byte(`{
"type": "string",
"nullable": true
}`)
result := transformOpenAPI30Schema(input)
// Should return valid transformed JSON even if marshal could theoretically fail
var schema map[string]interface{}
err := json.Unmarshal(result, &schema)
assert.NoError(t, err)
}
func TestTransformSchemaForCoercion_MarshalError(t *testing.T) {
// Create a transformation that could potentially cause marshal issues
// This is hard to test because Go's json.Marshal rarely fails
// The error path exists for defensive programming
input := []byte(`{
"type": "boolean"
}`)
result := transformSchemaForCoercion(input)
// Should return valid transformed JSON even if marshal could theoretically fail
var schema map[string]interface{}
err := json.Unmarshal(result, &schema)
assert.NoError(t, err)
}
func TestTransformCoercionInSchema_Array(t *testing.T) {
schema := []interface{}{
map[string]interface{}{
"type": "number",
},
"other-item",
}
result := transformCoercionInSchema(schema)
resultArray, ok := result.([]interface{})
require.True(t, ok)
assert.Len(t, resultArray, 2)
firstItem, ok := resultArray[0].(map[string]interface{})
require.True(t, ok)
schemaType := firstItem["type"].([]interface{})
assert.Contains(t, schemaType, "number")
assert.Contains(t, schemaType, "string")
}
func TestTransformCoercionInSchema_OtherTypes(t *testing.T) {
stringSchema := "string-value"
result := transformCoercionInSchema(stringSchema)
assert.Equal(t, stringSchema, result)
}
func TestTransformTypeForCoercion_ArrayWithNonStringItems(t *testing.T) {
input := []interface{}{"boolean", 123, "null"}
result := transformTypeForCoercion(input)
typeArray, ok := result.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "boolean")
assert.Contains(t, typeArray, 123)
assert.Contains(t, typeArray, "null")
assert.Contains(t, typeArray, "string")
}
func TestTransformTypeForCoercion_OtherTypes(t *testing.T) {
result := transformTypeForCoercion(123)
assert.Equal(t, 123, result)
result = transformTypeForCoercion(nil)
assert.Equal(t, nil, result)
result = transformTypeForCoercion(map[string]interface{}{})
assert.Equal(t, map[string]interface{}{}, result)
}
func TestTransformTypeForCoercion_EdgeCases(t *testing.T) {
result := transformTypeForCoercion("fudge")
assert.Equal(t, "fudge", result)
result = transformTypeForCoercion([]interface{}{"string"})
assert.Equal(t, []interface{}{"string"}, result)
}
func TestTransformNullableSchema_EnumWithoutNull(t *testing.T) {
// Test case: nullable: true with enum that doesn't contain null
// Expected: null should be automatically added to the enum
schema := map[string]interface{}{
"type": "string",
"enum": []interface{}{
"active",
"inactive",
"pending",
"archived",
},
"nullable": true,
}
result := transformNullableSchema(schema)
// nullable keyword should be removed
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
// type should be converted to array including null
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")
// enum should contain null
enum, ok := result["enum"]
require.True(t, ok)
enumSlice, ok := enum.([]interface{})
require.True(t, ok)
assert.Len(t, enumSlice, 5) // original 4 values + null
assert.Contains(t, enumSlice, "active")
assert.Contains(t, enumSlice, "inactive")
assert.Contains(t, enumSlice, "pending")
assert.Contains(t, enumSlice, "archived")
assert.Contains(t, enumSlice, nil)
}
func TestTransformNullableSchema_EnumWithNull(t *testing.T) {
// Test case: nullable: true with enum that already contains null
// Expected: null should NOT be added twice
schema := map[string]interface{}{
"type": "string",
"enum": []interface{}{
"active",
"inactive",
"pending",
"archived",
nil,
},
"nullable": true,
}
result := transformNullableSchema(schema)
// nullable keyword should be removed
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)
// type should be converted to array including null
schemaType, ok := result["type"]
require.True(t, ok)
typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")
// enum should still contain only one null (not duplicated)
enum, ok := result["enum"]
require.True(t, ok)
enumSlice, ok := enum.([]interface{})
require.True(t, ok)
assert.Len(t, enumSlice, 5) // original 5 values (no duplication)
assert.Contains(t, enumSlice, "active")
assert.Contains(t, enumSlice, "inactive")
assert.Contains(t, enumSlice, "pending")
assert.Contains(t, enumSlice, "archived")
// Count how many nulls are in the enum
nullCount := 0
for _, v := range enumSlice {
if v == nil {
nullCount++
}
}
assert.Equal(t, 1, nullCount, "enum should contain exactly one null value")
}
libopenapi-validator-0.13.8/helpers/url_loader.go 0000664 0000000 0000000 00000003003 15205340424 0022024 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import (
"crypto/tls"
"fmt"
"net/http"
"time"
"github.com/santhosh-tekuri/jsonschema/v6"
)
// HTTPURLLoader is a type that implements the Loader interface for loading schemas from HTTP URLs.
// this change was made in jsonschema v6. The httploader package was removed and the HTTPURLLoader
// type was introduced.
// https://github.com/santhosh-tekuri/jsonschema/blob/boon/example_http_test.go
// TODO: make all this stuff configurable, right now it's all hard wired and not very flexible.
//
// use interfaces and abstractions on all this.
type HTTPURLLoader http.Client
func (l *HTTPURLLoader) Load(url string) (any, error) {
client := (*http.Client)(l)
resp, err := client.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
_ = resp.Body.Close()
return nil, fmt.Errorf("%s returned status code %d", url, resp.StatusCode)
}
defer resp.Body.Close()
return jsonschema.UnmarshalJSON(resp.Body)
}
func NewHTTPURLLoader(insecure bool) *HTTPURLLoader {
httpLoader := HTTPURLLoader(http.Client{
Timeout: 15 * time.Second,
})
if insecure {
httpLoader.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
return &httpLoader
}
func NewCompilerLoader() jsonschema.SchemeURLLoader {
return jsonschema.SchemeURLLoader{
"file": jsonschema.FileLoader{},
"http": NewHTTPURLLoader(false),
"https": NewHTTPURLLoader(false),
}
}
libopenapi-validator-0.13.8/helpers/url_loader_test.go 0000664 0000000 0000000 00000006004 15205340424 0023067 0 ustar 00root root 0000000 0000000 // Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package helpers
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// Test the Load function for a successful case
func TestHTTPURLLoader_Load_Success(t *testing.T) {
// Create a mock HTTP server that returns a 200 response
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, `{"success": true}`)
}))
defer server.Close()
loader := NewHTTPURLLoader(false)
// Test the Load function
_, err := loader.Load(server.URL)
require.NoError(t, err)
}
// Test the Load function when the server returns a non-200 response
func TestHTTPURLLoader_Load_NonOKStatus(t *testing.T) {
// Create a mock HTTP server that returns a 404 response
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not found", http.StatusNotFound)
}))
defer server.Close()
loader := NewHTTPURLLoader(false)
// Test the Load function
_, err := loader.Load(server.URL)
require.Error(t, err)
require.Contains(t, err.Error(), "returned status code 404")
}
// Test the Load function when the server returns an error
func TestHTTPURLLoader_Load_Error(t *testing.T) {
loader := NewHTTPURLLoader(false)
// Test the Load function with an invalid URL
_, err := loader.Load("http://invalid-url")
require.Error(t, err)
}
// Test the Load function with an insecure TLS config
func TestHTTPURLLoader_Load_Insecure(t *testing.T) {
// Create a mock HTTPS server
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, `{"secure": true}`)
}))
defer server.Close()
loader := NewHTTPURLLoader(true)
// Test the Load function
_, err := loader.Load(server.URL)
require.NoError(t, err)
}
// Test the NewHTTPURLLoader function with insecure set to false
func TestNewHTTPURLLoader_Secure(t *testing.T) {
loader := NewHTTPURLLoader(false)
require.NotNil(t, loader)
// Assert that the loader has the correct timeout and secure transport
client := (*http.Client)(loader)
require.Equal(t, 15*time.Second, client.Timeout)
require.Nil(t, client.Transport) // Transport should be nil when secure
}
// Test the NewHTTPURLLoader function with insecure set to true
func TestNewHTTPURLLoader_Insecure(t *testing.T) {
loader := NewHTTPURLLoader(true)
require.NotNil(t, loader)
// Assert that the loader has an insecure transport configuration
client := (*http.Client)(loader)
transport, ok := client.Transport.(*http.Transport)
require.True(t, ok)
require.NotNil(t, transport.TLSClientConfig)
require.True(t, transport.TLSClientConfig.InsecureSkipVerify)
}
// Test the NewCompilerLoader function
func TestNewCompilerLoader(t *testing.T) {
loader := NewCompilerLoader()
require.NotNil(t, loader)
// Assert that the loader contains the correct schemes
require.NotNil(t, loader["http"])
require.NotNil(t, loader["https"])
require.NotNil(t, loader["file"])
}
libopenapi-validator-0.13.8/helpers/version.go 0000664 0000000 0000000 00000000402 15205340424 0021361 0 ustar 00root root 0000000 0000000 package helpers
import (
"strings"
)
// VersionToFloat converts a version string to a float32 for easier comparison.
func VersionToFloat(version string) float32 {
switch {
case strings.HasPrefix(version, "3.0"):
return 3.0
default:
return 3.1
}
}
libopenapi-validator-0.13.8/helpers/version_test.go 0000664 0000000 0000000 00000001767 15205340424 0022437 0 ustar 00root root 0000000 0000000 package helpers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestVersionToFloat(t *testing.T) {
tests := []struct {
name string
version string
expected float32
}{
{
name: "OpenAPI 3.0",
version: "3.0",
expected: 3.0,
},
{
name: "OpenAPI 3.0.0",
version: "3.0.0",
expected: 3.0,
},
{
name: "OpenAPI 3.0.3",
version: "3.0.3",
expected: 3.0,
},
{
name: "OpenAPI 3.1",
version: "3.1",
expected: 3.1,
},
{
name: "OpenAPI 3.1.0",
version: "3.1.0",
expected: 3.1,
},
{
name: "OpenAPI 3.1.1",
version: "3.1.1",
expected: 3.1,
},
{
name: "default to 3.1 for unknown version",
version: "4.0",
expected: 3.1,
},
{
name: "default to 3.1 for empty string",
version: "",
expected: 3.1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := VersionToFloat(tt.version)
assert.Equal(t, tt.expected, result)
})
}
}
libopenapi-validator-0.13.8/libopenapi-logo.png 0000664 0000000 0000000 00000253257 15205340424 0021513 0 ustar 00root root 0000000 0000000 PNG
IHDR Í Ū `å6) gAMA ąüa
IiCCPsRGB IEC61966-2.1 HSwX÷>ß÷eVBØđąl "#ŦČYĸ a@Å
VHUÄÕ
Hâ (¸gAZU\8îܧĩ}zīííû×ûŧįįüÎyĪ&æĸj 9R
<:ØOHÄÉŊHā æËÂgÅ đyx~t°?ü¯o pÕ.$Įá˙ēP&W