Skip to content

Commit

Permalink
protoc-gen-openapiv2: Support all HTTP methods supported in OpenAPI v2 (
Browse files Browse the repository at this point in the history
#2726)

* protoc-gen-openapiv2: Fully support all HTTP methods in OpenAPI

The handling for mappings with the same path and method caused a crash
when provided with an operation with a HEAD, OPTIONS, or TRACE HTTP
method, which are all technically supported by OpenAPI.

Also fix this same crash when an unsupported HTTP method is provided

* Add HEAD, OPTIONS, and TRACE examples to a_bit_of_everything.proto

Implement examples in server
Test examples in integration tests
Regenerate files

* Remove TRACE from OpenAPI generation

While TRACE is supported in OpenAPI v3, it is not supported in v2
  • Loading branch information
mnito authored May 30, 2022
1 parent 1b40888 commit da91d60
Show file tree
Hide file tree
Showing 12 changed files with 3,152 additions and 245 deletions.
672 changes: 643 additions & 29 deletions examples/internal/clients/abe/api/swagger.yaml

Large diffs are not rendered by default.

704 changes: 643 additions & 61 deletions examples/internal/clients/abe/api_a_bit_of_everything_service.go

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions examples/internal/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ func TestABE(t *testing.T) {
testABEBulkEchoZeroLength(t, 8088)
testAdditionalBindings(t, 8088)
testABERepeated(t, 8088)
testABEExists(t, 8088)
testABEExistsNotFound(t, 8088)
testABEOptions(t, 8088)
testABETrace(t, 8088)
}

func testABECreate(t *testing.T, port int) {
Expand Down Expand Up @@ -2284,3 +2288,122 @@ func testNonStandardNames(t *testing.T, port int, method string, jsonBody string
t.Errorf(diff)
}
}

func testABEExists(t *testing.T, port int) {
apiURL := fmt.Sprintf("http://localhost:%d/v1/example/a_bit_of_everything", port)
cresp, err := http.Post(apiURL, "application/json", strings.NewReader(`
{"bool_value": true, "string_value": "strprefix/example"}
`))
if err != nil {
t.Errorf("http.Post(%q) failed with %v; want success", apiURL, err)
return
}
defer cresp.Body.Close()
buf, err := ioutil.ReadAll(cresp.Body)
if err != nil {
t.Errorf("ioutil.ReadAll(cresp.Body) failed with %v; want success", err)
return
}
if got, want := cresp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
return
}

want := new(examplepb.ABitOfEverything)
if err := marshaler.Unmarshal(buf, want); err != nil {
t.Errorf("marshaler.Unmarshal(%s, want) failed with %v; want success", buf, err)
return
}

apiURL = fmt.Sprintf("%s/%s", apiURL, want.Uuid)
resp, err := http.Head(apiURL)
if err != nil {
t.Errorf("http.Head(%q) failed with %v; want success", apiURL, err)
return
}
defer resp.Body.Close()

if got, want := resp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
}
}

func testABEExistsNotFound(t *testing.T, port int) {
apiURL := fmt.Sprintf("http://localhost:%d/v1/example/a_bit_of_everything", port)
apiURL = fmt.Sprintf("%s/%s", apiURL, "not_exist")
resp, err := http.Head(apiURL)
if err != nil {
t.Errorf("http.Head(%q) failed with %v; want success", apiURL, err)
return
}
defer resp.Body.Close()

if got, want := resp.StatusCode, http.StatusNotFound; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
return
}
}

func testABEOptions(t *testing.T, port int) {
apiURL := fmt.Sprintf("http://localhost:%d/v1/example/a_bit_of_everything/test", port)
req, err := http.NewRequest(http.MethodOptions, apiURL, strings.NewReader(`
{"bool_value": true, "string_value": "strprefix/example"}
`))
req.Header.Set("Content-Type", "application/json")
if err != nil {
t.Errorf("http.NewRequest(http.MethodTrace, %q, ...) failed with %v; want success", apiURL, err)
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if got, want := resp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
return
}

value := resp.Header.Get("Grpc-Metadata-Allow")
if value != "OPTIONS, GET, HEAD, POST, PUT, TRACE" {
t.Errorf("Grpc-Metadata-Allow does not have the expected HTTP methods")
t.Logf("%s", value)
}
}

func testABETrace(t *testing.T, port int) {
apiURL := fmt.Sprintf("http://localhost:%d/v1/example/a_bit_of_everything/test", port)
req, err := http.NewRequest(http.MethodTrace, apiURL, strings.NewReader(`
{"bool_value": true, "string_value": "strprefix/example"}
`))
req.Header.Set("Content-Type", "application/json")
if err != nil {
t.Errorf("http.NewRequest(http.MethodTrace, %q, ...) failed with %v; want success", apiURL, err)
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("ioutil.ReadAll(cresp.Body) failed with %v; want success", err)
return
}
if got, want := resp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
return
}

want := new(examplepb.ABitOfEverything)
if err := marshaler.Unmarshal(buf, want); err != nil {
t.Errorf("marshaler.Unmarshal(%s, want) failed with %v; want success", buf, err)
return
}
}
338 changes: 189 additions & 149 deletions examples/internal/proto/examplepb/a_bit_of_everything.pb.go

Large diffs are not rendered by default.

Loading

0 comments on commit da91d60

Please sign in to comment.