Add operationId field to OpenAPI output (#5876)

Fixes #5842
This commit is contained in:
Jim Kalafut 2018-12-12 13:59:23 -08:00 committed by GitHub
parent 4c47e64611
commit 5687892530
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 1 deletions

View File

@ -119,6 +119,7 @@ func NewOASOperation() *OASOperation {
type OASOperation struct {
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
OperationID string `json:"operationId,omitempty"`
Tags []string `json:"tags,omitempty"`
Parameters []OASParameter `json:"parameters,omitempty"`
RequestBody *OASRequestBody `json:"requestBody,omitempty"`
@ -185,6 +186,7 @@ var cleanSuffixRe = regexp.MustCompile(`/\?\$?$`) // Path suf
var wsRe = regexp.MustCompile(`\s+`) // Match whitespace, to be compressed during cleaning
var altFieldsGroupRe = regexp.MustCompile(`\(\?P<\w+>\w+(\|\w+)+\)`) // Match named groups that limit options, e.g. "(?<foo>a|b|c)"
var altFieldsRe = regexp.MustCompile(`\w+(\|\w+)+`) // Match an options set, e.g. "a|b|c"
var nonWordRe = regexp.MustCompile(`[^\w]+`) // Match a sequence of non-word characters
// documentPaths parses all paths in a framework.Backend into OpenAPI paths.
func documentPaths(backend *Backend, doc *OASDocument) error {
@ -611,3 +613,52 @@ func cleanResponse(resp *logical.Response) (*cleanedResponse, error) {
return &r, nil
}
// CreateOperationIDs generates unique operationIds for all paths/methods.
// The transform will convert path/method into camelcase. e.g.:
//
// /sys/tools/random/{urlbytes} -> postSysToolsRandomUrlbytes
//
// In the unlikely case of a duplicate ids, a numeric suffix is added:
// postSysToolsRandomUrlbytes_2
//
// An optional user-provided suffix ("context") may also be appended.
func (d *OASDocument) CreateOperationIDs(context string) {
opIDCount := make(map[string]int)
for path, pi := range d.Paths {
for _, method := range []string{"get", "post", "delete"} {
var oasOperation *OASOperation
switch method {
case "get":
oasOperation = pi.Get
case "post":
oasOperation = pi.Post
case "delete":
oasOperation = pi.Delete
}
if oasOperation == nil {
continue
}
// Space-split on non-words, title case everything, recombine
opID := nonWordRe.ReplaceAllString(strings.ToLower(path), " ")
opID = strings.Title(opID)
opID = method + strings.Replace(opID, " ", "", -1)
// deduplicate operationIds. This is a safeguard, since generated IDs should
// already be unique given our current path naming conventions.
opIDCount[opID]++
if opIDCount[opID] > 1 {
opID = fmt.Sprintf("%s_%d", opID, opIDCount[opID])
}
if context != "" {
opID += "_" + context
}
oasOperation.OperationID = opID
}
}
}

View File

@ -3064,6 +3064,8 @@ func (b *SystemBackend) pathInternalOpenAPI(ctx context.Context, req *logical.Re
return nil, err
}
context := d.Get("context").(string)
// Set up target document and convert to map[string]interface{} which is what will
// be received from plugin backends.
doc := framework.NewOASDocument()
@ -3144,6 +3146,8 @@ func (b *SystemBackend) pathInternalOpenAPI(ctx context.Context, req *logical.Re
return nil, err
}
doc.CreateOperationIDs(context)
buf, err := json.Marshal(doc)
if err != nil {
return nil, err

View File

@ -754,8 +754,15 @@ func (b *SystemBackend) internalPaths() []*framework.Path {
return []*framework.Path{
{
Pattern: "internal/specs/openapi",
Fields: map[string]*framework.FieldSchema{
"context": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Context string appended to every operationId",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathInternalOpenAPI,
logical.ReadOperation: b.pathInternalOpenAPI,
logical.UpdateOperation: b.pathInternalOpenAPI,
},
},
{