diff --git a/.github/workflows/scans.yml b/.github/workflows/scans.yml index b68e846..e577ba2 100644 --- a/.github/workflows/scans.yml +++ b/.github/workflows/scans.yml @@ -287,6 +287,7 @@ jobs: "simple_api_key.openapi.json", "simple_http_bearer_jwt.openapi.json", "simple_http_bearer.openapi.json", + "complex.openapi.json", ] steps: diff --git a/openapi/operation.go b/openapi/operation.go index b61675b..143343c 100644 --- a/openapi/operation.go +++ b/openapi/operation.go @@ -41,8 +41,7 @@ func GetOperationPath(p string, params openapi3.Parameters) (string, error) { if v.Value.In != "path" { continue } - - subs[v.Value.Name] = getSchemaValue(v.Value.Schema.Value) + subs[v.Value.Name] = getSchemaValue(v.Value.Schema.Value, 0) } return stduritemplate.Expand(p, subs) diff --git a/openapi/param.go b/openapi/param.go index 014ae85..7e7a327 100644 --- a/openapi/param.go +++ b/openapi/param.go @@ -8,9 +8,11 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) +const maximumDepth = 4 + func getParameterValue(param *openapi3.Parameter) string { if param.Schema != nil { - value := getSchemaValue(param.Schema.Value) + value := getSchemaValue(param.Schema.Value, 0) switch { case param.Schema.Value.Type.Is("string"): return value.(string) @@ -66,7 +68,7 @@ func getRequestBodyValue(requestBody *openapi3.RequestBody) (*bytes.Buffer, stri if requestBody.Content != nil { for mediaType, mediaTypeValue := range requestBody.Content { if mediaTypeValue.Schema != nil { - body := getSchemaValue(mediaTypeValue.Schema.Value) + body := getSchemaValue(mediaTypeValue.Schema.Value, 0) switch mediaType { case "application/json": return mapRequestBodyFakeValueToJSON(mediaTypeValue.Schema.Value, body), "application/json" @@ -80,7 +82,7 @@ func getRequestBodyValue(requestBody *openapi3.RequestBody) (*bytes.Buffer, stri return bytes.NewBuffer(nil), "" } -func getSchemaValue(schema *openapi3.Schema) interface{} { +func getSchemaValue(schema *openapi3.Schema, depth int) interface{} { if schema.Example != nil { return schema.Example } else if len(schema.Enum) > 0 { @@ -94,11 +96,17 @@ func getSchemaValue(schema *openapi3.Schema) interface{} { case schema.Type.Is("boolean"): return gofakeit.Bool() case schema.Type.Is("array"): - return []interface{}{getSchemaValue(schema.Items.Value)} + if depth > maximumDepth { + return []interface{}{} + } + return []interface{}{getSchemaValue(schema.Items.Value, depth+1)} case schema.Type.Is("object"): object := map[string]interface{}{} + if depth > maximumDepth { + return object + } for key, value := range schema.Properties { - object[key] = getSchemaValue(value.Value) + object[key] = getSchemaValue(value.Value, depth+1) } return object case schema.Type.Is("string"): diff --git a/openapi/param_test.go b/openapi/param_test.go index b0ef7a5..fcc1a35 100644 --- a/openapi/param_test.go +++ b/openapi/param_test.go @@ -265,3 +265,17 @@ func TestGetSchemaValue_WhenRequestBodyParametersWithObjectExampleAndArrayExampl assert.NotNil(t, operations[0].Body) assert.Equal(t, expected, operations[0].Body) } + +func TestRecursiveParameters(t *testing.T) { + openapiContract, operr := openapi.LoadFromData( + context.Background(), + []byte(`{"openapi":"3.0.2","servers":[{"url":"http://localhost:8080"}],"paths":{"/":{"post":{"summary":"Create an item","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"responses":{"201":{"description":"Item created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}}}}}},"components":{"schemas":{"Item":{"type":"object","properties":{"details":{"type":"object","properties":{"description":{"type":"string"},"attributes":{"type":"array","items":{"$ref":"#/components/schemas/Attribute"}}}}}},"Attribute":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"},"subAttributes":{"type":"array","items":{"$ref":"#/components/schemas/Attribute"}}}}}}}}`), + ) + + securitySchemesMap, _ := openapiContract.SecuritySchemeMap(openapi.NewEmptySecuritySchemeValues()) + operations, err := openapiContract.Operations(nil, securitySchemesMap) + + assert.NoError(t, operr) + assert.NoError(t, err) + assert.NotNil(t, operations[0].Body) +} diff --git a/test/stub/complex.openapi.json b/test/stub/complex.openapi.json new file mode 100644 index 0000000..722d7f3 --- /dev/null +++ b/test/stub/complex.openapi.json @@ -0,0 +1,155 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Complex API", + "description": "API with complex and recursive parameters", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:8080" + } + ], + "paths": { + "/": { + "get": { + "summary": "Get items", + "parameters": [ + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + } + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + }, + { + "api_key_auth": [] + } + ] + }, + "post": { + "summary": "Create an item", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "responses": { + "201": { + "description": "Item created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + }, + { + "api_key_auth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "bearer_auth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "api_key_auth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key" + } + }, + "schemas": { + "Item": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "details": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attribute" + } + } + } + } + } + }, + "Attribute": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "subAttributes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attribute" + } + } + } + } + } + } +} \ No newline at end of file