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

Add OpenAPI support for query parameters #6490

Merged
merged 2 commits into from
Mar 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions logical/framework/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
kalafut marked this conversation as resolved.
Show resolved Hide resolved

// 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
Expand Down
45 changes: 29 additions & 16 deletions logical/framework/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -313,18 +322,22 @@ 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),
Format: openapiField.format,
Pattern: openapiField.pattern,
Enum: field.AllowedValues,
Default: field.Default,
Required: field.Required,
Deprecated: field.Deprecated,
DisplayName: field.DisplayName,
DisplayValue: field.DisplayValue,
Expand Down Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion logical/framework/openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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, "", " ")
Expand Down
10 changes: 9 additions & 1 deletion logical/framework/testdata/operations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -65,6 +73,7 @@
"application/json": {
"schema": {
"type": "object",
"required": ["age"],
"properties": {
"flavors": {
"type": "array",
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion vault/logical_system_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down