Skip to content

Commit

Permalink
fix: manage openapi wrongly parsed example params and unsupported med…
Browse files Browse the repository at this point in the history
…ia types
  • Loading branch information
emmanuelgautier committed Dec 15, 2024
1 parent 5df3881 commit c7da3af
Show file tree
Hide file tree
Showing 5 changed files with 1,351 additions and 24 deletions.
1 change: 1 addition & 0 deletions .github/workflows/scans.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ jobs:
"simple_http_bearer_jwt.openapi.json",
"simple_http_bearer.openapi.json",
"complex.openapi.json",
"petstore.openapi.json"
]

steps:
Expand Down
4 changes: 3 additions & 1 deletion openapi/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ func (openapi *OpenAPI) Operations(client *request.Client, securitySchemes auth.
var body *bytes.Buffer
var mediaType string
if o.RequestBody != nil {
body, mediaType = getRequestBodyValue(o.RequestBody.Value)
body, mediaType, _ = getRequestBodyValue(o.RequestBody.Value)
}
if body != nil && mediaType != "" {
header.Set("Content-Type", mediaType)
} else {
body = bytes.NewBuffer(nil)
Expand Down
129 changes: 106 additions & 23 deletions openapi/param.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package openapi

import (
"bytes"
"fmt"
"strconv"

"github.com/brianvoe/gofakeit/v7"
Expand All @@ -10,21 +11,36 @@ import (

const maximumDepth = 4

func NewErrNoSupportedBodyMediaType() error {
return fmt.Errorf("no supported body media type")
}

func getParameterValue(param *openapi3.Parameter) string {
if param.Schema != nil {
value := getSchemaValue(param.Schema.Value, 0)
switch {
case param.Schema.Value.Type.Is("string"):
return value.(string)
case param.Schema.Value.Type.Is("number"):
return strconv.FormatFloat(value.(float64), 'f', -1, 64)
switch param.Schema.Value.Format {
case "float":

Check failure on line 26 in openapi/param.go

View workflow job for this annotation

GitHub Actions / test

string `float` has 4 occurrences, make it a constant (goconst)
return strconv.FormatFloat(value.(float64), 'f', -1, 32)
case "double":

Check failure on line 28 in openapi/param.go

View workflow job for this annotation

GitHub Actions / test

string `double` has 4 occurrences, make it a constant (goconst)
default:
return strconv.FormatFloat(value.(float64), 'f', -1, 64)
}
case param.Schema.Value.Type.Is("integer"):
return strconv.Itoa(value.(int))
switch param.Schema.Value.Format {
case "int32":

Check failure on line 34 in openapi/param.go

View workflow job for this annotation

GitHub Actions / test

string `int32` has 4 occurrences, make it a constant (goconst)
return strconv.Itoa(int(value.(int32)))
case "int64":

Check failure on line 36 in openapi/param.go

View workflow job for this annotation

GitHub Actions / test

string `int64` has 4 occurrences, make it a constant (goconst)
default:
return strconv.Itoa(int(value.(int64)))
}
case param.Schema.Value.Type.Is("boolean"):
return strconv.FormatBool(value.(bool))
}
}

return ""
}

Expand All @@ -34,9 +50,21 @@ func mapRequestBodyFakeValueToJSON(schema *openapi3.Schema, fakeValue interface{
case schema.Type.Is("string"):
jsonResponse = []byte("\"" + fakeValue.(string) + "\"")
case schema.Type.Is("number"):
jsonResponse = []byte(strconv.FormatFloat(fakeValue.(float64), 'f', -1, 64))
switch schema.Format {
case "float":
jsonResponse = []byte(strconv.FormatFloat(fakeValue.(float64), 'f', -1, 32))
case "double":
default:
jsonResponse = []byte(strconv.FormatFloat(fakeValue.(float64), 'f', -1, 64))
}
case schema.Type.Is("integer"):
jsonResponse = []byte(strconv.Itoa(fakeValue.(int)))
switch schema.Format {
case "int32":
jsonResponse = []byte(strconv.Itoa(int(fakeValue.(int32))))
case "int64":
default:
jsonResponse = []byte(strconv.Itoa(int(fakeValue.(int64))))
}
case schema.Type.Is("boolean"):
jsonResponse = []byte(strconv.FormatBool(fakeValue.(bool)))
case schema.Type.Is("array"):
Expand Down Expand Up @@ -64,35 +92,90 @@ func mapRequestBodyFakeValueToJSON(schema *openapi3.Schema, fakeValue interface{
return bytes.NewBuffer(jsonResponse)
}

func getRequestBodyValue(requestBody *openapi3.RequestBody) (*bytes.Buffer, string) {
if requestBody.Content != nil {
for mediaType, mediaTypeValue := range requestBody.Content {
if mediaTypeValue.Schema != nil {
body := getSchemaValue(mediaTypeValue.Schema.Value, 0)
switch mediaType {
case "application/json":
return mapRequestBodyFakeValueToJSON(mediaTypeValue.Schema.Value, body), "application/json"
default:
return bytes.NewBuffer([]byte(body.(string))), mediaType
}
func getRequestBodyValue(requestBody *openapi3.RequestBody) (*bytes.Buffer, string, error) {
if requestBody == nil || requestBody.Content == nil {
return nil, "", nil
}
for mediaType, mediaTypeValue := range requestBody.Content {
if mediaTypeValue.Schema != nil {
body := getSchemaValue(mediaTypeValue.Schema.Value, 0)
switch mediaType {

Check failure on line 102 in openapi/param.go

View workflow job for this annotation

GitHub Actions / test

singleCaseSwitch: should rewrite switch statement to if statement (gocritic)
case "application/json":
return mapRequestBodyFakeValueToJSON(mediaTypeValue.Schema.Value, body), mediaType, nil
}
}
}

return bytes.NewBuffer(nil), ""
return nil, "", NewErrNoSupportedBodyMediaType()
}

func getSchemaValue(schema *openapi3.Schema, depth int) interface{} {
func parseSchemaExample(schema *openapi3.Schema) (interface{}, error) {
var example interface{}
if schema.Example != nil {
return schema.Example
example = schema.Example
} else if len(schema.Enum) > 0 {
return schema.Enum[gofakeit.Number(0, len(schema.Enum)-1)]
example = schema.Enum[gofakeit.Number(0, len(schema.Enum)-1)]
}
if example == nil {
return nil, nil
}

var ok bool
switch {
case schema.Type.Is("string"):
example, ok = example.(string)
case schema.Type.Is("number"):
switch schema.Format {
case "float":
example, ok = example.(float32)
case "double":
default:
example, ok = example.(float64)
}
case schema.Type.Is("integer"):
switch schema.Format {
case "int32":
example, ok = example.(int32)
case "int64":
default:
example, ok = example.(int64)
}
case schema.Type.Is("boolean"):
example, ok = example.(bool)
case schema.Type.Is("array"):
example, ok = example.([]interface{})
case schema.Type.Is("object"):
example, ok = example.(map[string]interface{})
}
if !ok {
return nil, fmt.Errorf("invalid example type")
}
return example, nil
}

func getSchemaValue(schema *openapi3.Schema, depth int) interface{} {
example, err := parseSchemaExample(schema)
if err == nil && example != nil {
return example
}

// if there is no example generate random param
switch {
case schema.Type.Is("number") || schema.Type.Is("integer"):
return gofakeit.Number(0, 10)
case schema.Type.Is("number"):
switch schema.Format {
case "float":
return gofakeit.Float32()
case "double":
default:
return gofakeit.Float64()
}
case schema.Type.Is("integer"):
switch schema.Format {
case "int32":
return gofakeit.Int32()
case "int64":
default:
return gofakeit.Int64()
}
case schema.Type.Is("boolean"):
return gofakeit.Bool()
case schema.Type.Is("array"):
Expand Down
16 changes: 16 additions & 0 deletions openapi/param_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ func TestGetSchemaValue_WhenRequestBodyParametersWithExample(t *testing.T) {
assert.Equal(t, expected, operations[0].Body)
}

func TestGetSchemaValue_WhenRequestBodyParametersWithMultiMediaTypes(t *testing.T) {
expected := []byte("\"example\"")
openapiContract, _ := openapi.LoadFromData(
context.Background(),
[]byte(`{openapi: 3.0.2, servers: [{url: 'http://localhost:8080'}], paths: {/: {post: {requestBody: {content: {'application/xml': {schema: {type: string, example: example}},'application/json': {schema: {type: string, example: example}}}}, responses: {'204': {description: successful operation}}}}}}`),
)

securitySchemesMap, _ := openapiContract.SecuritySchemeMap(openapi.NewEmptySecuritySchemeValues())
operations, err := openapiContract.Operations(nil, securitySchemesMap)

assert.NoError(t, err)
assert.Equal(t, "application/json", operations[0].Header.Get("Content-Type"))
assert.NotNil(t, operations[0].Body)
assert.Equal(t, expected, operations[0].Body)
}

func TestGetSchemaValue_WhenRequestBodyParametersWithoutExample(t *testing.T) {
openapiContract, _ := openapi.LoadFromData(
context.Background(),
Expand Down
Loading

0 comments on commit c7da3af

Please sign in to comment.