From 66522035699de6eeeb05c4db3f5ec0c86be90c77 Mon Sep 17 00:00:00 2001 From: VAL Date: Tue, 18 Jan 2022 09:21:44 -0800 Subject: [PATCH] Distinguish LIST-only paths in OpenAPI (#13643) * Distinguish LIST-only paths in OpenAPI * add changelog * Put enum field inside schema --- changelog/13643.txt | 3 + sdk/framework/openapi.go | 14 ++++- sdk/framework/openapi_test.go | 61 +++++++++++++++++++- sdk/framework/testdata/operations_list.json | 63 +++++++++++++++++++++ 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 changelog/13643.txt create mode 100644 sdk/framework/testdata/operations_list.json diff --git a/changelog/13643.txt b/changelog/13643.txt new file mode 100644 index 000000000..fd3c27844 --- /dev/null +++ b/changelog/13643.txt @@ -0,0 +1,3 @@ +```release-note:bug +sdk: Fixes OpenAPI to distinguish between paths that can do only List, or both List and Read. +``` \ No newline at end of file diff --git a/sdk/framework/openapi.go b/sdk/framework/openapi.go index 4c9d073f0..fb760774b 100644 --- a/sdk/framework/openapi.go +++ b/sdk/framework/openapi.go @@ -368,8 +368,18 @@ func documentPath(p *Path, specialPaths *logical.Paths, backendType logical.Back } } - // LIST is represented as GET with a `list` query parameter - if opType == logical.ListOperation || (opType == logical.ReadOperation && operations[logical.ListOperation] != nil) { + // LIST is represented as GET with a `list` query parameter. + if opType == logical.ListOperation { + // Only accepts List (due to the above skipping of ListOperations that also have ReadOperations) + op.Parameters = append(op.Parameters, OASParameter{ + Name: "list", + Description: "Must be set to `true`", + Required: true, + In: "query", + Schema: &OASSchema{Type: "string", Enum: []interface{}{"true"}}, + }) + } else if opType == logical.ReadOperation && operations[logical.ListOperation] != nil { + // Accepts both Read and List op.Parameters = append(op.Parameters, OASParameter{ Name: "list", Description: "Return a list if `true`", diff --git a/sdk/framework/openapi_test.go b/sdk/framework/openapi_test.go index 9c7226820..00f5d1649 100644 --- a/sdk/framework/openapi_test.go +++ b/sdk/framework/openapi_test.go @@ -320,7 +320,7 @@ func TestOpenAPI_Paths(t *testing.T) { testPath(t, p, sp, expected("legacy")) }) - t.Run("Operations", func(t *testing.T) { + t.Run("Operations - All Operations", func(t *testing.T) { p := &Path{ Pattern: "foo/" + GenericNameRegex("id"), Fields: map[string]*FieldSchema{ @@ -395,6 +395,65 @@ func TestOpenAPI_Paths(t *testing.T) { testPath(t, p, sp, expected("operations")) }) + t.Run("Operations - List Only", func(t *testing.T) { + p := &Path{ + Pattern: "foo/" + GenericNameRegex("id"), + Fields: map[string]*FieldSchema{ + "id": { + Type: TypeString, + Description: "id path parameter", + }, + "flavors": { + Type: TypeCommaStringSlice, + Description: "the flavors", + }, + "name": { + Type: TypeNameString, + Default: "Larry", + Description: "the name", + }, + "age": { + Type: TypeInt, + Description: "the age", + AllowedValues: []interface{}{1, 2, 3}, + Required: true, + DisplayAttrs: &DisplayAttributes{ + Name: "Age", + Sensitive: true, + Group: "Some Group", + Value: 7, + }, + }, + "x-abc-token": { + Type: TypeHeader, + Description: "a header value", + AllowedValues: []interface{}{"a", "b", "c"}, + }, + "format": { + Type: TypeString, + Description: "a query param", + Query: true, + }, + }, + HelpSynopsis: "Synopsis", + HelpDescription: "Description", + Operations: map[logical.Operation]OperationHandler{ + logical.ListOperation: &PathOperation{ + Summary: "List Summary", + Description: "List Description", + }, + }, + DisplayAttrs: &DisplayAttributes{ + Navigation: true, + }, + } + + sp := &logical.Paths{ + Root: []string{"foo*"}, + } + testPath(t, p, sp, expected("operations_list")) + }) + t.Run("Responses", func(t *testing.T) { p := &Path{ Pattern: "foo", diff --git a/sdk/framework/testdata/operations_list.json b/sdk/framework/testdata/operations_list.json new file mode 100644 index 000000000..bea40f61a --- /dev/null +++ b/sdk/framework/testdata/operations_list.json @@ -0,0 +1,63 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "HashiCorp Vault API", + "description": "HTTP API that gives you full access to Vault. All API routes are prefixed with `/v1/`.", + "version": "", + "license": { + "name": "Mozilla Public License 2.0", + "url": "https://www.mozilla.org/en-US/MPL/2.0" + } + }, + "paths": { + "/foo/{id}": { + "description": "Synopsis", + "x-vault-sudo": true, + "x-vault-displayAttrs": { + "navigation": true + }, + "parameters": [ + { + "name": "format", + "description": "a query param", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "description": "id path parameter", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "get": { + "operationId": "getFooId", + "tags": ["secrets"], + "summary": "List Summary", + "description": "List Description", + "responses": { + "200": { + "description": "OK" + } + }, + "parameters": [ + { + "name": "list", + "description": "Must be set to `true`", + "required": true, + "in": "query", + "schema": { + "type": "string", + "enum": ["true"] + } + } + ] + } + } + } +}