libopenapi-validator
libopenapi-validator copied to clipboard
Query Parameter Validation Miss
It looks like there's a bug when it comes to query parameter validation. I haven't done a deep dive on where it failed in libopenapi-validation, but it looks like the validation works correctly with openapi-core which is why I believe there's an issue.
For this schema, I would expect a list of <=10 items is fine, but anything >10 would be invalid. The explode has been disabled so everything is under a single id field then the schema has been flagged as an array type.
package main
import (
"fmt"
"net/http"
"strings"
"github.com/pb33f/libopenapi"
validator "github.com/pb33f/libopenapi-validator"
"github.com/pb33f/libopenapi/datamodel"
)
const openAPISpec = `
openapi: 3.1.0
info:
title: ID List API
version: "1.0.0"
paths:
/items:
get:
parameters:
- name: id
in: query
required: true
style: form
explode: false
schema:
type: array
items:
type: integer
maxItems: 10
responses:
'200':
description: OK
'400':
description: Invalid input
`
func main() {
// Load the OpenAPI spec from the string
doc, err := libopenapi.NewDocumentWithConfiguration([]byte(openAPISpec), &datamodel.DocumentConfiguration{})
if err != nil {
panic(err)
}
highLevelValidator, errs := validator.NewValidator(doc)
if errs != nil {
panic(errs)
}
// Helper to build a request
makeRequest := func(ids []int) *http.Request {
values := make([]string, len(ids))
for i, id := range ids {
values[i] = fmt.Sprintf("%d", id)
}
// query := url.Values{}
// query.Set("id", strings.Join(values, ","))
req, _ := http.NewRequest(http.MethodGet, "/items?id="+strings.Join(values, ","), nil)
return req
}
// Test valid case (10 items)
validReq := makeRequest([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
_, errs2 := highLevelValidator.ValidateHttpRequest(validReq)
if errs2 != nil {
panic(errs2)
}
// Test invalid case (12 items)
invalidReq := makeRequest([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
_, errs2 = highLevelValidator.ValidateHttpRequest(invalidReq)
if errs2 != nil {
panic(errs2)
}
}
This should be a working example (just need to install the dependencies). The valid request works and doesn't raise an exception, but the invalid request raises an exception.
import yaml
from openapi_core import OpenAPI
from openapi_core.validation.request.datatypes import RequestParameters
from pyramid.request import Request
from werkzeug.test import create_environ
# OpenAPI spec defining a query parameter "id" as an array with a maxItems constraint.
openapi_spec = """
openapi: 3.1.0
info:
title: ID List API
version: "1.0.0"
paths:
/items:
get:
parameters:
- name: id
in: query
required: true
style: form
explode: false
schema:
type: array
maxItems: 10
items:
type: integer
responses:
'200':
description: OK
'400':
description: Invalid input
"""
# Load the spec and create the OpenAPI validator.
spec_dict = yaml.safe_load(openapi_spec)
spec = OpenAPI.from_dict(spec_dict)
class OpenAPIRequest:
def __init__(self, request: Request) -> None:
self.request: Request = request
self.parameters = RequestParameters(
path=self.request.matchdict or {},
query=self.request.GET,
header=self.request.headers,
cookie=self.request.cookies,
)
@property
def host_url(self) -> str:
return "https://example.com"
@property
def path(self) -> str:
return self.request.path
@property
def path_pattern(self) -> str:
path_pattern = (
self.request.matched_route.pattern
if self.request.matched_route
else self.request.path_info
)
return path_pattern
@property
def method(self) -> str:
return self.request.method.lower()
@property
def body(self) -> bytes | None:
return self.request.body
@property
def content_type(self) -> str:
return self.request.content_type
@property
def mimetype(self) -> str:
return self.request.content_type
def make_request(ids):
"""
Create a fake GET /items request with a query parameter 'id'
that is a comma-separated list of integers.
"""
id_str = ",".join(str(i) for i in ids)
environ = create_environ(method="GET", path="/items", query_string=f"id={id_str}")
req = Request(environ)
# Wrap the Werkzeug request so openapi-core can use it.
return OpenAPIRequest(req)
# Validate a valid request (10 items)
valid_req = make_request([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
try:
result_invalid = spec.validate_request(valid_req)
except Exception as e:
print("Valid request failed validation with exception:", e)
# Validate an invalid request (12 items)
invalid_req = make_request([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
try:
result_invalid = spec.validate_request(invalid_req)
except Exception as e:
print("Invalid request failed validation with exception:", e)