diff --git a/corp_autopush_learningstate/v1/corp_autopush_learningstate-api.json b/corp_autopush_learningstate/v1/corp_autopush_learningstate-api.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/corp_autopush_learningstate/v1/corp_autopush_learningstate-api.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/corp_autopush_learningstate/v1/corp_autopush_learningstate-gen.go b/corp_autopush_learningstate/v1/corp_autopush_learningstate-gen.go deleted file mode 100644 index 0935986310c..00000000000 --- a/corp_autopush_learningstate/v1/corp_autopush_learningstate-gen.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2021 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 corp_autopush_learningstate provides access to the . -// -// # Creating a client -// -// Usage example: -// -// import "google.golang.org/api/corp_autopush_learningstate/v1" -// ... -// ctx := context.Background() -// corp_autopush_learningstateService, err := corp_autopush_learningstate.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 option.WithAPIKey: -// -// corp_autopush_learningstateService, err := corp_autopush_learningstate.NewService(ctx, option.WithAPIKey("AIza...")) -// -// To use an OAuth token (e.g., a user token obtained via a three-legged OAuth flow), use option.WithTokenSource: -// -// config := &oauth2.Config{...} -// // ... -// token, err := config.Exchange(ctx, ...) -// corp_autopush_learningstateService, err := corp_autopush_learningstate.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx, token))) -// -// See https://godoc.org/google.golang.org/api/option/ for details on options. -package corp_autopush_learningstate // import "google.golang.org/api/corp_autopush_learningstate/v1" - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - - googleapi "google.golang.org/api/googleapi" - 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 - -const apiId = "" -const apiName = "" -const apiVersion = "" -const basePath = "https://www.googleapis.com/discovery/v1/apis" - -// NewService creates a new Service. -func NewService(ctx context.Context, opts ...option.ClientOption) (*Service, error) { - opts = append(opts, internaloption.WithDefaultEndpoint(basePath)) - 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} - return s, nil -} - -type Service struct { - client *http.Client - BasePath string // API endpoint base URL - UserAgent string // optional additional User-Agent fragment -} - -func (s *Service) userAgent() string { - if s.UserAgent == "" { - return googleapi.UserAgent - } - return googleapi.UserAgent + " " + s.UserAgent -} diff --git a/corp_spatialanalytics/v1/corp_spatialanalytics-api.json b/corp_spatialanalytics/v1/corp_spatialanalytics-api.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/corp_spatialanalytics/v1/corp_spatialanalytics-api.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/corp_spatialanalytics/v1/corp_spatialanalytics-gen.go b/corp_spatialanalytics/v1/corp_spatialanalytics-gen.go deleted file mode 100644 index 474cd330600..00000000000 --- a/corp_spatialanalytics/v1/corp_spatialanalytics-gen.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2021 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 corp_spatialanalytics provides access to the . -// -// # Creating a client -// -// Usage example: -// -// import "google.golang.org/api/corp_spatialanalytics/v1" -// ... -// ctx := context.Background() -// corp_spatialanalyticsService, err := corp_spatialanalytics.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 option.WithAPIKey: -// -// corp_spatialanalyticsService, err := corp_spatialanalytics.NewService(ctx, option.WithAPIKey("AIza...")) -// -// To use an OAuth token (e.g., a user token obtained via a three-legged OAuth flow), use option.WithTokenSource: -// -// config := &oauth2.Config{...} -// // ... -// token, err := config.Exchange(ctx, ...) -// corp_spatialanalyticsService, err := corp_spatialanalytics.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx, token))) -// -// See https://godoc.org/google.golang.org/api/option/ for details on options. -package corp_spatialanalytics // import "google.golang.org/api/corp_spatialanalytics/v1" - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - - googleapi "google.golang.org/api/googleapi" - 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 - -const apiId = "" -const apiName = "" -const apiVersion = "" -const basePath = "https://www.googleapis.com/discovery/v1/apis" - -// NewService creates a new Service. -func NewService(ctx context.Context, opts ...option.ClientOption) (*Service, error) { - opts = append(opts, internaloption.WithDefaultEndpoint(basePath)) - 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} - return s, nil -} - -type Service struct { - client *http.Client - BasePath string // API endpoint base URL - UserAgent string // optional additional User-Agent fragment -} - -func (s *Service) userAgent() string { - if s.UserAgent == "" { - return googleapi.UserAgent - } - return googleapi.UserAgent + " " + s.UserAgent -} diff --git a/go.mod b/go.mod index 3bb653706bd..7ca696717ab 100644 --- a/go.mod +++ b/go.mod @@ -15,13 +15,13 @@ require ( go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.26.0 golang.org/x/oauth2 v0.21.0 golang.org/x/sync v0.7.0 golang.org/x/time v0.5.0 - google.golang.org/genproto v0.0.0-20240528184218-531527333157 - google.golang.org/genproto/googleapis/bytestream v0.0.0-20240528184218-531527333157 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 + google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 + google.golang.org/genproto/googleapis/bytestream v0.0.0-20240604185151-ef581f913117 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.1 ) @@ -35,7 +35,7 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index bd36978eaa1..e520224454f 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -94,8 +94,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -108,12 +108,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -127,13 +127,13 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= -google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20240528184218-531527333157 h1:znHUtThh5/fLbEC/p3Khp5xOucyAgMZ1Nj9ditbxd44= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20240528184218-531527333157/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc= +google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240604185151-ef581f913117 h1:q55t0kc1AUzhZ28wK3eMzkLs0abp7u0NCMzIIwgmAXo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240604185151-ef581f913117/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/google-api-go-generator/gen.go b/google-api-go-generator/gen.go index 9216cc38ad0..6d8d6debbca 100644 --- a/google-api-go-generator/gen.go +++ b/google-api-go-generator/gen.go @@ -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 != "" { @@ -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" { @@ -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)) @@ -2529,6 +2537,40 @@ 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 { + return false + } + + return isProtoStruct(meth.m.Request) +} + +// 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 { + return false + } + + return isProtoStruct(meth.m.Response) +} + +// isProtoStruct determines if the Schema represents a +// [google.golang.org/protobuf/types/known/structpb.Struct]. +func isProtoStruct(s *disco.Schema) bool { + if s == nil { + return false + } + + if s.Ref == "GoogleProtobufStruct" { + return true + } + + return false +} + func (meth *Method) IsRawResponse() bool { if meth.m.Response == nil { return false @@ -2567,6 +2609,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)) } diff --git a/google-api-go-generator/gen_test.go b/google-api-go-generator/gen_test.go index cb798a3a6e2..a9ad03a2823 100644 --- a/google-api-go-generator/gen_test.go +++ b/google-api-go-generator/gen_test.go @@ -39,6 +39,7 @@ func TestAPIs(t *testing.T) { "json-body", "mapofany", "mapofarrayofobjects", + "mapprotostruct", "mapofint64strings", "mapofobjects", "mapofstrings-1", diff --git a/google-api-go-generator/testdata/mapprotostruct.json b/google-api-go-generator/testdata/mapprotostruct.json new file mode 100644 index 00000000000..ee9bcd6230d --- /dev/null +++ b/google-api-go-generator/testdata/mapprotostruct.json @@ -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" + } + } + } + } + } +} diff --git a/google-api-go-generator/testdata/mapprotostruct.want b/google-api-go-generator/testdata/mapprotostruct.want new file mode 100644 index 00000000000..74118a411a2 --- /dev/null +++ b/google-api-go-generator/testdata/mapprotostruct.want @@ -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 +} diff --git a/internal/gensupport/send.go b/internal/gensupport/send.go index f39dd00d99f..f6716134ebf 100644 --- a/internal/gensupport/send.go +++ b/internal/gensupport/send.go @@ -48,8 +48,24 @@ func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (* if ctx != nil { headers := callctx.HeadersFromContext(ctx) for k, vals := range headers { - for _, v := range vals { - req.Header.Add(k, v) + if k == "x-goog-api-client" { + // Merge all values into a single "x-goog-api-client" header. + var mergedVal strings.Builder + baseXGoogHeader := req.Header.Get("X-Goog-Api-Client") + if baseXGoogHeader != "" { + mergedVal.WriteString(baseXGoogHeader) + mergedVal.WriteRune(' ') + } + for _, v := range vals { + mergedVal.WriteString(v) + mergedVal.WriteRune(' ') + } + // Remove the last space and replace the header on the request. + req.Header.Set(k, mergedVal.String()[:mergedVal.Len()-1]) + } else { + for _, v := range vals { + req.Header.Add(k, v) + } } } } @@ -118,7 +134,9 @@ func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, r var err error attempts := 1 invocationID := uuid.New().String() - baseXGoogHeader := req.Header.Get("X-Goog-Api-Client") + + xGoogHeaderVals := req.Header.Values("X-Goog-Api-Client") + baseXGoogHeader := strings.Join(xGoogHeaderVals, " ") // Loop to retry the request, up to the context deadline. var pause time.Duration diff --git a/internal/gensupport/send_test.go b/internal/gensupport/send_test.go index 59534408393..502ab352ebd 100644 --- a/internal/gensupport/send_test.go +++ b/internal/gensupport/send_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/http" + "regexp" "testing" "github.com/google/go-cmp/cmp" @@ -36,13 +37,24 @@ func TestSendRequestWithRetry(t *testing.T) { } type headerRoundTripper struct { - wantHeader http.Header + wantHeader http.Header + wantXgoogAPIRegex string // test x-goog-api-client separately } func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + // Test x-goog-api-client with a regex, since invocation ids are randomly generated + match, err := regexp.MatchString(rt.wantXgoogAPIRegex, r.Header.Get("X-Goog-Api-Client")) + if err != nil { + return nil, fmt.Errorf("compiling regexp: %v", err) + } + if !match { + return nil, fmt.Errorf("X-Goog-Api-Client header has wrong format\ngot %v\nwant regex matching %v", r.Header.Get("X-Goog-Api-Client"), rt.wantXgoogAPIRegex) + } + // Ignore x-goog headers sent by SendRequestWithRetry - r.Header.Del("X-Goog-Api-Client") r.Header.Del("X-Goog-Gcs-Idempotency-Token") + r.Header.Del("X-Goog-Api-Client") // this was tested above already + if diff := cmp.Diff(r.Header, rt.wantHeader); diff != "" { return nil, fmt.Errorf("headers don't match: %v", diff) } @@ -53,19 +65,48 @@ func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) func TestSendRequestHeader(t *testing.T) { ctx := context.Background() ctx = callctx.SetHeaders(ctx, "foo", "100", "bar", "200") - client := http.Client{ - Transport: &headerRoundTripper{ - wantHeader: map[string][]string{"Foo": {"100"}, "Bar": {"200"}}, - }, + transport := &headerRoundTripper{ + wantHeader: map[string][]string{"Foo": {"100"}, "Bar": {"200"}}, } + client := http.Client{Transport: transport} + req, _ := http.NewRequest("GET", "url", nil) if _, err := SendRequest(ctx, &client, req); err != nil { t.Errorf("SendRequest: %v", err) } + + // SendRequestWithRetry adds retry and idempotency headers + transport.wantXgoogAPIRegex = "^gccl-invocation-id/.{36} gccl-attempt-count/[0-9]+ $" req2, _ := http.NewRequest("GET", "url", nil) if _, err := SendRequestWithRetry(ctx, &client, req2, nil); err != nil { + t.Errorf("SendRequestWithRetry: %v", err) + } +} + +// Ensure that x-goog-api-client headers set via the context are merged properly +// and passed through to the request as expected. +func TestSendRequestXgoogHeaderxxx(t *testing.T) { + ctx := context.Background() + ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/1", "bar", "200", "x-goog-api-client", "val/2") + ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/11 val/22") + + transport := &headerRoundTripper{ + wantHeader: map[string][]string{"Bar": {"200"}}, + wantXgoogAPIRegex: "^val/1 val/2 val/11 val/22$", + } + client := http.Client{Transport: transport} + + req, _ := http.NewRequest("GET", "url", nil) + if _, err := SendRequest(ctx, &client, req); err != nil { t.Errorf("SendRequest: %v", err) } + + // SendRequestWithRetry adds retry and idempotency headers + transport.wantXgoogAPIRegex = fmt.Sprintf("^gccl-invocation-id/.{36} gccl-attempt-count/[0-9]+ %s$", transport.wantXgoogAPIRegex[1:len(transport.wantXgoogAPIRegex)-1]) + req2, _ := http.NewRequest("GET", "url", nil) + if _, err := SendRequestWithRetry(ctx, &client, req2, nil); err != nil { + t.Errorf("SendRequestWithRetry: %v", err) + } } type brokenRoundTripper struct{}