Skip to content

Commit

Permalink
feat: Support LRO mixins over REST (#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
vchudnov-g authored Jun 13, 2022
1 parent 9d31e6d commit 5ca6fe1
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 28 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/googleapis/gapic-showcase

require (
cloud.google.com/go v0.102.0
github.com/ghodss/yaml v1.0.0
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.8
github.com/googleapis/gax-go/v2 v2.4.0
Expand All @@ -13,6 +14,7 @@ require (
github.com/spf13/viper v1.12.0
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df
google.golang.org/api v0.83.0
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8
google.golang.org/grpc v1.47.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down
6 changes: 3 additions & 3 deletions schema/google/showcase/v1beta1/showcase_v1beta1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ http:
- selector: google.longrunning.Operations.ListOperations
get: '/v1beta1/operations'
- selector: google.longrunning.Operations.GetOperation
get: '/v1beta1/{name=/operations/*}'
get: '/v1beta1/{name=operations/**}'
- selector: google.longrunning.Operations.DeleteOperation
delete: '/v1beta1/{name=/operations/*}'
delete: '/v1beta1/{name=operations/**}:delete'
- selector: google.longrunning.Operations.CancelOperation
post: '/v1beta1/{name=/operations/*}:cancel'
post: '/v1beta1/{name=operations/**}:cancel'
4 changes: 4 additions & 0 deletions server/genrest/genrest.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func RegisterHandlers(router *gmux.Router, backend *services.Backend) {
router.HandleFunc("/v1beta1/{parent:sessions/.+}/tests", rest.HandleListTests).Methods("GET")
router.HandleFunc("/v1beta1/{name:sessions/.+/tests/.+}", rest.HandleDeleteTest).Methods("DELETE")
router.HandleFunc("/v1beta1/{name:sessions/.+/tests/.+}:check", rest.HandleVerifyTest).Methods("POST")
router.HandleFunc("/v1beta1/operations", rest.HandleListOperations).Methods("GET")
router.HandleFunc("/v1beta1/{name:operations/.+}", rest.HandleGetOperation).Methods("GET")
router.HandleFunc("/v1beta1/{name:operations/.+}:delete", rest.HandleDeleteOperation).Methods("DELETE")
router.HandleFunc("/v1beta1/{name:operations/.+}:cancel", rest.HandleCancelOperation).Methods("POST")
router.PathPrefix("/").HandlerFunc(rest.catchAllHandler)
}

Expand Down
264 changes: 264 additions & 0 deletions server/genrest/operations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// DO NOT EDIT. This is an auto-generated file containing the REST handlers
// for service #6: "Operations" (.google.longrunning.Operations).

package genrest

import (
"github.com/googleapis/gapic-showcase/util/genrest/resttools"
gmux "github.com/gorilla/mux"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
"net/http"
)

// HandleListOperations translates REST requests/responses on the wire to internal proto messages for ListOperations
// Generated for HTTP binding pattern: "/v1beta1/operations"
func (backend *RESTBackend) HandleListOperations(w http.ResponseWriter, r *http.Request) {
urlPathParams := gmux.Vars(r)
numUrlPathParams := len(urlPathParams)

backend.StdLog.Printf("Received %s request matching '/v1beta1/operations': %q", r.Method, r.URL)
backend.StdLog.Printf(" urlPathParams (expect 0, have %d): %q", numUrlPathParams, urlPathParams)

if numUrlPathParams != 0 {
backend.Error(w, http.StatusBadRequest, "found unexpected number of URL variables: expected 0, have %d: %#v", numUrlPathParams, urlPathParams)
return
}

systemParameters, queryParams, err := resttools.GetSystemParameters(r)
if err != nil {
backend.Error(w, http.StatusBadRequest, "error in query string: %s", err)
return
}

request := &longrunningpb.ListOperationsRequest{}
if err := resttools.CheckRequestFormat(nil, r, request.ProtoReflect()); err != nil {
backend.Error(w, http.StatusBadRequest, "REST request failed format check: %s", err)
return
}
if err := resttools.PopulateSingularFields(request, urlPathParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading URL path params: %s", err)
return
}

// TODO: Decide whether query-param value or URL-path value takes precedence when a field appears in both
if err := resttools.PopulateFields(request, queryParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading query params: %s", err)
return
}

marshaler := resttools.ToJSON()
marshaler.UseEnumNumbers = systemParameters.EnumEncodingAsInt
requestJSON, _ := marshaler.Marshal(request)
backend.StdLog.Printf(" request: %s", requestJSON)

response, err := backend.OperationsServer.ListOperations(r.Context(), request)
if err != nil {
backend.ReportGRPCError(w, err)
return
}

json, err := marshaler.Marshal(response)
if err != nil {
backend.Error(w, http.StatusInternalServerError, "error json-encoding response: %s", err.Error())
return
}

w.Write(json)
}

// HandleGetOperation translates REST requests/responses on the wire to internal proto messages for GetOperation
// Generated for HTTP binding pattern: "/v1beta1/{name=operations/**}"
func (backend *RESTBackend) HandleGetOperation(w http.ResponseWriter, r *http.Request) {
urlPathParams := gmux.Vars(r)
numUrlPathParams := len(urlPathParams)

backend.StdLog.Printf("Received %s request matching '/v1beta1/{name=operations/**}': %q", r.Method, r.URL)
backend.StdLog.Printf(" urlPathParams (expect 1, have %d): %q", numUrlPathParams, urlPathParams)

if numUrlPathParams != 1 {
backend.Error(w, http.StatusBadRequest, "found unexpected number of URL variables: expected 1, have %d: %#v", numUrlPathParams, urlPathParams)
return
}

systemParameters, queryParams, err := resttools.GetSystemParameters(r)
if err != nil {
backend.Error(w, http.StatusBadRequest, "error in query string: %s", err)
return
}

request := &longrunningpb.GetOperationRequest{}
if err := resttools.CheckRequestFormat(nil, r, request.ProtoReflect()); err != nil {
backend.Error(w, http.StatusBadRequest, "REST request failed format check: %s", err)
return
}
if err := resttools.PopulateSingularFields(request, urlPathParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading URL path params: %s", err)
return
}

// TODO: Decide whether query-param value or URL-path value takes precedence when a field appears in both
excludedQueryParams := []string{"name"}
if duplicates := resttools.KeysMatchPath(queryParams, excludedQueryParams); len(duplicates) > 0 {
backend.Error(w, http.StatusBadRequest, "(QueryParamsInvalidFieldError) found keys that should not appear in query params: %v", duplicates)
return
}
if err := resttools.PopulateFields(request, queryParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading query params: %s", err)
return
}

marshaler := resttools.ToJSON()
marshaler.UseEnumNumbers = systemParameters.EnumEncodingAsInt
requestJSON, _ := marshaler.Marshal(request)
backend.StdLog.Printf(" request: %s", requestJSON)

response, err := backend.OperationsServer.GetOperation(r.Context(), request)
if err != nil {
backend.ReportGRPCError(w, err)
return
}

json, err := marshaler.Marshal(response)
if err != nil {
backend.Error(w, http.StatusInternalServerError, "error json-encoding response: %s", err.Error())
return
}

w.Write(json)
}

// HandleDeleteOperation translates REST requests/responses on the wire to internal proto messages for DeleteOperation
// Generated for HTTP binding pattern: "/v1beta1/{name=operations/**}:delete"
func (backend *RESTBackend) HandleDeleteOperation(w http.ResponseWriter, r *http.Request) {
urlPathParams := gmux.Vars(r)
numUrlPathParams := len(urlPathParams)

backend.StdLog.Printf("Received %s request matching '/v1beta1/{name=operations/**}:delete': %q", r.Method, r.URL)
backend.StdLog.Printf(" urlPathParams (expect 1, have %d): %q", numUrlPathParams, urlPathParams)

if numUrlPathParams != 1 {
backend.Error(w, http.StatusBadRequest, "found unexpected number of URL variables: expected 1, have %d: %#v", numUrlPathParams, urlPathParams)
return
}

systemParameters, queryParams, err := resttools.GetSystemParameters(r)
if err != nil {
backend.Error(w, http.StatusBadRequest, "error in query string: %s", err)
return
}

request := &longrunningpb.DeleteOperationRequest{}
if err := resttools.CheckRequestFormat(nil, r, request.ProtoReflect()); err != nil {
backend.Error(w, http.StatusBadRequest, "REST request failed format check: %s", err)
return
}
if err := resttools.PopulateSingularFields(request, urlPathParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading URL path params: %s", err)
return
}

// TODO: Decide whether query-param value or URL-path value takes precedence when a field appears in both
excludedQueryParams := []string{"name"}
if duplicates := resttools.KeysMatchPath(queryParams, excludedQueryParams); len(duplicates) > 0 {
backend.Error(w, http.StatusBadRequest, "(QueryParamsInvalidFieldError) found keys that should not appear in query params: %v", duplicates)
return
}
if err := resttools.PopulateFields(request, queryParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading query params: %s", err)
return
}

marshaler := resttools.ToJSON()
marshaler.UseEnumNumbers = systemParameters.EnumEncodingAsInt
requestJSON, _ := marshaler.Marshal(request)
backend.StdLog.Printf(" request: %s", requestJSON)

response, err := backend.OperationsServer.DeleteOperation(r.Context(), request)
if err != nil {
backend.ReportGRPCError(w, err)
return
}

json, err := marshaler.Marshal(response)
if err != nil {
backend.Error(w, http.StatusInternalServerError, "error json-encoding response: %s", err.Error())
return
}

w.Write(json)
}

// HandleCancelOperation translates REST requests/responses on the wire to internal proto messages for CancelOperation
// Generated for HTTP binding pattern: "/v1beta1/{name=operations/**}:cancel"
func (backend *RESTBackend) HandleCancelOperation(w http.ResponseWriter, r *http.Request) {
urlPathParams := gmux.Vars(r)
numUrlPathParams := len(urlPathParams)

backend.StdLog.Printf("Received %s request matching '/v1beta1/{name=operations/**}:cancel': %q", r.Method, r.URL)
backend.StdLog.Printf(" urlPathParams (expect 1, have %d): %q", numUrlPathParams, urlPathParams)

if numUrlPathParams != 1 {
backend.Error(w, http.StatusBadRequest, "found unexpected number of URL variables: expected 1, have %d: %#v", numUrlPathParams, urlPathParams)
return
}

systemParameters, queryParams, err := resttools.GetSystemParameters(r)
if err != nil {
backend.Error(w, http.StatusBadRequest, "error in query string: %s", err)
return
}

request := &longrunningpb.CancelOperationRequest{}
if err := resttools.CheckRequestFormat(nil, r, request.ProtoReflect()); err != nil {
backend.Error(w, http.StatusBadRequest, "REST request failed format check: %s", err)
return
}
if err := resttools.PopulateSingularFields(request, urlPathParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading URL path params: %s", err)
return
}

// TODO: Decide whether query-param value or URL-path value takes precedence when a field appears in both
excludedQueryParams := []string{"name"}
if duplicates := resttools.KeysMatchPath(queryParams, excludedQueryParams); len(duplicates) > 0 {
backend.Error(w, http.StatusBadRequest, "(QueryParamsInvalidFieldError) found keys that should not appear in query params: %v", duplicates)
return
}
if err := resttools.PopulateFields(request, queryParams); err != nil {
backend.Error(w, http.StatusBadRequest, "error reading query params: %s", err)
return
}

marshaler := resttools.ToJSON()
marshaler.UseEnumNumbers = systemParameters.EnumEncodingAsInt
requestJSON, _ := marshaler.Marshal(request)
backend.StdLog.Printf(" request: %s", requestJSON)

response, err := backend.OperationsServer.CancelOperation(r.Context(), request)
if err != nil {
backend.ReportGRPCError(w, err)
return
}

json, err := marshaler.Marshal(response)
if err != nil {
backend.Error(w, http.StatusInternalServerError, "error json-encoding response: %s", err.Error())
return
}

w.Write(json)
}
Loading

0 comments on commit 5ca6fe1

Please sign in to comment.