Git Product home page Git Product logo

pb33f / libopenapi-validator Goto Github PK

View Code? Open in Web Editor NEW
30.0 4.0 13.0 487 KB

OpenAPI validation extension for libopenapi, validate http requests and responses as well as schemas

Home Page: https://pb33f.io/libopenapi/validation/

License: Other

Go 100.00%
go golang http openapi openapi-spec openapi-specification openapi3 schema validation validator openapi3-1 openapi3-validation openapi31

libopenapi-validator's Introduction

libopenapi

Enterprise grade OpenAPI validation tools for golang.

Pipeline codecov discord Docs

A validation module for 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 πŸ‘ˆπŸ‘ˆ


Installation

go get github.com/pb33f/libopenapi-validator

Documentation

libopenapi and libopenapi-validator are products of Princess Beef Heavy Industries, LLC

libopenapi-validator's People

Contributors

commoddity avatar daveshanley avatar emilien-puget avatar jacobm-splunk avatar k1low avatar martinsirbe avatar nickajacks1 avatar tristanspeakeasy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

libopenapi-validator's Issues

/ server breaks validation

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
}

A malformed JSON body allows missing required properties to validate...

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

Nested object schema validation in query params

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.

Redundant bool return in Validation functions

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

nil pointer panic validating simple enum schema against invalid value

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?

Feature Request: `ValidateHttpRequestSync`

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

Validation of simple parameters

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.

Incorrect behaviour when validating request body

Observed below two behaviours while testing the library:

1. 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=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
}


2. Does not return any validation error when called with a http.NoBody

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
}

checkPathAgainstBase with basePath '/'

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]
		}

Path not found error handling

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:

  • Expose a specific error for a path not found
  • Expose a function to check if the error is a path not found

wdyt?

A spec with an empty `content` block will always fail to validate

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

Feature Request - Document validator: Validate defaults, examples, formats, patterns

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?

Concurency safe validators?

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.

Header validation with oneOf or anyOf defined in schema

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

Empty `Type` returned for missing required header

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)}

Response Without Content Fails Validation

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!

OpenAPI 3.0.1 : path parameter validation does not comply the required schema

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

Incorrect Behavior While Validating Request Body

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"
          }
        }
      }
    }
  }
}

Validating Path Parameters with Regex

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
}

Issue with Path Parameter Validation

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)

Handles optional request bodies

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

contentType := request.Header.Get(helpers.ContentTypeHeader)

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.

Question / feature proposal: Using the validator with `httptest.ResponseRecorder`

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?

Response Headers aren't easily validated

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.

Problem with passing string type parameters using numbers in path

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:

"/user/{username}": {
"get": {
"tags": [
"user"
],
"summary": "Get user by user name",
"description": "",
"operationId": "getUserByName",
"parameters": [
{
"name": "username",
"in": "path",
"description": "The name that needs to be fetched. Use user1 for testing. ",
"required": true,
"schema": {
"type": "string"
}
}
],

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?

case helpers.String, helpers.Object, helpers.Array:
// should not be a number.
if _, err := strconv.ParseFloat(s, 64); err == nil {
s = helpers.FailSegment
}

Nil Pointer Panic when Validating Boolean exclusiveMinimum Schema Parameter

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.