diff --git a/.golangci.yml b/.golangci.yml index 37bb309..1799277 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -70,7 +70,7 @@ run: # Define the Go version limit. # Mainly related to generics support in go1.18. # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17 - go: '1.17' + go: '1.20' # output configuration options @@ -457,7 +457,7 @@ linters-settings: govet: # Report about shadowed variables. # Default: false - check-shadowing: false + shadow: true # Settings per analyzer. settings: @@ -625,6 +625,7 @@ linters: - bodyclose - containedctx - contextcheck + - copyloopvar - cyclop # - decorder # - depguard @@ -637,7 +638,6 @@ linters: # - errorlint # - exhaustive # - exhaustivestruct - - exportloopref # - forbidigo # - forcetypeassert # - funlen @@ -650,7 +650,7 @@ linters: - gocyclo - godot # - godox - - goerr113 + - err113 - gofmt - gofumpt # - goheader @@ -697,7 +697,6 @@ linters: - unparam - unused # - varnamelen - - vetshadow # - wastedassign # - whitespace # - wrapcheck @@ -729,7 +728,7 @@ issues: - dupl - gocognit - gocyclo - - goerr113 + - err113 - gosec - path: _test\.go diff --git a/README.md b/README.md index 0a46d4e..6b797c2 100644 --- a/README.md +++ b/README.md @@ -41,35 +41,44 @@ This project is an adaptation for Google's Go / Golang programming language. ## Table of content -- [Simple VCR example](#simple-vcr-example) -- [Install](#install) -- [Glossary of Terms](#glossary-of-terms) -- [Concepts](#concepts) - - [VCRSettings](#vcrsettings) -- [Match a request to a cassette track](#match-a-request-to-a-cassette-track) -- [Track mutators](#track-mutators) -- [Cassette encryption](#cassette-encryption) -- [Cookbook](#cookbook) - - [Run the examples](#run-the-examples) - - [Recipe: VCR with custom `http.Client`](#recipe-vcr-with-custom-httpclient) - - [Recipe: Remove Response TLS](#recipe-remove-response-tls) - - [Recipe: Change the playback mode of the VCR](#recipe-change-the-playback-mode-of-the-vcr) - - [Recipe: VCR with encrypted cassette](#recipe-vcr-with-encrypted-cassette) - - [Recipe: VCR with encrypted cassette - custom nonce generator](#recipe-vcr-with-encrypted-cassette---custom-nonce-generator) - - [Recipe: Cassette decryption](#recipe-cassette-decryption) - - [Recipe: Changing cassette encryption](#recipe-changing-cassette-encryption) - - [Recipe: VCR with cassette storage on AWS S3](#recipe-vcr-with-cassette-storage-on-aws-s3) - - [Recipe: VCR with a custom RequestMatcher](#recipe-vcr-with-a-custom-requestmatcher) - - [Recipe: VCR with a replaying Track Mutator](#recipe-vcr-with-a-replaying-track-mutator) - - [Recipe: VCR with a recording Track Mutator](#recipe-vcr-with-a-recording-track-mutator) - - [More](#more) -- [Stats](#stats) -- [Run the tests](#run-the-tests) -- [Bugs](#bugs) -- [Improvements](#improvements) -- [Limitations](#limitations) -- [Contribute](#contribute) -- [Community Support Appeal](#community-support-appeal) +- [govcr](#govcr) + - [Table of content](#table-of-content) + - [Simple VCR example](#simple-vcr-example) + - [Install](#install) + - [Glossary of Terms](#glossary-of-terms) + - [Concepts](#concepts) + - [VCRSettings](#vcrsettings) + - [Match a request to a cassette track](#match-a-request-to-a-cassette-track) + - [Track mutators](#track-mutators) + - [Cassette encryption](#cassette-encryption) + - [Cookbook](#cookbook) + - [Run the examples](#run-the-examples) + - [Recipe: VCR with custom `http.Client`](#recipe-vcr-with-custom-httpclient) + - [Recipe: Remove Response TLS](#recipe-remove-response-tls) + - [Recipe: Change the playback mode of the VCR](#recipe-change-the-playback-mode-of-the-vcr) + - [Normal HTTP mode](#normal-http-mode) + - [Live only HTTP mode](#live-only-http-mode) + - [Read only cassette mode](#read-only-cassette-mode) + - [Offline HTTP mode](#offline-http-mode) + - [Recipe: VCR with encrypted cassette](#recipe-vcr-with-encrypted-cassette) + - [Recipe: VCR with encrypted cassette - custom nonce generator](#recipe-vcr-with-encrypted-cassette---custom-nonce-generator) + - [Recipe: Cassette decryption](#recipe-cassette-decryption) + - [Recipe: Changing cassette encryption](#recipe-changing-cassette-encryption) + - [Recipe: VCR with cassette storage on AWS S3](#recipe-vcr-with-cassette-storage-on-aws-s3) + - [Recipe: VCR with a custom RequestMatcher](#recipe-vcr-with-a-custom-requestmatcher) + - [Recipe: VCR with a replaying Track Mutator](#recipe-vcr-with-a-replaying-track-mutator) + - [Recipe: VCR with a recording Track Mutator](#recipe-vcr-with-a-recording-track-mutator) + - [More](#more) + - [Stats](#stats) + - [Run the tests](#run-the-tests) + - [Bugs](#bugs) + - [Improvements](#improvements) + - [Limitations](#limitations) + - [Go empty interfaces (`interface{}` / `any`)](#go-empty-interfaces-interface--any) + - [Support for multiple values in HTTP headers](#support-for-multiple-values-in-http-headers) + - [HTTP transport errors](#http-transport-errors) + - [Contribute](#contribute) + - [Community Support Appeal](#community-support-appeal) ## Simple VCR example @@ -626,7 +635,7 @@ Objects cannot be created by name at runtime in Go. Rather than re-create the or In practice, the implications for you depend on how much you care about the error type. If all you need to know is that an error occurred, you won't mind this limitation. -Mitigation: Support for common errors (network down) has been implemented. Support for more error types can be implemented, if there is appetite for it. +Mitigation: Support for common errors (network down, dns failure, timeout) has been implemented. Support for more error types can be implemented, if there is appetite for it. [(toc)](#table-of-content) diff --git a/cassette/cassette.go b/cassette/cassette.go index 8dedc26..8c54ebb 100644 --- a/cassette/cassette.go +++ b/cassette/cassette.go @@ -23,7 +23,7 @@ import ( ) // Cassette contains a set of tracks. -// nolint: govet +// nolint:govet type Cassette struct { Tracks []track.Track @@ -134,7 +134,7 @@ func (k7 *Cassette) NumberOfTracks() int32 { k7.trackSliceMutex.RLock() defer k7.trackSliceMutex.RUnlock() - return int32(len(k7.Tracks)) + return int32(len(k7.Tracks)) //nolint:gosec // int32 can more than sufficiently hold the number of tracks on a cassette. } // ReplayTrack returns the specified track number, as recorded on cassette. @@ -257,7 +257,7 @@ func (k7 *Cassette) EncryptionFilter(data []byte) ([]byte, error) { header = append(header, byte(nonceLen)) header = append(header, nonce...) - // nolint: gocritic + // nolint:gocritic eData := append(header, ciphertext...) return eData, nil diff --git a/cassette/track/http.go b/cassette/track/http.go index b14704a..2d3b90c 100644 --- a/cassette/track/http.go +++ b/cassette/track/http.go @@ -19,7 +19,7 @@ import ( // with a RequestMatcher (albeit perfectly possible). // These fields also help when converting Response to http.Response to // populate http.Response.Request. -// nolint: govet +// nolint:govet type Request struct { Method string URL *url.URL @@ -172,7 +172,7 @@ func ToRequest(httpRequest *http.Request) *Request { } // Response is a track HTTP Response. -// nolint: govet +// nolint:govet type Response struct { Status string StatusCode int @@ -270,13 +270,13 @@ func cloneTLS(tlsCS *tls.ConnectionState) *tls.ConnectionState { DidResume: tlsCS.DidResume, CipherSuite: tlsCS.CipherSuite, NegotiatedProtocol: tlsCS.NegotiatedProtocol, - NegotiatedProtocolIsMutual: tlsCS.NegotiatedProtocolIsMutual, //nolint: staticcheck + NegotiatedProtocolIsMutual: tlsCS.NegotiatedProtocolIsMutual, //nolint:staticcheck ServerName: tlsCS.ServerName, PeerCertificates: peerCertificatesClone, VerifiedChains: verifiedChainsClone, SignedCertificateTimestamps: signedCertificateTimestampsClone, OCSPResponse: []byte(string(tlsCS.OCSPResponse)), - TLSUnique: []byte(string(tlsCS.TLSUnique)), //nolint: staticcheck + TLSUnique: []byte(string(tlsCS.TLSUnique)), //nolint:staticcheck } } diff --git a/cassette/track/track.go b/cassette/track/track.go index 2e7c4ba..38b59e9 100644 --- a/cassette/track/track.go +++ b/cassette/track/track.go @@ -6,6 +6,7 @@ import ( "io" "net" "net/http" + "os" "github.com/pkg/errors" @@ -73,7 +74,8 @@ func (trk *Track) ToErr() error { errMsg = *trk.ErrMsg } - if errType == "*net.OpError" { + switch errType { + case "*net.OpError": return &net.OpError{ Op: "govcr", Net: "govcr", @@ -81,6 +83,23 @@ func (trk *Track) ToErr() error { Addr: nil, Err: errors.WithStack(trkerr.NewErrTransportFailure(errType, errMsg)), } + + case "*os.SyscallError": + return &os.SyscallError{ + Syscall: errMsg, + Err: errors.WithStack(trkerr.NewErrTransportFailure(errType, errMsg)), + } + + case "*net.DNSError": + return &net.DNSError{ + UnwrapErr: errors.WithStack(trkerr.NewErrTransportFailure(errType, errMsg)), + Err: errMsg, + Name: "govcr", + Server: "govcr", + IsTimeout: false, + IsTemporary: false, + IsNotFound: false, + } } return errors.WithStack(trkerr.NewErrTransportFailure(errType, errMsg)) @@ -117,7 +136,7 @@ func (trk *Track) toHTTPRequest() *http.Request { } // ToHTTPResponse converts the track Response to an http.Response. -// nolint: gocritic +// nolint:gocritic func (trk Track) ToHTTPResponse() *http.Response { if trk.Response == nil { return nil diff --git a/concurrency_test.go b/concurrency_test.go index b965167..60160d9 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -97,7 +97,7 @@ func TestConcurrencySafety(t *testing.T) { // check outcome of the request expectedBody := generateBinaryBody(i1) if err := validateResponseForTestPlaybackOrder(resp, expectedBody); err != nil { - t.Fatalf(err.Error()) + t.Fatal(err.Error()) } }() }) diff --git a/controlpanel.go b/controlpanel.go index 768f990..7f7d683 100644 --- a/controlpanel.go +++ b/controlpanel.go @@ -100,5 +100,5 @@ func (controlPanel *ControlPanel) NumberOfTracks() int32 { } func (controlPanel *ControlPanel) vcrTransport() *vcrTransport { - return controlPanel.client.Transport.(*vcrTransport) + return controlPanel.client.Transport.(*vcrTransport) //nolint:errcheck // either way, this would require a panic. } diff --git a/encryption/.study/rsa.go b/encryption/.study/rsa.go index dcc38ec..df14552 100644 --- a/encryption/.study/rsa.go +++ b/encryption/.study/rsa.go @@ -14,7 +14,7 @@ import ( cryptoerr "github.com/seborama/govcr/v15/encryption/errors" ) -// nolint: deadcode +// nolint:deadcode // TODO: offer ability to supply the key via an environment variable in base64 format. func readSSHRSAPrivateKeyFile(privKeyFile, passphrase string) (rsaPrivKey *rsa.PrivateKey, sshSigner ssh.Signer, rsaPubKey *rsa.PublicKey, err error) { keyData, err := os.ReadFile(privKeyFile) @@ -61,7 +61,7 @@ func readSSHRSAPrivateKeyFile(privKeyFile, passphrase string) (rsaPrivKey *rsa.P return rsaPrivKey, sshSigner, rsaPubKey, nil } -// nolint: deadcode +// nolint:deadcode // TODO: offer ability to supply the key via an environment variable in base64 format. func readSSHRSAPublicKeyFile(pubKeyFile string) (*rsa.PublicKey, error) { keyData, err := os.ReadFile(pubKeyFile) diff --git a/govcr_test.go b/govcr_test.go index 8b90445..67fd3dc 100644 --- a/govcr_test.go +++ b/govcr_test.go @@ -294,7 +294,7 @@ func (ts *GoVCRTestSuite) TestVCR_OfflineMode() { // we've run out of tracks on the cassette and we're in offline mode so we expect a transport error req, err := http.NewRequest(http.MethodGet, ts.testServer.URL, nil) ts.Require().NoError(err) - resp, err := vcr.HTTPClient().Do(req) //nolint: bodyclose + resp, err := vcr.HTTPClient().Do(req) //nolint:bodyclose ts.Require().Error(err) ts.Assert().Contains(err.Error(), "no track matched on cassette and offline mode is active") ts.Assert().Nil(resp) @@ -338,7 +338,7 @@ func (ts *GoVCRTestSuite) TestRoundTrip_ReplaysError() { // execute HTTP call and record on cassette vcr := ts.newVCR(cassetteName, actionDeleteCassette) - resp, err := vcr.HTTPClient().Get(tc.reqURL) //nolint: bodyclose + resp, err := vcr.HTTPClient().Get(tc.reqURL) //nolint:bodyclose ts.Require().Error(err) ts.EqualError(err, tc.wantErr) ts.Require().Nil(resp) @@ -356,7 +356,7 @@ func (ts *GoVCRTestSuite) TestRoundTrip_ReplaysError() { vcr = ts.newVCR(cassetteName, actionKeepCassette) ts.EqualValues(1, vcr.NumberOfTracks()) - resp, err = vcr.HTTPClient().Get(tc.reqURL) //nolint: bodyclose + resp, err = vcr.HTTPClient().Get(tc.reqURL) //nolint:bodyclose ts.Require().Error(err) ts.EqualError(err, tc.wantVCRErr) ts.Require().Nil(resp) diff --git a/govcr_wb_test.go b/govcr_wb_test.go index d6f6c21..76a1c41 100644 --- a/govcr_wb_test.go +++ b/govcr_wb_test.go @@ -140,7 +140,7 @@ func (ts *GoVCRWBTestSuite) TestRoundTrip_RequestMatcherDoesNotMutateState() { req, err = http.NewRequest(http.MethodGet, ts.testServer.URL, nil) ts.Require().NoError(err) - resp2, err := vcr.HTTPClient().Do(req) //nolint: bodyclose + resp2, err := vcr.HTTPClient().Do(req) //nolint:bodyclose ts.Require().NoError(err) defer func() { _ = resp.Body.Close() }() diff --git a/matchers.go b/matchers.go index d509cbb..e2e4c13 100644 --- a/matchers.go +++ b/matchers.go @@ -62,7 +62,7 @@ func DefaultMethodMatcher(httpRequest, trackRequest *track.Request) bool { } // DefaultURLMatcher is the default implementation of URLMatcher. -// nolint: gocyclo,gocognit +// nolint:gocyclo,gocognit func DefaultURLMatcher(httpRequest, trackRequest *track.Request) bool { httpURL := httpRequest.URL if httpURL == nil { @@ -95,7 +95,7 @@ func DefaultTrailerMatcher(httpRequest, trackRequest *track.Request) bool { return areHTTPHeadersEqual(httpRequest.Trailer, trackRequest.Trailer) } -// nolint: gocyclo,gocognit +// nolint:gocyclo,gocognit func areHTTPHeadersEqual(httpHeaders1, httpHeaders2 http.Header) bool { if len(httpHeaders1) != len(httpHeaders2) { return false diff --git a/vcrtransport.go b/vcrtransport.go index d302525..f9075e2 100644 --- a/vcrtransport.go +++ b/vcrtransport.go @@ -43,7 +43,7 @@ func (t *vcrTransport) RoundTrip(httpRequest *http.Request) (*http.Response, err httpResponse := trk.ToHTTPResponse() httpError := trk.ToErr() - return httpResponse, httpError //nolint: wrapcheck + return httpResponse, httpError //nolint:wrapcheck } if t.pcb.httpMode == HTTPModeOffline {