├── .gitignore ├── v2 ├── internallog │ ├── testdata │ │ ├── off.log │ │ ├── envar-error.log │ │ ├── envar-warn.log │ │ ├── envar-info.log │ │ ├── httpResponse.log │ │ ├── httpRequest-form.log │ │ ├── httpRequest.log │ │ ├── envar-debug.log │ │ └── httpRequest-array.log │ ├── grpclog │ │ ├── testdata │ │ │ ├── response.log │ │ │ └── request.log │ │ ├── grpclog_test.go │ │ └── grpclog.go │ ├── internal │ │ ├── bookpb │ │ │ ├── book.proto │ │ │ └── book.pb.go │ │ ├── logtest │ │ │ └── logtest.go │ │ └── internal.go │ ├── internallog.go │ └── internallog_test.go ├── .release-please-manifest.json ├── apierror │ ├── testdata │ │ ├── empty.golden │ │ ├── help.golden │ │ ├── unknown.golden │ │ ├── bad_request.golden │ │ ├── debug_info.golden │ │ ├── http_err.golden │ │ ├── localized_message.golden │ │ ├── error_info.golden │ │ ├── resource_info.golden │ │ └── multiple_info.golden │ └── internal │ │ └── proto │ │ ├── README.md │ │ ├── custom_error.proto │ │ ├── error.proto │ │ ├── custom_error.pb.go │ │ └── error.pb.go ├── release-please-config.json ├── go.mod ├── internal │ └── version.go ├── gax.go ├── iterator │ ├── iterator.go │ └── example_test.go ├── callctx │ ├── callctx_example_test.go │ ├── callctx.go │ └── callctx_test.go ├── feature.go ├── go.sum ├── invoke.go ├── content_type.go ├── proto_json_stream.go ├── call_option_test.go ├── proto_json_stream_test.go ├── content_type_test.go ├── header_test.go ├── feature_test.go ├── header.go ├── call_option.go ├── invoke_test.go ├── CHANGES.md └── example_test.go ├── .github ├── CODEOWNERS └── renovate.json ├── go.work ├── .librarian ├── config.yaml └── state.yaml ├── SECURITY.md ├── go.mod ├── README.md ├── internal └── kokoro │ ├── trampoline.sh │ ├── vet.sh │ └── test.sh ├── RELEASING.md ├── LICENSE ├── CONTRIBUTING.md ├── header.go ├── CODE_OF_CONDUCT.md ├── gax.go ├── invoke.go ├── call_option.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | *.cover 2 | -------------------------------------------------------------------------------- /v2/internallog/testdata/off.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @googleapis/cloud-sdk-go-eng 2 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.24.0 2 | 3 | use ( 4 | . 5 | ./v2 6 | ) 7 | -------------------------------------------------------------------------------- /v2/.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "v2": "2.15.0" 3 | } 4 | -------------------------------------------------------------------------------- /v2/apierror/testdata/empty.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br -------------------------------------------------------------------------------- /v2/internallog/testdata/envar-error.log: -------------------------------------------------------------------------------- 1 | {"message":"one","severity":"ERROR"} 2 | -------------------------------------------------------------------------------- /v2/internallog/testdata/envar-warn.log: -------------------------------------------------------------------------------- 1 | {"message":"one","severity":"ERROR"} 2 | {"message":"three","severity":"WARN"} 3 | -------------------------------------------------------------------------------- /v2/apierror/testdata/help.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = Help desc = Foo url = Bar -------------------------------------------------------------------------------- /v2/apierror/testdata/unknown.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = Unknown desc = unknown detail 1 -------------------------------------------------------------------------------- /v2/apierror/testdata/bad_request.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = BadRequest field = Foo desc = Bar -------------------------------------------------------------------------------- /v2/apierror/testdata/debug_info.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = DebugInfo detail = Stack stack = Foo Bar -------------------------------------------------------------------------------- /v2/internallog/grpclog/testdata/response.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","response":{"payload":{"author":"The author","title":"The book"}},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/apierror/testdata/http_err.golden: -------------------------------------------------------------------------------- 1 | googleapi: Error 0: just because 2 | error details: name = ErrorInfo reason = just because domain = tests metadata = map[] -------------------------------------------------------------------------------- /v2/apierror/testdata/localized_message.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = LocalizedMessage locale = Foo msg = Bar -------------------------------------------------------------------------------- /v2/internallog/testdata/envar-info.log: -------------------------------------------------------------------------------- 1 | {"message":"one","severity":"ERROR"} 2 | {"message":"two","severity":"INFO"} 3 | {"message":"three","severity":"WARN"} 4 | -------------------------------------------------------------------------------- /v2/apierror/testdata/error_info.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = Unauthenticated desc = ei 2 | error details: name = ErrorInfo reason = Foo domain = Bar metadata = map[type:test] -------------------------------------------------------------------------------- /v2/internallog/grpclog/testdata/request.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","request":{"headers":{"foo":"bar"},"payload":{"author":"The author","title":"The book"}},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/internallog/testdata/httpResponse.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","response":{"headers":{"Foo":"bar"},"payload":{"secret":"shh, it's a secret"},"status":"200"},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/apierror/testdata/resource_info.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = InvalidArgument desc = br 2 | error details: name = ResourceInfo type = Foo resourcename = Bar owner = Client desc = Directory not Found -------------------------------------------------------------------------------- /v2/internallog/testdata/httpRequest-form.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","request":{"headers":{"Foo":"bar"},"method":"POST","payload":"baz=qux\u0026foo=bar","url":"https://example.com"},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/internallog/testdata/httpRequest.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","request":{"headers":{"Foo":"bar"},"method":"POST","payload":{"secret":"shh, it's a secret"},"url":"https://example.com"},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/internallog/testdata/envar-debug.log: -------------------------------------------------------------------------------- 1 | {"message":"one","severity":"ERROR"} 2 | {"message":"two","severity":"INFO"} 3 | {"message":"three","severity":"WARN"} 4 | {"message":"four","severity":"DEBUG"} 5 | -------------------------------------------------------------------------------- /v2/internallog/testdata/httpRequest-array.log: -------------------------------------------------------------------------------- 1 | {"message":"msg","request":{"headers":{"Foo":"bar"},"method":"POST","payload":[{"secret":"shh, it's a secret"},{"secret":"and, another"}],"url":"https://example.com"},"severity":"DEBUG"} 2 | -------------------------------------------------------------------------------- /v2/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "release-type": "go-yoshi", 3 | "separate-pull-requests": true, 4 | "include-component-in-tag": false, 5 | "packages": { 6 | "v2": { 7 | "component": "v2" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.librarian/config.yaml: -------------------------------------------------------------------------------- 1 | # Hand-maintained configuration for libraries. 2 | # 3 | # All libraries with handwritten code (core, hybrid and handwritten) 4 | # libraries have "release_blocked: true" so that releases are 5 | # explicitly initiated 6 | libraries: 7 | - id: "v2" 8 | release_blocked: true -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /v2/apierror/testdata/multiple_info.golden: -------------------------------------------------------------------------------- 1 | rpc error: code = Canceled desc = Request cancelled by client 2 | error details: name = BadRequest field = Foo desc = Bar 3 | error details: name = PreconditionFailure type = Foo subj = Bar desc = desc 4 | error details: name = QuotaFailure subj = Foo desc = Bar 5 | error details: name = RequestInfo id = Foo data = Bar 6 | error details: retry in 10.00000001s -------------------------------------------------------------------------------- /.librarian/state.yaml: -------------------------------------------------------------------------------- 1 | image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/librarian-go@sha256:f2cbb6b904fdbf086efec0100536c52a79a654a5b9df21f975a2b6f6d50395a4 2 | libraries: 3 | - id: v2 4 | version: 2.16.0 5 | last_generated_commit: "" 6 | apis: [] 7 | source_roots: 8 | - v2 9 | preserve_regex: [] 10 | remove_regex: [] 11 | tag_format: v{version} 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/googleapis/gax-go 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/googleapis/gax-go/v2 v2.16.0 7 | google.golang.org/grpc v1.77.0 8 | ) 9 | 10 | require ( 11 | golang.org/x/net v0.47.0 // indirect 12 | golang.org/x/sys v0.38.0 // indirect 13 | golang.org/x/text v0.31.0 // indirect 14 | google.golang.org/api v0.257.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect 16 | google.golang.org/protobuf v1.36.11 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "schedule:weekly", 5 | ":disableDependencyDashboard" 6 | ], 7 | "semanticCommits": false, 8 | "postUpdateOptions": [ 9 | "gomodTidy" 10 | ], 11 | "commitMessagePrefix": "chore(all): ", 12 | "commitMessageAction": "update", 13 | "labels": [ 14 | "automerge" 15 | ], 16 | "groupName": "all", 17 | "force": { 18 | "constraints": { 19 | "go": "1.24" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/googleapis/gax-go/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/google/go-cmp v0.7.0 7 | google.golang.org/api v0.258.0 8 | google.golang.org/genproto v0.0.0-20251213004720-97cd9d5aeac2 9 | google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 10 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 11 | google.golang.org/grpc v1.77.0 12 | google.golang.org/protobuf v1.36.11 13 | 14 | ) 15 | 16 | require ( 17 | golang.org/x/net v0.48.0 // indirect 18 | golang.org/x/sys v0.39.0 // indirect 19 | golang.org/x/text v0.32.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /v2/internal/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by gapicgen. DO NOT EDIT. 16 | 17 | package internal 18 | 19 | // Version is the current tagged release of the library. 20 | const Version = "2.16.0" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google API Extensions for Go 2 | ============================ 3 | 4 | [![GoDoc](https://godoc.org/github.com/googleapis/gax-go?status.svg)](https://godoc.org/github.com/googleapis/gax-go) 5 | 6 | Google API Extensions for Go (gax-go) is a set of modules which aids the 7 | development of APIs for clients and servers based on `gRPC` and Google API 8 | conventions. 9 | 10 | To install the API extensions, use: 11 | 12 | ``` 13 | go get -u github.com/googleapis/gax-go/v2 14 | ``` 15 | 16 | **Note:** Application code will rarely need to use this library directly, 17 | but the code generated automatically from API definition files can use it 18 | to simplify code generation and to provide more convenient and idiomatic API surface. 19 | 20 | Go Versions 21 | =========== 22 | This library requires Go 1.6 or above. 23 | 24 | License 25 | ======= 26 | BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/main/LICENSE) 27 | for more information. 28 | -------------------------------------------------------------------------------- /internal/kokoro/trampoline.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2018 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | set -eo pipefail 16 | # Always run the cleanup script, regardless of the success of bouncing into 17 | # the container. 18 | function cleanup() { 19 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 20 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 21 | echo "cleanup"; 22 | } 23 | trap cleanup EXIT 24 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" 25 | -------------------------------------------------------------------------------- /internal/kokoro/vet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error 4 | set -eo 5 | 6 | # Display commands being run 7 | set -x 8 | 9 | if [[ $KOKORO_JOB_NAME != *"latest-version"* ]]; then 10 | exit 0 11 | fi 12 | 13 | # Look at all .go files (ignoring .pb.go files) and make sure they have a Copyright. Fail if any don't. 14 | find . -type f -name "*.go" ! -name "*.pb.go" -exec grep -L "\(Copyright [0-9]\{4,\}\)" {} \; 2>&1 | tee /dev/stderr | (! read) 15 | 16 | # Fail if a dependency was added without the necessary go.mod/go.sum change 17 | # being part of the commit. 18 | go mod tidy 19 | git diff go.mod | tee /dev/stderr | (! read) 20 | git diff go.sum | tee /dev/stderr | (! read) 21 | 22 | pushd v2 23 | go mod tidy 24 | git diff go.mod | tee /dev/stderr | (! read) 25 | git diff go.sum | tee /dev/stderr | (! read) 26 | popd 27 | 28 | # Easier to debug CI. 29 | pwd 30 | 31 | gofmt -s -d -l . 2>&1 | tee /dev/stderr | (! read) 32 | goimports -l . 2>&1 | tee /dev/stderr | (! read) 33 | 34 | golint ./... 2>&1 | tee /dev/stderr | (! read) 35 | staticcheck ./... 36 | -------------------------------------------------------------------------------- /internal/kokoro/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error 4 | set -eo 5 | 6 | # Display commands being run 7 | set -x 8 | 9 | # cd to project dir on Kokoro instance 10 | cd github/gax-go 11 | 12 | go version 13 | 14 | # Set $GOPATH 15 | export GOPATH="$HOME/go" 16 | export PATH="$GOPATH/bin:$PATH" 17 | export GO111MODULE=on 18 | 19 | try3() { eval "$*" || eval "$*" || eval "$*"; } 20 | 21 | # All packages, including +build tools, are fetched. 22 | try3 go mod download 23 | ./internal/kokoro/vet.sh 24 | 25 | go get github.com/jstemmer/go-junit-report 26 | 27 | set +e 28 | 29 | go test -race -v . 2>&1 | tee sponge_log.log 30 | cat sponge_log.log | go-junit-report -set-exit-code > sponge_log.xml 31 | exit_code=$? 32 | 33 | cd v2 34 | set -e 35 | try3 go mod download 36 | set +e 37 | 38 | go test -race -v . 2>&1 | tee sponge_log.log 39 | cat sponge_log.log | go-junit-report -set-exit-code > sponge_log.xml 40 | exit_code=$(($exit_code+$?)) 41 | 42 | # Send logs to Flaky Bot for continuous builds. 43 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]]; then 44 | cd .. 45 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 46 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 47 | fi 48 | 49 | exit $exit_code 50 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # How to release v1 2 | 3 | 1. Determine the current release version with `git tag -l`. It should look 4 | something like `vX.Y.Z`. We'll call the current version `$CV` and the new 5 | version `$NV`. 6 | 1. On main, run `git log $CV..` to list all the changes since the last 7 | release. 8 | a. NOTE: Some commits may pertain to only v1 or v2. Manually introspect 9 | each commit to figure which occurred in v1. 10 | 1. Edit `CHANGES.md` to include a summary of the changes. 11 | 1. Mail the CL containing the `CHANGES.md` changes. When the CL is approved, 12 | submit it. 13 | 1. Without submitting any other CLs: 14 | a. Switch to main. 15 | b. `git pull` 16 | c. Tag the repo with the next version: `git tag $NV`. It should be of the 17 | form `v1.Y.Z`. 18 | d. Push the tag: `git push origin $NV`. 19 | 1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases) 20 | with the new release, copying the contents of the CHANGES.md. 21 | 22 | # How to release v2 23 | 24 | Same process as v1, once again noting that the commit list may include v1 25 | commits (which should be pruned out). Note also whilst v1 tags are `v1.Y.Z`, v2 26 | tags are `v2.Y.Z`. 27 | 28 | # On releasing multiple major versions 29 | 30 | Please see https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher. 31 | -------------------------------------------------------------------------------- /v2/apierror/internal/proto/README.md: -------------------------------------------------------------------------------- 1 | # HTTP JSON Error Schema 2 | 3 | The `error.proto` represents the HTTP-JSON schema used by Google APIs to convey 4 | error payloads as described by https://cloud.google.com/apis/design/errors#http_mapping. 5 | This package is for internal parsing logic only and should not be used in any 6 | other context. 7 | 8 | ## Regeneration 9 | 10 | To regenerate the protobuf Go code you will need the following: 11 | 12 | * A local copy of [googleapis], the absolute path to which should be exported to 13 | the environment variable `GOOGLEAPIS` 14 | * The protobuf compiler [protoc] 15 | * The Go [protobuf plugin] 16 | * The [goimports] tool 17 | 18 | From this directory run the following command: 19 | ```sh 20 | protoc -I $GOOGLEAPIS -I. --go_out=. --go_opt=module=github.com/googleapis/gax-go/v2/apierror/internal/proto error.proto 21 | goimports -w . 22 | ``` 23 | 24 | Note: the `module` plugin option ensures the generated code is placed in this 25 | directory, and not in several nested directories defined by `go_package` option. 26 | 27 | [googleapis]: https://github.com/googleapis/googleapis 28 | [protoc]: https://github.com/protocolbuffers/protobuf#protocol-compiler-installation 29 | [protobuf plugin]: https://developers.google.com/protocol-buffers/docs/reference/go-generated 30 | [goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016, Google Inc. 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /v2/apierror/internal/proto/custom_error.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package error; 18 | 19 | option go_package = "github.com/googleapis/gax-go/v2/apierror/internal/proto;jsonerror"; 20 | 21 | 22 | // CustomError is an example of a custom error message which may be included 23 | // in an rpc status. It is not meant to reflect a standard error. 24 | message CustomError { 25 | 26 | // Error code for `CustomError`. 27 | enum CustomErrorCode { 28 | // Default error. 29 | CUSTOM_ERROR_CODE_UNSPECIFIED = 0; 30 | 31 | // Too many foo. 32 | TOO_MANY_FOO = 1; 33 | 34 | // Not enough foo. 35 | NOT_ENOUGH_FOO = 2; 36 | 37 | // Catastrophic error. 38 | UNIVERSE_WAS_DESTROYED = 3; 39 | 40 | } 41 | 42 | // Error code specific to the custom API being invoked. 43 | CustomErrorCode code = 1; 44 | 45 | // Name of the failed entity. 46 | string entity = 2; 47 | 48 | // Message that describes the error. 49 | string error_message = 3; 50 | } 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement] 6 | (https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use Github pull requests for this purpose. 22 | 23 | ### Breaking code changes 24 | When a breaking change is added, CI/CD will fail. If the change is expected, 25 | add a BREAKING_CHANGE_ACCEPTABLE= line to the CL description. This will 26 | cause CI/CD to skip checking breaking changes. 27 | 28 | ### The small print 29 | Contributions made by corporations are covered by a different agreement than 30 | the one above, the 31 | [Software Grant and Corporate Contributor License Agreement] 32 | (https://cla.developers.google.com/about/google-corporate). 33 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import v2 "github.com/googleapis/gax-go/v2" 33 | 34 | // XGoogHeader is for use by the Google Cloud Libraries only. 35 | // 36 | // XGoogHeader formats key-value pairs. 37 | // The resulting string is suitable for x-goog-api-client header. 38 | func XGoogHeader(keyval ...string) string { 39 | return v2.XGoogHeader(keyval...) 40 | } 41 | -------------------------------------------------------------------------------- /v2/apierror/internal/proto/error.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package error; 18 | 19 | import "google/protobuf/any.proto"; 20 | import "google/rpc/code.proto"; 21 | 22 | option go_package = "github.com/googleapis/gax-go/v2/apierror/internal/proto;jsonerror"; 23 | 24 | // The error format v2 for Google JSON REST APIs. 25 | // Copied from https://cloud.google.com/apis/design/errors#http_mapping. 26 | // 27 | // NOTE: This schema is not used for other wire protocols. 28 | message Error { 29 | // This message has the same semantics as `google.rpc.Status`. It uses HTTP 30 | // status code instead of gRPC status code. It has an extra field `status` 31 | // for backward compatibility with Google API Client Libraries. 32 | message Status { 33 | // The HTTP status code that corresponds to `google.rpc.Status.code`. 34 | int32 code = 1; 35 | // This corresponds to `google.rpc.Status.message`. 36 | string message = 2; 37 | // This is the enum version for `google.rpc.Status.code`. 38 | google.rpc.Code status = 4; 39 | // This corresponds to `google.rpc.Status.details`. 40 | repeated google.protobuf.Any details = 5; 41 | } 42 | // The actual error payload. The nested message structure is for backward 43 | // compatibility with Google API client libraries. It also makes the error 44 | // more readable to developers. 45 | Status error = 1; 46 | } 47 | -------------------------------------------------------------------------------- /v2/internallog/internal/bookpb/book.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | syntax = "proto3"; 31 | 32 | package book; 33 | 34 | option go_package = "github.com/googleapis/gax-go/v2/internallog/internal/bookpb;bookpb"; 35 | 36 | // A single book in the library. 37 | message Book { 38 | // The title of the book. 39 | string title = 1; 40 | 41 | // The name of the book author. 42 | string author = 2; 43 | } 44 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /gax.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package gax contains a set of modules which aid the development of APIs 31 | // for clients and servers based on gRPC and Google API conventions. 32 | // 33 | // Application code will rarely need to use this library directly. 34 | // However, code generated automatically from API definition files can use it 35 | // to simplify code generation and to provide more convenient and idiomatic API surfaces. 36 | package gax 37 | 38 | // Version specifies the gax version. 39 | const Version = "1.0.1" 40 | -------------------------------------------------------------------------------- /v2/gax.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package gax contains a set of modules which aid the development of APIs 31 | // for clients and servers based on gRPC and Google API conventions. 32 | // 33 | // Application code will rarely need to use this library directly. 34 | // However, code generated automatically from API definition files can use it 35 | // to simplify code generation and to provide more convenient and idiomatic API surfaces. 36 | package gax 37 | 38 | import "github.com/googleapis/gax-go/v2/internal" 39 | 40 | // Version specifies the gax-go version being used. 41 | const Version = internal.Version 42 | -------------------------------------------------------------------------------- /invoke.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "context" 34 | "time" 35 | 36 | v2 "github.com/googleapis/gax-go/v2" 37 | ) 38 | 39 | // APICall is a user defined call stub. 40 | type APICall = v2.APICall 41 | 42 | // Invoke calls the given APICall, 43 | // performing retries as specified by opts, if any. 44 | func Invoke(ctx context.Context, call APICall, opts ...CallOption) error { 45 | return v2.Invoke(ctx, call, opts...) 46 | } 47 | 48 | // Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing. 49 | // If interrupted, Sleep returns ctx.Err(). 50 | func Sleep(ctx context.Context, d time.Duration) error { 51 | return v2.Sleep(ctx, d) 52 | } 53 | -------------------------------------------------------------------------------- /v2/iterator/iterator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | //go:build go1.23 31 | 32 | // Package iterator contains helper for working with iterators. It is meant for 33 | // internal use only by the Go Client Libraries. 34 | package iterator 35 | 36 | import ( 37 | "iter" 38 | 39 | otherit "google.golang.org/api/iterator" 40 | ) 41 | 42 | // RangeAdapter transforms client iterator type into a [iter.Seq2] that can 43 | // be used with Go's range expressions. 44 | // 45 | // This is for internal use only. 46 | func RangeAdapter[T any](next func() (T, error)) iter.Seq2[T, error] { 47 | var err error 48 | return func(yield func(T, error) bool) { 49 | for { 50 | if err != nil { 51 | return 52 | } 53 | var resp T 54 | resp, err = next() 55 | if err == otherit.Done { 56 | return 57 | } 58 | if !yield(resp, err) { 59 | return 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /v2/iterator/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | //go:build go1.23 31 | 32 | package iterator_test 33 | 34 | import ( 35 | "fmt" 36 | "iter" 37 | 38 | "github.com/googleapis/gax-go/v2/iterator" 39 | otherit "google.golang.org/api/iterator" 40 | ) 41 | 42 | type exampleType struct { 43 | data []int 44 | i int 45 | } 46 | 47 | func (t *exampleType) next() (int, error) { 48 | var v int 49 | if t.i == len(t.data) { 50 | return v, otherit.Done 51 | } 52 | v = t.data[t.i] 53 | t.i++ 54 | return v, nil 55 | } 56 | 57 | func (t *exampleType) All() iter.Seq2[int, error] { 58 | return iterator.RangeAdapter[int](t.next) 59 | } 60 | 61 | func ExampleRangeAdapter() { 62 | t := &exampleType{ 63 | data: []int{1, 2, 3}, 64 | } 65 | for v, err := range t.All() { 66 | if err != nil { 67 | // TODO: handle error 68 | } 69 | fmt.Println(v) 70 | } 71 | // Output: 72 | // 1 73 | // 2 74 | // 3 75 | } 76 | -------------------------------------------------------------------------------- /v2/callctx/callctx_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package callctx_test 31 | 32 | import ( 33 | "context" 34 | "fmt" 35 | 36 | "github.com/googleapis/gax-go/v2/callctx" 37 | "google.golang.org/genproto/googleapis/api/metric" 38 | "google.golang.org/protobuf/types/known/fieldmaskpb" 39 | ) 40 | 41 | func ExampleSetHeaders() { 42 | ctx := context.Background() 43 | ctx = callctx.SetHeaders(ctx, "key", "value") 44 | 45 | // Send the returned context to the request you are making. Later on these 46 | // values will be retrieved and set on outgoing requests. 47 | 48 | headers := callctx.HeadersFromContext(ctx) 49 | fmt.Println(headers["key"][0]) 50 | // Output: value 51 | } 52 | 53 | func ExampleXGoogFieldMaskHeader() { 54 | ctx := context.Background() 55 | ctx = callctx.SetHeaders(ctx, callctx.XGoogFieldMaskHeader, "field_one,field.two") 56 | 57 | // Send the returned context to the request you are making. 58 | } 59 | 60 | func ExampleXGoogFieldMaskHeader_fieldmaskpb() { 61 | // Build a mask using the expected response protobuf message. 62 | mask, err := fieldmaskpb.New(&metric.MetricDescriptor{}, "display_name", "metadata.launch_stage") 63 | if err != nil { 64 | // handle error 65 | } 66 | 67 | ctx := context.Background() 68 | ctx = callctx.SetHeaders(ctx, callctx.XGoogFieldMaskHeader, mask.String()) 69 | 70 | // Send the returned context to the request you are making. 71 | } 72 | -------------------------------------------------------------------------------- /call_option.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | v2 "github.com/googleapis/gax-go/v2" 34 | "google.golang.org/grpc" 35 | "google.golang.org/grpc/codes" 36 | ) 37 | 38 | // CallOption is an option used by Invoke to control behaviors of RPC calls. 39 | // CallOption works by modifying relevant fields of CallSettings. 40 | type CallOption = v2.CallOption 41 | 42 | // Retryer is used by Invoke to determine retry behavior. 43 | type Retryer = v2.Retryer 44 | 45 | // WithRetry sets CallSettings.Retry to fn. 46 | func WithRetry(fn func() Retryer) CallOption { 47 | return v2.WithRetry(fn) 48 | } 49 | 50 | // OnCodes returns a Retryer that retries if and only if 51 | // the previous attempt returns a GRPC error whose error code is stored in cc. 52 | // Pause times between retries are specified by bo. 53 | // 54 | // bo is only used for its parameters; each Retryer has its own copy. 55 | func OnCodes(cc []codes.Code, bo Backoff) Retryer { 56 | return v2.OnCodes(cc, bo) 57 | } 58 | 59 | // Backoff implements exponential backoff. 60 | // The wait time between retries is a random value between 0 and the "retry envelope". 61 | // The envelope starts at Initial and increases by the factor of Multiplier every retry, 62 | // but is capped at Max. 63 | type Backoff = v2.Backoff 64 | 65 | // WithGRPCOptions allows passing gRPC call options during client creation. 66 | func WithGRPCOptions(opt ...grpc.CallOption) CallOption { 67 | return v2.WithGRPCOptions(opt...) 68 | } 69 | 70 | // CallSettings allow fine-grained control over how calls are made. 71 | type CallSettings = v2.CallSettings 72 | -------------------------------------------------------------------------------- /v2/internallog/grpclog/grpclog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // - Redistributions of source code must retain the above copyright 9 | // 10 | // notice, this list of conditions and the following disclaimer. 11 | // - Redistributions in binary form must reproduce the above 12 | // 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // - Neither the name of Google Inc. nor the names of its 17 | // 18 | // contributors may be used to endorse or promote products derived from 19 | // this software without specific prior written permission. 20 | // 21 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | package grpclog 34 | 35 | import ( 36 | "context" 37 | "flag" 38 | "log/slog" 39 | "os" 40 | "testing" 41 | 42 | "github.com/googleapis/gax-go/v2/internallog/internal" 43 | "github.com/googleapis/gax-go/v2/internallog/internal/bookpb" 44 | "github.com/googleapis/gax-go/v2/internallog/internal/logtest" 45 | "google.golang.org/grpc/metadata" 46 | ) 47 | 48 | // To update conformance tests in this package run `go test -update_golden` 49 | func TestMain(m *testing.M) { 50 | flag.Parse() 51 | os.Exit(m.Run()) 52 | } 53 | 54 | func TestLog_protoMessageRequest(t *testing.T) { 55 | golden := "request.log" 56 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 57 | logger, f := setupLogger(t, golden) 58 | ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("foo", "bar")) 59 | book := &bookpb.Book{ 60 | Title: "The book", 61 | Author: "The author", 62 | } 63 | logger.DebugContext(ctx, "msg", "request", ProtoMessageRequest(ctx, book)) 64 | f.Close() 65 | logtest.DiffTest(t, f.Name(), golden) 66 | } 67 | 68 | func TestLog_protoMessageResponse(t *testing.T) { 69 | golden := "response.log" 70 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 71 | logger, f := setupLogger(t, golden) 72 | ctx := context.Background() 73 | book := &bookpb.Book{ 74 | Title: "The book", 75 | Author: "The author", 76 | } 77 | logger.DebugContext(ctx, "msg", "response", ProtoMessageResponse(book)) 78 | f.Close() 79 | logtest.DiffTest(t, f.Name(), golden) 80 | } 81 | 82 | func setupLogger(t *testing.T, golden string) (*slog.Logger, *os.File) { 83 | t.Helper() 84 | f, err := os.CreateTemp(t.TempDir(), golden) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | logger := internal.NewLoggerWithWriter(f) 89 | return logger, f 90 | } 91 | -------------------------------------------------------------------------------- /v2/feature.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "os" 34 | "strings" 35 | "sync" 36 | ) 37 | 38 | var ( 39 | // featureEnabledOnce caches results for IsFeatureEnabled. 40 | featureEnabledOnce sync.Once 41 | featureEnabledStore map[string]bool 42 | ) 43 | 44 | // IsFeatureEnabled checks if an experimental feature is enabled via 45 | // environment variable. The environment variable must be prefixed with 46 | // "GOOGLE_SDK_GO_EXPERIMENTAL_". The feature name passed to this 47 | // function must be the suffix (e.g., "FOO" for "GOOGLE_SDK_GO_EXPERIMENTAL_FOO"). 48 | // To enable the feature, the environment variable's value must be "true", 49 | // case-insensitive. The result for each name is cached on the first call. 50 | func IsFeatureEnabled(name string) bool { 51 | featureEnabledOnce.Do(func() { 52 | featureEnabledStore = make(map[string]bool) 53 | for _, env := range os.Environ() { 54 | if strings.HasPrefix(env, "GOOGLE_SDK_GO_EXPERIMENTAL_") { 55 | // Parse "KEY=VALUE" 56 | kv := strings.SplitN(env, "=", 2) 57 | if len(kv) == 2 && strings.ToLower(kv[1]) == "true" { 58 | key := strings.TrimPrefix(kv[0], "GOOGLE_SDK_GO_EXPERIMENTAL_") 59 | featureEnabledStore[key] = true 60 | } 61 | } 62 | } 63 | }) 64 | return featureEnabledStore[name] 65 | } 66 | 67 | // TestOnlyResetIsFeatureEnabled is for testing purposes only. It resets the cached 68 | // feature flags, allowing environment variables to be re-read on the next call to IsFeatureEnabled. 69 | // This function is not thread-safe; if another goroutine reads a feature after this 70 | // function is called but before the `featureEnabledOnce` is re-initialized by IsFeatureEnabled, 71 | // it may see an inconsistent state. 72 | func TestOnlyResetIsFeatureEnabled() { 73 | featureEnabledOnce = sync.Once{} 74 | featureEnabledStore = nil 75 | } 76 | -------------------------------------------------------------------------------- /v2/internallog/grpclog/grpclog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package grpclog in intended for internal use by generated clients only. 31 | package grpclog 32 | 33 | import ( 34 | "context" 35 | "encoding/json" 36 | "log/slog" 37 | "strings" 38 | 39 | "google.golang.org/grpc/metadata" 40 | "google.golang.org/protobuf/encoding/protojson" 41 | "google.golang.org/protobuf/proto" 42 | ) 43 | 44 | // ProtoMessageRequest returns a lazily evaluated [slog.LogValuer] for 45 | // the provided message. The context is used to extract outgoing headers. 46 | func ProtoMessageRequest(ctx context.Context, msg proto.Message) slog.LogValuer { 47 | return &protoMessage{ctx: ctx, msg: msg} 48 | } 49 | 50 | // ProtoMessageResponse returns a lazily evaluated [slog.LogValuer] for 51 | // the provided message. 52 | func ProtoMessageResponse(msg proto.Message) slog.LogValuer { 53 | return &protoMessage{msg: msg} 54 | } 55 | 56 | type protoMessage struct { 57 | ctx context.Context 58 | msg proto.Message 59 | } 60 | 61 | func (m *protoMessage) LogValue() slog.Value { 62 | if m == nil || m.msg == nil { 63 | return slog.Value{} 64 | } 65 | 66 | var groupValueAttrs []slog.Attr 67 | 68 | if m.ctx != nil { 69 | var headerAttr []slog.Attr 70 | if m, ok := metadata.FromOutgoingContext(m.ctx); ok { 71 | for k, v := range m { 72 | headerAttr = append(headerAttr, slog.String(k, strings.Join(v, ","))) 73 | } 74 | } 75 | if len(headerAttr) > 0 { 76 | groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr)) 77 | } 78 | } 79 | mo := protojson.MarshalOptions{AllowPartial: true, UseEnumNumbers: true} 80 | if b, err := mo.Marshal(m.msg); err == nil { 81 | var m map[string]any 82 | if err := json.Unmarshal(b, &m); err == nil { 83 | groupValueAttrs = append(groupValueAttrs, slog.Any("payload", m)) 84 | } 85 | } 86 | 87 | return slog.GroupValue(groupValueAttrs...) 88 | } 89 | -------------------------------------------------------------------------------- /v2/internallog/internal/logtest/logtest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package logtest is a helper for validating logging tests. 31 | // 32 | // To update conformance tests in this package run `go test -update_golden` 33 | package logtest 34 | 35 | import ( 36 | "bytes" 37 | "encoding/json" 38 | "flag" 39 | "os" 40 | "path/filepath" 41 | "testing" 42 | 43 | "github.com/google/go-cmp/cmp" 44 | ) 45 | 46 | var updateGolden = flag.Bool("update-golden", false, "update golden files") 47 | 48 | // DiffTest is a test helper, testing got against contents of a goldenFile. 49 | func DiffTest(t *testing.T, tempFile, goldenFile string) { 50 | rawGot, err := os.ReadFile(tempFile) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | t.Helper() 55 | if *updateGolden { 56 | got := removeLogVariance(t, rawGot) 57 | if err := os.WriteFile(filepath.Join("testdata", goldenFile), got, os.ModePerm); err != nil { 58 | t.Fatal(err) 59 | } 60 | return 61 | } 62 | 63 | want, err := os.ReadFile(filepath.Join("testdata", goldenFile)) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | got := removeLogVariance(t, rawGot) 68 | 69 | if diff := cmp.Diff(want, got); diff != "" { 70 | t.Errorf("mismatch(-want, +got): %s", diff) 71 | } 72 | } 73 | 74 | // removeLogVariance removes parts of log lines that may differ between runs 75 | // and/or machines. 76 | func removeLogVariance(t *testing.T, in []byte) []byte { 77 | if len(in) == 0 { 78 | return in 79 | } 80 | bs := bytes.Split(in, []byte("\n")) 81 | for i, b := range bs { 82 | if len(b) == 0 { 83 | continue 84 | } 85 | m := map[string]any{} 86 | if err := json.Unmarshal(b, &m); err != nil { 87 | t.Fatal(err) 88 | } 89 | delete(m, "timestamp") 90 | if sl, ok := m["sourceLocation"].(map[string]any); ok { 91 | delete(sl, "file") 92 | // So that if test cases move around in this file they don't cause 93 | // failures 94 | delete(sl, "line") 95 | } 96 | b2, err := json.Marshal(m) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | t.Logf("%s", b2) 101 | bs[i] = b2 102 | } 103 | return bytes.Join(bs, []byte("\n")) 104 | } 105 | -------------------------------------------------------------------------------- /v2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 2 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 3 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 4 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 5 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 6 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 10 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= 12 | go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 13 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 14 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 15 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 16 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 17 | go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= 18 | go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= 19 | go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= 20 | go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= 21 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 22 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 23 | golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= 24 | golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 25 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 26 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 27 | golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 28 | golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 29 | gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= 30 | gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 31 | google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= 32 | google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= 33 | google.golang.org/genproto v0.0.0-20251213004720-97cd9d5aeac2 h1:stRtB2UVzFOWnorVuwF0BVVEjQ3AN6SjHWdg811UIQM= 34 | google.golang.org/genproto v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= 35 | google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= 36 | google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= 37 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= 38 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 39 | google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= 40 | google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= 41 | google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= 42 | google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 43 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 2 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 3 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 4 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 5 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 6 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 10 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= 12 | github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= 13 | go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= 14 | go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 15 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 16 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 17 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 18 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 19 | go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= 20 | go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= 21 | go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= 22 | go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= 23 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 24 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 25 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 26 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 27 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 28 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 29 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 30 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 31 | gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= 32 | gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 33 | google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= 34 | google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= 35 | google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= 36 | google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= 37 | google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= 38 | google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= 39 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= 40 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 41 | google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= 42 | google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= 43 | google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= 44 | google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 45 | -------------------------------------------------------------------------------- /v2/invoke.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "context" 34 | "strings" 35 | "time" 36 | 37 | "github.com/googleapis/gax-go/v2/apierror" 38 | ) 39 | 40 | // APICall is a user defined call stub. 41 | type APICall func(context.Context, CallSettings) error 42 | 43 | // Invoke calls the given APICall, performing retries as specified by opts, if 44 | // any. 45 | func Invoke(ctx context.Context, call APICall, opts ...CallOption) error { 46 | var settings CallSettings 47 | for _, opt := range opts { 48 | opt.Resolve(&settings) 49 | } 50 | return invoke(ctx, call, settings, Sleep) 51 | } 52 | 53 | // Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing. 54 | // If interrupted, Sleep returns ctx.Err(). 55 | func Sleep(ctx context.Context, d time.Duration) error { 56 | t := time.NewTimer(d) 57 | select { 58 | case <-ctx.Done(): 59 | t.Stop() 60 | return ctx.Err() 61 | case <-t.C: 62 | return nil 63 | } 64 | } 65 | 66 | type sleeper func(ctx context.Context, d time.Duration) error 67 | 68 | // invoke implements Invoke, taking an additional sleeper argument for testing. 69 | func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error { 70 | var retryer Retryer 71 | 72 | // Only use the value provided via WithTimeout if the context doesn't 73 | // already have a deadline. This is important for backwards compatibility if 74 | // the user already set a deadline on the context given to Invoke. 75 | if _, ok := ctx.Deadline(); !ok && settings.timeout != 0 { 76 | c, cc := context.WithTimeout(ctx, settings.timeout) 77 | defer cc() 78 | ctx = c 79 | } 80 | 81 | for { 82 | err := call(ctx, settings) 83 | if err == nil { 84 | return nil 85 | } 86 | // Never retry permanent certificate errors. (e.x. if ca-certificates 87 | // are not installed). We should only make very few, targeted 88 | // exceptions: many (other) status=Unavailable should be retried, such 89 | // as if there's a network hiccup, or the internet goes out for a 90 | // minute. This is also why here we are doing string parsing instead of 91 | // simply making Unavailable a non-retried code elsewhere. 92 | if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { 93 | return err 94 | } 95 | if apierr, ok := apierror.FromError(err); ok { 96 | err = apierr 97 | } 98 | if settings.Retry == nil { 99 | return err 100 | } 101 | if retryer == nil { 102 | if r := settings.Retry(); r != nil { 103 | retryer = r 104 | } else { 105 | return err 106 | } 107 | } 108 | if d, ok := retryer.Retry(err); !ok { 109 | return err 110 | } else if err = sp(ctx, d); err != nil { 111 | return err 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /v2/callctx/callctx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package callctx provides helpers for storing and retrieving values out of 31 | // [context.Context]. These values are used by our client libraries in various 32 | // ways across the stack. 33 | package callctx 34 | 35 | import ( 36 | "context" 37 | "fmt" 38 | ) 39 | 40 | const ( 41 | // XGoogFieldMaskHeader is the canonical header key for the [System Parameter] 42 | // that specifies the response read mask. The value(s) for this header 43 | // must adhere to format described in [fieldmaskpb]. 44 | // 45 | // [System Parameter]: https://cloud.google.com/apis/docs/system-parameters 46 | // [fieldmaskpb]: https://google.golang.org/protobuf/types/known/fieldmaskpb 47 | XGoogFieldMaskHeader = "x-goog-fieldmask" 48 | 49 | headerKey = contextKey("header") 50 | ) 51 | 52 | // contextKey is a private type used to store/retrieve context values. 53 | type contextKey string 54 | 55 | // HeadersFromContext retrieves headers set from [SetHeaders]. These headers 56 | // can then be cast to http.Header or metadata.MD to send along on requests. 57 | func HeadersFromContext(ctx context.Context) map[string][]string { 58 | m, ok := ctx.Value(headerKey).(map[string][]string) 59 | if !ok { 60 | return nil 61 | } 62 | return m 63 | } 64 | 65 | // SetHeaders stores key value pairs in the returned context that can later 66 | // be retrieved by [HeadersFromContext]. Values stored in this manner will 67 | // automatically be retrieved by client libraries and sent as outgoing headers 68 | // on all requests. keyvals should have a corresponding value for every key 69 | // provided. If there is an odd number of keyvals this method will panic. 70 | func SetHeaders(ctx context.Context, keyvals ...string) context.Context { 71 | if len(keyvals)%2 != 0 { 72 | panic(fmt.Sprintf("callctx: an even number of key value pairs must be provided, got %d", len(keyvals))) 73 | } 74 | h, ok := ctx.Value(headerKey).(map[string][]string) 75 | if !ok { 76 | h = make(map[string][]string) 77 | } else { 78 | h = cloneHeaders(h) 79 | } 80 | 81 | for i := 0; i < len(keyvals); i = i + 2 { 82 | h[keyvals[i]] = append(h[keyvals[i]], keyvals[i+1]) 83 | } 84 | return context.WithValue(ctx, headerKey, h) 85 | } 86 | 87 | // cloneHeaders makes a new key-value map while reusing the value slices. 88 | // As such, new values should be appended to the value slice, and modifying 89 | // indexed values is not thread safe. 90 | // 91 | // TODO: Replace this with maps.Clone when Go 1.21 is the minimum version. 92 | func cloneHeaders(h map[string][]string) map[string][]string { 93 | c := make(map[string][]string, len(h)) 94 | for k, v := range h { 95 | vc := make([]string, len(v)) 96 | copy(vc, v) 97 | c[k] = vc 98 | } 99 | return c 100 | } 101 | -------------------------------------------------------------------------------- /v2/callctx/callctx_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package callctx 31 | 32 | import ( 33 | "context" 34 | "sync" 35 | "testing" 36 | 37 | "github.com/google/go-cmp/cmp" 38 | // A workaround to fix some module ambiguity in tests 39 | _ "google.golang.org/genproto/googleapis/type/color" 40 | ) 41 | 42 | func TestAll(t *testing.T) { 43 | testCases := []struct { 44 | name string 45 | pairs []string 46 | want map[string][]string 47 | }{ 48 | { 49 | name: "standard", 50 | pairs: []string{"key", "value"}, 51 | want: map[string][]string{"key": {"value"}}, 52 | }, 53 | { 54 | name: "multiple values", 55 | pairs: []string{"key", "value", "key2", "value2"}, 56 | want: map[string][]string{"key": {"value"}, "key2": {"value2"}}, 57 | }, 58 | { 59 | name: "multiple values with same key", 60 | pairs: []string{"key", "value", "key", "value2"}, 61 | want: map[string][]string{"key": {"value", "value2"}}, 62 | }, 63 | } 64 | for _, tc := range testCases { 65 | ctx := context.Background() 66 | ctx = SetHeaders(ctx, tc.pairs...) 67 | got := HeadersFromContext(ctx) 68 | if diff := cmp.Diff(tc.want, got); diff != "" { 69 | t.Errorf("HeadersFromContext() mismatch (-want +got):\n%s", diff) 70 | } 71 | } 72 | } 73 | 74 | func TestSetHeaders_panics(t *testing.T) { 75 | defer func() { 76 | if r := recover(); r == nil { 77 | t.Errorf("expected panic with odd key value pairs") 78 | } 79 | }() 80 | ctx := context.Background() 81 | SetHeaders(ctx, "1", "2", "3") 82 | } 83 | 84 | func TestSetHeaders_reuse(t *testing.T) { 85 | c := SetHeaders(context.Background(), "key", "value1") 86 | v1 := HeadersFromContext(c) 87 | c = SetHeaders(c, "key", "value2") 88 | v2 := HeadersFromContext(c) 89 | 90 | if cmp.Diff(v2, v1) == "" { 91 | t.Errorf("Second header set did not differ from first header set as expected") 92 | } 93 | } 94 | 95 | func TestSetHeaders_race(t *testing.T) { 96 | key := "key" 97 | value := "value" 98 | want := map[string][]string{ 99 | key: {value, value}, 100 | } 101 | 102 | // Init the ctx so a value already exists to be "shared". 103 | cctx := SetHeaders(context.Background(), key, value) 104 | 105 | // Reusing the same cctx and adding to the same header key 106 | // should *not* produce a race condition when run with -race. 107 | var wg sync.WaitGroup 108 | for i := 0; i < 3; i++ { 109 | wg.Add(1) 110 | go func(ctx context.Context) { 111 | defer wg.Done() 112 | c := SetHeaders(ctx, key, value) 113 | h := HeadersFromContext(c) 114 | 115 | // Additionally, if there was a race condition, 116 | // we may see that one instance of these headers 117 | // contains extra values. 118 | if diff := cmp.Diff(h, want); diff != "" { 119 | t.Errorf("got(-),want(+):\n%s", diff) 120 | } 121 | }(cctx) 122 | } 123 | wg.Wait() 124 | } 125 | -------------------------------------------------------------------------------- /v2/content_type.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "io" 34 | "io/ioutil" 35 | "net/http" 36 | ) 37 | 38 | const sniffBuffSize = 512 39 | 40 | func newContentSniffer(r io.Reader) *contentSniffer { 41 | return &contentSniffer{r: r} 42 | } 43 | 44 | // contentSniffer wraps a Reader, and reports the content type determined by sniffing up to 512 bytes from the Reader. 45 | type contentSniffer struct { 46 | r io.Reader 47 | start []byte // buffer for the sniffed bytes. 48 | err error // set to any error encountered while reading bytes to be sniffed. 49 | 50 | ctype string // set on first sniff. 51 | sniffed bool // set to true on first sniff. 52 | } 53 | 54 | func (cs *contentSniffer) Read(p []byte) (n int, err error) { 55 | // Ensure that the content type is sniffed before any data is consumed from Reader. 56 | _, _ = cs.ContentType() 57 | 58 | if len(cs.start) > 0 { 59 | n := copy(p, cs.start) 60 | cs.start = cs.start[n:] 61 | return n, nil 62 | } 63 | 64 | // We may have read some bytes into start while sniffing, even if the read ended in an error. 65 | // We should first return those bytes, then the error. 66 | if cs.err != nil { 67 | return 0, cs.err 68 | } 69 | 70 | // Now we have handled all bytes that were buffered while sniffing. Now just delegate to the underlying reader. 71 | return cs.r.Read(p) 72 | } 73 | 74 | // ContentType returns the sniffed content type, and whether the content type was successfully sniffed. 75 | func (cs *contentSniffer) ContentType() (string, bool) { 76 | if cs.sniffed { 77 | return cs.ctype, cs.ctype != "" 78 | } 79 | cs.sniffed = true 80 | // If ReadAll hits EOF, it returns err==nil. 81 | cs.start, cs.err = ioutil.ReadAll(io.LimitReader(cs.r, sniffBuffSize)) 82 | 83 | // Don't try to detect the content type based on possibly incomplete data. 84 | if cs.err != nil { 85 | return "", false 86 | } 87 | 88 | cs.ctype = http.DetectContentType(cs.start) 89 | return cs.ctype, true 90 | } 91 | 92 | // DetermineContentType determines the content type of the supplied reader. 93 | // The content of media will be sniffed to determine the content type. 94 | // After calling DetectContentType the caller must not perform further reads on 95 | // media, but rather read from the Reader that is returned. 96 | func DetermineContentType(media io.Reader) (io.Reader, string) { 97 | // For backwards compatibility, allow clients to set content 98 | // type by providing a ContentTyper for media. 99 | // Note: This is an anonymous interface definition copied from googleapi.ContentTyper. 100 | if typer, ok := media.(interface { 101 | ContentType() string 102 | }); ok { 103 | return media, typer.ContentType() 104 | } 105 | 106 | sniffer := newContentSniffer(media) 107 | if ctype, ok := sniffer.ContentType(); ok { 108 | return sniffer, ctype 109 | } 110 | // If content type could not be sniffed, reads from sniffer will eventually fail with an error. 111 | return sniffer, "" 112 | } 113 | -------------------------------------------------------------------------------- /v2/internallog/internal/internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package internal provides some common logic and types to other logging 31 | // sub-packages. 32 | package internal 33 | 34 | import ( 35 | "context" 36 | "io" 37 | "log/slog" 38 | "os" 39 | "strings" 40 | "time" 41 | ) 42 | 43 | const ( 44 | // LoggingLevelEnvVar is the environment variable used to enable logging 45 | // at a particular level. 46 | LoggingLevelEnvVar = "GOOGLE_SDK_GO_LOGGING_LEVEL" 47 | 48 | googLvlKey = "severity" 49 | googMsgKey = "message" 50 | googSourceKey = "sourceLocation" 51 | googTimeKey = "timestamp" 52 | ) 53 | 54 | // NewLoggerWithWriter is exposed for testing. 55 | func NewLoggerWithWriter(w io.Writer) *slog.Logger { 56 | lvl, loggingEnabled := checkLoggingLevel() 57 | if !loggingEnabled { 58 | return slog.New(noOpHandler{}) 59 | } 60 | return slog.New(newGCPSlogHandler(lvl, w)) 61 | } 62 | 63 | // checkLoggingLevel returned the configured logging level and whether or not 64 | // logging is enabled. 65 | func checkLoggingLevel() (slog.Leveler, bool) { 66 | sLevel := strings.ToLower(os.Getenv(LoggingLevelEnvVar)) 67 | var level slog.Level 68 | switch sLevel { 69 | case "debug": 70 | level = slog.LevelDebug 71 | case "info": 72 | level = slog.LevelInfo 73 | case "warn": 74 | level = slog.LevelWarn 75 | case "error": 76 | level = slog.LevelError 77 | default: 78 | return nil, false 79 | } 80 | return level, true 81 | } 82 | 83 | // newGCPSlogHandler returns a Handler that is configured to output in a JSON 84 | // format with well-known keys. For more information on this format see 85 | // https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields. 86 | func newGCPSlogHandler(lvl slog.Leveler, w io.Writer) slog.Handler { 87 | return slog.NewJSONHandler(w, &slog.HandlerOptions{ 88 | Level: lvl, 89 | ReplaceAttr: replaceAttr, 90 | }) 91 | } 92 | 93 | // replaceAttr remaps default Go logging keys to match what is expected in 94 | // cloud logging. 95 | func replaceAttr(groups []string, a slog.Attr) slog.Attr { 96 | if groups == nil { 97 | if a.Key == slog.LevelKey { 98 | a.Key = googLvlKey 99 | return a 100 | } else if a.Key == slog.MessageKey { 101 | a.Key = googMsgKey 102 | return a 103 | } else if a.Key == slog.SourceKey { 104 | a.Key = googSourceKey 105 | return a 106 | } else if a.Key == slog.TimeKey { 107 | a.Key = googTimeKey 108 | if a.Value.Kind() == slog.KindTime { 109 | a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339)) 110 | } 111 | return a 112 | } 113 | } 114 | return a 115 | } 116 | 117 | // The handler returned if logging is not enabled. 118 | type noOpHandler struct{} 119 | 120 | func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool { 121 | return false 122 | } 123 | 124 | func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error { 125 | return nil 126 | } 127 | 128 | func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler { 129 | return h 130 | } 131 | 132 | func (h noOpHandler) WithGroup(_ string) slog.Handler { 133 | return h 134 | } 135 | -------------------------------------------------------------------------------- /v2/proto_json_stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "encoding/json" 34 | "errors" 35 | "io" 36 | 37 | "google.golang.org/protobuf/encoding/protojson" 38 | "google.golang.org/protobuf/proto" 39 | "google.golang.org/protobuf/reflect/protoreflect" 40 | ) 41 | 42 | var ( 43 | arrayOpen = json.Delim('[') 44 | arrayClose = json.Delim(']') 45 | errBadOpening = errors.New("unexpected opening token, expected '['") 46 | ) 47 | 48 | // ProtoJSONStream represents a wrapper for consuming a stream of protobuf 49 | // messages encoded using protobuf-JSON format. More information on this format 50 | // can be found at https://developers.google.com/protocol-buffers/docs/proto3#json. 51 | // The stream must appear as a comma-delimited, JSON array of obbjects with 52 | // opening and closing square braces. 53 | // 54 | // This is for internal use only. 55 | type ProtoJSONStream struct { 56 | first, closed bool 57 | reader io.ReadCloser 58 | stream *json.Decoder 59 | typ protoreflect.MessageType 60 | } 61 | 62 | // NewProtoJSONStreamReader accepts a stream of bytes via an io.ReadCloser that are 63 | // protobuf-JSON encoded protobuf messages of the given type. The ProtoJSONStream 64 | // must be closed when done. 65 | // 66 | // This is for internal use only. 67 | func NewProtoJSONStreamReader(rc io.ReadCloser, typ protoreflect.MessageType) *ProtoJSONStream { 68 | return &ProtoJSONStream{ 69 | first: true, 70 | reader: rc, 71 | stream: json.NewDecoder(rc), 72 | typ: typ, 73 | } 74 | } 75 | 76 | // Recv decodes the next protobuf message in the stream or returns io.EOF if 77 | // the stream is done. It is not safe to call Recv on the same stream from 78 | // different goroutines, just like it is not safe to do so with a single gRPC 79 | // stream. Type-cast the protobuf message returned to the type provided at 80 | // ProtoJSONStream creation. 81 | // Calls to Recv after calling Close will produce io.EOF. 82 | func (s *ProtoJSONStream) Recv() (proto.Message, error) { 83 | if s.closed { 84 | return nil, io.EOF 85 | } 86 | if s.first { 87 | s.first = false 88 | 89 | // Consume the opening '[' so Decode gets one object at a time. 90 | if t, err := s.stream.Token(); err != nil { 91 | return nil, err 92 | } else if t != arrayOpen { 93 | return nil, errBadOpening 94 | } 95 | } 96 | 97 | // Capture the next block of data for the item (a JSON object) in the stream. 98 | var raw json.RawMessage 99 | if err := s.stream.Decode(&raw); err != nil { 100 | e := err 101 | // To avoid checking the first token of each stream, just attempt to 102 | // Decode the next blob and if that fails, double check if it is just 103 | // the closing token ']'. If it is the closing, return io.EOF. If it 104 | // isn't, return the original error. 105 | if t, _ := s.stream.Token(); t == arrayClose { 106 | e = io.EOF 107 | } 108 | return nil, e 109 | } 110 | 111 | // Initialize a new instance of the protobuf message to unmarshal the 112 | // raw data into. 113 | m := s.typ.New().Interface() 114 | unm := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} 115 | err := unm.Unmarshal(raw, m) 116 | 117 | return m, err 118 | } 119 | 120 | // Close closes the stream so that resources are cleaned up. 121 | func (s *ProtoJSONStream) Close() error { 122 | // Dereference the *json.Decoder so that the memory is gc'd. 123 | s.stream = nil 124 | s.closed = true 125 | 126 | return s.reader.Close() 127 | } 128 | -------------------------------------------------------------------------------- /v2/call_option_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "context" 34 | "net/http" 35 | "testing" 36 | "time" 37 | 38 | "google.golang.org/api/googleapi" 39 | "google.golang.org/grpc/codes" 40 | "google.golang.org/grpc/status" 41 | ) 42 | 43 | var _ Retryer = &boRetryer{} 44 | 45 | func TestBackofDefault(t *testing.T) { 46 | backoff := Backoff{} 47 | 48 | max := []time.Duration{1, 2, 4, 8, 16, 30, 30, 30, 30, 30} 49 | for i, m := range max { 50 | max[i] = m * time.Second 51 | } 52 | 53 | for i, w := range max { 54 | if d := backoff.Pause(); d > w { 55 | t.Errorf("Backoff duration should be at most %s, got %s", w, d) 56 | } else if i < len(max)-1 && backoff.cur != max[i+1] { 57 | t.Errorf("current envelope is %s, want %s", backoff.cur, max[i+1]) 58 | } 59 | } 60 | } 61 | 62 | func TestBackoffExponential(t *testing.T) { 63 | backoff := Backoff{Initial: 1, Max: 20, Multiplier: 2} 64 | want := []time.Duration{1, 2, 4, 8, 16, 20, 20, 20, 20, 20} 65 | for _, w := range want { 66 | if d := backoff.Pause(); d > w { 67 | t.Errorf("Backoff duration should be at most %s, got %s", w, d) 68 | } 69 | } 70 | } 71 | 72 | func TestOnCodes(t *testing.T) { 73 | // Lint errors grpc.Errorf in 1.6. It mistakenly expects the first arg to Errorf to be a string. 74 | errf := status.Errorf 75 | apiErr := errf(codes.Unavailable, "") 76 | tests := []struct { 77 | c []codes.Code 78 | retry bool 79 | }{ 80 | {nil, false}, 81 | {[]codes.Code{codes.DeadlineExceeded}, false}, 82 | {[]codes.Code{codes.DeadlineExceeded, codes.Unavailable}, true}, 83 | {[]codes.Code{codes.Unavailable}, true}, 84 | } 85 | for _, tst := range tests { 86 | b := OnCodes(tst.c, Backoff{}) 87 | if _, retry := b.Retry(apiErr); retry != tst.retry { 88 | t.Errorf("retriable codes: %v, error: %s, retry: %t, want %t", tst.c, apiErr, retry, tst.retry) 89 | } 90 | } 91 | } 92 | 93 | func TestOnErrorFunc(t *testing.T) { 94 | // Use errors.Is if on go 1.13 or higher. 95 | is := func(err, target error) bool { 96 | return err == target 97 | } 98 | tests := []struct { 99 | e error 100 | shouldRetry func(err error) bool 101 | retry bool 102 | }{ 103 | {context.DeadlineExceeded, func(err error) bool { return false }, false}, 104 | {context.DeadlineExceeded, func(err error) bool { return is(err, context.DeadlineExceeded) }, true}, 105 | } 106 | for _, tst := range tests { 107 | b := OnErrorFunc(Backoff{}, tst.shouldRetry) 108 | if _, retry := b.Retry(tst.e); retry != tst.retry { 109 | t.Errorf("retriable func: error: %s, retry: %t, want %t", tst.e, retry, tst.retry) 110 | } 111 | } 112 | } 113 | 114 | func TestOnHTTPCodes(t *testing.T) { 115 | apiErr := &googleapi.Error{Code: http.StatusBadGateway} 116 | tests := []struct { 117 | c []int 118 | retry bool 119 | }{ 120 | {nil, false}, 121 | {[]int{http.StatusConflict}, false}, 122 | {[]int{http.StatusConflict, http.StatusBadGateway}, true}, 123 | {[]int{http.StatusBadGateway}, true}, 124 | } 125 | for _, tst := range tests { 126 | b := OnHTTPCodes(Backoff{}, tst.c...) 127 | if _, retry := b.Retry(apiErr); retry != tst.retry { 128 | t.Errorf("retriable codes: %v, error: %s, retry: %t, want %t", tst.c, apiErr, retry, tst.retry) 129 | } 130 | } 131 | } 132 | 133 | func TestWithTimeout(t *testing.T) { 134 | settings := CallSettings{} 135 | to := 10 * time.Second 136 | 137 | WithTimeout(to).Resolve(&settings) 138 | 139 | if settings.timeout != to { 140 | t.Errorf("got %v, want %v", settings.timeout, to) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /v2/internallog/internallog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Package internallog in intended for internal use by generated clients only. 31 | package internallog 32 | 33 | import ( 34 | "bytes" 35 | "encoding/json" 36 | "fmt" 37 | "log/slog" 38 | "net/http" 39 | "os" 40 | "strings" 41 | 42 | "github.com/googleapis/gax-go/v2/internallog/internal" 43 | ) 44 | 45 | // New returns a new [slog.Logger] default logger, or the provided logger if 46 | // non-nil. The returned logger will be a no-op logger unless the environment 47 | // variable GOOGLE_SDK_GO_LOGGING_LEVEL is set. 48 | func New(l *slog.Logger) *slog.Logger { 49 | if l != nil { 50 | return l 51 | } 52 | return internal.NewLoggerWithWriter(os.Stderr) 53 | } 54 | 55 | // HTTPRequest returns a lazily evaluated [slog.LogValuer] for a 56 | // [http.Request] and the associated body. 57 | func HTTPRequest(req *http.Request, body []byte) slog.LogValuer { 58 | return &request{ 59 | req: req, 60 | payload: body, 61 | } 62 | } 63 | 64 | type request struct { 65 | req *http.Request 66 | payload []byte 67 | } 68 | 69 | func (r *request) LogValue() slog.Value { 70 | if r == nil || r.req == nil { 71 | return slog.Value{} 72 | } 73 | var groupValueAttrs []slog.Attr 74 | groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method)) 75 | groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String())) 76 | 77 | var headerAttr []slog.Attr 78 | for k, val := range r.req.Header { 79 | headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ","))) 80 | } 81 | if len(headerAttr) > 0 { 82 | groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr)) 83 | } 84 | 85 | if len(r.payload) > 0 { 86 | if attr, ok := processPayload(r.payload); ok { 87 | groupValueAttrs = append(groupValueAttrs, attr) 88 | } 89 | } 90 | return slog.GroupValue(groupValueAttrs...) 91 | } 92 | 93 | // HTTPResponse returns a lazily evaluated [slog.LogValuer] for a 94 | // [http.Response] and the associated body. 95 | func HTTPResponse(resp *http.Response, body []byte) slog.LogValuer { 96 | return &response{ 97 | resp: resp, 98 | payload: body, 99 | } 100 | } 101 | 102 | type response struct { 103 | resp *http.Response 104 | payload []byte 105 | } 106 | 107 | func (r *response) LogValue() slog.Value { 108 | if r == nil { 109 | return slog.Value{} 110 | } 111 | var groupValueAttrs []slog.Attr 112 | groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode))) 113 | 114 | var headerAttr []slog.Attr 115 | for k, val := range r.resp.Header { 116 | headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ","))) 117 | } 118 | if len(headerAttr) > 0 { 119 | groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr)) 120 | } 121 | 122 | if len(r.payload) > 0 { 123 | if attr, ok := processPayload(r.payload); ok { 124 | groupValueAttrs = append(groupValueAttrs, attr) 125 | } 126 | } 127 | return slog.GroupValue(groupValueAttrs...) 128 | } 129 | 130 | func processPayload(payload []byte) (slog.Attr, bool) { 131 | peekChar := payload[0] 132 | if peekChar == '{' { 133 | // JSON object 134 | var m map[string]any 135 | if err := json.Unmarshal(payload, &m); err == nil { 136 | return slog.Any("payload", m), true 137 | } 138 | } else if peekChar == '[' { 139 | // JSON array 140 | var m []any 141 | if err := json.Unmarshal(payload, &m); err == nil { 142 | return slog.Any("payload", m), true 143 | } 144 | } else { 145 | // Everything else 146 | buf := &bytes.Buffer{} 147 | if err := json.Compact(buf, payload); err != nil { 148 | // Write raw payload incase of error 149 | buf.Write(payload) 150 | } 151 | return slog.String("payload", buf.String()), true 152 | } 153 | return slog.Attr{}, false 154 | } 155 | -------------------------------------------------------------------------------- /v2/proto_json_stream_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "bytes" 34 | "errors" 35 | "io" 36 | "io/ioutil" 37 | "testing" 38 | "time" 39 | 40 | "github.com/google/go-cmp/cmp" 41 | serviceconfigpb "google.golang.org/genproto/googleapis/api/serviceconfig" 42 | "google.golang.org/genproto/googleapis/rpc/code" 43 | "google.golang.org/genproto/googleapis/rpc/status" 44 | "google.golang.org/protobuf/encoding/protojson" 45 | "google.golang.org/protobuf/proto" 46 | "google.golang.org/protobuf/reflect/protoreflect" 47 | "google.golang.org/protobuf/types/known/anypb" 48 | "google.golang.org/protobuf/types/known/durationpb" 49 | ) 50 | 51 | func TestRecv(t *testing.T) { 52 | locations := []proto.Message{ 53 | &serviceconfigpb.Property{ 54 | Name: "property1", 55 | Type: serviceconfigpb.Property_STRING, 56 | Description: "Property 1", 57 | }, 58 | &serviceconfigpb.Property{ 59 | Name: "property2", 60 | Type: serviceconfigpb.Property_STRING, 61 | Description: "Property 2", 62 | }, 63 | &serviceconfigpb.Property{ 64 | Name: "property3", 65 | Type: serviceconfigpb.Property_STRING, 66 | Description: "Property 3", 67 | }, 68 | } 69 | 70 | durations := []proto.Message{ 71 | durationpb.New(time.Second), 72 | durationpb.New(time.Minute), 73 | durationpb.New(time.Hour), 74 | } 75 | 76 | detail, err := anypb.New(locations[0]) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | nested := []proto.Message{ 81 | &status.Status{ 82 | Code: int32(code.Code_INTERNAL), 83 | Message: "oops", 84 | Details: []*anypb.Any{ 85 | detail, 86 | }, 87 | }, 88 | } 89 | 90 | for _, tst := range []struct { 91 | name string 92 | want []proto.Message 93 | typ protoreflect.MessageType 94 | }{ 95 | { 96 | name: "empty", 97 | }, 98 | { 99 | name: "simple_locations", 100 | want: locations, 101 | typ: locations[0].ProtoReflect().Type(), 102 | }, 103 | { 104 | // google.type.Duration is JSON encoded as a string, not an object. 105 | name: "message_as_primitive", 106 | want: durations, 107 | typ: durations[0].ProtoReflect().Type(), 108 | }, 109 | { 110 | name: "nested", 111 | want: nested, 112 | typ: nested[0].ProtoReflect().Type(), 113 | }, 114 | } { 115 | s, err := prepareStream(tst.want) 116 | if err != nil { 117 | t.Errorf("%s: %v", tst.name, err) 118 | continue 119 | } 120 | stream := NewProtoJSONStreamReader(s, tst.typ) 121 | defer stream.Close() 122 | 123 | got, err := stream.Recv() 124 | for ndx := 0; err == nil; ndx++ { 125 | if diff := cmp.Diff(got, tst.want[ndx], cmp.Comparer(proto.Equal)); diff != "" { 126 | t.Errorf("%s: got(-),want(+):\n%s", tst.name, diff) 127 | } 128 | got, err = stream.Recv() 129 | } 130 | if !errors.Is(err, io.EOF) { 131 | t.Errorf("%s: expected %v but got %v", tst.name, io.EOF, err) 132 | } 133 | } 134 | } 135 | 136 | func TestRecvAfterClose(t *testing.T) { 137 | empty := ioutil.NopCloser(bytes.NewReader([]byte("[]"))) 138 | s := NewProtoJSONStreamReader(empty, nil) 139 | if _, err := s.Recv(); !errors.Is(err, io.EOF) { 140 | t.Errorf("Expected %v but got %v", io.EOF, err) 141 | } 142 | 143 | // Close to ensure reader is closed. 144 | s.Close() 145 | if _, err := s.Recv(); !errors.Is(err, io.EOF) { 146 | t.Errorf("Expected %v after close but got %v", io.EOF, err) 147 | } 148 | 149 | } 150 | 151 | func TestRecvError(t *testing.T) { 152 | noOpening := ioutil.NopCloser(bytes.NewReader([]byte{'{'})) 153 | s := NewProtoJSONStreamReader(noOpening, nil) 154 | if _, err := s.Recv(); !errors.Is(err, errBadOpening) { 155 | t.Errorf("Expected %v but got %v", errBadOpening, err) 156 | } 157 | } 158 | 159 | func prepareStream(messages []proto.Message) (io.ReadCloser, error) { 160 | if len(messages) == 0 { 161 | return ioutil.NopCloser(bytes.NewReader([]byte("[]"))), nil 162 | } 163 | 164 | data := []byte("[") 165 | mo := protojson.MarshalOptions{AllowPartial: true, UseEnumNumbers: true} 166 | for _, m := range messages { 167 | d, err := mo.Marshal(m) 168 | if err != nil { 169 | return nil, err 170 | } 171 | data = append(data, d...) 172 | data = append(data, ',') 173 | } 174 | // Set the trailing ',' to a closing ']'. 175 | data[len(data)-1] = ']' 176 | return ioutil.NopCloser(bytes.NewReader(data)), nil 177 | } 178 | -------------------------------------------------------------------------------- /v2/content_type_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "bytes" 34 | "io" 35 | "io/ioutil" 36 | "reflect" 37 | "testing" 38 | ) 39 | 40 | // errReader reads out of a buffer until it is empty, then returns the specified error. 41 | type errReader struct { 42 | buf []byte 43 | err error 44 | } 45 | 46 | func (er *errReader) Read(p []byte) (int, error) { 47 | if len(er.buf) == 0 { 48 | if er.err == nil { 49 | return 0, io.EOF 50 | } 51 | return 0, er.err 52 | } 53 | n := copy(p, er.buf) 54 | er.buf = er.buf[n:] 55 | return n, nil 56 | } 57 | 58 | func TestContentSniffing(t *testing.T) { 59 | type testCase struct { 60 | data []byte // the data to read from the Reader 61 | finalErr error // error to return after data has been read 62 | 63 | wantContentType string 64 | wantContentTypeResult bool 65 | } 66 | 67 | for _, tc := range []testCase{ 68 | { 69 | data: []byte{0, 0, 0, 0}, 70 | finalErr: nil, 71 | wantContentType: "application/octet-stream", 72 | wantContentTypeResult: true, 73 | }, 74 | { 75 | data: []byte(""), 76 | finalErr: nil, 77 | wantContentType: "text/plain; charset=utf-8", 78 | wantContentTypeResult: true, 79 | }, 80 | { 81 | data: []byte(""), 82 | finalErr: io.ErrUnexpectedEOF, 83 | wantContentType: "text/plain; charset=utf-8", 84 | wantContentTypeResult: false, 85 | }, 86 | { 87 | data: []byte("abc"), 88 | finalErr: nil, 89 | wantContentType: "text/plain; charset=utf-8", 90 | wantContentTypeResult: true, 91 | }, 92 | { 93 | data: []byte("abc"), 94 | finalErr: io.ErrUnexpectedEOF, 95 | wantContentType: "text/plain; charset=utf-8", 96 | wantContentTypeResult: false, 97 | }, 98 | // The following examples contain more bytes than are buffered for sniffing. 99 | { 100 | data: bytes.Repeat([]byte("a"), 513), 101 | finalErr: nil, 102 | wantContentType: "text/plain; charset=utf-8", 103 | wantContentTypeResult: true, 104 | }, 105 | { 106 | data: bytes.Repeat([]byte("a"), 513), 107 | finalErr: io.ErrUnexpectedEOF, 108 | wantContentType: "text/plain; charset=utf-8", 109 | wantContentTypeResult: true, // true because error is after first 512 bytes. 110 | }, 111 | } { 112 | er := &errReader{buf: tc.data, err: tc.finalErr} 113 | 114 | sct := newContentSniffer(er) 115 | 116 | // Even if was an error during the first 512 bytes, we should still be able to read those bytes. 117 | buf, err := ioutil.ReadAll(sct) 118 | 119 | if !reflect.DeepEqual(buf, tc.data) { 120 | t.Fatalf("Failed reading buffer: got: %q; want:%q", buf, tc.data) 121 | } 122 | 123 | if err != tc.finalErr { 124 | t.Fatalf("Reading buffer error: got: %v; want: %v", err, tc.finalErr) 125 | } 126 | 127 | ct, ok := sct.ContentType() 128 | if ok != tc.wantContentTypeResult { 129 | t.Fatalf("Content type result got: %v; want: %v", ok, tc.wantContentTypeResult) 130 | } 131 | if ok && ct != tc.wantContentType { 132 | t.Fatalf("Content type got: %q; want: %q", ct, tc.wantContentType) 133 | } 134 | } 135 | } 136 | 137 | type staticContentTyper struct { 138 | io.Reader 139 | } 140 | 141 | func (sct staticContentTyper) ContentType() string { 142 | return "static content type" 143 | } 144 | 145 | func TestDetermineContentType(t *testing.T) { 146 | data := []byte("abc") 147 | rdr := func() io.Reader { 148 | return bytes.NewBuffer(data) 149 | } 150 | 151 | type testCase struct { 152 | r io.Reader 153 | wantContentType string 154 | } 155 | 156 | for _, tc := range []testCase{ 157 | { 158 | r: rdr(), 159 | wantContentType: "text/plain; charset=utf-8", 160 | }, 161 | { 162 | r: staticContentTyper{rdr()}, 163 | wantContentType: "static content type", 164 | }, 165 | } { 166 | r, ctype := DetermineContentType(tc.r) 167 | got, err := ioutil.ReadAll(r) 168 | if err != nil { 169 | t.Fatalf("Failed reading buffer: %v", err) 170 | } 171 | if !reflect.DeepEqual(got, data) { 172 | t.Fatalf("Failed reading buffer: got: %q; want:%q", got, data) 173 | } 174 | 175 | if ctype != tc.wantContentType { 176 | t.Fatalf("Content type got: %q; want: %q", ctype, tc.wantContentType) 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /v2/header_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "context" 34 | "net/http" 35 | "testing" 36 | 37 | "github.com/google/go-cmp/cmp" 38 | "github.com/googleapis/gax-go/v2/callctx" 39 | "google.golang.org/grpc/metadata" 40 | ) 41 | 42 | func TestXGoogHeader(t *testing.T) { 43 | for _, tst := range []struct { 44 | kv []string 45 | want string 46 | }{ 47 | {nil, ""}, 48 | {[]string{"abc", "def"}, "abc/def"}, 49 | {[]string{"abc", "def", "xyz", "123", "foo", ""}, "abc/def xyz/123 foo/"}, 50 | } { 51 | got := XGoogHeader(tst.kv...) 52 | if got != tst.want { 53 | t.Errorf("Header(%q) = %q, want %q", tst.kv, got, tst.want) 54 | } 55 | } 56 | } 57 | 58 | func TestGoVersion(t *testing.T) { 59 | testVersion := func(v string) func() string { 60 | return func() string { 61 | return v 62 | } 63 | } 64 | for _, tst := range []struct { 65 | v func() string 66 | want string 67 | }{ 68 | { 69 | testVersion("go1.19"), 70 | "1.19.0", 71 | }, 72 | { 73 | testVersion("go1.21-20230317-RC01"), 74 | "1.21.0-20230317-RC01", 75 | }, 76 | { 77 | testVersion("devel +abc1234"), 78 | "abc1234", 79 | }, 80 | { 81 | testVersion("this should be unknown"), 82 | versionUnknown, 83 | }, 84 | { 85 | testVersion("go1.21-20230101-RC01 cl/1234567 +abc1234"), 86 | "1.21.0-20230101-RC01", 87 | }, 88 | } { 89 | version = tst.v 90 | got := goVersion() 91 | if diff := cmp.Diff(got, tst.want); diff != "" { 92 | t.Errorf("got(-),want(+):\n%s", diff) 93 | } 94 | } 95 | } 96 | 97 | func TestInsertMetadataIntoOutgoingContext(t *testing.T) { 98 | for _, tst := range []struct { 99 | // User-provided metadata set in context 100 | userMd metadata.MD 101 | // User-provided headers set in context 102 | userHeaders []string 103 | // Client-provided headers passed to func 104 | clientHeaders []string 105 | want metadata.MD 106 | }{ 107 | { 108 | userMd: metadata.Pairs("key_1", "val_1", "key_2", "val_21"), 109 | want: metadata.Pairs("key_1", "val_1", "key_2", "val_21"), 110 | }, 111 | { 112 | userHeaders: []string{"key_2", "val_22"}, 113 | want: metadata.Pairs("key_2", "val_22"), 114 | }, 115 | { 116 | clientHeaders: []string{"key_2", "val_23", "key_2", "val_24"}, 117 | want: metadata.Pairs("key_2", "val_23", "key_2", "val_24"), 118 | }, 119 | { 120 | userMd: metadata.Pairs("key_1", "val_1", "key_2", "val_21"), 121 | userHeaders: []string{"key_2", "val_22"}, 122 | clientHeaders: []string{"key_2", "val_23", "key_2", "val_24"}, 123 | want: metadata.Pairs("key_1", "val_1", "key_2", "val_21", "key_2", "val_22", "key_2", "val_23", "key_2", "val_24"), 124 | }, 125 | { 126 | clientHeaders: []string{"x-goog-api-client", "val_23 val_22", "key_2", "val_24", "x-goog-api-client", "val_1"}, 127 | want: metadata.Pairs("key_2", "val_24", "x-goog-api-client", "val_23 val_22 val_1"), 128 | }, 129 | { 130 | userHeaders: []string{"key_2", "val_22", "x-goog-api-client", "val_1 val_2"}, 131 | clientHeaders: []string{"x-goog-api-client", "val_3 val_4", "key_2", "val_24", "x-goog-api-client", "val_11 val_22"}, 132 | want: metadata.Pairs("key_2", "val_22", "key_2", "val_24", "x-goog-api-client", "val_1 val_2 val_3 val_4 val_11 val_22"), 133 | }, 134 | } { 135 | ctx := context.Background() 136 | if tst.userMd != nil { 137 | ctx = metadata.NewOutgoingContext(ctx, tst.userMd) 138 | } 139 | ctx = callctx.SetHeaders(ctx, tst.userHeaders...) 140 | 141 | ctx = InsertMetadataIntoOutgoingContext(ctx, tst.clientHeaders...) 142 | 143 | got, _ := metadata.FromOutgoingContext(ctx) 144 | if diff := cmp.Diff(tst.want, got); diff != "" { 145 | t.Errorf("InsertMetadata(ctx, %q) mismatch (-want +got):\n%s", tst.clientHeaders, diff) 146 | } 147 | } 148 | } 149 | 150 | func TestBuildHeaders(t *testing.T) { 151 | // User-provided metadata set in context 152 | existingMd := metadata.Pairs("key_1", "val_1", "key_2", "val_21") 153 | ctx := metadata.NewOutgoingContext(context.Background(), existingMd) 154 | // User-provided headers set in context 155 | ctx = callctx.SetHeaders(ctx, "key_2", "val_22") 156 | // Client-provided headers 157 | keyvals := []string{"key_2", "val_23", "key_2", "val_24"} 158 | 159 | got := BuildHeaders(ctx, keyvals...) 160 | 161 | want := http.Header{"key_1": []string{"val_1"}, "key_2": []string{"val_21", "val_22", "val_23", "val_24"}} 162 | if diff := cmp.Diff(want, got); diff != "" { 163 | t.Errorf("InsertMetadata(ctx, %q) mismatch (-want +got):\n%s", keyvals, diff) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /v2/feature_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "os" 34 | "testing" 35 | ) 36 | 37 | func TestIsFeatureEnabled(t *testing.T) { 38 | tests := []struct { 39 | name string 40 | envVar string 41 | envValue string 42 | expected bool 43 | expectedCache bool 44 | }{ 45 | { 46 | name: "EnabledFeature", 47 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_TRACING", 48 | envValue: "true", 49 | expected: true, 50 | expectedCache: true, 51 | }, 52 | { 53 | name: "DisabledFeature", 54 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_ANOTHER", 55 | envValue: "false", 56 | expected: false, 57 | expectedCache: false, 58 | }, 59 | { 60 | name: "MissingFeature", 61 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_MISSING", 62 | envValue: "", 63 | expected: false, 64 | expectedCache: false, 65 | }, 66 | { 67 | name: "CaseInsensitiveTrue", 68 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_MIXED_CASE", 69 | envValue: "True", 70 | expected: true, 71 | expectedCache: true, 72 | }, 73 | { 74 | name: "CaseInsensitiveTrue", 75 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_UPPER_CASE", 76 | envValue: "TRUE", 77 | expected: true, 78 | expectedCache: true, 79 | }, 80 | { 81 | name: "OtherValue", 82 | envVar: "GOOGLE_SDK_GO_EXPERIMENTAL_INVALID", 83 | envValue: "1", 84 | expected: false, 85 | expectedCache: false, 86 | }, 87 | } 88 | 89 | for _, tt := range tests { 90 | t.Run(tt.name, func(t *testing.T) { 91 | // Reset the global state for each test to ensure isolation 92 | TestOnlyResetIsFeatureEnabled() 93 | 94 | if tt.envValue != "" { 95 | os.Setenv(tt.envVar, tt.envValue) 96 | defer os.Unsetenv(tt.envVar) 97 | } 98 | 99 | if got := IsFeatureEnabled(tt.envVar[len("GOOGLE_SDK_GO_EXPERIMENTAL_"):]); got != tt.expected { 100 | t.Errorf("IsFeatureEnabled() = %v, want %v", got, tt.expected) 101 | } 102 | 103 | // Verify caching behavior after the first call 104 | if tt.expectedCache && featureEnabledStore[tt.envVar[len("GOOGLE_SDK_GO_EXPERIMENTAL_"):]] != true { 105 | t.Errorf("Feature %s not correctly cached as true", tt.envVar) 106 | } else if !tt.expectedCache && featureEnabledStore[tt.envVar[len("GOOGLE_SDK_GO_EXPERIMENTAL_"):]] == true { 107 | t.Errorf("Feature %s incorrectly cached as true", tt.envVar) 108 | } 109 | }) 110 | } 111 | 112 | // Test that subsequent calls to IsFeatureEnabled do not re-read environment variables 113 | t.Run("CachingPreventsReread", func(t *testing.T) { 114 | TestOnlyResetIsFeatureEnabled() 115 | 116 | // Set an environment variable for the first call 117 | os.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_CACHED_FEATURE", "true") 118 | defer os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_CACHED_FEATURE") 119 | 120 | // First call, should read from env and cache 121 | if !IsFeatureEnabled("CACHED_FEATURE") { 122 | t.Fatalf("Expected CACHED_FEATURE to be enabled on first call") 123 | } 124 | 125 | // Unset the environment variable after the first call 126 | os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_CACHED_FEATURE") 127 | 128 | // Second call, should use cached value and still be true 129 | if !IsFeatureEnabled("CACHED_FEATURE") { 130 | t.Errorf("Expected CACHED_FEATURE to remain enabled due to caching") 131 | } 132 | // Check a new feature that was never set, should be false 133 | if IsFeatureEnabled("NEW_FEATURE_AFTER_CACHE") { 134 | t.Errorf("Expected NEW_FEATURE_AFTER_CACHE to be false as it was set after init") 135 | } 136 | }) 137 | 138 | // Test with multiple environment variables set 139 | t.Run("MultipleEnvVars", func(t *testing.T) { 140 | TestOnlyResetIsFeatureEnabled() 141 | 142 | os.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE1", "true") 143 | os.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE2", "false") 144 | os.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE3", "true") 145 | defer os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE1") 146 | defer os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE2") 147 | defer os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_FEATURE3") 148 | 149 | if !IsFeatureEnabled("FEATURE1") { 150 | t.Errorf("Expected FEATURE1 to be enabled") 151 | } 152 | if IsFeatureEnabled("FEATURE2") { 153 | t.Errorf("Expected FEATURE2 to be disabled") 154 | } 155 | if !IsFeatureEnabled("FEATURE3") { 156 | t.Errorf("Expected FEATURE3 to be enabled") 157 | } 158 | if IsFeatureEnabled("NONEXISTENT_FEATURE") { 159 | t.Errorf("Expected NONEXISTENT_FEATURE to be disabled") 160 | } 161 | }) 162 | } 163 | -------------------------------------------------------------------------------- /v2/internallog/internallog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package internallog 31 | 32 | import ( 33 | "bytes" 34 | "context" 35 | "flag" 36 | "io" 37 | "log/slog" 38 | "net/http" 39 | "net/url" 40 | "os" 41 | "strings" 42 | "testing" 43 | 44 | "github.com/googleapis/gax-go/v2/internallog/internal" 45 | "github.com/googleapis/gax-go/v2/internallog/internal/logtest" 46 | ) 47 | 48 | // To update conformance tests in this package run `go test -update_golden` 49 | func TestMain(m *testing.M) { 50 | flag.Parse() 51 | os.Exit(m.Run()) 52 | } 53 | 54 | func TestLog_off(t *testing.T) { 55 | golden := "off.log" 56 | logger, f := setupLogger(t, golden) 57 | logger.Error("one") 58 | logger.Info("two") 59 | logger.Warn("three") 60 | logger.Debug("four") 61 | f.Close() 62 | logtest.DiffTest(t, f.Name(), golden) 63 | } 64 | 65 | func TestLog_envarError(t *testing.T) { 66 | golden := "envar-error.log" 67 | t.Setenv(internal.LoggingLevelEnvVar, "eRrOr") 68 | logger, f := setupLogger(t, golden) 69 | logger.Error("one") 70 | logger.Info("two") 71 | logger.Warn("three") 72 | logger.Debug("four") 73 | f.Close() 74 | logtest.DiffTest(t, f.Name(), golden) 75 | } 76 | 77 | func TestLog_envarInfo(t *testing.T) { 78 | golden := "envar-info.log" 79 | t.Setenv(internal.LoggingLevelEnvVar, "info") 80 | logger, f := setupLogger(t, golden) 81 | logger.Error("one") 82 | logger.Info("two") 83 | logger.Warn("three") 84 | logger.Debug("four") 85 | f.Close() 86 | logtest.DiffTest(t, f.Name(), golden) 87 | } 88 | 89 | func TestLog_envarWarn(t *testing.T) { 90 | golden := "envar-warn.log" 91 | t.Setenv(internal.LoggingLevelEnvVar, "warn") 92 | logger, f := setupLogger(t, golden) 93 | logger.Error("one") 94 | logger.Info("two") 95 | logger.Warn("three") 96 | logger.Debug("four") 97 | f.Close() 98 | logtest.DiffTest(t, f.Name(), golden) 99 | } 100 | 101 | func TestLog_envarDebug(t *testing.T) { 102 | golden := "envar-debug.log" 103 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 104 | logger, f := setupLogger(t, golden) 105 | logger.Error("one") 106 | logger.Info("two") 107 | logger.Warn("three") 108 | logger.Debug("four") 109 | f.Close() 110 | logtest.DiffTest(t, f.Name(), golden) 111 | } 112 | 113 | func TestLog_HTTPRequest(t *testing.T) { 114 | golden := "httpRequest.log" 115 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 116 | logger, f := setupLogger(t, golden) 117 | ctx := context.Background() 118 | body := []byte(`{"secret":"shh, it's a secret"}`) 119 | request, err := http.NewRequest(http.MethodPost, "https://example.com", bytes.NewReader(body)) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | request.Header.Add("foo", "bar") 124 | logger.DebugContext(ctx, "msg", "request", HTTPRequest(request, body)) 125 | f.Close() 126 | logtest.DiffTest(t, f.Name(), golden) 127 | } 128 | 129 | func TestLog_HTTPResponse(t *testing.T) { 130 | golden := "httpResponse.log" 131 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 132 | logger, f := setupLogger(t, golden) 133 | ctx := context.Background() 134 | body := []byte(`{"secret":"shh, it's a secret"}`) 135 | response := &http.Response{ 136 | StatusCode: 200, 137 | Header: http.Header{"Foo": []string{"bar"}}, 138 | Body: io.NopCloser(bytes.NewReader(body)), 139 | } 140 | logger.DebugContext(ctx, "msg", "response", HTTPResponse(response, body)) 141 | f.Close() 142 | logtest.DiffTest(t, f.Name(), golden) 143 | } 144 | 145 | func TestLog_HTTPRequest_formData(t *testing.T) { 146 | golden := "httpRequest-form.log" 147 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 148 | logger, f := setupLogger(t, golden) 149 | ctx := context.Background() 150 | form := url.Values{} 151 | form.Add("foo", "bar") 152 | form.Add("baz", "qux") 153 | request, err := http.NewRequest(http.MethodPost, "https://example.com", strings.NewReader(form.Encode())) 154 | if err != nil { 155 | t.Fatal(err) 156 | } 157 | request.Header.Add("foo", "bar") 158 | logger.DebugContext(ctx, "msg", "request", HTTPRequest(request, []byte(form.Encode()))) 159 | f.Close() 160 | logtest.DiffTest(t, f.Name(), golden) 161 | } 162 | 163 | func TestLog_HTTPRequest_jsonArray(t *testing.T) { 164 | golden := "httpRequest-array.log" 165 | t.Setenv(internal.LoggingLevelEnvVar, "debug") 166 | logger, f := setupLogger(t, golden) 167 | ctx := context.Background() 168 | body := []byte(`[{"secret":"shh, it's a secret"},{"secret":"and, another"}]`) 169 | request, err := http.NewRequest(http.MethodPost, "https://example.com", bytes.NewReader(body)) 170 | if err != nil { 171 | t.Fatal(err) 172 | } 173 | request.Header.Add("foo", "bar") 174 | logger.DebugContext(ctx, "msg", "request", HTTPRequest(request, body)) 175 | f.Close() 176 | logtest.DiffTest(t, f.Name(), golden) 177 | } 178 | 179 | func setupLogger(t *testing.T, golden string) (*slog.Logger, *os.File) { 180 | t.Helper() 181 | f, err := os.CreateTemp(t.TempDir(), golden) 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | logger := internal.NewLoggerWithWriter(f) 186 | return logger, f 187 | } 188 | -------------------------------------------------------------------------------- /v2/internallog/internal/bookpb/book.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Code generated by protoc-gen-go. DO NOT EDIT. 31 | // versions: 32 | // protoc-gen-go v1.33.0 33 | // protoc v5.28.2 34 | // source: book.proto 35 | 36 | package bookpb 37 | 38 | import ( 39 | reflect "reflect" 40 | sync "sync" 41 | 42 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 43 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 44 | ) 45 | 46 | const ( 47 | // Verify that this generated code is sufficiently up-to-date. 48 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 49 | // Verify that runtime/protoimpl is sufficiently up-to-date. 50 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 51 | ) 52 | 53 | // A single book in the library. 54 | type Book struct { 55 | state protoimpl.MessageState 56 | sizeCache protoimpl.SizeCache 57 | unknownFields protoimpl.UnknownFields 58 | 59 | // The title of the book. 60 | Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` 61 | // The name of the book author. 62 | Author string `protobuf:"bytes,2,opt,name=author,proto3" json:"author,omitempty"` 63 | } 64 | 65 | func (x *Book) Reset() { 66 | *x = Book{} 67 | if protoimpl.UnsafeEnabled { 68 | mi := &file_book_proto_msgTypes[0] 69 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 70 | ms.StoreMessageInfo(mi) 71 | } 72 | } 73 | 74 | func (x *Book) String() string { 75 | return protoimpl.X.MessageStringOf(x) 76 | } 77 | 78 | func (*Book) ProtoMessage() {} 79 | 80 | func (x *Book) ProtoReflect() protoreflect.Message { 81 | mi := &file_book_proto_msgTypes[0] 82 | if protoimpl.UnsafeEnabled && x != nil { 83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 84 | if ms.LoadMessageInfo() == nil { 85 | ms.StoreMessageInfo(mi) 86 | } 87 | return ms 88 | } 89 | return mi.MessageOf(x) 90 | } 91 | 92 | // Deprecated: Use Book.ProtoReflect.Descriptor instead. 93 | func (*Book) Descriptor() ([]byte, []int) { 94 | return file_book_proto_rawDescGZIP(), []int{0} 95 | } 96 | 97 | func (x *Book) GetTitle() string { 98 | if x != nil { 99 | return x.Title 100 | } 101 | return "" 102 | } 103 | 104 | func (x *Book) GetAuthor() string { 105 | if x != nil { 106 | return x.Author 107 | } 108 | return "" 109 | } 110 | 111 | var File_book_proto protoreflect.FileDescriptor 112 | 113 | var file_book_proto_rawDesc = []byte{ 114 | 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x62, 0x6f, 115 | 0x6f, 0x6b, 0x22, 0x34, 0x0a, 0x04, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 116 | 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 117 | 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 118 | 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 119 | 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 120 | 0x73, 0x2f, 0x67, 0x61, 0x78, 0x2d, 0x67, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6c, 0x6f, 0x67, 121 | 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x62, 0x6f, 0x6f, 0x6b, 0x70, 0x62, 122 | 0x3b, 0x62, 0x6f, 0x6f, 0x6b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 123 | } 124 | 125 | var ( 126 | file_book_proto_rawDescOnce sync.Once 127 | file_book_proto_rawDescData = file_book_proto_rawDesc 128 | ) 129 | 130 | func file_book_proto_rawDescGZIP() []byte { 131 | file_book_proto_rawDescOnce.Do(func() { 132 | file_book_proto_rawDescData = protoimpl.X.CompressGZIP(file_book_proto_rawDescData) 133 | }) 134 | return file_book_proto_rawDescData 135 | } 136 | 137 | var file_book_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 138 | var file_book_proto_goTypes = []interface{}{ 139 | (*Book)(nil), // 0: book.Book 140 | } 141 | var file_book_proto_depIdxs = []int32{ 142 | 0, // [0:0] is the sub-list for method output_type 143 | 0, // [0:0] is the sub-list for method input_type 144 | 0, // [0:0] is the sub-list for extension type_name 145 | 0, // [0:0] is the sub-list for extension extendee 146 | 0, // [0:0] is the sub-list for field type_name 147 | } 148 | 149 | func init() { file_book_proto_init() } 150 | func file_book_proto_init() { 151 | if File_book_proto != nil { 152 | return 153 | } 154 | if !protoimpl.UnsafeEnabled { 155 | file_book_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 156 | switch v := v.(*Book); i { 157 | case 0: 158 | return &v.state 159 | case 1: 160 | return &v.sizeCache 161 | case 2: 162 | return &v.unknownFields 163 | default: 164 | return nil 165 | } 166 | } 167 | } 168 | type x struct{} 169 | out := protoimpl.TypeBuilder{ 170 | File: protoimpl.DescBuilder{ 171 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 172 | RawDescriptor: file_book_proto_rawDesc, 173 | NumEnums: 0, 174 | NumMessages: 1, 175 | NumExtensions: 0, 176 | NumServices: 0, 177 | }, 178 | GoTypes: file_book_proto_goTypes, 179 | DependencyIndexes: file_book_proto_depIdxs, 180 | MessageInfos: file_book_proto_msgTypes, 181 | }.Build() 182 | File_book_proto = out.File 183 | file_book_proto_rawDesc = nil 184 | file_book_proto_goTypes = nil 185 | file_book_proto_depIdxs = nil 186 | } 187 | -------------------------------------------------------------------------------- /v2/header.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "bytes" 34 | "context" 35 | "fmt" 36 | "net/http" 37 | "runtime" 38 | "strings" 39 | "unicode" 40 | 41 | "github.com/googleapis/gax-go/v2/callctx" 42 | "google.golang.org/grpc/metadata" 43 | ) 44 | 45 | var ( 46 | // GoVersion is a header-safe representation of the current runtime 47 | // environment's Go version. This is for GAX consumers that need to 48 | // report the Go runtime version in API calls. 49 | GoVersion string 50 | // version is a package internal global variable for testing purposes. 51 | version = runtime.Version 52 | ) 53 | 54 | // versionUnknown is only used when the runtime version cannot be determined. 55 | const versionUnknown = "UNKNOWN" 56 | 57 | func init() { 58 | GoVersion = goVersion() 59 | } 60 | 61 | // goVersion returns a Go runtime version derived from the runtime environment 62 | // that is modified to be suitable for reporting in a header, meaning it has no 63 | // whitespace. If it is unable to determine the Go runtime version, it returns 64 | // versionUnknown. 65 | func goVersion() string { 66 | const develPrefix = "devel +" 67 | 68 | s := version() 69 | if strings.HasPrefix(s, develPrefix) { 70 | s = s[len(develPrefix):] 71 | if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 { 72 | s = s[:p] 73 | } 74 | return s 75 | } else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 { 76 | s = s[:p] 77 | } 78 | 79 | notSemverRune := func(r rune) bool { 80 | return !strings.ContainsRune("0123456789.", r) 81 | } 82 | 83 | if strings.HasPrefix(s, "go1") { 84 | s = s[2:] 85 | var prerelease string 86 | if p := strings.IndexFunc(s, notSemverRune); p >= 0 { 87 | s, prerelease = s[:p], s[p:] 88 | } 89 | if strings.HasSuffix(s, ".") { 90 | s += "0" 91 | } else if strings.Count(s, ".") < 2 { 92 | s += ".0" 93 | } 94 | if prerelease != "" { 95 | // Some release candidates already have a dash in them. 96 | if !strings.HasPrefix(prerelease, "-") { 97 | prerelease = "-" + prerelease 98 | } 99 | s += prerelease 100 | } 101 | return s 102 | } 103 | return "UNKNOWN" 104 | } 105 | 106 | // XGoogHeader is for use by the Google Cloud Libraries only. See package 107 | // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving 108 | // request/response headers. 109 | // 110 | // XGoogHeader formats key-value pairs. 111 | // The resulting string is suitable for x-goog-api-client header. 112 | func XGoogHeader(keyval ...string) string { 113 | if len(keyval) == 0 { 114 | return "" 115 | } 116 | if len(keyval)%2 != 0 { 117 | panic("gax.Header: odd argument count") 118 | } 119 | var buf bytes.Buffer 120 | for i := 0; i < len(keyval); i += 2 { 121 | buf.WriteByte(' ') 122 | buf.WriteString(keyval[i]) 123 | buf.WriteByte('/') 124 | buf.WriteString(keyval[i+1]) 125 | } 126 | return buf.String()[1:] 127 | } 128 | 129 | // InsertMetadataIntoOutgoingContext is for use by the Google Cloud Libraries 130 | // only. See package [github.com/googleapis/gax-go/v2/callctx] for help 131 | // setting/retrieving request/response headers. 132 | // 133 | // InsertMetadataIntoOutgoingContext returns a new context that merges the 134 | // provided keyvals metadata pairs with any existing metadata/headers in the 135 | // provided context. keyvals should have a corresponding value for every key 136 | // provided. If there is an odd number of keyvals this method will panic. 137 | // Existing values for keys will not be overwritten, instead provided values 138 | // will be appended to the list of existing values. 139 | func InsertMetadataIntoOutgoingContext(ctx context.Context, keyvals ...string) context.Context { 140 | return metadata.NewOutgoingContext(ctx, insertMetadata(ctx, keyvals...)) 141 | } 142 | 143 | // BuildHeaders is for use by the Google Cloud Libraries only. See package 144 | // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving 145 | // request/response headers. 146 | // 147 | // BuildHeaders returns a new http.Header that merges the provided 148 | // keyvals header pairs with any existing metadata/headers in the provided 149 | // context. keyvals should have a corresponding value for every key provided. 150 | // If there is an odd number of keyvals this method will panic. 151 | // Existing values for keys will not be overwritten, instead provided values 152 | // will be appended to the list of existing values. 153 | func BuildHeaders(ctx context.Context, keyvals ...string) http.Header { 154 | return http.Header(insertMetadata(ctx, keyvals...)) 155 | } 156 | 157 | func insertMetadata(ctx context.Context, keyvals ...string) metadata.MD { 158 | if len(keyvals)%2 != 0 { 159 | panic(fmt.Sprintf("gax: an even number of key value pairs must be provided, got %d", len(keyvals))) 160 | } 161 | out, ok := metadata.FromOutgoingContext(ctx) 162 | if !ok { 163 | out = metadata.MD(make(map[string][]string)) 164 | } 165 | headers := callctx.HeadersFromContext(ctx) 166 | 167 | // x-goog-api-client is a special case that we want to make sure gets merged 168 | // into a single header. 169 | const xGoogHeader = "x-goog-api-client" 170 | var mergedXgoogHeader strings.Builder 171 | 172 | for k, vals := range headers { 173 | if k == xGoogHeader { 174 | // Merge all values for the x-goog-api-client header set on the ctx. 175 | for _, v := range vals { 176 | mergedXgoogHeader.WriteString(v) 177 | mergedXgoogHeader.WriteRune(' ') 178 | } 179 | continue 180 | } 181 | out[k] = append(out[k], vals...) 182 | } 183 | for i := 0; i < len(keyvals); i = i + 2 { 184 | out[keyvals[i]] = append(out[keyvals[i]], keyvals[i+1]) 185 | 186 | if keyvals[i] == xGoogHeader { 187 | // Merge the x-goog-api-client header values set on the ctx with any 188 | // values passed in for it from the client. 189 | mergedXgoogHeader.WriteString(keyvals[i+1]) 190 | mergedXgoogHeader.WriteRune(' ') 191 | } 192 | } 193 | 194 | // Add the x goog header back in, replacing the separate values that were set. 195 | if mergedXgoogHeader.Len() > 0 { 196 | out[xGoogHeader] = []string{mergedXgoogHeader.String()[:mergedXgoogHeader.Len()-1]} 197 | } 198 | 199 | return out 200 | } 201 | -------------------------------------------------------------------------------- /v2/call_option.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "errors" 34 | "math/rand" 35 | "time" 36 | 37 | "google.golang.org/api/googleapi" 38 | "google.golang.org/grpc" 39 | "google.golang.org/grpc/codes" 40 | "google.golang.org/grpc/status" 41 | ) 42 | 43 | // CallOption is an option used by Invoke to control behaviors of RPC calls. 44 | // CallOption works by modifying relevant fields of CallSettings. 45 | type CallOption interface { 46 | // Resolve applies the option by modifying cs. 47 | Resolve(cs *CallSettings) 48 | } 49 | 50 | // Retryer is used by Invoke to determine retry behavior. 51 | type Retryer interface { 52 | // Retry reports whether a request should be retried and how long to pause before retrying 53 | // if the previous attempt returned with err. Invoke never calls Retry with nil error. 54 | Retry(err error) (pause time.Duration, shouldRetry bool) 55 | } 56 | 57 | type retryerOption func() Retryer 58 | 59 | func (o retryerOption) Resolve(s *CallSettings) { 60 | s.Retry = o 61 | } 62 | 63 | // WithRetry sets CallSettings.Retry to fn. 64 | func WithRetry(fn func() Retryer) CallOption { 65 | return retryerOption(fn) 66 | } 67 | 68 | // OnErrorFunc returns a Retryer that retries if and only if the previous attempt 69 | // returns an error that satisfies shouldRetry. 70 | // 71 | // Pause times between retries are specified by bo. bo is only used for its 72 | // parameters; each Retryer has its own copy. 73 | func OnErrorFunc(bo Backoff, shouldRetry func(err error) bool) Retryer { 74 | return &errorRetryer{ 75 | shouldRetry: shouldRetry, 76 | backoff: bo, 77 | } 78 | } 79 | 80 | type errorRetryer struct { 81 | backoff Backoff 82 | shouldRetry func(err error) bool 83 | } 84 | 85 | func (r *errorRetryer) Retry(err error) (time.Duration, bool) { 86 | if r.shouldRetry(err) { 87 | return r.backoff.Pause(), true 88 | } 89 | 90 | return 0, false 91 | } 92 | 93 | // OnCodes returns a Retryer that retries if and only if 94 | // the previous attempt returns a GRPC error whose error code is stored in cc. 95 | // Pause times between retries are specified by bo. 96 | // 97 | // bo is only used for its parameters; each Retryer has its own copy. 98 | func OnCodes(cc []codes.Code, bo Backoff) Retryer { 99 | return &boRetryer{ 100 | backoff: bo, 101 | codes: append([]codes.Code(nil), cc...), 102 | } 103 | } 104 | 105 | type boRetryer struct { 106 | backoff Backoff 107 | codes []codes.Code 108 | } 109 | 110 | func (r *boRetryer) Retry(err error) (time.Duration, bool) { 111 | st, ok := status.FromError(err) 112 | if !ok { 113 | return 0, false 114 | } 115 | c := st.Code() 116 | for _, rc := range r.codes { 117 | if c == rc { 118 | return r.backoff.Pause(), true 119 | } 120 | } 121 | return 0, false 122 | } 123 | 124 | // OnHTTPCodes returns a Retryer that retries if and only if 125 | // the previous attempt returns a googleapi.Error whose status code is stored in 126 | // cc. Pause times between retries are specified by bo. 127 | // 128 | // bo is only used for its parameters; each Retryer has its own copy. 129 | func OnHTTPCodes(bo Backoff, cc ...int) Retryer { 130 | codes := make(map[int]bool, len(cc)) 131 | for _, c := range cc { 132 | codes[c] = true 133 | } 134 | 135 | return &httpRetryer{ 136 | backoff: bo, 137 | codes: codes, 138 | } 139 | } 140 | 141 | type httpRetryer struct { 142 | backoff Backoff 143 | codes map[int]bool 144 | } 145 | 146 | func (r *httpRetryer) Retry(err error) (time.Duration, bool) { 147 | var gerr *googleapi.Error 148 | if !errors.As(err, &gerr) { 149 | return 0, false 150 | } 151 | 152 | if r.codes[gerr.Code] { 153 | return r.backoff.Pause(), true 154 | } 155 | 156 | return 0, false 157 | } 158 | 159 | // Backoff implements backoff logic for retries. The configuration for retries 160 | // is described in https://google.aip.dev/client-libraries/4221. The current 161 | // retry limit starts at Initial and increases by a factor of Multiplier every 162 | // retry, but is capped at Max. The actual wait time between retries is a 163 | // random value between 1ns and the current retry limit. The purpose of this 164 | // random jitter is explained in 165 | // https://www.awsarchitectureblog.com/2015/03/backoff.html. 166 | // 167 | // Note: MaxNumRetries / RPCDeadline is specifically not provided. These should 168 | // be built on top of Backoff. 169 | type Backoff struct { 170 | // Initial is the initial value of the retry period, defaults to 1 second. 171 | Initial time.Duration 172 | 173 | // Max is the maximum value of the retry period, defaults to 30 seconds. 174 | Max time.Duration 175 | 176 | // Multiplier is the factor by which the retry period increases. 177 | // It should be greater than 1 and defaults to 2. 178 | Multiplier float64 179 | 180 | // cur is the current retry period. 181 | cur time.Duration 182 | } 183 | 184 | // Pause returns the next time.Duration that the caller should use to backoff. 185 | func (bo *Backoff) Pause() time.Duration { 186 | if bo.Initial == 0 { 187 | bo.Initial = time.Second 188 | } 189 | if bo.cur == 0 { 190 | bo.cur = bo.Initial 191 | } 192 | if bo.Max == 0 { 193 | bo.Max = 30 * time.Second 194 | } 195 | if bo.Multiplier < 1 { 196 | bo.Multiplier = 2 197 | } 198 | // Select a duration between 1ns and the current max. It might seem 199 | // counterintuitive to have so much jitter, but 200 | // https://www.awsarchitectureblog.com/2015/03/backoff.html argues that 201 | // that is the best strategy. 202 | d := time.Duration(1 + rand.Int63n(int64(bo.cur))) 203 | bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier) 204 | if bo.cur > bo.Max { 205 | bo.cur = bo.Max 206 | } 207 | return d 208 | } 209 | 210 | type grpcOpt []grpc.CallOption 211 | 212 | func (o grpcOpt) Resolve(s *CallSettings) { 213 | s.GRPC = o 214 | } 215 | 216 | type pathOpt struct { 217 | p string 218 | } 219 | 220 | func (p pathOpt) Resolve(s *CallSettings) { 221 | s.Path = p.p 222 | } 223 | 224 | type timeoutOpt struct { 225 | t time.Duration 226 | } 227 | 228 | func (t timeoutOpt) Resolve(s *CallSettings) { 229 | s.timeout = t.t 230 | } 231 | 232 | // WithPath applies a Path override to the HTTP-based APICall. 233 | // 234 | // This is for internal use only. 235 | func WithPath(p string) CallOption { 236 | return &pathOpt{p: p} 237 | } 238 | 239 | // WithGRPCOptions allows passing gRPC call options during client creation. 240 | func WithGRPCOptions(opt ...grpc.CallOption) CallOption { 241 | return grpcOpt(append([]grpc.CallOption(nil), opt...)) 242 | } 243 | 244 | // WithTimeout is a convenience option for setting a context.WithTimeout on the 245 | // singular context.Context used for **all** APICall attempts. Calculated from 246 | // the start of the first APICall attempt. 247 | // If the context.Context provided to Invoke already has a Deadline set, that 248 | // will always be respected over the deadline calculated using this option. 249 | func WithTimeout(t time.Duration) CallOption { 250 | return &timeoutOpt{t: t} 251 | } 252 | 253 | // CallSettings allow fine-grained control over how calls are made. 254 | type CallSettings struct { 255 | // Retry returns a Retryer to be used to control retry logic of a method call. 256 | // If Retry is nil or the returned Retryer is nil, the call will not be retried. 257 | Retry func() Retryer 258 | 259 | // CallOptions to be forwarded to GRPC. 260 | GRPC []grpc.CallOption 261 | 262 | // Path is an HTTP override for an APICall. 263 | Path string 264 | 265 | // Timeout defines the amount of time that Invoke has to complete. 266 | // Unexported so it cannot be changed by the code in an APICall. 267 | timeout time.Duration 268 | } 269 | -------------------------------------------------------------------------------- /v2/invoke_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax 31 | 32 | import ( 33 | "context" 34 | "errors" 35 | "testing" 36 | "time" 37 | 38 | "github.com/google/go-cmp/cmp" 39 | "github.com/google/go-cmp/cmp/cmpopts" 40 | "github.com/googleapis/gax-go/v2/apierror" 41 | "google.golang.org/genproto/googleapis/rpc/errdetails" 42 | "google.golang.org/grpc/codes" 43 | "google.golang.org/grpc/status" 44 | ) 45 | 46 | var canceledContext context.Context 47 | 48 | func init() { 49 | ctx, cancel := context.WithCancel(context.Background()) 50 | cancel() 51 | canceledContext = ctx 52 | } 53 | 54 | // recordSleeper is a test implementation of sleeper. 55 | type recordSleeper int 56 | 57 | func (s *recordSleeper) sleep(ctx context.Context, _ time.Duration) error { 58 | *s++ 59 | return ctx.Err() 60 | } 61 | 62 | type boolRetryer bool 63 | 64 | func (r boolRetryer) Retry(err error) (time.Duration, bool) { return 0, bool(r) } 65 | 66 | func TestInvokeSuccess(t *testing.T) { 67 | apiCall := func(context.Context, CallSettings) error { return nil } 68 | var sp recordSleeper 69 | err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 70 | 71 | if err != nil { 72 | t.Errorf("found error %s, want nil", err) 73 | } 74 | if sp != 0 { 75 | t.Errorf("slept %d times, should not have slept since the call succeeded", int(sp)) 76 | } 77 | } 78 | 79 | func TestInvokeCertificateError(t *testing.T) { 80 | stat := status.New(codes.Unavailable, "x509: certificate signed by unknown authority") 81 | apiErr := stat.Err() 82 | apiCall := func(context.Context, CallSettings) error { return apiErr } 83 | var sp recordSleeper 84 | err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 85 | if diff := cmp.Diff(err, apiErr, cmpopts.EquateErrors()); diff != "" { 86 | t.Errorf("got(-), want(+): \n%s", diff) 87 | } 88 | } 89 | 90 | func TestInvokeAPIError(t *testing.T) { 91 | qf := &errdetails.QuotaFailure{ 92 | Violations: []*errdetails.QuotaFailure_Violation{{Subject: "Foo", Description: "Bar"}}, 93 | } 94 | stat, _ := status.New(codes.ResourceExhausted, "Per user quota has been exhausted").WithDetails(qf) 95 | apiErr, _ := apierror.FromError(stat.Err()) 96 | apiCall := func(context.Context, CallSettings) error { return stat.Err() } 97 | var sp recordSleeper 98 | err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 99 | if diff := cmp.Diff(err.Error(), apiErr.Error()); diff != "" { 100 | t.Errorf("got(-), want(+): \n%s", diff) 101 | } 102 | if sp != 0 { 103 | t.Errorf("slept %d times, should not have slept since the call succeeded", int(sp)) 104 | } 105 | } 106 | 107 | func TestInvokeCtxError(t *testing.T) { 108 | ctxErr := context.DeadlineExceeded 109 | apiCall := func(context.Context, CallSettings) error { return ctxErr } 110 | var sp recordSleeper 111 | err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 112 | if err != ctxErr { 113 | t.Errorf("found error %s, want %s", err, ctxErr) 114 | } 115 | if sp != 0 { 116 | t.Errorf("slept %d times, should not have slept since the call succeeded", int(sp)) 117 | } 118 | 119 | } 120 | 121 | func TestInvokeNoRetry(t *testing.T) { 122 | apiErr := errors.New("foo error") 123 | apiCall := func(context.Context, CallSettings) error { return apiErr } 124 | var sp recordSleeper 125 | err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 126 | 127 | if err != apiErr { 128 | t.Errorf("found error %s, want %s", err, apiErr) 129 | } 130 | if sp != 0 { 131 | t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 132 | } 133 | } 134 | 135 | func TestInvokeNilRetry(t *testing.T) { 136 | apiErr := errors.New("foo error") 137 | apiCall := func(context.Context, CallSettings) error { return apiErr } 138 | var settings CallSettings 139 | WithRetry(func() Retryer { return nil }).Resolve(&settings) 140 | var sp recordSleeper 141 | err := invoke(context.Background(), apiCall, settings, sp.sleep) 142 | 143 | if err != apiErr { 144 | t.Errorf("found error %s, want %s", err, apiErr) 145 | } 146 | if sp != 0 { 147 | t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 148 | } 149 | } 150 | 151 | func TestInvokeNeverRetry(t *testing.T) { 152 | apiErr := errors.New("foo error") 153 | apiCall := func(context.Context, CallSettings) error { return apiErr } 154 | var settings CallSettings 155 | WithRetry(func() Retryer { return boolRetryer(false) }).Resolve(&settings) 156 | var sp recordSleeper 157 | err := invoke(context.Background(), apiCall, settings, sp.sleep) 158 | 159 | if err != apiErr { 160 | t.Errorf("found error %s, want %s", err, apiErr) 161 | } 162 | if sp != 0 { 163 | t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 164 | } 165 | } 166 | 167 | func TestInvokeRetry(t *testing.T) { 168 | const target = 3 169 | 170 | retryNum := 0 171 | apiErr := errors.New("foo error") 172 | apiCall := func(context.Context, CallSettings) error { 173 | retryNum++ 174 | if retryNum < target { 175 | return apiErr 176 | } 177 | return nil 178 | } 179 | var settings CallSettings 180 | WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings) 181 | var sp recordSleeper 182 | err := invoke(context.Background(), apiCall, settings, sp.sleep) 183 | 184 | if err != nil { 185 | t.Errorf("found error %s, want nil, call should have succeeded after %d tries", err, target) 186 | } 187 | if sp != target-1 { 188 | t.Errorf("retried %d times, want %d", int(sp), int(target-1)) 189 | } 190 | } 191 | 192 | func TestInvokeRetryTimeout(t *testing.T) { 193 | apiErr := errors.New("foo error") 194 | apiCall := func(context.Context, CallSettings) error { return apiErr } 195 | var settings CallSettings 196 | WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings) 197 | var sp recordSleeper 198 | 199 | err := invoke(canceledContext, apiCall, settings, sp.sleep) 200 | 201 | if err != context.Canceled { 202 | t.Errorf("found error %s, want %s", err, context.Canceled) 203 | } 204 | } 205 | 206 | func TestInvokeWithTimeout(t *testing.T) { 207 | // Dummy APICall that sleeps for the given amount of time. This simulates an 208 | // APICall executing, allowing us to verify which deadline was respected, 209 | // that which is already set on the Context, or the one calculated using the 210 | // WithTimeout option's value. 211 | sleepingCall := func(sleep time.Duration) APICall { 212 | return func(ctx context.Context, _ CallSettings) error { 213 | time.Sleep(sleep) 214 | return ctx.Err() 215 | } 216 | } 217 | 218 | bg := context.Background() 219 | preset, pcc := context.WithTimeout(bg, time.Second) 220 | defer pcc() 221 | 222 | for _, tst := range []struct { 223 | name string 224 | timeout time.Duration 225 | sleep time.Duration 226 | ctx context.Context 227 | want error 228 | }{ 229 | { 230 | name: "success", 231 | timeout: 10 * time.Millisecond, 232 | sleep: 1 * time.Millisecond, 233 | ctx: bg, 234 | want: nil, 235 | }, 236 | { 237 | name: "respect_context_deadline", 238 | timeout: 1 * time.Millisecond, 239 | sleep: 300 * time.Millisecond, 240 | ctx: preset, 241 | want: nil, 242 | }, 243 | { 244 | name: "with_timeout_deadline_exceeded", 245 | timeout: 1 * time.Millisecond, 246 | sleep: 300 * time.Millisecond, 247 | ctx: bg, 248 | want: context.DeadlineExceeded, 249 | }, 250 | } { 251 | t.Run(tst.name, func(t *testing.T) { 252 | // Recording sleep isn't really necessary since there is 253 | // no retry here, but we need a sleeper so might as well. 254 | var sp recordSleeper 255 | var settings CallSettings 256 | 257 | WithTimeout(tst.timeout).Resolve(&settings) 258 | 259 | err := invoke(tst.ctx, sleepingCall(tst.sleep), settings, sp.sleep) 260 | 261 | if err != tst.want { 262 | t.Errorf("found error %v, want %v", err, tst.want) 263 | } 264 | }) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /v2/CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## [2.16.0](https://github.com/googleapis/google-cloud-go/releases/tag/v2.16.0) (2025-12-17) 4 | 5 | ### Features 6 | 7 | * add IsFeatureEnabled (#454) ([2700b8a](https://github.com/googleapis/google-cloud-go/commit/2700b8ab3062c6c6c5a26d0fc6ba1fc064a8fc04)) 8 | 9 | ## [2.15.0](https://github.com/googleapis/gax-go/compare/v2.14.2...v2.15.0) (2025-07-09) 10 | 11 | 12 | ### Features 13 | 14 | * **apierror:** improve gRPC status code mapping for HTTP errors ([#431](https://github.com/googleapis/gax-go/issues/431)) ([c207f2a](https://github.com/googleapis/gax-go/commit/c207f2a19ab91d3baee458b57d4aa992519025c7)) 15 | 16 | ## [2.14.2](https://github.com/googleapis/gax-go/compare/v2.14.1...v2.14.2) (2025-05-12) 17 | 18 | 19 | ### Documentation 20 | 21 | * **v2:** Fix Backoff doc to accurately explain Multiplier ([#423](https://github.com/googleapis/gax-go/issues/423)) ([16d1791](https://github.com/googleapis/gax-go/commit/16d17917121ea9f5d84ba52b5c7c7f2ec0f9e784)), refs [#422](https://github.com/googleapis/gax-go/issues/422) 22 | 23 | ## [2.14.1](https://github.com/googleapis/gax-go/compare/v2.14.0...v2.14.1) (2024-12-19) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * update golang.org/x/net to v0.33.0 ([#391](https://github.com/googleapis/gax-go/issues/391)) ([547a5b4](https://github.com/googleapis/gax-go/commit/547a5b43aa6f376f71242da9f18e65fbdfb342f6)) 29 | 30 | 31 | ### Documentation 32 | 33 | * fix godoc to refer to the proper envvar ([#387](https://github.com/googleapis/gax-go/issues/387)) ([dc6baf7](https://github.com/googleapis/gax-go/commit/dc6baf75c1a737233739630b5af6c9759f08abcd)) 34 | 35 | ## [2.14.0](https://github.com/googleapis/gax-go/compare/v2.13.0...v2.14.0) (2024-11-13) 36 | 37 | 38 | ### Features 39 | 40 | * **internallog:** add a logging support package ([#380](https://github.com/googleapis/gax-go/issues/380)) ([c877470](https://github.com/googleapis/gax-go/commit/c87747098135631a3de5865ed03aaf2c79fd9319)) 41 | 42 | ## [2.13.0](https://github.com/googleapis/gax-go/compare/v2.12.5...v2.13.0) (2024-07-22) 43 | 44 | 45 | ### Features 46 | 47 | * **iterator:** add package to help work with new iter.Seq types ([#358](https://github.com/googleapis/gax-go/issues/358)) ([6bccdaa](https://github.com/googleapis/gax-go/commit/6bccdaac011fe6fd147e4eb533a8e6520b7d4acc)) 48 | 49 | ## [2.12.5](https://github.com/googleapis/gax-go/compare/v2.12.4...v2.12.5) (2024-06-18) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * **v2/apierror:** fix (*APIError).Error() for unwrapped Status ([#351](https://github.com/googleapis/gax-go/issues/351)) ([22c16e7](https://github.com/googleapis/gax-go/commit/22c16e7bff5402bdc4c25063771cdd01c650b500)), refs [#350](https://github.com/googleapis/gax-go/issues/350) 55 | 56 | ## [2.12.4](https://github.com/googleapis/gax-go/compare/v2.12.3...v2.12.4) (2024-05-03) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * provide unmarshal options for streams ([#343](https://github.com/googleapis/gax-go/issues/343)) ([ddf9a90](https://github.com/googleapis/gax-go/commit/ddf9a90bf180295d49875e15cb80b2136a49dbaf)) 62 | 63 | ## [2.12.3](https://github.com/googleapis/gax-go/compare/v2.12.2...v2.12.3) (2024-03-14) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * bump protobuf dep to v1.33 ([#333](https://github.com/googleapis/gax-go/issues/333)) ([2892b22](https://github.com/googleapis/gax-go/commit/2892b22c1ae8a70dec3448d82e634643fe6c1be2)) 69 | 70 | ## [2.12.2](https://github.com/googleapis/gax-go/compare/v2.12.1...v2.12.2) (2024-02-23) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * **v2/callctx:** fix SetHeader race by cloning header map ([#326](https://github.com/googleapis/gax-go/issues/326)) ([534311f](https://github.com/googleapis/gax-go/commit/534311f0f163d101f30657736c0e6f860e9c39dc)) 76 | 77 | ## [2.12.1](https://github.com/googleapis/gax-go/compare/v2.12.0...v2.12.1) (2024-02-13) 78 | 79 | 80 | ### Bug Fixes 81 | 82 | * add XGoogFieldMaskHeader constant ([#321](https://github.com/googleapis/gax-go/issues/321)) ([666ee08](https://github.com/googleapis/gax-go/commit/666ee08931041b7fed56bed7132649785b2d3dfe)) 83 | 84 | ## [2.12.0](https://github.com/googleapis/gax-go/compare/v2.11.0...v2.12.0) (2023-06-26) 85 | 86 | 87 | ### Features 88 | 89 | * **v2/callctx:** add new callctx package ([#291](https://github.com/googleapis/gax-go/issues/291)) ([11503ed](https://github.com/googleapis/gax-go/commit/11503ed98df4ae1bbdedf91ff64d47e63f187d68)) 90 | * **v2:** add BuildHeaders and InsertMetadataIntoOutgoingContext to header ([#290](https://github.com/googleapis/gax-go/issues/290)) ([6a4b89f](https://github.com/googleapis/gax-go/commit/6a4b89f5551a40262e7c3caf2e1bdc7321b76ea1)) 91 | 92 | ## [2.11.0](https://github.com/googleapis/gax-go/compare/v2.10.0...v2.11.0) (2023-06-13) 93 | 94 | 95 | ### Features 96 | 97 | * **v2:** add GoVersion package variable ([#283](https://github.com/googleapis/gax-go/issues/283)) ([26553cc](https://github.com/googleapis/gax-go/commit/26553ccadb4016b189881f52e6c253b68bb3e3d5)) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * **v2:** handle space in non-devel go version ([#288](https://github.com/googleapis/gax-go/issues/288)) ([fd7bca0](https://github.com/googleapis/gax-go/commit/fd7bca029a1c5e63def8f0a5fd1ec3f725d92f75)) 103 | 104 | ## [2.10.0](https://github.com/googleapis/gax-go/compare/v2.9.1...v2.10.0) (2023-05-30) 105 | 106 | 107 | ### Features 108 | 109 | * update dependencies ([#280](https://github.com/googleapis/gax-go/issues/280)) ([4514281](https://github.com/googleapis/gax-go/commit/4514281058590f3637c36bfd49baa65c4d3cfb21)) 110 | 111 | ## [2.9.1](https://github.com/googleapis/gax-go/compare/v2.9.0...v2.9.1) (2023-05-23) 112 | 113 | 114 | ### Bug Fixes 115 | 116 | * **v2:** drop cloud lro test dep ([#276](https://github.com/googleapis/gax-go/issues/276)) ([c67eeba](https://github.com/googleapis/gax-go/commit/c67eeba0f10a3294b1d93c1b8fbe40211a55ae5f)), refs [#270](https://github.com/googleapis/gax-go/issues/270) 117 | 118 | ## [2.9.0](https://github.com/googleapis/gax-go/compare/v2.8.0...v2.9.0) (2023-05-22) 119 | 120 | 121 | ### Features 122 | 123 | * **apierror:** add method to return HTTP status code conditionally ([#274](https://github.com/googleapis/gax-go/issues/274)) ([5874431](https://github.com/googleapis/gax-go/commit/587443169acd10f7f86d1989dc8aaf189e645e98)), refs [#229](https://github.com/googleapis/gax-go/issues/229) 124 | 125 | 126 | ### Documentation 127 | 128 | * add ref to usage with clients ([#272](https://github.com/googleapis/gax-go/issues/272)) ([ea4d72d](https://github.com/googleapis/gax-go/commit/ea4d72d514beba4de450868b5fb028601a29164e)), refs [#228](https://github.com/googleapis/gax-go/issues/228) 129 | 130 | ## [2.8.0](https://github.com/googleapis/gax-go/compare/v2.7.1...v2.8.0) (2023-03-15) 131 | 132 | 133 | ### Features 134 | 135 | * **v2:** add WithTimeout option ([#259](https://github.com/googleapis/gax-go/issues/259)) ([9a8da43](https://github.com/googleapis/gax-go/commit/9a8da43693002448b1e8758023699387481866d1)) 136 | 137 | ## [2.7.1](https://github.com/googleapis/gax-go/compare/v2.7.0...v2.7.1) (2023-03-06) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * **v2/apierror:** return Unknown GRPCStatus when err source is HTTP ([#260](https://github.com/googleapis/gax-go/issues/260)) ([043b734](https://github.com/googleapis/gax-go/commit/043b73437a240a91229207fb3ee52a9935a36f23)), refs [#254](https://github.com/googleapis/gax-go/issues/254) 143 | 144 | ## [2.7.0](https://github.com/googleapis/gax-go/compare/v2.6.0...v2.7.0) (2022-11-02) 145 | 146 | 147 | ### Features 148 | 149 | * update google.golang.org/api to latest ([#240](https://github.com/googleapis/gax-go/issues/240)) ([f690a02](https://github.com/googleapis/gax-go/commit/f690a02c806a2903bdee943ede3a58e3a331ebd6)) 150 | * **v2/apierror:** add apierror.FromWrappingError ([#238](https://github.com/googleapis/gax-go/issues/238)) ([9dbd96d](https://github.com/googleapis/gax-go/commit/9dbd96d59b9d54ceb7c025513aa8c1a9d727382f)) 151 | 152 | ## [2.6.0](https://github.com/googleapis/gax-go/compare/v2.5.1...v2.6.0) (2022-10-13) 153 | 154 | 155 | ### Features 156 | 157 | * **v2:** copy DetermineContentType functionality ([#230](https://github.com/googleapis/gax-go/issues/230)) ([2c52a70](https://github.com/googleapis/gax-go/commit/2c52a70bae965397f740ed27d46aabe89ff249b3)) 158 | 159 | ## [2.5.1](https://github.com/googleapis/gax-go/compare/v2.5.0...v2.5.1) (2022-08-04) 160 | 161 | 162 | ### Bug Fixes 163 | 164 | * **v2:** resolve bad genproto pseudoversion in go.mod ([#218](https://github.com/googleapis/gax-go/issues/218)) ([1379b27](https://github.com/googleapis/gax-go/commit/1379b27e9846d959f7e1163b9ef298b3c92c8d23)) 165 | 166 | ## [2.5.0](https://github.com/googleapis/gax-go/compare/v2.4.0...v2.5.0) (2022-08-04) 167 | 168 | 169 | ### Features 170 | 171 | * add ExtractProtoMessage to apierror ([#213](https://github.com/googleapis/gax-go/issues/213)) ([a6ce70c](https://github.com/googleapis/gax-go/commit/a6ce70c725c890533a9de6272d3b5ba2e336d6bb)) 172 | 173 | ## [2.4.0](https://github.com/googleapis/gax-go/compare/v2.3.0...v2.4.0) (2022-05-09) 174 | 175 | 176 | ### Features 177 | 178 | * **v2:** add OnHTTPCodes CallOption ([#188](https://github.com/googleapis/gax-go/issues/188)) ([ba7c534](https://github.com/googleapis/gax-go/commit/ba7c5348363ab6c33e1cee3c03c0be68a46ca07c)) 179 | 180 | 181 | ### Bug Fixes 182 | 183 | * **v2/apierror:** use errors.As in FromError ([#189](https://github.com/googleapis/gax-go/issues/189)) ([f30f05b](https://github.com/googleapis/gax-go/commit/f30f05be583828f4c09cca4091333ea88ff8d79e)) 184 | 185 | 186 | ### Miscellaneous Chores 187 | 188 | * **v2:** bump release-please processing ([#192](https://github.com/googleapis/gax-go/issues/192)) ([56172f9](https://github.com/googleapis/gax-go/commit/56172f971d1141d7687edaac053ad3470af76719)) 189 | -------------------------------------------------------------------------------- /v2/apierror/internal/proto/custom_error.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.28.0 18 | // protoc v3.17.3 19 | // source: custom_error.proto 20 | 21 | package jsonerror 22 | 23 | import ( 24 | reflect "reflect" 25 | sync "sync" 26 | 27 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 28 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 29 | ) 30 | 31 | const ( 32 | // Verify that this generated code is sufficiently up-to-date. 33 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 34 | // Verify that runtime/protoimpl is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 36 | ) 37 | 38 | // Error code for `CustomError`. 39 | type CustomError_CustomErrorCode int32 40 | 41 | const ( 42 | // Default error. 43 | CustomError_CUSTOM_ERROR_CODE_UNSPECIFIED CustomError_CustomErrorCode = 0 44 | // Too many foo. 45 | CustomError_TOO_MANY_FOO CustomError_CustomErrorCode = 1 46 | // Not enough foo. 47 | CustomError_NOT_ENOUGH_FOO CustomError_CustomErrorCode = 2 48 | // Catastrophic error. 49 | CustomError_UNIVERSE_WAS_DESTROYED CustomError_CustomErrorCode = 3 50 | ) 51 | 52 | // Enum value maps for CustomError_CustomErrorCode. 53 | var ( 54 | CustomError_CustomErrorCode_name = map[int32]string{ 55 | 0: "CUSTOM_ERROR_CODE_UNSPECIFIED", 56 | 1: "TOO_MANY_FOO", 57 | 2: "NOT_ENOUGH_FOO", 58 | 3: "UNIVERSE_WAS_DESTROYED", 59 | } 60 | CustomError_CustomErrorCode_value = map[string]int32{ 61 | "CUSTOM_ERROR_CODE_UNSPECIFIED": 0, 62 | "TOO_MANY_FOO": 1, 63 | "NOT_ENOUGH_FOO": 2, 64 | "UNIVERSE_WAS_DESTROYED": 3, 65 | } 66 | ) 67 | 68 | func (x CustomError_CustomErrorCode) Enum() *CustomError_CustomErrorCode { 69 | p := new(CustomError_CustomErrorCode) 70 | *p = x 71 | return p 72 | } 73 | 74 | func (x CustomError_CustomErrorCode) String() string { 75 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 76 | } 77 | 78 | func (CustomError_CustomErrorCode) Descriptor() protoreflect.EnumDescriptor { 79 | return file_custom_error_proto_enumTypes[0].Descriptor() 80 | } 81 | 82 | func (CustomError_CustomErrorCode) Type() protoreflect.EnumType { 83 | return &file_custom_error_proto_enumTypes[0] 84 | } 85 | 86 | func (x CustomError_CustomErrorCode) Number() protoreflect.EnumNumber { 87 | return protoreflect.EnumNumber(x) 88 | } 89 | 90 | // Deprecated: Use CustomError_CustomErrorCode.Descriptor instead. 91 | func (CustomError_CustomErrorCode) EnumDescriptor() ([]byte, []int) { 92 | return file_custom_error_proto_rawDescGZIP(), []int{0, 0} 93 | } 94 | 95 | // CustomError is an example of a custom error message which may be included 96 | // in an rpc status. It is not meant to reflect a standard error. 97 | type CustomError struct { 98 | state protoimpl.MessageState 99 | sizeCache protoimpl.SizeCache 100 | unknownFields protoimpl.UnknownFields 101 | 102 | // Error code specific to the custom API being invoked. 103 | Code CustomError_CustomErrorCode `protobuf:"varint,1,opt,name=code,proto3,enum=error.CustomError_CustomErrorCode" json:"code,omitempty"` 104 | // Name of the failed entity. 105 | Entity string `protobuf:"bytes,2,opt,name=entity,proto3" json:"entity,omitempty"` 106 | // Message that describes the error. 107 | ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` 108 | } 109 | 110 | func (x *CustomError) Reset() { 111 | *x = CustomError{} 112 | if protoimpl.UnsafeEnabled { 113 | mi := &file_custom_error_proto_msgTypes[0] 114 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 115 | ms.StoreMessageInfo(mi) 116 | } 117 | } 118 | 119 | func (x *CustomError) String() string { 120 | return protoimpl.X.MessageStringOf(x) 121 | } 122 | 123 | func (*CustomError) ProtoMessage() {} 124 | 125 | func (x *CustomError) ProtoReflect() protoreflect.Message { 126 | mi := &file_custom_error_proto_msgTypes[0] 127 | if protoimpl.UnsafeEnabled && x != nil { 128 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 129 | if ms.LoadMessageInfo() == nil { 130 | ms.StoreMessageInfo(mi) 131 | } 132 | return ms 133 | } 134 | return mi.MessageOf(x) 135 | } 136 | 137 | // Deprecated: Use CustomError.ProtoReflect.Descriptor instead. 138 | func (*CustomError) Descriptor() ([]byte, []int) { 139 | return file_custom_error_proto_rawDescGZIP(), []int{0} 140 | } 141 | 142 | func (x *CustomError) GetCode() CustomError_CustomErrorCode { 143 | if x != nil { 144 | return x.Code 145 | } 146 | return CustomError_CUSTOM_ERROR_CODE_UNSPECIFIED 147 | } 148 | 149 | func (x *CustomError) GetEntity() string { 150 | if x != nil { 151 | return x.Entity 152 | } 153 | return "" 154 | } 155 | 156 | func (x *CustomError) GetErrorMessage() string { 157 | if x != nil { 158 | return x.ErrorMessage 159 | } 160 | return "" 161 | } 162 | 163 | var File_custom_error_proto protoreflect.FileDescriptor 164 | 165 | var file_custom_error_proto_rawDesc = []byte{ 166 | 0x0a, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 167 | 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xfa, 0x01, 0x0a, 0x0b, 168 | 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x04, 0x63, 169 | 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x65, 0x72, 0x72, 0x6f, 170 | 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x43, 0x75, 171 | 0x73, 0x74, 0x6f, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 172 | 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 173 | 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x65, 174 | 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 175 | 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 176 | 0x22, 0x76, 0x0a, 0x0f, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 177 | 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x5f, 0x45, 0x52, 178 | 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 179 | 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x4f, 0x4f, 0x5f, 0x4d, 0x41, 180 | 0x4e, 0x59, 0x5f, 0x46, 0x4f, 0x4f, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4e, 0x4f, 0x54, 0x5f, 181 | 0x45, 0x4e, 0x4f, 0x55, 0x47, 0x48, 0x5f, 0x46, 0x4f, 0x4f, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 182 | 0x55, 0x4e, 0x49, 0x56, 0x45, 0x52, 0x53, 0x45, 0x5f, 0x57, 0x41, 0x53, 0x5f, 0x44, 0x45, 0x53, 183 | 0x54, 0x52, 0x4f, 0x59, 0x45, 0x44, 0x10, 0x03, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 184 | 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 185 | 0x73, 0x2f, 0x67, 0x61, 0x78, 0x2d, 0x67, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x65, 186 | 0x72, 0x72, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 187 | 0x6f, 0x74, 0x6f, 0x3b, 0x6a, 0x73, 0x6f, 0x6e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x62, 0x06, 0x70, 188 | 0x72, 0x6f, 0x74, 0x6f, 0x33, 189 | } 190 | 191 | var ( 192 | file_custom_error_proto_rawDescOnce sync.Once 193 | file_custom_error_proto_rawDescData = file_custom_error_proto_rawDesc 194 | ) 195 | 196 | func file_custom_error_proto_rawDescGZIP() []byte { 197 | file_custom_error_proto_rawDescOnce.Do(func() { 198 | file_custom_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_custom_error_proto_rawDescData) 199 | }) 200 | return file_custom_error_proto_rawDescData 201 | } 202 | 203 | var file_custom_error_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 204 | var file_custom_error_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 205 | var file_custom_error_proto_goTypes = []interface{}{ 206 | (CustomError_CustomErrorCode)(0), // 0: error.CustomError.CustomErrorCode 207 | (*CustomError)(nil), // 1: error.CustomError 208 | } 209 | var file_custom_error_proto_depIdxs = []int32{ 210 | 0, // 0: error.CustomError.code:type_name -> error.CustomError.CustomErrorCode 211 | 1, // [1:1] is the sub-list for method output_type 212 | 1, // [1:1] is the sub-list for method input_type 213 | 1, // [1:1] is the sub-list for extension type_name 214 | 1, // [1:1] is the sub-list for extension extendee 215 | 0, // [0:1] is the sub-list for field type_name 216 | } 217 | 218 | func init() { file_custom_error_proto_init() } 219 | func file_custom_error_proto_init() { 220 | if File_custom_error_proto != nil { 221 | return 222 | } 223 | if !protoimpl.UnsafeEnabled { 224 | file_custom_error_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 225 | switch v := v.(*CustomError); i { 226 | case 0: 227 | return &v.state 228 | case 1: 229 | return &v.sizeCache 230 | case 2: 231 | return &v.unknownFields 232 | default: 233 | return nil 234 | } 235 | } 236 | } 237 | type x struct{} 238 | out := protoimpl.TypeBuilder{ 239 | File: protoimpl.DescBuilder{ 240 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 241 | RawDescriptor: file_custom_error_proto_rawDesc, 242 | NumEnums: 1, 243 | NumMessages: 1, 244 | NumExtensions: 0, 245 | NumServices: 0, 246 | }, 247 | GoTypes: file_custom_error_proto_goTypes, 248 | DependencyIndexes: file_custom_error_proto_depIdxs, 249 | EnumInfos: file_custom_error_proto_enumTypes, 250 | MessageInfos: file_custom_error_proto_msgTypes, 251 | }.Build() 252 | File_custom_error_proto = out.File 253 | file_custom_error_proto_rawDesc = nil 254 | file_custom_error_proto_goTypes = nil 255 | file_custom_error_proto_depIdxs = nil 256 | } 257 | -------------------------------------------------------------------------------- /v2/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | package gax_test 31 | 32 | import ( 33 | "context" 34 | "io" 35 | "net/http" 36 | "time" 37 | 38 | gax "github.com/googleapis/gax-go/v2" 39 | "google.golang.org/grpc/codes" 40 | "google.golang.org/grpc/status" 41 | "google.golang.org/protobuf/reflect/protoreflect" 42 | "google.golang.org/protobuf/types/known/structpb" 43 | ) 44 | 45 | // Some result that the client might return. 46 | type fakeResponse struct{} 47 | 48 | // Some client that can perform RPCs. 49 | type fakeClient struct{} 50 | 51 | // PerformSomeRPC is a fake RPC that a client might perform. 52 | func (c *fakeClient) PerformSomeRPC(ctx context.Context) (*fakeResponse, error) { 53 | // An actual client would return something meaningful here. 54 | return nil, nil 55 | } 56 | 57 | func ExampleOnErrorFunc() { 58 | ctx := context.Background() 59 | c := &fakeClient{} 60 | 61 | shouldRetryUnavailableUnKnown := func(err error) bool { 62 | st, ok := status.FromError(err) 63 | if !ok { 64 | return false 65 | } 66 | 67 | return st.Code() == codes.Unavailable || st.Code() == codes.Unknown 68 | } 69 | retryer := gax.OnErrorFunc(gax.Backoff{ 70 | Initial: time.Second, 71 | Max: 32 * time.Second, 72 | Multiplier: 2, 73 | }, shouldRetryUnavailableUnKnown) 74 | 75 | performSomeRPCWithRetry := func(ctx context.Context) (*fakeResponse, error) { 76 | for { 77 | resp, err := c.PerformSomeRPC(ctx) 78 | if err != nil { 79 | if delay, shouldRetry := retryer.Retry(err); shouldRetry { 80 | if err := gax.Sleep(ctx, delay); err != nil { 81 | return nil, err 82 | } 83 | continue 84 | } 85 | return nil, err 86 | } 87 | return resp, err 88 | } 89 | } 90 | 91 | // It's recommended to set deadlines on RPCs and around retrying. This is 92 | // also usually preferred over setting some fixed number of retries: one 93 | // advantage this has is that backoff settings can be changed independently 94 | // of the deadline, whereas with a fixed number of retries the deadline 95 | // would be a constantly-shifting goalpost. 96 | ctxWithTimeout, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute)) 97 | defer cancel() 98 | 99 | resp, err := performSomeRPCWithRetry(ctxWithTimeout) 100 | if err != nil { 101 | // TODO: handle err 102 | } 103 | _ = resp // TODO: use resp if err is nil 104 | } 105 | 106 | func ExampleOnCodes() { 107 | ctx := context.Background() 108 | c := &fakeClient{} 109 | 110 | // UNKNOWN and UNAVAILABLE are typically safe to retry for idempotent RPCs. 111 | retryer := gax.OnCodes([]codes.Code{codes.Unknown, codes.Unavailable}, gax.Backoff{ 112 | Initial: time.Second, 113 | Max: 32 * time.Second, 114 | Multiplier: 2, 115 | }) 116 | 117 | performSomeRPCWithRetry := func(ctx context.Context) (*fakeResponse, error) { 118 | for { 119 | resp, err := c.PerformSomeRPC(ctx) 120 | if err != nil { 121 | if delay, shouldRetry := retryer.Retry(err); shouldRetry { 122 | if err := gax.Sleep(ctx, delay); err != nil { 123 | return nil, err 124 | } 125 | continue 126 | } 127 | return nil, err 128 | } 129 | return resp, err 130 | } 131 | } 132 | 133 | // It's recommended to set deadlines on RPCs and around retrying. This is 134 | // also usually preferred over setting some fixed number of retries: one 135 | // advantage this has is that backoff settings can be changed independently 136 | // of the deadline, whereas with a fixed number of retries the deadline 137 | // would be a constantly-shifting goalpost. 138 | ctxWithTimeout, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute)) 139 | defer cancel() 140 | 141 | resp, err := performSomeRPCWithRetry(ctxWithTimeout) 142 | if err != nil { 143 | // TODO: handle err 144 | } 145 | _ = resp // TODO: use resp if err is nil 146 | } 147 | 148 | func ExampleOnHTTPCodes() { 149 | ctx := context.Background() 150 | c := &fakeClient{} 151 | 152 | retryer := gax.OnHTTPCodes(gax.Backoff{ 153 | Initial: time.Second, 154 | Max: 32 * time.Second, 155 | Multiplier: 2, 156 | }, http.StatusBadGateway, http.StatusServiceUnavailable) 157 | 158 | performSomeRPCWithRetry := func(ctx context.Context) (*fakeResponse, error) { 159 | for { 160 | resp, err := c.PerformSomeRPC(ctx) 161 | if err != nil { 162 | if delay, shouldRetry := retryer.Retry(err); shouldRetry { 163 | if err := gax.Sleep(ctx, delay); err != nil { 164 | return nil, err 165 | } 166 | continue 167 | } 168 | return nil, err 169 | } 170 | return resp, err 171 | } 172 | } 173 | 174 | // It's recommended to set deadlines on RPCs and around retrying. This is 175 | // also usually preferred over setting some fixed number of retries: one 176 | // advantage this has is that backoff settings can be changed independently 177 | // of the deadline, whereas with a fixed number of retries the deadline 178 | // would be a constantly-shifting goalpost. 179 | ctxWithTimeout, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute)) 180 | defer cancel() 181 | 182 | resp, err := performSomeRPCWithRetry(ctxWithTimeout) 183 | if err != nil { 184 | // TODO: handle err 185 | } 186 | _ = resp // TODO: use resp if err is nil 187 | } 188 | 189 | func ExampleBackoff() { 190 | ctx := context.Background() 191 | 192 | bo := gax.Backoff{ 193 | Initial: time.Second, 194 | Max: time.Minute, // Maximum amount of time between retries. 195 | Multiplier: 2, 196 | } 197 | 198 | performHTTPCallWithRetry := func(ctx context.Context, doHTTPCall func(ctx context.Context) (*http.Response, error)) (*http.Response, error) { 199 | for { 200 | resp, err := doHTTPCall(ctx) 201 | if err != nil { 202 | // Retry 503 UNAVAILABLE. 203 | if resp.StatusCode == http.StatusServiceUnavailable { 204 | if err := gax.Sleep(ctx, bo.Pause()); err != nil { 205 | return nil, err 206 | } 207 | continue 208 | } 209 | return nil, err 210 | } 211 | return resp, err 212 | } 213 | } 214 | 215 | // It's recommended to set deadlines on HTTP calls and around retrying. This 216 | // is also usually preferred over setting some fixed number of retries: one 217 | // advantage this has is that backoff settings can be changed independently 218 | // of the deadline, whereas with a fixed number of retries the deadline 219 | // would be a constantly-shifting goalpost. 220 | ctxWithTimeout, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute)) 221 | defer cancel() 222 | 223 | resp, err := performHTTPCallWithRetry(ctxWithTimeout, func(ctx context.Context) (*http.Response, error) { 224 | req, err := http.NewRequest("some-method", "example.com", nil) 225 | if err != nil { 226 | return nil, err 227 | } 228 | req = req.WithContext(ctx) 229 | return http.DefaultClient.Do(req) 230 | }) 231 | if err != nil { 232 | // TODO: handle err 233 | } 234 | _ = resp // TODO: use resp if err is nil 235 | } 236 | 237 | func ExampleProtoJSONStream() { 238 | var someHTTPCall func() (http.Response, error) 239 | 240 | res, err := someHTTPCall() 241 | if err != nil { 242 | // TODO: handle err 243 | } 244 | 245 | // The type of message expected in the stream. 246 | var typ protoreflect.MessageType = (&structpb.Struct{}).ProtoReflect().Type() 247 | 248 | stream := gax.NewProtoJSONStreamReader(res.Body, typ) 249 | defer stream.Close() 250 | 251 | for { 252 | m, err := stream.Recv() 253 | if err != nil { 254 | break 255 | } 256 | // TODO: use resp 257 | _ = m.(*structpb.Struct) 258 | } 259 | if err != io.EOF { 260 | // TODO: handle err 261 | } 262 | } 263 | 264 | func ExampleInvoke_grpc() { 265 | ctx := context.Background() 266 | c := &fakeClient{} 267 | opt := gax.WithRetry(func() gax.Retryer { 268 | return gax.OnCodes([]codes.Code{codes.Unknown, codes.Unavailable}, gax.Backoff{ 269 | Initial: time.Second, 270 | Max: 32 * time.Second, 271 | Multiplier: 2, 272 | }) 273 | }) 274 | 275 | var resp *fakeResponse 276 | err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error { 277 | var err error 278 | resp, err = c.PerformSomeRPC(ctx) 279 | return err 280 | }, opt) 281 | if err != nil { 282 | // TODO: handle err 283 | } 284 | _ = resp // TODO: use resp if err is nil 285 | } 286 | 287 | func ExampleInvoke_http() { 288 | ctx := context.Background() 289 | c := &fakeClient{} 290 | opt := gax.WithRetry(func() gax.Retryer { 291 | return gax.OnHTTPCodes(gax.Backoff{ 292 | Initial: time.Second, 293 | Max: 32 * time.Second, 294 | Multiplier: 2, 295 | }, http.StatusBadGateway, http.StatusServiceUnavailable) 296 | }) 297 | 298 | var resp *fakeResponse 299 | err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error { 300 | var err error 301 | resp, err = c.PerformSomeRPC(ctx) 302 | return err 303 | }, opt) 304 | if err != nil { 305 | // TODO: handle err 306 | } 307 | _ = resp // TODO: use resp if err is nil 308 | } 309 | -------------------------------------------------------------------------------- /v2/apierror/internal/proto/error.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.28.0 18 | // protoc v3.15.8 19 | // source: apierror/internal/proto/error.proto 20 | 21 | package jsonerror 22 | 23 | import ( 24 | reflect "reflect" 25 | sync "sync" 26 | 27 | code "google.golang.org/genproto/googleapis/rpc/code" 28 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 29 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 30 | anypb "google.golang.org/protobuf/types/known/anypb" 31 | ) 32 | 33 | const ( 34 | // Verify that this generated code is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 36 | // Verify that runtime/protoimpl is sufficiently up-to-date. 37 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 38 | ) 39 | 40 | // The error format v2 for Google JSON REST APIs. 41 | // Copied from https://cloud.google.com/apis/design/errors#http_mapping. 42 | // 43 | // NOTE: This schema is not used for other wire protocols. 44 | type Error struct { 45 | state protoimpl.MessageState 46 | sizeCache protoimpl.SizeCache 47 | unknownFields protoimpl.UnknownFields 48 | 49 | // The actual error payload. The nested message structure is for backward 50 | // compatibility with Google API client libraries. It also makes the error 51 | // more readable to developers. 52 | Error *Error_Status `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` 53 | } 54 | 55 | func (x *Error) Reset() { 56 | *x = Error{} 57 | if protoimpl.UnsafeEnabled { 58 | mi := &file_apierror_internal_proto_error_proto_msgTypes[0] 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | ms.StoreMessageInfo(mi) 61 | } 62 | } 63 | 64 | func (x *Error) String() string { 65 | return protoimpl.X.MessageStringOf(x) 66 | } 67 | 68 | func (*Error) ProtoMessage() {} 69 | 70 | func (x *Error) ProtoReflect() protoreflect.Message { 71 | mi := &file_apierror_internal_proto_error_proto_msgTypes[0] 72 | if protoimpl.UnsafeEnabled && x != nil { 73 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 74 | if ms.LoadMessageInfo() == nil { 75 | ms.StoreMessageInfo(mi) 76 | } 77 | return ms 78 | } 79 | return mi.MessageOf(x) 80 | } 81 | 82 | // Deprecated: Use Error.ProtoReflect.Descriptor instead. 83 | func (*Error) Descriptor() ([]byte, []int) { 84 | return file_apierror_internal_proto_error_proto_rawDescGZIP(), []int{0} 85 | } 86 | 87 | func (x *Error) GetError() *Error_Status { 88 | if x != nil { 89 | return x.Error 90 | } 91 | return nil 92 | } 93 | 94 | // This message has the same semantics as `google.rpc.Status`. It uses HTTP 95 | // status code instead of gRPC status code. It has an extra field `status` 96 | // for backward compatibility with Google API Client Libraries. 97 | type Error_Status struct { 98 | state protoimpl.MessageState 99 | sizeCache protoimpl.SizeCache 100 | unknownFields protoimpl.UnknownFields 101 | 102 | // The HTTP status code that corresponds to `google.rpc.Status.code`. 103 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 104 | // This corresponds to `google.rpc.Status.message`. 105 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` 106 | // This is the enum version for `google.rpc.Status.code`. 107 | Status code.Code `protobuf:"varint,4,opt,name=status,proto3,enum=google.rpc.Code" json:"status,omitempty"` 108 | // This corresponds to `google.rpc.Status.details`. 109 | Details []*anypb.Any `protobuf:"bytes,5,rep,name=details,proto3" json:"details,omitempty"` 110 | } 111 | 112 | func (x *Error_Status) Reset() { 113 | *x = Error_Status{} 114 | if protoimpl.UnsafeEnabled { 115 | mi := &file_apierror_internal_proto_error_proto_msgTypes[1] 116 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 117 | ms.StoreMessageInfo(mi) 118 | } 119 | } 120 | 121 | func (x *Error_Status) String() string { 122 | return protoimpl.X.MessageStringOf(x) 123 | } 124 | 125 | func (*Error_Status) ProtoMessage() {} 126 | 127 | func (x *Error_Status) ProtoReflect() protoreflect.Message { 128 | mi := &file_apierror_internal_proto_error_proto_msgTypes[1] 129 | if protoimpl.UnsafeEnabled && x != nil { 130 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 131 | if ms.LoadMessageInfo() == nil { 132 | ms.StoreMessageInfo(mi) 133 | } 134 | return ms 135 | } 136 | return mi.MessageOf(x) 137 | } 138 | 139 | // Deprecated: Use Error_Status.ProtoReflect.Descriptor instead. 140 | func (*Error_Status) Descriptor() ([]byte, []int) { 141 | return file_apierror_internal_proto_error_proto_rawDescGZIP(), []int{0, 0} 142 | } 143 | 144 | func (x *Error_Status) GetCode() int32 { 145 | if x != nil { 146 | return x.Code 147 | } 148 | return 0 149 | } 150 | 151 | func (x *Error_Status) GetMessage() string { 152 | if x != nil { 153 | return x.Message 154 | } 155 | return "" 156 | } 157 | 158 | func (x *Error_Status) GetStatus() code.Code { 159 | if x != nil { 160 | return x.Status 161 | } 162 | return code.Code(0) 163 | } 164 | 165 | func (x *Error_Status) GetDetails() []*anypb.Any { 166 | if x != nil { 167 | return x.Details 168 | } 169 | return nil 170 | } 171 | 172 | var File_apierror_internal_proto_error_proto protoreflect.FileDescriptor 173 | 174 | var file_apierror_internal_proto_error_proto_rawDesc = []byte{ 175 | 0x0a, 0x23, 0x61, 0x70, 0x69, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 176 | 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 177 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x19, 0x67, 0x6f, 178 | 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 179 | 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 180 | 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc5, 181 | 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 182 | 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 183 | 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x65, 0x72, 184 | 0x72, 0x6f, 0x72, 0x1a, 0x90, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 185 | 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 186 | 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 187 | 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x06, 188 | 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x67, 189 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 190 | 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 191 | 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 192 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x64, 193 | 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 194 | 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2f, 195 | 0x67, 0x61, 0x78, 0x2d, 0x67, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x65, 0x72, 0x72, 196 | 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 197 | 0x6f, 0x3b, 0x6a, 0x73, 0x6f, 0x6e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 198 | 0x74, 0x6f, 0x33, 199 | } 200 | 201 | var ( 202 | file_apierror_internal_proto_error_proto_rawDescOnce sync.Once 203 | file_apierror_internal_proto_error_proto_rawDescData = file_apierror_internal_proto_error_proto_rawDesc 204 | ) 205 | 206 | func file_apierror_internal_proto_error_proto_rawDescGZIP() []byte { 207 | file_apierror_internal_proto_error_proto_rawDescOnce.Do(func() { 208 | file_apierror_internal_proto_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_apierror_internal_proto_error_proto_rawDescData) 209 | }) 210 | return file_apierror_internal_proto_error_proto_rawDescData 211 | } 212 | 213 | var file_apierror_internal_proto_error_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 214 | var file_apierror_internal_proto_error_proto_goTypes = []interface{}{ 215 | (*Error)(nil), // 0: error.Error 216 | (*Error_Status)(nil), // 1: error.Error.Status 217 | (code.Code)(0), // 2: google.rpc.Code 218 | (*anypb.Any)(nil), // 3: google.protobuf.Any 219 | } 220 | var file_apierror_internal_proto_error_proto_depIdxs = []int32{ 221 | 1, // 0: error.Error.error:type_name -> error.Error.Status 222 | 2, // 1: error.Error.Status.status:type_name -> google.rpc.Code 223 | 3, // 2: error.Error.Status.details:type_name -> google.protobuf.Any 224 | 3, // [3:3] is the sub-list for method output_type 225 | 3, // [3:3] is the sub-list for method input_type 226 | 3, // [3:3] is the sub-list for extension type_name 227 | 3, // [3:3] is the sub-list for extension extendee 228 | 0, // [0:3] is the sub-list for field type_name 229 | } 230 | 231 | func init() { file_apierror_internal_proto_error_proto_init() } 232 | func file_apierror_internal_proto_error_proto_init() { 233 | if File_apierror_internal_proto_error_proto != nil { 234 | return 235 | } 236 | if !protoimpl.UnsafeEnabled { 237 | file_apierror_internal_proto_error_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 238 | switch v := v.(*Error); i { 239 | case 0: 240 | return &v.state 241 | case 1: 242 | return &v.sizeCache 243 | case 2: 244 | return &v.unknownFields 245 | default: 246 | return nil 247 | } 248 | } 249 | file_apierror_internal_proto_error_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 250 | switch v := v.(*Error_Status); i { 251 | case 0: 252 | return &v.state 253 | case 1: 254 | return &v.sizeCache 255 | case 2: 256 | return &v.unknownFields 257 | default: 258 | return nil 259 | } 260 | } 261 | } 262 | type x struct{} 263 | out := protoimpl.TypeBuilder{ 264 | File: protoimpl.DescBuilder{ 265 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 266 | RawDescriptor: file_apierror_internal_proto_error_proto_rawDesc, 267 | NumEnums: 0, 268 | NumMessages: 2, 269 | NumExtensions: 0, 270 | NumServices: 0, 271 | }, 272 | GoTypes: file_apierror_internal_proto_error_proto_goTypes, 273 | DependencyIndexes: file_apierror_internal_proto_error_proto_depIdxs, 274 | MessageInfos: file_apierror_internal_proto_error_proto_msgTypes, 275 | }.Build() 276 | File_apierror_internal_proto_error_proto = out.File 277 | file_apierror_internal_proto_error_proto_rawDesc = nil 278 | file_apierror_internal_proto_error_proto_goTypes = nil 279 | file_apierror_internal_proto_error_proto_depIdxs = nil 280 | } 281 | --------------------------------------------------------------------------------