pb33f / libopenapi-validator Goto Github PK
View Code? Open in Web Editor NEWOpenAPI validation extension for libopenapi, validate http requests and responses as well as schemas
Home Page: https://pb33f.io/libopenapi/validation/
License: Other
OpenAPI validation extension for libopenapi, validate http requests and responses as well as schemas
Home Page: https://pb33f.io/libopenapi/validation/
License: Other
As i just discovered, request bodies are optional by default.
see https://spec.openapis.org/oas/v3.1.0#fixed-fields-10 or https://swagger.io/docs/specification/describing-request-body/
this dear library doesn't handle such cases
as we can see above, if an operation has a request body, we will require to have a content type set, however that request body could be not required which means that a content type should not be required.
i haven't go any further in my investigation, maybe the underlying lib is already capable of handling such cases, any if we agree that this is not an expected behavior i can propose a fix in a few hours/days.
In the case that an OpenAPI spec is not defining the response body - which can be a valid thing to do - we receive an error:
validator.go:50: Error: GET / 200 operation response content type '' does not exist, Reason: The content type '' of the GET response received has not been defined, it's an unknown type, Line: 11, Column: 11
For spec:
openapi: "3.0.0"
info:
title: Healthcheck
version: '0.1.0'
paths:
/health:
get:
responses:
'200':
description: pet response
content: {}
There's quite a few other folks doing this, too https://github.com/search?q=%22content%3A+%7B%7D%22+language%3Ayaml&type=code so not sure if maybe this needs to be something we can opt-in to allowing maybe? Not sure if it's something we want to have on-by-default
Thanks your great job.
I found problem when Server path is no base path like this.
servers:
- url: 'http://127.0.0.1/'
This will call checkPathAgainstBase with basePath '/'. And the checkPathAgainstBase trim basePath last slash here, but if basePath is '/', basePaths is to be empty string. It's unexpected behavior, right?
so I think the code should be fixed like this.
if len(basePaths[i]) > 1 && basePaths[i][len(basePaths[i])-1] == '/' {
basePaths[i] = basePaths[i][:len(basePaths[i])-1]
}
Hi, I have the following spec.
{
"openapi": "3.0.0",
"info": {
"title": "API Spec With Mandatory Header",
"version": "1.0.0"
},
"paths": {
"/api-endpoint": {
"get": {
"summary": "Restricted API Endpoint",
"parameters": [
{
"name": "apiKey",
"in": "header",
"required": true,
"schema": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "integer"
}
]
}
}
],
"responses": {
"200": {
"description": "Successful response"
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyHeader": {
"type": "apiKey",
"name": "apiKey",
"in": "header"
}
}
},
"security": [
{
"ApiKeyHeader": []
}
]
}
However, the library is not checking the header type during validation. Here is the code to reproduce the issue.
package main
import (
"fmt"
"github.com/pb33f/libopenapi"
libopenapiValidator "github.com/pb33f/libopenapi-validator"
"net/http"
"os"
)
func main() {
specBytes, _ := os.ReadFile("temp.json")
doc, err := libopenapi.NewDocument(specBytes)
if err != nil {
fmt.Println("error while creating open api spec document", err)
return
}
req, err := http.NewRequest("GET", "/api-endpoint", nil)
if err != nil {
fmt.Println("error while creating new HTTP request", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("apiKey", "headerValue")
v3Model, errs := doc.BuildV3Model()
if len(errs) > 0 {
fmt.Println("error while building a Open API spec V3 model", errs)
return
}
v3Model.Model.Servers = nil
// render the document back to bytes and reload the model.
_, doc, v3Model, errs = doc.RenderAndReload()
validator, errs := libopenapiValidator.NewValidator(doc)
if len(errs) > 0 {
fmt.Println("error while getting validator", errs)
return
}
paramValidator := validator.GetParameterValidator()
isSuccess, valErrs := paramValidator.ValidateHeaderParams(req)
fmt.Println("is validation successful-", isSuccess)
if len(valErrs) > 0 {
fmt.Println("error during validation ", valErrs)
return
}
}
Outcome of this program is is validation successful- true
Our expectation is that the validation should fail as the header value type is string.
Thanks,
Triptesh
First of all, i love what you did there.
What do you think about handling default values in query parameters ?
I'm looking at adding libopenapi to https://gitlab.com/jamietanna/httptest-openapi, a library I've got for writing (unit) tests with Go's net/http
handlers by validating the response matches an OpenAPI spec.
I've looked at using this library to give me full OpenAPI 3.0 and 3.1 support, instead of Kin's OpenAPI 3.0 only, but noticed that I couldn't use it right away.
In the test code i.e. https://gitlab.com/jamietanna/httptest-openapi/-/blob/v0.3.0/openapi3/validator.go?ref_type=tags#L95 we only have access to a httptest.ResponseRecorder
whereas the library expects an http.Response
.
Would it be of interest to add support for providing a httptest.ResponseRecorder
or should I instead look at how to convert between the two types?
Currently as stated it supports only JSON:
We should support https://jsonapi.org/ format like application/vnd.api+json
Background:
We were using github.com/pb33f/libopenapi-validator
in our production code and receiving occasional panics, which I believe match the error shown in #75 (now solved).
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x88 pc=0x2aad6c9]
goroutine 128159 [running]:
github.com/pb33f/libopenapi-validator/requests.ValidateRequestSchema(0xc0a612cb00, 0xc0a377fb80, {0xc08e17d7a0, 0x3, 0x8}, {0xc08e17d7b8, 0x2, 0xc000680000?})
/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_request.go:89 +0x469
github.com/pb33f/libopenapi-validator/requests.(*requestBodyValidator).ValidateRequestBody(0xc04fa91a80, 0xc0a612cb00)
/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_body.go:91 +0x4a6
github.com/pb33f/libopenapi-validator.(*validator).ValidateHttpRequest.func2(0x717285?, 0xc04bc7a420?)
/go/pkg/mod/github.com/pb33f/[email protected]/validator.go:247 +0x30
created by github.com/pb33f/libopenapi-validator.(*validator).ValidateHttpRequest in goroutine 48754
/go/pkg/mod/github.com/pb33f/[email protected]/validator.go:267 +0x346
These panics were causing a fatal crash in our production code, so we attempted to wrap the method call to ValidateHttpRequest
in a recover()
in order to ensure it could not result in crashing our application.
After digging into the codebase, we discovered the recover()
did not work because ValidateHttpRequest
spawns multiple goroutines in order to perform the request validation and thus panics emitted from these goroutines could not be recovered from in our code.
We solved the issue by forking the repo and creating a ValidateHttpRequestSync
method that performs the validation synchronously, without any goroutines. This allows the method call to be safely wrapped in recover()
.
While I believe the particular panic we were seeing has been solved, for use in production we needed to have absolute certainty that a panic or nil-pointer deref emitted in this lib could not lead to a fatal crash of our application.
It's also worth noting that the time savings between ValidateHttpRequest
and ValidateHttpRequestSync
were negligible in my opinion (16ms vs 21ms, respectively, in my tests). ValidateHttpRequestSync
from our fork is currently in use in our production codebase without any issue.
Request:
Add the ValidateHttpRequestSync
method to the library so that the user of the library can determine if & how they wish to use goroutines for the request validation, as opposed to goroutines being spawned inside the library, which can lead to unrecoverable panics as shown above.
Example:
Here is the implementation of ValidateHttpRequestSync
from our fork:
https://github.com/pokt-foundation/libopenapi-validator/blob/main/validator.go#L284
Observed below two behaviours while testing the library:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x1096e15]
goroutine 1 [running]:
io.ReadAll({0x0, 0x0})
/usr/local/opt/go/libexec/src/io/io.go:704 +0x55
github.com/pb33f/libopenapi-validator/requests.ValidateRequestSchema(0xc000256400, 0xc000166a00, {0xc000174000, 0x48f, 0x580}, {0xc00018a000, 0x2dc, 0x0?})
/home/pratik/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_request.go:36 +0x85
github.com/pb33f/libopenapi-validator/requests.(*requestBodyValidator).ValidateRequestBody(0xc0004f3da0, 0xc000256400)
/home/pratik/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_body.go:89 +0x485
main.main()
/home/pratik/libopenapi-eval/main.go:38 +0x2e5
Process finished with the exit code 2
Code to reproduce:
package main
import (
"fmt"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/requests"
"io"
"net/http"
)
func main() {
//petstore spec
petStoreSpecSource := "https://petstore3.swagger.io/api/v3/openapi.json"
specBytes := readSpecFromURL(petStoreSpecSource)
doc, err := libopenapi.NewDocument(specBytes)
if err != nil {
fmt.Println("error while creating open api spec document", err)
return
}
// new PUT request with nil Body
// body is mandatory as per the spec
req, err := http.NewRequest("PUT", "https://somehost.com/api/v3/pet", nil)
if err != nil {
fmt.Println("error while creating new HTTP request", err)
return
}
req.Header.Set("Content-Type", "application/json")
v3Model, errs := doc.BuildV3Model()
if len(errs) > 0 {
fmt.Println("error while building a Open API spec V3 model", errs)
return
}
reqBodyValidator := requests.NewRequestBodyValidator(&v3Model.Model)
isSuccess, valErrs := reqBodyValidator.ValidateRequestBody(req)
fmt.Println("is validation successful-", isSuccess)
if len(valErrs) > 0 {
fmt.Println("error while validating request body", valErrs)
return
}
}
func readSpecFromURL(url string) []byte {
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
specBytes, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
return specBytes
}
Expected result : return appropriate validation error
Actual result : no error
package main
import (
"fmt"
"io"
"net/http"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/requests"
)
func main() {
//petstore spec
petStoreSpecSource := "https://petstore3.swagger.io/api/v3/openapi.json"
specBytes := readSpecFromURL(petStoreSpecSource)
doc, err := libopenapi.NewDocument(specBytes)
if err != nil {
fmt.Println("error while creating open api spec document", err)
return
}
// new PUT request with http.NoBody
// body is mandatory as per the spec
req, err := http.NewRequest("PUT", "https://somehost.com/api/v3/pet", http.NoBody)
if err != nil {
fmt.Println("error while creating new HTTP request", err)
return
}
req.Header.Set("Content-Type", "application/json")
v3Model, errs := doc.BuildV3Model()
if len(errs) > 0 {
fmt.Println("error while building a Open API spec V3 model", errs)
return
}
reqBodyValidator := requests.NewRequestBodyValidator(&v3Model.Model)
isSuccess, valErrs := reqBodyValidator.ValidateRequestBody(req)
fmt.Println("is validation successful-", isSuccess)
if len(valErrs) > 0 {
fmt.Println("error while validating request body", valErrs)
return
}
}
func readSpecFromURL(url string) []byte {
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
specBytes, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
return specBytes
}
Hi community, I have been using libopenapi-validator library. I have the following api-spec.
{
"openapi": "3.0.0",
"info": {
"title": "API Spec With Mandatory Header and Query Parameters",
"version": "1.0.0"
},
"paths": {
"/api-endpoint/{id}": {
"get": {
"summary": "Restricted API Endpoint",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "apiKey",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "userId",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response"
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyHeader": {
"type": "apiKey",
"name": "apiKey",
"in": "header"
}
}
},
"security": [
{
"ApiKeyHeader": []
}
]
}
(1) /<host>/api-endpoint
gives the following error which is expected
GET Path '/api-endpoint' not found
(2) /<host>/api-endpoint/
does not give any error. Can anyone confirm if the library is taking empty string as path parameter ? (I have kept path parameter required as true. So, my expectation is if its empty then it should fail)
Hi, I have the following schema defined.
paths:
'/path123/{name}':
get:
description: Obtain information about a country from unique country name
parameters:
- name: name
in: path
required: true
schema:
type: string
minLength: 3
(1) /path123/de path gives the following error which is expected.
"Path /path123/de is invalid or not supported"
(2) However if we pass empty path parameter /path123/ it passes and does not the return error
Observed below behavior while testing the library:
Panics when a request for which the body has to be validated is called with a nil body
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x88 pc=0x104ffe140]
goroutine 1 [running]:
github.com/pb33f/libopenapi-validator/requests.ValidateRequestSchema(0x140001d8300, 0x14000334000, {0x14000020600, 0x161, 0x200}, {0x14000370000, 0xec, 0x104c707a4?})
/Users/Triptesh/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_request.go:89 +0x390
github.com/pb33f/libopenapi-validator/requests.(*requestBodyValidator).ValidateRequestBody(0x14000249ee8, 0x140001d8300)
/Users/Triptesh/go/pkg/mod/github.com/pb33f/[email protected]/requests/validate_body.go:101 +0x464
main.main()
/Users/Triptesh/Downloads/go-test/main.go:40 +0x21c
exit status 2
Code to reproduce:
package main
import (
"fmt"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/requests"
"net/http"
"os"
)
func main() {
specBytes, _ := os.ReadFile("temp.json")
doc, err := libopenapi.NewDocument(specBytes)
if err != nil {
fmt.Println("error while creating open api spec document", err)
return
}
req, err := http.NewRequest("PUT", "/path1", nil)
if err != nil {
fmt.Println("error while creating new HTTP request", err)
return
}
req.Header.Set("Content-Type", "application/json")
v3Model, errs := doc.BuildV3Model()
if len(errs) > 0 {
fmt.Println("error while building a Open API spec V3 model", errs)
return
}
reqBodyValidator := requests.NewRequestBodyValidator(&v3Model.Model)
isSuccess, valErrs := reqBodyValidator.ValidateRequestBody(req)
fmt.Println("is validation successful-", isSuccess)
if len(valErrs) > 0 {
fmt.Println("error while validating request body", valErrs)
return
}
}
temp.json ->
{
"openapi": "3.0.1",
"info": {
"title": "testing",
"description": "<p>This is for testing purpose</p>",
"version": "1.0",
"x-targetEndpoint": "https://mocktarget.apigee.net/json"
},
"servers": [
{
"url": "https://some-url.com"
}
],
"paths": {
"/path1": {
"put": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"age": {
"type": "integer"
}
},
"required": [
"name"
]
},
{
"type": "object",
"properties": {
"email": {
"type": "string"
},
"address": {
"type": "string"
}
},
"required": [
"email"
]
}
]
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/path2": {
"get": {
"parameters": [
{
"name": "X-My-Header",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/path3": {
"get": {
"parameters": [
{
"name": "id",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
openapi: 3.1.0
servers:
- url: /
paths:
/burgers/{burgerId}/locate:
patch:
operationId: locateBurger
parameters:
- name: burgerId
in: path
required: true
schema:
type: string
format: uuid
a validation of a http request for that open api path will yield an unexpected result
Error: Path parameter 'burgerId' failed to validate, Reason: The path parameter 'burgerId' is defined as an string, however it failed to pass a schema validation, Validation Errors: [Reason: 'locate' is not valid 'uuid', Location: /format
the cause is this function
// StripRequestPath strips the base path from the request path, based on the server paths provided in the specification
func StripRequestPath(request *http.Request, document *v3.Document) string {
basePaths := getBasePaths(document)
// strip any base path
stripped := stripBaseFromPath(request.URL.Path, basePaths)
if request.URL.Fragment != "" {
stripped = fmt.Sprintf("%s#%s", stripped, request.URL.Fragment)
}
return stripped
}
which remove the base path from the request path, in our case it yields
burgers/{burgerId}/locate
but the foundPath is still valued as
/burgers/{burgerId}/locate
if we want to support the / server url, we could modify the StripRequestPath to make sure that a path always starts with a / even if we drop it during the stripBaseFromPath phase.
wdyt ?
something like this
// StripRequestPath strips the base path from the request path, based on the server paths provided in the specification
func StripRequestPath(request *http.Request, document *v3.Document) string {
basePaths := getBasePaths(document)
// strip any base path
stripped := stripBaseFromPath(request.URL.Path, basePaths)
if request.URL.Fragment != "" {
stripped = fmt.Sprintf("%s#%s", stripped, request.URL.Fragment)
}
if len(stripped) > 0 && !strings.HasPrefix(stripped, "/") {
stripped = "/" + stripped
}
return stripped
}
I've a use case where concurrency safety is needed (a middleware), and I noticed that validator instances are not concurrency safe. I wasn't sure if it was by design?
I think the library would be more useful if validators were concurrency safe. I'm happy to do the work myself if outside contributions are accepted.
I encountered a runtime error while attempting to validate a data blob against an OpenAPI v3.0 schema that includes the boolean exclusiveMinimum
parameter. The error message is panic: runtime error: invalid memory address or nil pointer dereference
.
After debugging, I traced the issue to an unchecked error at line 115 in the code. This panic occurs because the variable jsch
is nil
and is later used to access its properties at line 119.
Error message that ignored is "expected number, but got boolean". It seems that libopenapi-validator
processes exclusiveMinimum
as per the v3.1 OpenAPI specification, despite v3.0 specification being provided. According to the Migrating from OpenAPI 3.0 to 3.1.0, exclusiveMinimum
is defined only as a boolean for OpenAPI v3.0:
# OpenAPI v3.0
minimum: 7
exclusiveMinimum: true
In contrast, OpenAPI v3.1 defines it as a numeric value:
# OpenAPI v3.1
exclusiveMinimum: 7
To reproduce the issue, you can use the following tests:
func TestValidateSchema_v3_0_BooleanExclusiveMinimum(t *testing.T) {
spec := `openapi: 3.0.0
paths:
/burgers/createBurger:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
minimum: 0
exclusiveMinimum: true`
doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
body := map[string]interface{}{"amount": 3}
bodyBytes, _ := json.Marshal(body)
sch := m.Model.Paths.PathItems["/burgers/createBurger"].Post.RequestBody.Content["application/json"].Schema
// create a schema validator
v := NewSchemaValidator()
// validate!
valid, errors := v.ValidateSchemaString(sch.Schema(), string(bodyBytes))
assert.True(t, valid)
assert.Empty(t, errors)
}
func TestValidateSchema_v3_0_NumericExclusiveMinimum(t *testing.T) {
spec := `openapi: 3.0.0
paths:
/burgers/createBurger:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
exclusiveMinimum: 0`
doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
body := map[string]interface{}{"amount": 3}
bodyBytes, _ := json.Marshal(body)
sch := m.Model.Paths.PathItems["/burgers/createBurger"].Post.RequestBody.Content["application/json"].Schema
// create a schema validator
v := NewSchemaValidator()
// validate!
valid, errors := v.ValidateSchemaString(sch.Schema(), string(bodyBytes))
assert.False(t, valid)
assert.NotEmpty(t, errors)
}
func TestValidateSchema_v3_1_BooleanExclusiveMinimum(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/burgers/createBurger:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
minimum: 0
exclusiveMinimum: true`
doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
body := map[string]interface{}{"amount": 3}
bodyBytes, _ := json.Marshal(body)
sch := m.Model.Paths.PathItems["/burgers/createBurger"].Post.RequestBody.Content["application/json"].Schema
// create a schema validator
v := NewSchemaValidator()
// validate!
valid, errors := v.ValidateSchemaString(sch.Schema(), string(bodyBytes))
assert.False(t, valid)
assert.NotEmpty(t, errors)
}
func TestValidateSchema_v3_1_NumericExclusiveMinimum(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/burgers/createBurger:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
exclusiveMinimum: 0`
doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
body := map[string]interface{}{"amount": 3}
bodyBytes, _ := json.Marshal(body)
sch := m.Model.Paths.PathItems["/burgers/createBurger"].Post.RequestBody.Content["application/json"].Schema
// create a schema validator
v := NewSchemaValidator()
// validate!
valid, errors := v.ValidateSchemaString(sch.Schema(), string(bodyBytes))
assert.True(t, valid)
assert.Empty(t, errors)
}
The appropriate solution is to modify libopenapi-validator
to handle exclusiveMinimum
according to the provided OpenAPI specification version (v3.0 or v3.1). Maybe adding error check will be helpful to detect similar issues in the future.
Please feel free to reach out if you need further assistance or clarification.
As per the changes on this branch of one of my projects, returning an invalid content-type
header in either the implementation or the OpenAPI spec leads to no failure.
Hi there, I think I'm facing an issue with validation.
Example like the following:
- in: query
name: count
description: number of facts to return
required: false
schema:
type: integer
format: int32
minimum: 1
There is no check on the minimum value in that case with simple schema having a primitive type like integer.
When it is working with:
ListSpacesPage:
name: "page"
in: "query"
style: "deepObject"
description: "list spaces paginated request"
required: false
schema:
type: object
properties:
size:
type: integer
description: The maximum number of items to return. The service may return fewer than this value.
minimum: 1
maximum: 50
default: 30
format: int32
require: false
Is this by design, a bug, or I'm missing something?
Cheers and thank you for this amazing lib and the work you're putting in.
I was testing your library and ran into an issue https://go.dev/play/p/YEu9wDp3sBZ
It sounds similar to #16 but is related to query params validation and happens only when the query param is omitted.
In the current state of the api the ValidateHttpRequest
does the path lookup and the request validation at the same time. It ouputs an error slice of errors.ValidationError
.
Issue is when you want to do composition in middleware you end up with logic like:
skipServe := false
valid, validationErrs := docValidator.ValidateHttpRequest(r)
if !valid {
for _, e := range validationErrs {
if opts.RouteNotFoundHook != nil && e.ValidationType == "path" && e.ValidationSubType == "missing" {
skipServe = opts.RouteNotFoundHook(e, w, r)
break
}
if opts.RouteValidationErrorHook != nil {
skipServe = opts.RouteValidationErrorHook(e, w, r)
break
}
}
}
Solutions:
wdyt?
In the context of the following YAML code snippet from the OpenAPI specification:
/companies/{company_id}:
patch:
parameters:
- name: company_id
in: path
required: true
example: 9de0691c-bc8d-409b-8f40-75d4f45db2f3
schema:
type: string
pattern: '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i'
I am attempting to validate the company_id path parameter using a regular expression. However, despite my efforts, it seems that the validation is not working as expected.
func (s Spec) ValidateRequest(request *http.Request) error {
valid, errs := v.validator.ValidateHttpRequest(request)
if len(errs) > 0 {
for _, err := range errs {
fmt.Println(err)
}
}
if !valid {
return fmt.Errorf("request is not valid")
}
return nil
}
Or with :
func (s Spec) FindRoute(request *http.Request) error {
_, errs, _ := paths.FindPath(request, v.document)
if len(errs) > 0 {
return fmt.Errorf("request is not valid : %v", errs)
}
valid, errs := v.validator.GetParameterValidator().ValidatePathParams(request)
if len(errs) > 0 {
for _, err := range errs {
fmt.Println(err)
}
return fmt.Errorf("request is not valid: %v", errs)
}
if !valid {
return fmt.Errorf("request is not valid")
}
return nil
}
Hello,
I would like to ask, if you can add a feature where in Request/Response Validation Errors we can get the "field" name . This is also provided in Kin api but not in this API.
I think this would be great addittion to this great API
Given my understanding of JSON schema and OpenAPI (3.1) the following schema is supposed to be valid:
paths:
/path:
get:
parameters:
- name: obj
in: query
style: deepObject
schema:
type: object
properties:
root:
type: string
nested:
type: object
properties:
child:
type: string
required: true
Meaning an URL like /path?obj[root]=test1&obj[nested][child]=test2
should pass the validation against the schema above.
As far as I can tell from the code deepObject style query parameters are decoded to a flat map of QueryParam arrays. This works for objects with a depth of one, but won't work for objects with more depth.
The validation functions return both a bool and error(s).
It leads to simpler code to simply ignore the returned bool and check for len(errs) > 0
or err != nil
I suggest dropping the returned bool for the next breaking change unless there's some other purpose for it that I've missed
I am trying to use the schema_validation module to validate against a schema containing a single string enum property. Validation succeeds when one of the enum values is provided, but instead of giving an error when validation fails there's a nil pointer panic.
Here's a full example:
package main
import (
"fmt"
"github.com/pb33f/libopenapi"
validator "github.com/pb33f/libopenapi-validator"
"github.com/pb33f/libopenapi-validator/schema_validation"
)
const yamlSchema = `
openapi: 3.1.0
info:
version: v1
title: test API
description: test api
servers:
- url: https://localhost/api/v1
description: test api
components:
schemas:
Logging:
properties:
logLevel:
type: string
enum: [critical, error, info, debug]
`
func main() {
doc, err := libopenapi.NewDocument([]byte(yamlSchema))
if err != nil {
panic(err)
}
docValidator, errs := validator.NewValidator(doc)
if len(errs) > 0 {
panic(errs)
}
valid, docErrors := docValidator.ValidateDocument()
if !valid {
fmt.Printf("document validation failed: %v\n", docErrors)
}
model, errs := doc.BuildV3Model()
if len(errs) > 0 {
panic(errs)
}
fmt.Println(model.Model.Components.Schemas)
schemaProxy := model.Model.Components.Schemas["Logging"]
schema := schemaProxy.Schema()
low := schema.GoLow()
fmt.Println(low.Type.KeyNode)
validator := schema_validation.NewSchemaValidator()
obj := map[string]interface{}{"logLevel": "invalid"}
valid, errors := validator.ValidateSchemaObject(schema, obj)
if !valid {
fmt.Printf("schema validation failed: %v\n", errors)
}
}
and the output:
map[Logging:0xc00011a300]
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x88 pc=0x7802a4]
goroutine 1 [running]:
github.com/pb33f/libopenapi-validator/schema_validation.validateSchema(0xc0000b9b80, {0x0, 0x0, 0x0}, {0x7c9600, 0xc0003410e0}, 0xc0000ce000?)
<path>/go/pkg/mod/github.com/pb33f/[email protected]/schema_validation/validate_schema.go:170 +0x524
github.com/pb33f/libopenapi-validator/schema_validation.(*schemaValidator).ValidateSchemaObject(0x7c9600?, 0xc0003410e0?, {0x7c9600?, 0xc0003410e0?})
<path>/go/pkg/mod/github.com/pb33f/[email protected]/schema_validation/validate_schema.go:59 +0x36
main.main()
<path>/main.go:56 +0x276
exit status 2
It appears that schema.GoLow().Type.KeyNode
is nil and the code trying to add the error attempts to use it without a nil check. Is it expected that KeyNode should ever be nil?
Firstly, thanks for the great library!
I just tried upgrading to v0.0.40 of validator and I'm seeing a new failure.
My contract describes a couple of error states in responses with only a code and a description, such as:
'400':
description: this is the reason you would see this response code for this path/verb
When validating a response to an intentionally bad request I get the following validation error:
error validating: POST operation request response code '400' does not exist
I believe I have traced the error to the block that begins on line 47 within validate_body.go
// check if the response code is in the contract
foundResponse := operation.Responses.FindResponseByCode(httpCode)
if foundResponse != nil && foundResponse.Content != nil {
foundResponse finds the correct response with the correct description, however as Content is nil the if statement here fails. There is an else block below that that would examine the operation's default responses but that property is also nil so the validation fails with the incorrect error about the response code not existing.
Please let me know if you need any additional info/clarification, and thanks again!
If you call NewValidator() the resulting validator has the v.document field set. However if I want you call NewValidatorFromV3Model() it doesn't. Without this being set you get a panic when you call ValidateDocument(). I eventually dug into the code, and I found that the v3 Model is cached in the document, so just did that.
It's possible that I'm just missing something obvious with Go, but due to the private access, you can't actually repair the invariant if you try and build it from the model, because the document field is private (as is the type).
For example, in this schema, the username is defined as a string type. If called in this way, it will lead to the inability to find the route:
libopenapi-validator/test_specs/petstorev3.json
Lines 818 to 836 in 657229e
request := &http.Request{
Method: request.Method,
URL: 'http://localhost:8080/user/123456',
}
_, _, pathKey := paths.FindPath(request, xxxSchema)
// pathKey is nil
is it possible to remove the string validation here since numbers can be safely used as strings?
libopenapi-validator/paths/paths.go
Lines 284 to 288 in 657229e
For the branch libopenapi-validator-required-header running go test ./server/... -v
results in the following:
=== RUN TestCareRequestApi_GetRequest/when_no_request_found/it_matches_OpenAPI
implementation_test.go:81: Type: , Failure: Header parameter 'tracing-id' is missing
Notice that the Type
, which comes from ValidationType
is empty.
The result of:
fmt.Printf("e: %#v\n", e)
Is:
e: &errors.ValidationError{Message:"Header parameter 'tracing-id' is missing", Reason:"The header parameter 'tracing-id' is defined as being required, however it's missmissinging from the requests", ValidationType:"", ValidationSubType:"", SpecLine:1, SpecCol:299, HowToFix:"", SchemaValidationErrors:[]*errors.SchemaValidationFailure(nil), Context:interface {}(nil)}
Hello Dave,
when I pass a malformed JSON body, I can trick the validation to pass a document with missing required
properties:
Example code:
package main
import (
"bytes"
"flag"
"fmt"
"net/http"
"os"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/requests"
"github.com/pb33f/libopenapi/datamodel"
)
func main() {
var path, payload, specFile string
flag.StringVar(&path, "path",
"/burgers/create-burger",
"request path",
)
flag.StringVar(&payload, "payload",
"burger-with-garbage.json",
"payload file",
)
flag.StringVar(&specFile, "spec",
"burgers-inline.yaml",
"spec file",
)
flag.Parse()
fmt.Println("Spec file is: " + specFile)
fmt.Println("Path is: " + path)
fmt.Println("Payload file is: " + payload)
// Load an OpenAPI Spec file
specBytes, err := os.ReadFile(specFile)
if err != nil {
panic(err)
}
// Load the payload file
payloadBytes, err := os.ReadFile(payload)
if err != nil {
panic(err)
}
// Enable file references
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
}
// Create a new OpenAPI document using libopenapi
document, docErrs := libopenapi.NewDocumentWithConfiguration(specBytes, &config)
if docErrs != nil {
panic(docErrs)
}
// Build a model
document.SetConfiguration(&datamodel.DocumentConfiguration{AllowFileReferences: true})
docModel, errors := document.BuildV3Model()
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %e\n", errors[i])
}
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
}
request, err := http.NewRequest(http.MethodPost, "http://localhost"+path, bytes.NewReader(payloadBytes))
if err != nil {
panic(err)
}
request.Header.Set("Content-Type", "application/json")
valdtr := requests.NewRequestBodyValidator(&docModel.Model)
valid, valdtrErrors := valdtr.ValidateRequestBody(request)
if !valid {
for _, err := range valdtrErrors {
for _, reason := range err.SchemaValidationErrors {
fmt.Printf("-------> %+v\n", reason)
}
}
}
Schema file:
openapi: 3.1.0
info:
title: Burgers
license:
name: License Agreement
url: https://www.example.com/licensing.html
version: latest
description: |
More burgers!
A unified API for consuming burgers
contact:
name: Ronald Macdonald
email: [email protected]
servers:
- url: https://api.example.com
description: Development environment
externalDocs:
description: Find out more about burgers
url: https://www.example.com
security:
- Bearer: []
paths:
/burgers/create-burger:
post:
operationId: createBurger
requestBody:
content:
application/json:
schema:
type: object
required:
- name
- patties
- vegetarian
properties:
name:
type: string
patties:
type: integer
vegetarian:
type: boolean
unevaluatedProperties: false
examples:
pbjBurger:
summary: A horrible, nutty, sticky mess.
value:
name: Peanut And Jelly
patties: 3
vegetarian: true
responses:
'201':
description: Burger created
headers:
Location:
description: URL for the created burger
schema:
type: string
format: uri
example: burgers/0e7f516c-0829-4135-83d6-09ce844ddd9d
components:
securitySchemes:
Bearer:
description: Uses a token for authorization
type: http
scheme: bearer
Malformed payload:
{
"name": "Garbage burger",
"patties": 3,
}
Result:
Spec file is: burgers-inline.yaml
Path is: /burgers/create-burger
Payload file is: burger-malformed.json
Correct payload:
{
"name": "Garbage burger",
"patties": 3
}
Result:
Spec file is: burgers-inline.yaml
Path is: /burgers/create-burger
Payload file is: burger-missing.json
-------> Reason: missing properties: 'vegetarian', Location: /required
When attempting to validate an http response with a mandatory response header, validation is successful even when the mandatory header is missing.
Challenge is that currently there is a ValidateResponseBody function, but no equivalant ValidateResponseHeaders. Request headers are correctly validated.
Hello,
I am getting race errors when running tests: go test -race -count=2 ./...
I have a simple repo here:
https://github.com/fcwoknhenuxdfiyv/burgers.git
kin-openapi has some functionality where it checks that defaults and examples match the schema, and that formats match types and patterns are valid. This can, for example, reject the following schema:
{
"properties": {
"num": {
"default": 1,
"description": "Number of things",
"maximum": 10,
"minimum": 2,
"title": "Num",
"type": "integer",
"x-order": 0
}
},
"title": "Input",
"type": "object"
}
Could this be implemented in libopenapi-validator?
libopenapi-validator currently is unable to handle fixed fields in schema objects, an example being writeOnly. This property specifies that the schema object may appear in a request but not to come back in the response, as noted https://spec.openapis.org/oas/v3.0.3#fixed-fields-19
However libopenapi-validator has no reference to these fields and this behaviour.
Is there a plan to implement this?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.