Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: implement nil-safe Context methods on typhon.Request #176

Merged
merged 3 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/actions.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Run Tests
on:
on:
- pull_request
jobs:
test:
strategy:
matrix:
go-version: [1.17.x, 1.18.x]
go-version: [1.19.x, 1.23.x]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fly-by change: the minimum supported version in go.mod is already 1.19, and I've introduced a use of any that causes this library to fail to compile on 1.17.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there may be references to 1.17 and 1.18 elsewhere, breaking CI 🙈

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, no. Not sure why CI is blocked on actions that no longer exist. 🤔

os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
40 changes: 36 additions & 4 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
legacyproto "github.com/golang/protobuf/proto"
"github.com/monzo/terrors"
"google.golang.org/protobuf/proto"
"io"
"io/ioutil"
"net/http"
"strings"

legacyproto "github.com/golang/protobuf/proto"
"github.com/monzo/terrors"
"google.golang.org/protobuf/proto"
"time"
)

// A Request is Typhon's wrapper around http.Request, used by both clients and servers.
Expand Down Expand Up @@ -262,3 +262,35 @@ func NewRawRequest(ctx context.Context, method, url string, body io.ReadCloser)
// as an io.ReadCloser, the body won't be encoded as JSON
return NewRequest(ctx, method, url, body)
}

// Deadline wraps the embedded context.Context method to return no deadline if the inner context is nil.
func (r Request) Deadline() (time.Time, bool) {
if r.Context == nil {
return time.Time{}, false
}
return r.Context.Deadline()
}

// Done wraps the embedded context.Context method to return nil if the inner context is nil.
func (r Request) Done() <-chan struct{} {
if r.Context == nil {
return nil
}
return r.Context.Done()
}

// Err wraps the embedded context.Context method to return nil if the inner context is nil.
func (r Request) Err() error {
if r.Context == nil {
return nil
}
return r.Context.Err()
}

// Value wraps the embedded context.Context method to return a nil if the inner context is nil.
func (r Request) Value(key any) any {
if r.Context == nil {
return nil
}
return r.Context.Value(key)
}
56 changes: 56 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"strings"
"testing"
"time"

"github.com/monzo/terrors"

Expand Down Expand Up @@ -245,6 +246,61 @@ func TestRequestMethod(t *testing.T) {
assert.Equal(t, http.MethodGet, req.RequestMethod())
}

func TestRequestContextImplementation(t *testing.T) {
// This test asserts that typhon.Request correctly implements the `context.Context`
// interface (including not panicking when these methods are called on the empty
// request).

t.Run("Deadline", func(t *testing.T) {
t.Run("returns false on an empty Request", func(t *testing.T) {
_, ok := Request{}.Deadline()
assert.False(t, ok)
})

t.Run("passes through a non-zero deadline", func(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Time{})
defer cancel()
_, ok := Request{Context: ctx}.Deadline()
assert.True(t, ok)
})
})

t.Run("Done", func(t *testing.T) {
t.Run("returns nil on an empty Request", func(t *testing.T) {
assert.Nil(t, Request{}.Done())
})

t.Run("passes through a non-zero Done", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
assert.NotNil(t, Request{Context: ctx}.Done())
})
})

t.Run("Err", func(t *testing.T) {
t.Run("returns nil on an empty Request", func(t *testing.T) {
assert.Nil(t, Request{}.Err())
})

t.Run("passes through a non-zero Err", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
assert.NotNil(t, Request{Context: ctx}.Err())
})
})

t.Run("Value", func(t *testing.T) {
t.Run("returns nil on an empty Request", func(t *testing.T) {
assert.Nil(t, Request{}.Value(struct{}{}))
})

t.Run("passes through a non-zero Value", func(t *testing.T) {
ctx := context.WithValue(context.Background(), struct{}{}, true)
assert.NotNil(t, Request{Context: ctx}.Value(struct{}{}))
})
})
}

func jsonStreamMarshal(v interface{}) ([]byte, error) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
Expand Down
Loading