Skip to content
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ are marked **Breaking** and listed first in their section.
client, structured results and all response assertion constructors. The
library invokes its configured HTTP client once and leaves retries, logging
and presentation to its caller.
- `ha.Must(...)` keeps assertions built from static expressions inline while
preserving the constructors' explicit error returns for runtime input.
- Generic `ha.Must(...)` keeps values built from static input inline, including
HTTP requests and assertions, while preserving explicit errors for runtime
input.

### Changed

Expand Down
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,24 +138,19 @@ import (
)

func Check(url string) (*ha.Result, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

return (ha.Client{}).Do(
req,
ha.Must(http.NewRequest(http.MethodGet, url, nil)),
ha.AssertStatusOK(),
ha.AssertHeaderEqual("Content-Type", "application/json"),
ha.Must(ha.AssertJQ(`.status == "healthy"`)),
)
}
```

Constructors that parse a status expression, regular expression or jq query
return `(ha.Assertion, error)`. `ha.Must(...)` keeps static, programmer-owned
expressions inline; it panics on invalid input, so runtime values should handle
the constructor error normally.
`ha.Must(...)` unwraps any `(T, error)` result, including requests and assertion
constructors. It keeps static, programmer-owned values inline and panics on an
error, so applications should handle errors from untrusted runtime input
normally.

`Client.Do` calls the configured HTTP client once and never retries. A returned
error means no complete response was available, such as a transport or
Expand Down
8 changes: 6 additions & 2 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (customAssertion) Check(*ha.Response) (*ha.Failure, error) { return nil, ni
var _ ha.Assertion = customAssertion{}

func ExampleClient_Do() {
req, _ := http.NewRequest(http.MethodGet, "https://example.test/health", nil)
req := ha.Must(http.NewRequest(http.MethodGet, "https://example.test/health", nil))
client := ha.Client{HTTPClient: &http.Client{Transport: exampleTransport(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusNoContent,
Expand All @@ -43,8 +43,12 @@ func ExampleClient_Do() {
}

func ExampleMust() {
req := ha.Must(http.NewRequest(http.MethodGet, "https://example.test/health", nil))
assertion := ha.Must(ha.AssertJQ(`.status == "healthy"`))
fmt.Println(req.Method)
fmt.Println(assertion.Kind())

// Output: jq
// Output:
// GET
// jq
}
10 changes: 0 additions & 10 deletions assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ type Assertion interface {
Check(res *Response) (*Failure, error)
}

// Must returns assertion when err is nil and panics otherwise. It makes
// assertions built from static, programmer-controlled expressions convenient
// to declare inline. Runtime input should handle the constructor error instead.
func Must(assertion Assertion, err error) Assertion {
if err != nil {
panic(err)
}
return assertion
}

// FailureCode identifies why an assertion did not hold without prescribing how
// a caller presents that fact.
type FailureCode string
Expand Down
22 changes: 0 additions & 22 deletions assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,6 @@ import (
"testing"
)

func TestMust(t *testing.T) {
t.Parallel()

t.Run("returns a successfully constructed assertion", func(t *testing.T) {
assertion := Must(AssertJQ(`.status == "healthy"`))
if assertion == nil || assertion.Kind() != "jq" {
t.Errorf("Must() = %v, want jq assertion", assertion)
}
})

t.Run("panics with the constructor error", func(t *testing.T) {
want := errors.New("invalid assertion")
defer func() {
if got := recover(); got != want {
t.Errorf("panic = %v, want %v", got, want)
}
}()

Must(nil, want)
})
}

func Test_AssertStatusOK(t *testing.T) {
t.Parallel()

Expand Down
11 changes: 11 additions & 0 deletions must.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package httpassert

// Must returns value when err is nil and panics otherwise. It makes values
// built from static, programmer-controlled input convenient to declare inline.
// Runtime input should handle the constructor error instead.
func Must[T any](value T, err error) T {
if err != nil {
panic(err)
}
return value
}
36 changes: 36 additions & 0 deletions must_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package httpassert

import (
"errors"
"net/http"
"testing"
)

func TestMust(t *testing.T) {
t.Parallel()

t.Run("returns a successfully constructed assertion", func(t *testing.T) {
assertion := Must(AssertJQ(`.status == "healthy"`))
if assertion == nil || assertion.Kind() != KindJQ {
t.Errorf("Must() = %v, want jq assertion", assertion)
}
})

t.Run("returns an HTTP request", func(t *testing.T) {
request := Must(http.NewRequest(http.MethodGet, "https://example.test/health", nil))
if request.Method != http.MethodGet || request.URL.String() != "https://example.test/health" {
t.Errorf("Must() = %s %s, want GET https://example.test/health", request.Method, request.URL)
}
})

t.Run("panics with the constructor error", func(t *testing.T) {
want := errors.New("invalid value")
defer func() {
if got := recover(); got != want {
t.Errorf("panic = %v, want %v", got, want)
}
}()

Must("", want)
})
}
Loading