Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nil Pointer Panic when Validating Boolean exclusiveMinimum Schema Parameter #26

Closed
RomanKrivetsky opened this issue Sep 20, 2023 · 4 comments
Labels
bug Something isn't working

Comments

@RomanKrivetsky
Copy link

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.

@daveshanley
Copy link
Member

Thanks for reporting this. I think I know what the issue is, I'll look into it asap.

@daveshanley daveshanley added the bug Something isn't working label Sep 21, 2023
@daveshanley
Copy link
Member

I have spent a huge amount of time on this issue this weekend. I can absolutely fix the null pointer exception, however there are some complexities.

The schema being validated is rendered by libopenapi from the model that was read in. The model has been updated to detect the version being read and ensure it reads a bool or a float.

Now the problem here is that when the library re-renders out the schema to be validated, it's going to render the correct value (bool or float), regardless of what you're feeding into as the source (that original YAML/JSON has been processed and parsed into a model, the model is printed back out (so it's mutable).

So the validation will improve with the next release, and I will add some code to showcase it in this ticket, but it won't pass all the tests you have created. For example:

An openapi 3.0 model will read in this 3.1 schema as:

schema:
  type: object
  properties:
    amount:
      type: number
      minimum: 0
      exclusiveMinimum: 0

but what is printed back out to be used by the validator, will be this:

schema:
  type: object
  properties:
    amount:
      type: number
      minimum: 0
      exclusiveMinimum: false

It converts the type to the correct one, based on the version. This output is a rendered version of of the schema, based on the model and not the raw YAML used to create the model.

So some of the tests won't ever pass, the bits are set to default (false/0) based on the version of the spec.

However I will fix the NPE you're seeing, and add some fixes to ensure validation is working as expected, as there are a couple of glitches I have noticed while debugging this.

daveshanley added a commit that referenced this issue Oct 8, 2023
bumped `libopenapi` to `v0.12.1`

Signed-off-by: Dave Shanley <[email protected]>
@daveshanley daveshanley mentioned this issue Oct 8, 2023
daveshanley added a commit that referenced this issue Oct 8, 2023
bumped `libopenapi` to `v0.12.1`

Signed-off-by: Dave Shanley <[email protected]>
@daveshanley
Copy link
Member

In v0.0.21, I have tuned up handling of these problems the best I can. Three of the tests pass, one I cannot get to pass, because of how the libopenapi is designed, it does not print what the original spec states, it essentially 'corrects' it underneath and renders out the correct property.

Three of the tests have been added to the suite.

https://github.com/pb33f/libopenapi-validator/blob/main/schema_validation/validate_schema_test.go#L539

@daveshanley
Copy link
Member

I have had to roll back some changes in v0.0.22. vacuum depends on the existing (always on draft2020) schema validator. If we use versions, it breaks everything in vacuum.

This means only 2 of the 4 tests will now pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants