Skip to content

Commit

Permalink
feat: add support for some more errors (syscall errors and DNS specif…
Browse files Browse the repository at this point in the history
…ically) (#114)

* feat: add support for some more errors

* linter compliance

---------

Co-authored-by: seborama <[email protected]>
  • Loading branch information
dpittner and seborama authored Nov 29, 2024
1 parent ecffe9c commit 2a2e1ff
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 56 deletions.
11 changes: 5 additions & 6 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -457,7 +457,7 @@ linters-settings:
govet:
# Report about shadowed variables.
# Default: false
check-shadowing: false
shadow: true

# Settings per analyzer.
settings:
Expand Down Expand Up @@ -625,6 +625,7 @@ linters:
- bodyclose
- containedctx
- contextcheck
- copyloopvar
- cyclop
# - decorder
# - depguard
Expand All @@ -637,7 +638,6 @@ linters:
# - errorlint
# - exhaustive
# - exhaustivestruct
- exportloopref
# - forbidigo
# - forcetypeassert
# - funlen
Expand All @@ -650,7 +650,7 @@ linters:
- gocyclo
- godot
# - godox
- goerr113
- err113
- gofmt
- gofumpt
# - goheader
Expand Down Expand Up @@ -697,7 +697,6 @@ linters:
- unparam
- unused
# - varnamelen
- vetshadow
# - wastedassign
# - whitespace
# - wrapcheck
Expand Down Expand Up @@ -729,7 +728,7 @@ issues:
- dupl
- gocognit
- gocyclo
- goerr113
- err113
- gosec

- path: _test\.go
Expand Down
69 changes: 39 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions cassette/cassette.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

// Cassette contains a set of tracks.
// nolint: govet
// nolint:govet
type Cassette struct {
Tracks []track.Track

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions cassette/track/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}

Expand Down
23 changes: 21 additions & 2 deletions cassette/track/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net"
"net/http"
"os"

"github.com/pkg/errors"

Expand Down Expand Up @@ -73,14 +74,32 @@ func (trk *Track) ToErr() error {
errMsg = *trk.ErrMsg
}

if errType == "*net.OpError" {
switch errType {
case "*net.OpError":
return &net.OpError{
Op: "govcr",
Net: "govcr",
Source: nil,
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))
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}()
})
Expand Down
2 changes: 1 addition & 1 deletion controlpanel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
4 changes: 2 additions & 2 deletions encryption/.study/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions govcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion govcr_wb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() }()

Expand Down
4 changes: 2 additions & 2 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion vcrtransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 2a2e1ff

Please sign in to comment.