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

feat: support structpb.Struct as req/resp #2632

Merged
merged 3 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzc
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
cloud.google.com/go/compute v1.27.0 h1:EGawh2RUnfHT5g8f/FX3Ds6KZuIBC77hZoDrBvEZw94=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI=
cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA=
Expand Down
39 changes: 38 additions & 1 deletion google-api-go-generator/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,8 @@ func (meth *Method) generateCode() {
retType := responseType(a, meth.m)
if meth.IsRawResponse() {
retType = "*http.Response"
} else if meth.IsProtoStructResponse() {
retType = "map[string]any"
}
retTypeComma := retType
if retTypeComma != "" {
Expand Down Expand Up @@ -2247,6 +2249,10 @@ func (meth *Method) generateCode() {
pn("var body io.Reader = nil")
if meth.IsRawRequest() {
pn("body = c.body_")
} else if meth.IsProtoStructRequest() {
pn("protoBytes, err := json.Marshal(c.req)")
pn("if err != nil { return nil, err }")
pn("body = bytes.NewReader(protoBytes)")
} else {
if ba := args.bodyArg(); ba != nil && httpMethod != "GET" {
if meth.m.ID == "ml.projects.predict" {
Expand Down Expand Up @@ -2384,7 +2390,9 @@ func (meth *Method) generateCode() {
if retTypeComma == "" {
pn("return nil")
} else {
if mapRetType {
if meth.IsProtoStructResponse() {
pn("var ret map[string]any")
} else if mapRetType {
pn("var ret %s", responseType(a, meth.m))
} else {
pn("ret := &%s{", responseTypeLiteral(a, meth.m))
Expand Down Expand Up @@ -2529,6 +2537,30 @@ func (meth *Method) IsRawRequest() bool {
return meth.m.Request.Ref == "HttpBody"
}

// IsProtoStructRequest determines if the method request type is a
// [google.golang.org/protobuf/types/known/structpb.Struct].
func (meth *Method) IsProtoStructRequest() bool {
if meth == nil || meth.m == nil || meth.m.Request == nil {
return false
}
if meth.m.Request.Ref == "GoogleProtobufStruct" {
return true
}
return false
}

// IsProtoStructResponse determines if the method response type is a
// [google.golang.org/protobuf/types/known/structpb.Struct].
func (meth *Method) IsProtoStructResponse() bool {
if meth == nil || meth.m == nil || meth.m.Response == nil {
return false
}
if meth.m.Response.Ref == "GoogleProtobufStruct" {
return true
}
return false
}

codyoss marked this conversation as resolved.
Show resolved Hide resolved
func (meth *Method) IsRawResponse() bool {
if meth.m.Response == nil {
return false
Expand Down Expand Up @@ -2567,6 +2599,11 @@ func (meth *Method) NewArguments() *arguments {
goname: "body_",
gotype: "io.Reader",
})
} else if meth.IsProtoStructRequest() {
args.AddArg(&argument{
goname: "req",
gotype: "map[string]any",
})
} else {
args.AddArg(meth.NewBodyArg(rs))
}
Expand Down
1 change: 1 addition & 0 deletions google-api-go-generator/gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestAPIs(t *testing.T) {
"json-body",
"mapofany",
"mapofarrayofobjects",
"mapprotostruct",
"mapofint64strings",
"mapofobjects",
"mapofstrings-1",
Expand Down
42 changes: 42 additions & 0 deletions google-api-go-generator/testdata/mapprotostruct.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"kind": "discovery#restDescription",
"etag": "\"kEk3sFj6Ef5_yR1-H3bAO6qw9mI/3m5rB86FE5KuW1K3jAl88AxCreg\"",
"discoveryVersion": "v1",
"id": "mapprotostruct:v1",
"name": "mapprotostruct",
"version": "v1",
"title": "Example API",
"description": "The Example API demonstrates handling structpb.Struct.",
"ownerDomain": "google.com",
"ownerName": "Google",
"protocol": "rest",
"schemas": {
"GoogleProtobufStruct": {
"id": "GoogleProtobufStruct",
"description": "`Struct` represents a structured data value, consisting of fields which map to dynamically typed values. In some languages, `Struct` might be supported by a native representation. For example, in scripting languages like JS a struct is represented as an object. The details of that representation are described together with the proto support for the language. The JSON representation for `Struct` is JSON object.",
"type": "object",
"additionalProperties": {
"type": "any",
"description": "Properties of the object."
}
}
},
"resources": {
"atlas": {
"methods": {
"getMap": {
"id": "mapprotostruct.getMap",
"path": "map",
"httpMethod": "GET",
"description": "Get a map.",
"request": {
"$ref": "GoogleProtobufStruct"
},
"response": {
"$ref": "GoogleProtobufStruct"
}
}
}
}
}
}
235 changes: 235 additions & 0 deletions google-api-go-generator/testdata/mapprotostruct.want
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Copyright YEAR Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Code generated file. DO NOT EDIT.

// Package mapprotostruct provides access to the Example API.
//
// # Library status
//
// These client libraries are officially supported by Google. However, this
// library is considered complete and is in maintenance mode. This means
// that we will address critical bugs and security issues but will not add
// any new features.
//
// When possible, we recommend using our newer
// [Cloud Client Libraries for Go](https://pkg.go.dev/cloud.google.com/go)
// that are still actively being worked and iterated on.
//
// # Creating a client
//
// Usage example:
//
// import "google.golang.org/api/mapprotostruct/v1"
// ...
// ctx := context.Background()
// mapprotostructService, err := mapprotostruct.NewService(ctx)
//
// In this example, Google Application Default Credentials are used for
// authentication. For information on how to create and obtain Application
// Default Credentials, see https://developers.google.com/identity/protocols/application-default-credentials.
//
// # Other authentication options
//
// To use an API key for authentication (note: some APIs do not support API
// keys), use [google.golang.org/api/option.WithAPIKey]:
//
// mapprotostructService, err := mapprotostruct.NewService(ctx, option.WithAPIKey("AIza..."))
//
// To use an OAuth token (e.g., a user token obtained via a three-legged OAuth
// flow, use [google.golang.org/api/option.WithTokenSource]:
//
// config := &oauth2.Config{...}
// // ...
// token, err := config.Exchange(ctx, ...)
// mapprotostructService, err := mapprotostruct.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx, token)))
//
// See [google.golang.org/api/option.ClientOption] for details on options.
package mapprotostruct // import "google.golang.org/api/mapprotostruct/v1"

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"

googleapi "google.golang.org/api/googleapi"
internal "google.golang.org/api/internal"
gensupport "google.golang.org/api/internal/gensupport"
option "google.golang.org/api/option"
internaloption "google.golang.org/api/option/internaloption"
htransport "google.golang.org/api/transport/http"
)

// Always reference these packages, just in case the auto-generated code
// below doesn't.
var _ = bytes.NewBuffer
var _ = strconv.Itoa
var _ = fmt.Sprintf
var _ = json.NewDecoder
var _ = io.Copy
var _ = url.Parse
var _ = gensupport.MarshalJSON
var _ = googleapi.Version
var _ = errors.New
var _ = strings.Replace
var _ = context.Canceled
var _ = internaloption.WithDefaultEndpoint
var _ = internal.Version

const apiId = "mapprotostruct:v1"
const apiName = "mapprotostruct"
const apiVersion = "v1"
const basePath = "https://www.googleapis.com/discovery/v1/apis"
const basePathTemplate = "https://www.UNIVERSE_DOMAIN/discovery/v1/apis"

// NewService creates a new Service.
func NewService(ctx context.Context, opts ...option.ClientOption) (*Service, error) {
opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
opts = append(opts, internaloption.WithDefaultEndpointTemplate(basePathTemplate))
opts = append(opts, internaloption.EnableNewAuthLibrary())
client, endpoint, err := htransport.NewClient(ctx, opts...)
if err != nil {
return nil, err
}
s, err := New(client)
if err != nil {
return nil, err
}
if endpoint != "" {
s.BasePath = endpoint
}
return s, nil
}

// New creates a new Service. It uses the provided http.Client for requests.
//
// Deprecated: please use NewService instead.
// To provide a custom HTTP client, use option.WithHTTPClient.
// If you are using google.golang.org/api/googleapis/transport.APIKey, use option.WithAPIKey with NewService instead.
func New(client *http.Client) (*Service, error) {
if client == nil {
return nil, errors.New("client is nil")
}
s := &Service{client: client, BasePath: basePath}
s.Atlas = NewAtlasService(s)
return s, nil
}

type Service struct {
client *http.Client
BasePath string // API endpoint base URL
UserAgent string // optional additional User-Agent fragment

Atlas *AtlasService
}

func (s *Service) userAgent() string {
if s.UserAgent == "" {
return googleapi.UserAgent
}
return googleapi.UserAgent + " " + s.UserAgent
}

func NewAtlasService(s *Service) *AtlasService {
rs := &AtlasService{s: s}
return rs
}

type AtlasService struct {
s *Service
}

type AtlasGetMapCall struct {
s *Service
req map[string]any
urlParams_ gensupport.URLParams
ifNoneMatch_ string
ctx_ context.Context
header_ http.Header
}

// GetMap: Get a map.
func (r *AtlasService) GetMap(req map[string]any) *AtlasGetMapCall {
c := &AtlasGetMapCall{s: r.s, urlParams_: make(gensupport.URLParams)}
c.req = req
return c
}

// Fields allows partial responses to be retrieved. See
// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse for more
// details.
func (c *AtlasGetMapCall) Fields(s ...googleapi.Field) *AtlasGetMapCall {
c.urlParams_.Set("fields", googleapi.CombineFields(s))
return c
}

// IfNoneMatch sets an optional parameter which makes the operation fail if the
// object's ETag matches the given value. This is useful for getting updates
// only after the object has changed since the last request.
func (c *AtlasGetMapCall) IfNoneMatch(entityTag string) *AtlasGetMapCall {
c.ifNoneMatch_ = entityTag
return c
}

// Context sets the context to be used in this call's Do method.
func (c *AtlasGetMapCall) Context(ctx context.Context) *AtlasGetMapCall {
c.ctx_ = ctx
return c
}

// Header returns a http.Header that can be modified by the caller to add
// headers to the request.
func (c *AtlasGetMapCall) Header() http.Header {
if c.header_ == nil {
c.header_ = make(http.Header)
}
return c.header_
}

func (c *AtlasGetMapCall) doRequest(alt string) (*http.Response, error) {
reqHeaders := gensupport.SetHeaders(c.s.userAgent(), "", c.header_)
if c.ifNoneMatch_ != "" {
reqHeaders.Set("If-None-Match", c.ifNoneMatch_)
}
var body io.Reader = nil
protoBytes, err := json.Marshal(c.req)
if err != nil {
return nil, err
}
body = bytes.NewReader(protoBytes)
urls := googleapi.ResolveRelative(c.s.BasePath, "map")
urls += "?" + c.urlParams_.Encode()
req, err := http.NewRequest("GET", urls, body)
if err != nil {
return nil, err
}
req.Header = reqHeaders
return gensupport.SendRequest(c.ctx_, c.s.client, req)
}

// Do executes the "mapprotostruct.getMap" call.
func (c *AtlasGetMapCall) Do(opts ...googleapi.CallOption) (map[string]any, error) {
gensupport.SetOptions(c.urlParams_, opts...)
res, err := c.doRequest("json")
if err != nil {
return nil, err
}
defer googleapi.CloseBody(res)
if err := googleapi.CheckResponse(res); err != nil {
return nil, gensupport.WrapError(err)
}
var ret map[string]any
target := &ret
if err := gensupport.DecodeResponse(target, res); err != nil {
return nil, err
}
return ret, nil
}