Add OpenAPI support for query parameters (#6490)

Also, fix handling of required properties in request body.
This commit is contained in:
Jim Kalafut 2019-03-28 14:40:56 -07:00 committed by GitHub
parent d14a2d326d
commit 265e61b993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 19 deletions

View File

@ -506,6 +506,13 @@ type FieldSchema struct {
Required bool
Deprecated bool
// Query indicates this field will be sent as a query parameter:
//
// /v1/foo/bar?some_param=some_value
//
// It doesn't affect handling of the value, but may be used for documentation.
Query bool
// AllowedValues is an optional list of permitted values for this field.
// This constraint is not (yet) enforced by the framework, but the list is
// output as part of OpenAPI generation and may effect documentation and

View File

@ -148,20 +148,24 @@ type OASMediaTypeObject struct {
}
type OASSchema struct {
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Properties map[string]*OASSchema `json:"properties,omitempty"`
Items *OASSchema `json:"items,omitempty"`
Format string `json:"format,omitempty"`
Pattern string `json:"pattern,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
Required bool `json:"required,omitempty"`
DisplayName string `json:"x-vault-displayName,omitempty" mapstructure:"x-vault-displayName,omitempty"`
DisplayValue interface{} `json:"x-vault-displayValue,omitempty" mapstructure:"x-vault-displayValue,omitempty"`
DisplaySensitive bool `json:"x-vault-displaySensitive,omitempty" mapstructure:"x-vault-displaySensitive,omitempty"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Properties map[string]*OASSchema `json:"properties,omitempty"`
// Required is a list of keys in Properties that are required to be present. This is a different
// approach than OASParameter (unfortunately), but is how JSONSchema handles 'required'.
Required []string `json:"required,omitempty"`
Items *OASSchema `json:"items,omitempty"`
Format string `json:"format,omitempty"`
Pattern string `json:"pattern,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
DisplayName string `json:"x-vault-displayName,omitempty" mapstructure:"x-vault-displayName,omitempty"`
DisplayValue interface{} `json:"x-vault-displayValue,omitempty" mapstructure:"x-vault-displayValue,omitempty"`
DisplaySensitive bool `json:"x-vault-displaySensitive,omitempty" mapstructure:"x-vault-displaySensitive,omitempty"`
}
type OASResponse struct {
@ -248,6 +252,11 @@ func documentPath(p *Path, specialPaths *logical.Paths, backendType logical.Back
location := "path"
required := true
if field.Query {
location = "query"
required = false
}
// Header parameters are part of the Parameters group but with
// a dedicated "header" location, a header parameter is not required.
if field.Type == TypeHeader {
@ -313,10 +322,15 @@ func documentPath(p *Path, specialPaths *logical.Paths, backendType logical.Back
s := &OASSchema{
Type: "object",
Properties: make(map[string]*OASSchema),
Required: make([]string, 0),
}
for name, field := range bodyFields {
openapiField := convertType(field.Type)
if field.Required {
s.Required = append(s.Required, name)
}
p := OASSchema{
Type: openapiField.baseType,
Description: cleanString(field.Description),
@ -324,7 +338,6 @@ func documentPath(p *Path, specialPaths *logical.Paths, backendType logical.Back
Pattern: openapiField.pattern,
Enum: field.AllowedValues,
Default: field.Default,
Required: field.Required,
Deprecated: field.Deprecated,
DisplayName: field.DisplayName,
DisplayValue: field.DisplayValue,
@ -596,7 +609,7 @@ func splitFields(allFields map[string]*FieldSchema, pattern string) (pathFields,
for name, field := range allFields {
if _, ok := pathFields[name]; !ok {
// Header fields are in "parameters" with other path fields
if field.Type == TypeHeader {
if field.Type == TypeHeader || field.Query {
pathFields[name] = field
} else {
bodyFields[name] = field

View File

@ -345,6 +345,11 @@ func TestOpenAPI_Paths(t *testing.T) {
Description: "a header value",
AllowedValues: []interface{}{"a", "b", "c"},
},
"format": {
Type: TypeString,
Description: "a query param",
Query: true,
},
},
HelpSynopsis: "Synopsis",
HelpDescription: "Description",
@ -555,7 +560,9 @@ func testPath(t *testing.T, path *Path, sp *logical.Paths, expectedJSON string)
t.Helper()
doc := NewOASDocument()
documentPath(path, sp, logical.TypeLogical, doc)
if err := documentPath(path, sp, logical.TypeLogical, doc); err != nil {
t.Fatal(err)
}
doc.CreateOperationIDs("")
docJSON, err := json.MarshalIndent(doc, "", " ")

View File

@ -15,6 +15,14 @@
"x-vault-createSupported": true,
"x-vault-sudo": true,
"parameters": [
{
"name": "format",
"description": "a query param",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "id",
"description": "id path parameter",
@ -65,6 +73,7 @@
"application/json": {
"schema": {
"type": "object",
"required": ["age"],
"properties": {
"flavors": {
"type": "array",
@ -77,7 +86,6 @@
"type": "integer",
"description": "the age",
"enum": [1, 2, 3],
"required": true,
"x-vault-displayName": "Age",
"x-vault-displayValue": 7,
"x-vault-displaySensitive": true

View File

@ -1117,7 +1117,8 @@ func (b *SystemBackend) metricsPath() *framework.Path {
Fields: map[string]*framework.FieldSchema{
"format": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Format to export metrics into. Currently accept only \"prometheus\"",
Description: "Format to export metrics into. Currently accepts only \"prometheus\".",
Query: true,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{