From 04c79c8e6aaf0e72c2b9dc6a5e51f08fee544007 Mon Sep 17 00:00:00 2001 From: Carlana Date: Mon, 9 Nov 2026 15:49:09 -0500 Subject: [PATCH 01/14] v2: Rename repo --- builder_core.go | 2 +- builder_example_test.go | 2 +- builder_test.go | 10 +++++----- config_example_test.go | 4 ++-- cookies_example_test.go | 2 +- core_req.go | 2 +- core_url_test.go | 2 +- errorkind_test.go | 6 +++--- go.mod | 2 +- handler_example_test.go | 2 +- internal/be/be/be_example_test.go | 2 +- internal/be/be_example_test.go | 2 +- internal/be/be_test.go | 2 +- internal/be/debug_example_test.go | 2 +- internal/be/deep_equal_example_test.go | 2 +- internal/minitrue/minitrue_test.go | 4 ++-- recorder_example_test.go | 2 +- recorder_test.go | 4 ++-- redirects_example_test.go | 2 +- reqhtml/html.go | 2 +- reqhtml/html_test.go | 6 +++--- reqtest/json.go | 2 +- reqtest/json_example_test.go | 4 ++-- reqtest/json_test.go | 6 +++--- reqtest/recorder_example_test.go | 4 ++-- reqtest/recorder_test.go | 6 +++--- reqtest/server.go | 2 +- reqtest/server_example_test.go | 4 ++-- reqtest/server_test.go | 6 +++--- reqtest/transport.go | 2 +- reqxml/body.go | 2 +- reqxml/body_example_test.go | 4 ++-- reqxml/err.go | 2 +- reqxml/err_example_test.go | 4 ++-- reqxml/to.go | 2 +- reqxml/to_example_test.go | 6 +++--- transport_example_test.go | 2 +- transport_test.go | 4 ++-- validator_example_test.go | 2 +- validator_handler_example_test.go | 2 +- 40 files changed, 65 insertions(+), 65 deletions(-) diff --git a/builder_core.go b/builder_core.go index dac6a74..d51d376 100644 --- a/builder_core.go +++ b/builder_core.go @@ -8,7 +8,7 @@ import ( "net/url" "slices" - "github.com/carlmjohnson/requests/internal/minitrue" + "github.com/gophertools/requests/internal/minitrue" ) // Builder is a convenient way to build, send, and handle HTTP requests. diff --git a/builder_example_test.go b/builder_example_test.go index 7c4ed85..6fbad88 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -16,7 +16,7 @@ import ( "path/filepath" "strings" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func init() { diff --git a/builder_test.go b/builder_test.go index d32a130..6ecf037 100644 --- a/builder_test.go +++ b/builder_test.go @@ -15,9 +15,9 @@ import ( "testing" "time" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/reqtest" ) func TestClone(t *testing.T) { @@ -47,7 +47,7 @@ func TestClone(t *testing.T) { Cookie("alternate", "value"). Param("b", "5"). Param("c", "6") - req1, err := rb1.Request(context.Background()) + req1, err := rb1.Request(t.Context()) be.NilErr(t, err) be.Equal(t, "example.com", req1.URL.Host) be.Equal(t, "/a/", req1.URL.Path) @@ -57,7 +57,7 @@ func TestClone(t *testing.T) { be.Equal(t, "2", req1.URL.Query().Get("b")) be.Equal(t, "", req1.URL.Query().Get("c")) - req2, err := rb2.Request(context.Background()) + req2, err := rb2.Request(t.Context()) be.NilErr(t, err) be.Equal(t, "host.example", req2.URL.Host) be.Equal(t, "/a/b", req2.URL.Path) diff --git a/config_example_test.go b/config_example_test.go index 63d80e6..94bfa2b 100644 --- a/config_example_test.go +++ b/config_example_test.go @@ -12,8 +12,8 @@ import ( "net/textproto" "strings" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func ExampleNew() { diff --git a/cookies_example_test.go b/cookies_example_test.go index e620d52..73b0acc 100644 --- a/cookies_example_test.go +++ b/cookies_example_test.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleNewCookieJar() { diff --git a/core_req.go b/core_req.go index b7f6ac9..30508ad 100644 --- a/core_req.go +++ b/core_req.go @@ -8,7 +8,7 @@ import ( "net/url" "slices" - "github.com/carlmjohnson/requests/internal/minitrue" + "github.com/gophertools/requests/internal/minitrue" ) // nopCloser is like io.NopCloser(), diff --git a/core_url_test.go b/core_url_test.go index f47c285..a305d75 100644 --- a/core_url_test.go +++ b/core_url_test.go @@ -3,7 +3,7 @@ package requests import ( "testing" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) // PathCases is exported to share tests with requests_test diff --git a/errorkind_test.go b/errorkind_test.go index c967a6f..19fbd1d 100644 --- a/errorkind_test.go +++ b/errorkind_test.go @@ -6,8 +6,8 @@ import ( "io" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" ) func TestErrorKind(t *testing.T) { @@ -20,7 +20,7 @@ func TestErrorKind(t *testing.T) { requests.ErrValidator, requests.ErrHandler, } - ctx := context.Background() + ctx := t.Context() res200 := requests.ReplayString("HTTP/1.1 200 OK\n\n") for _, tc := range []struct { ctx context.Context diff --git a/go.mod b/go.mod index 39ef988..c116988 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/carlmjohnson/requests +module github.com/gophertools/requests go 1.27 diff --git a/handler_example_test.go b/handler_example_test.go index ba40f43..2f5f0fd 100644 --- a/handler_example_test.go +++ b/handler_example_test.go @@ -8,7 +8,7 @@ import ( "io" "strings" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) diff --git a/internal/be/be/be_example_test.go b/internal/be/be/be_example_test.go index 50b331e..1e050aa 100644 --- a/internal/be/be/be_example_test.go +++ b/internal/be/be/be_example_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) type mockingT struct{ *testing.T } diff --git a/internal/be/be_example_test.go b/internal/be/be_example_test.go index 50b331e..1e050aa 100644 --- a/internal/be/be_example_test.go +++ b/internal/be/be_example_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) type mockingT struct{ *testing.T } diff --git a/internal/be/be_test.go b/internal/be/be_test.go index 93bdc14..d9e7371 100644 --- a/internal/be/be_test.go +++ b/internal/be/be_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) type testingTB struct { diff --git a/internal/be/debug_example_test.go b/internal/be/debug_example_test.go index 934717c..6f9b9d2 100644 --- a/internal/be/debug_example_test.go +++ b/internal/be/debug_example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) type mockDebug struct { diff --git a/internal/be/deep_equal_example_test.go b/internal/be/deep_equal_example_test.go index c57224d..5cc1549 100644 --- a/internal/be/deep_equal_example_test.go +++ b/internal/be/deep_equal_example_test.go @@ -1,7 +1,7 @@ package be_test import ( - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests/internal/be" ) func ExampleDeepEqual() { diff --git a/internal/minitrue/minitrue_test.go b/internal/minitrue/minitrue_test.go index 34b393f..6ab8e88 100644 --- a/internal/minitrue/minitrue_test.go +++ b/internal/minitrue/minitrue_test.go @@ -3,8 +3,8 @@ package minitrue_test import ( "testing" - "github.com/carlmjohnson/requests/internal/be" - "github.com/carlmjohnson/requests/internal/minitrue" + "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/internal/minitrue" ) func TestCond(t *testing.T) { diff --git a/recorder_example_test.go b/recorder_example_test.go index 572b7a9..9654993 100644 --- a/recorder_example_test.go +++ b/recorder_example_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing/fstest" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleReplayFS() { diff --git a/recorder_test.go b/recorder_test.go index 52ba562..cff200b 100644 --- a/recorder_test.go +++ b/recorder_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" ) func TestRecordReplay(t *testing.T) { diff --git a/redirects_example_test.go b/redirects_example_test.go index 2d351b3..7d075c1 100644 --- a/redirects_example_test.go +++ b/redirects_example_test.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleCheckRedirectPolicy() { diff --git a/reqhtml/html.go b/reqhtml/html.go index 6e73d9e..b6f0640 100644 --- a/reqhtml/html.go +++ b/reqhtml/html.go @@ -4,7 +4,7 @@ package reqhtml import ( "io" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" "golang.org/x/net/html" ) diff --git a/reqhtml/html_test.go b/reqhtml/html_test.go index 460de6f..f6d7a16 100644 --- a/reqhtml/html_test.go +++ b/reqhtml/html_test.go @@ -6,9 +6,9 @@ import ( "net/http" "net/http/httputil" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqhtml" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqhtml" + "github.com/gophertools/requests/reqtest" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) diff --git a/reqtest/json.go b/reqtest/json.go index 9349c2e..c3c8c44 100644 --- a/reqtest/json.go +++ b/reqtest/json.go @@ -7,7 +7,7 @@ import ( "io" "net/http" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) // ReplayJSON returns a [requests.Transport] diff --git a/reqtest/json_example_test.go b/reqtest/json_example_test.go index 6fa6415..ee6cca6 100644 --- a/reqtest/json_example_test.go +++ b/reqtest/json_example_test.go @@ -5,8 +5,8 @@ import ( "fmt" "net/http" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func ExampleReplayJSON() { diff --git a/reqtest/json_test.go b/reqtest/json_test.go index c3c283d..a2721a7 100644 --- a/reqtest/json_test.go +++ b/reqtest/json_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/reqtest" ) func TestReplayJSON(t *testing.T) { diff --git a/reqtest/recorder_example_test.go b/reqtest/recorder_example_test.go index eb7ec25..2695f70 100644 --- a/reqtest/recorder_example_test.go +++ b/reqtest/recorder_example_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing/fstest" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func ExampleReplayString() { diff --git a/reqtest/recorder_test.go b/reqtest/recorder_test.go index 7d1453b..4d031e9 100644 --- a/reqtest/recorder_test.go +++ b/reqtest/recorder_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/reqtest" ) func TestRecordReplay(t *testing.T) { diff --git a/reqtest/server.go b/reqtest/server.go index d404e74..afe9824 100644 --- a/reqtest/server.go +++ b/reqtest/server.go @@ -3,7 +3,7 @@ package reqtest import ( "net/http/httptest" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) // Server takes an httptest.Server and returns a requests.Config diff --git a/reqtest/server_example_test.go b/reqtest/server_example_test.go index 700c845..08ca838 100644 --- a/reqtest/server_example_test.go +++ b/reqtest/server_example_test.go @@ -6,8 +6,8 @@ import ( "net/http" "net/http/httptest" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func ExampleServer() { diff --git a/reqtest/server_test.go b/reqtest/server_test.go index 0a9b6cd..d029511 100644 --- a/reqtest/server_test.go +++ b/reqtest/server_test.go @@ -7,9 +7,9 @@ import ( "net/http/httptest" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" - "github.com/carlmjohnson/requests/reqtest" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/reqtest" ) func TestServer(t *testing.T) { diff --git a/reqtest/transport.go b/reqtest/transport.go index e1bc0c7..82f2ad0 100644 --- a/reqtest/transport.go +++ b/reqtest/transport.go @@ -7,7 +7,7 @@ import ( "net/http" "os" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) // ReplayString returns an http.RoundTripper that always responds with a diff --git a/reqxml/body.go b/reqxml/body.go index d91eff8..bfc05aa 100644 --- a/reqxml/body.go +++ b/reqxml/body.go @@ -3,7 +3,7 @@ package reqxml import ( "encoding/xml" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) // Body is a BodyGetter that marshals a XML object. diff --git a/reqxml/body_example_test.go b/reqxml/body_example_test.go index fe795cc..0e990c7 100644 --- a/reqxml/body_example_test.go +++ b/reqxml/body_example_test.go @@ -7,8 +7,8 @@ import ( "net/http/httputil" "strings" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqxml" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqxml" ) func ExampleBody() { diff --git a/reqxml/err.go b/reqxml/err.go index d0a7936..6ebc8d7 100644 --- a/reqxml/err.go +++ b/reqxml/err.go @@ -1,6 +1,6 @@ package reqxml -import "github.com/carlmjohnson/requests" +import "github.com/gophertools/requests" // Error is a ValidatorHandler that applies DefaultValidator // and decodes the response as an XML object diff --git a/reqxml/err_example_test.go b/reqxml/err_example_test.go index 1efc0fc..05c3c0b 100644 --- a/reqxml/err_example_test.go +++ b/reqxml/err_example_test.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqxml" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqxml" ) func ExampleError() { diff --git a/reqxml/to.go b/reqxml/to.go index c7b61a5..b43b633 100644 --- a/reqxml/to.go +++ b/reqxml/to.go @@ -3,7 +3,7 @@ package reqxml import ( "encoding/xml" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) // To decodes a response as an XML object. diff --git a/reqxml/to_example_test.go b/reqxml/to_example_test.go index 913fe4b..b501ed7 100644 --- a/reqxml/to_example_test.go +++ b/reqxml/to_example_test.go @@ -5,9 +5,9 @@ import ( "fmt" "net/http" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/reqtest" - "github.com/carlmjohnson/requests/reqxml" + "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" + "github.com/gophertools/requests/reqxml" ) func init() { diff --git a/transport_example_test.go b/transport_example_test.go index 256108f..1c326da 100644 --- a/transport_example_test.go +++ b/transport_example_test.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleReplayString() { diff --git a/transport_test.go b/transport_test.go index 43bf23e..6bd05a6 100644 --- a/transport_test.go +++ b/transport_test.go @@ -5,8 +5,8 @@ import ( "net/http" "testing" - "github.com/carlmjohnson/requests" - "github.com/carlmjohnson/requests/internal/be" + "github.com/gophertools/requests" + "github.com/gophertools/requests/internal/be" ) func TestUserAgentTransport(t *testing.T) { diff --git a/validator_example_test.go b/validator_example_test.go index 9747d7a..202bab1 100644 --- a/validator_example_test.go +++ b/validator_example_test.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleHasStatusErr() { diff --git a/validator_handler_example_test.go b/validator_handler_example_test.go index 35f3945..d5c038c 100644 --- a/validator_handler_example_test.go +++ b/validator_handler_example_test.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/carlmjohnson/requests" + "github.com/gophertools/requests" ) func ExampleValidatorHandler() { From 7aeaec3d4e15fb49518dde36c5b1cc432ab5c1c5 Mon Sep 17 00:00:00 2001 From: Carlana Date: Mon, 9 Nov 2026 15:51:32 -0500 Subject: [PATCH 02/14] CI: Update versions --- .github/workflows/go.yml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 88f984b..86c1da4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,6 +1,5 @@ name: Go - -on: [ push, pull_request ] +on: [push, pull_request] permissions: contents: read jobs: @@ -8,16 +7,16 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: stable - - name: Get dependencies - run: go mod download - - name: Check go.mod - run: go mod tidy -diff - - name: Test - run: go test -race -v -coverprofile=profile.cov ./... - - uses: shogo82148/actions-goveralls@v1 - with: - path-to-profile: profile.cov + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: "stable" + - name: Download modules + run: go mod download + - name: Check go.mod + run: go mod tidy -diff + - name: Test + run: go test -race -v -coverprofile=profile.cov ./... + - uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: profile.cov From 320058693973ffde25a438ff0ea752236ba5c928 Mon Sep 17 00:00:00 2001 From: Carlana Date: Mon, 9 Nov 2026 16:12:40 -0500 Subject: [PATCH 03/14] Get things working, remove deprecations --- SECURITY.md | 2 +- builder_example_test.go | 9 +-- config.go | 17 ----- config_example_test.go | 45 ------------- core_req.go | 2 +- errorkind_test.go | 7 +- handler.go | 21 ------ handler_example_test.go | 29 --------- recorder.go | 131 -------------------------------------- recorder_example_test.go | 33 ---------- recorder_test.go | 68 -------------------- reqhtml/html.go | 10 ++- reqtest/recorder_test.go | 2 +- reqtest/server.go | 9 ++- reqtest/transport.go | 97 ++++++++++++++++++++++++++-- transport.go | 14 ---- transport_example_test.go | 3 +- 17 files changed, 123 insertions(+), 376 deletions(-) delete mode 100644 recorder.go delete mode 100644 recorder_example_test.go delete mode 100644 recorder_test.go diff --git a/SECURITY.md b/SECURITY.md index 2117275..a2e568b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,4 +6,4 @@ Only the most recently tagged version of this repository is supported. ## Reporting a Vulnerability -Email me@carlmjohnson.net with any security concerns or vulnerabilities. +Email me@carlana.net with any security concerns or vulnerabilities. diff --git a/builder_example_test.go b/builder_example_test.go index 6fbad88..eeb455f 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -17,10 +17,11 @@ import ( "strings" "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func init() { - http.DefaultClient.Transport = requests.Replay("testdata") + http.DefaultClient.Transport = reqtest.ReplayFS(os.DirFS("testdata")) } func Example() { @@ -541,7 +542,7 @@ func ExampleBuilder_Transport() { func ExampleBuilder_ErrorJSON() { { - trans := requests.ReplayString(`HTTP/1.1 200 OK + trans := reqtest.ReplayString(`HTTP/1.1 200 OK {"x": 1}`) @@ -560,7 +561,7 @@ func ExampleBuilder_ErrorJSON() { } } { - trans := requests.ReplayString(`HTTP/1.1 418 I'm a teapot + trans := reqtest.ReplayString(`HTTP/1.1 418 I'm a teapot {"error": "brewing"}`) @@ -620,7 +621,7 @@ func ExampleBuilder_BodySerializer() { } func ExampleBuilder_ToDeserializer() { - trans := requests.ReplayString( + trans := reqtest.ReplayString( "HTTP/1.1 200 OK\r\n\r\nXYZ\x00\xde\xca\xff", ) // Have some binary structure diff --git a/config.go b/config.go index 06b2312..d1ddd44 100644 --- a/config.go +++ b/config.go @@ -6,7 +6,6 @@ import ( "io" "mime" "mime/multipart" - "net/http/httptest" ) // Config allows Builder to be extended by setting several options at once. @@ -34,22 +33,6 @@ func GzipConfig(level int, h func(gw *gzip.Writer) error) Config { } } -// TestServerConfig returns a Config -// which sets the Builder's BaseURL to s.URL -// and the Builder's Client to s.Client(). -// -// Deprecated: Use reqtest.Server. -func TestServerConfig(s *httptest.Server) Config { - return func(rb *Builder) { - // In memory servers don't set s.URL until after s.Client is called, - // so call that first. - cl := s.Client() - rb. - BaseURL(s.URL). - Client(cl) - } -} - // BodyMultipart returns a Config // that uses a multipart.Writer for the request body. // If boundary is "", a multipart boundary is chosen at random. diff --git a/config_example_test.go b/config_example_test.go index 94bfa2b..8b50213 100644 --- a/config_example_test.go +++ b/config_example_test.go @@ -6,8 +6,6 @@ import ( "fmt" "io" "mime/multipart" - "net/http" - "net/http/httptest" "net/http/httputil" "net/textproto" "strings" @@ -64,49 +62,6 @@ func ExampleGzipConfig() { // hello, world } -func ExampleTestServerConfig() { - // Create an httptest.Server for your project's router - mux := http.NewServeMux() - mux.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, world!") - }) - mux.HandleFunc("/salutation", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Howdy, planet!") - }) - - srv := httptest.NewServer(mux) - defer srv.Close() - - // Now test that the handler has the expected return values - { - var s string - err := requests. - New(requests.TestServerConfig(srv)). - Path("/greeting"). - ToString(&s). - Fetch(context.Background()) - if err != nil { - fmt.Println("Error!", err) - } - fmt.Println(s) // Hello, world! - } - { - var s string - err := requests. - New(requests.TestServerConfig(srv)). - Path("/salutation"). - ToString(&s). - Fetch(context.Background()) - if err != nil { - fmt.Println("Error!", err) - } - fmt.Println(s) // Howdy, planet! - } - // Output: - // Hello, world! - // Howdy, planet! -} - func ExampleBodyMultipart() { req, err := requests. URL("http://example.com"). diff --git a/core_req.go b/core_req.go index 30508ad..da903ac 100644 --- a/core_req.go +++ b/core_req.go @@ -14,7 +14,7 @@ import ( // nopCloser is like io.NopCloser(), // but it is a concrete type so we can strip it out // before setting a body on a request. -// See https://github.com/carlmjohnson/requests/discussions/49 +// See https://github.com/gophertools/requests/discussions/49 type nopCloser struct { io.Reader } diff --git a/errorkind_test.go b/errorkind_test.go index 19fbd1d..3c22007 100644 --- a/errorkind_test.go +++ b/errorkind_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophertools/requests" "github.com/gophertools/requests/internal/be" + "github.com/gophertools/requests/reqtest" ) func TestErrorKind(t *testing.T) { @@ -21,7 +22,7 @@ func TestErrorKind(t *testing.T) { requests.ErrHandler, } ctx := t.Context() - res200 := requests.ReplayString("HTTP/1.1 200 OK\n\n") + res200 := reqtest.ReplayString("HTTP/1.1 200 OK\n\n") for _, tc := range []struct { ctx context.Context want requests.ErrorKind @@ -61,11 +62,11 @@ func TestErrorKind(t *testing.T) { }, {ctx, requests.ErrTransport, requests. URL(""). - Transport(requests.ReplayString("")), + Transport(reqtest.ReplayString("")), }, {ctx, requests.ErrValidator, requests. URL(""). - Transport(requests.ReplayString("HTTP/1.1 404 Nope\n\n")), + Transport(reqtest.ReplayString("HTTP/1.1 404 Nope\n\n")), }, {ctx, requests.ErrHandler, requests. URL(""). diff --git a/handler.go b/handler.go index c7230c9..c63d6fa 100644 --- a/handler.go +++ b/handler.go @@ -8,8 +8,6 @@ import ( "os" "path/filepath" "strings" - - "golang.org/x/net/html" ) // ResponseHandler is used to validate or handle the response to a request. @@ -93,20 +91,6 @@ func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler { } } -// ToHTML parses the page with x/net/html.Parse. -// -// Deprecated: Use reqhtml.To. -func ToHTML(n *html.Node) ResponseHandler { - return ToBufioReader(func(r *bufio.Reader) error { - n2, err := html.Parse(r) - if err != nil { - return err - } - *n = *n2 - return nil - }) -} - // ToWriter copies the response body to w. func ToWriter(w io.Writer) ResponseHandler { return ToBufioReader(func(r *bufio.Reader) error { @@ -132,8 +116,3 @@ func ToFile(name string) ResponseHandler { return err } } - -// ToHeaders is an alias for backwards compatibility. -// -// Deprecated: Use CopyHeaders -var ToHeaders = CopyHeaders diff --git a/handler_example_test.go b/handler_example_test.go index 2f5f0fd..bc55ef0 100644 --- a/handler_example_test.go +++ b/handler_example_test.go @@ -9,8 +9,6 @@ import ( "strings" "github.com/gophertools/requests" - "golang.org/x/net/html" - "golang.org/x/net/html/atom" ) func ExampleToBufioReader() { @@ -66,30 +64,3 @@ func ExampleToBufioScanner() { // Output: // true } - -func ExampleToHTML() { - var doc html.Node - err := requests. - URL("http://example.com"). - Handle(requests.ToHTML(&doc)). - Fetch(context.Background()) - if err != nil { - fmt.Println("could not connect to example.com:", err) - } - var f func(*html.Node) - f = func(n *html.Node) { - if n.DataAtom == atom.A { - for _, attr := range n.Attr { - if attr.Key == "href" { - fmt.Println("link:", attr.Val) - } - } - } - for c := n.FirstChild; c != nil; c = c.NextSibling { - f(c) - } - } - f(&doc) - // Output: - // link: https://www.iana.org/domains/example -} diff --git a/recorder.go b/recorder.go deleted file mode 100644 index a493ead..0000000 --- a/recorder.go +++ /dev/null @@ -1,131 +0,0 @@ -package requests - -import ( - "bufio" - "bytes" - "crypto/md5" - "encoding/base64" - "errors" - "fmt" - "io/fs" - "net/http" - "net/http/httputil" - "os" - "path/filepath" -) - -// Record returns an http.RoundTripper that writes out its -// requests and their responses to text files in basepath. -// Requests are named according to a hash of their contents. -// Responses are named according to the request that made them. -// -// Deprecated: Use reqtest.Record. -func Record(rt http.RoundTripper, basepath string) Transport { - if rt == nil { - rt = http.DefaultTransport - } - return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { - defer func() { - if err != nil { - err = fmt.Errorf("problem while recording transport: %w", err) - } - }() - _ = os.MkdirAll(basepath, 0755) - b, err := httputil.DumpRequest(req, true) - if err != nil { - return nil, err - } - reqname, resname := buildName(b) - name := filepath.Join(basepath, reqname) - if err = os.WriteFile(name, b, 0644); err != nil { - return nil, err - } - if res, err = rt.RoundTrip(req); err != nil { - return - } - b, err = httputil.DumpResponse(res, true) - if err != nil { - return nil, err - } - name = filepath.Join(basepath, resname) - if err = os.WriteFile(name, b, 0644); err != nil { - return nil, err - } - return - }) -} - -// Replay returns an http.RoundTripper that reads its -// responses from text files in basepath. -// Responses are looked up according to a hash of the request. -// -// Deprecated: Use reqtest.Replay. -func Replay(basepath string) Transport { - return ReplayFS(os.DirFS(basepath)) -} - -var errNotFound = errors.New("response not found") - -// ReplayFS returns an http.RoundTripper that reads its -// responses from text files in the fs.FS. -// Responses are looked up according to a hash of the request. -// Response file names may optionally be prefixed with comments for better human organization. -// -// Deprecated: Use reqtest.ReplayFS. -func ReplayFS(fsys fs.FS) Transport { - return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { - defer func() { - if err != nil { - err = fmt.Errorf("problem while replaying transport: %w", err) - } - }() - b, err := httputil.DumpRequest(req, true) - if err != nil { - return nil, err - } - _, name := buildName(b) - glob := "*" + name - matches, err := fs.Glob(fsys, glob) - if err != nil { - return nil, err - } - if len(matches) == 0 { - return nil, fmt.Errorf("%w: no replay file matches %q", errNotFound, glob) - } - if len(matches) > 1 { - return nil, fmt.Errorf("ambiguous response: multiple replay files match %q", glob) - } - b, err = fs.ReadFile(fsys, matches[0]) - if err != nil { - return nil, err - } - r := bufio.NewReader(bytes.NewReader(b)) - return http.ReadResponse(r, req) - }) -} - -func buildName(b []byte) (reqname, resname string) { - h := md5.New() - h.Write(b) - s := base64.URLEncoding.EncodeToString(h.Sum(nil)) - return s[:8] + ".req.txt", s[:8] + ".res.txt" -} - -// Caching returns an http.RoundTripper that attempts to read its -// responses from text files in basepath. If the response is absent, -// it caches the result of issuing the request with rt in basepath. -// Requests are named according to a hash of their contents. -// Responses are named according to the request that made them. -// -// Deprecated: Use reqtest.Caching. -func Caching(rt http.RoundTripper, basepath string) Transport { - replay := Replay(basepath).RoundTrip - record := Record(rt, basepath).RoundTrip - return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { - res, err = replay(req) - if errors.Is(err, errNotFound) { - res, err = record(req) - } - return - }) -} diff --git a/recorder_example_test.go b/recorder_example_test.go deleted file mode 100644 index 9654993..0000000 --- a/recorder_example_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package requests_test - -import ( - "context" - "fmt" - "testing/fstest" - - "github.com/gophertools/requests" -) - -func ExampleReplayFS() { - fsys := fstest.MapFS{ - "fsys.example - MKIYDwjs.res.txt": &fstest.MapFile{ - Data: []byte(`HTTP/1.1 200 OK -Content-Type: text/plain; charset=UTF-8 -Date: Mon, 24 May 2021 18:48:50 GMT - -An example response.`), - }, - } - var s string - const expected = `An example response.` - if err := requests. - URL("http://fsys.example"). - Transport(requests.ReplayFS(fsys)). - ToString(&s). - Fetch(context.Background()); err != nil { - panic(err) - } - fmt.Println(s == expected) - // Output: - // true -} diff --git a/recorder_test.go b/recorder_test.go deleted file mode 100644 index cff200b..0000000 --- a/recorder_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package requests_test - -import ( - "context" - "io" - "net/http" - "os" - "strings" - "testing" - - "github.com/gophertools/requests" - "github.com/gophertools/requests/internal/be" -) - -func TestRecordReplay(t *testing.T) { - baseTrans := requests.ReplayString(`HTTP/1.1 200 OK - -Test Document 1`) - dir := t.TempDir() - - var s1, s2 string - err := requests.URL("http://example.com"). - Transport(requests.Record(baseTrans, dir)). - ToString(&s1). - Fetch(context.Background()) - be.NilErr(t, err) - - err = requests.URL("http://example.com"). - Transport(requests.Replay(dir)). - ToString(&s2). - Fetch(context.Background()) - be.NilErr(t, err) - be.Equal(t, s1, s2) - be.Equal(t, "Test Document 1", s1) -} - -func TestCaching(t *testing.T) { - dir := t.TempDir() - hasRun := false - content := "some content" - var onceTrans requests.RoundTripFunc = func(req *http.Request) (res *http.Response, err error) { - be.False(t, hasRun) - hasRun = true - res = &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(content)), - } - return - } - trans := requests.Caching(onceTrans, dir) - var s1, s2 string - err := requests.URL("http://example.com"). - Transport(trans). - ToString(&s1). - Fetch(context.Background()) - be.NilErr(t, err) - err = requests.URL("http://example.com"). - Transport(trans). - ToString(&s2). - Fetch(context.Background()) - be.NilErr(t, err) - be.Equal(t, content, s1) - be.Equal(t, s1, s2) - - entries, err := os.ReadDir(dir) - be.NilErr(t, err) - be.Equal(t, 2, len(entries)) -} diff --git a/reqhtml/html.go b/reqhtml/html.go index b6f0640..9713409 100644 --- a/reqhtml/html.go +++ b/reqhtml/html.go @@ -2,6 +2,7 @@ package reqhtml import ( + "bufio" "io" "github.com/gophertools/requests" @@ -10,7 +11,14 @@ import ( // To decodes a response as an html document. func To(n *html.Node) requests.ResponseHandler { - return requests.ToHTML(n) + return requests.ToBufioReader(func(r *bufio.Reader) error { + n2, err := html.Parse(r) + if err != nil { + return err + } + *n = *n2 + return nil + }) } // Body sets the requests.Builder's request body to the HTML document. diff --git a/reqtest/recorder_test.go b/reqtest/recorder_test.go index 4d031e9..87d8397 100644 --- a/reqtest/recorder_test.go +++ b/reqtest/recorder_test.go @@ -14,7 +14,7 @@ import ( ) func TestRecordReplay(t *testing.T) { - baseTrans := requests.ReplayString(`HTTP/1.1 200 OK + baseTrans := reqtest.ReplayString(`HTTP/1.1 200 OK Test Document 1`) dir := t.TempDir() diff --git a/reqtest/server.go b/reqtest/server.go index afe9824..36d0213 100644 --- a/reqtest/server.go +++ b/reqtest/server.go @@ -10,5 +10,12 @@ import ( // which sets the requests.Builder's BaseURL to s.URL // and the requests.Builder's Client to s.Client(). func Server(s *httptest.Server) requests.Config { - return requests.TestServerConfig(s) + return func(rb *requests.Builder) { + // In memory servers don't set s.URL until after s.Client is called, + // so call that first. + cl := s.Client() + rb. + BaseURL(s.URL). + Client(cl) + } } diff --git a/reqtest/transport.go b/reqtest/transport.go index 82f2ad0..fb1d31b 100644 --- a/reqtest/transport.go +++ b/reqtest/transport.go @@ -3,9 +3,16 @@ package reqtest import ( "bufio" "bytes" + "crypto/md5" + "encoding/base64" + "errors" + "fmt" "io/fs" "net/http" + "net/http/httputil" "os" + "path/filepath" + "strings" "github.com/gophertools/requests" ) @@ -13,7 +20,11 @@ import ( // ReplayString returns an http.RoundTripper that always responds with a // request built from rawResponse. It is intended for use in one-off tests. func ReplayString(rawResponse string) requests.Transport { - return requests.ReplayString(rawResponse) + return requests.Transport(requests.RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { + r := bufio.NewReader(strings.NewReader(rawResponse)) + res, err = http.ReadResponse(r, req) + return + })) } // Record returns an http.RoundTripper that writes out its @@ -21,7 +32,38 @@ func ReplayString(rawResponse string) requests.Transport { // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. func Record(rt http.RoundTripper, basepath string) requests.Transport { - return requests.Record(rt, basepath) + if rt == nil { + rt = http.DefaultTransport + } + return requests.RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("problem while recording transport: %w", err) + } + }() + _ = os.MkdirAll(basepath, 0755) + b, err := httputil.DumpRequest(req, true) + if err != nil { + return nil, err + } + reqname, resname := buildName(b) + name := filepath.Join(basepath, reqname) + if err = os.WriteFile(name, b, 0644); err != nil { + return nil, err + } + if res, err = rt.RoundTrip(req); err != nil { + return + } + b, err = httputil.DumpResponse(res, true) + if err != nil { + return nil, err + } + name = filepath.Join(basepath, resname) + if err = os.WriteFile(name, b, 0644); err != nil { + return nil, err + } + return + }) } // Replay returns an http.RoundTripper that reads its @@ -29,9 +71,11 @@ func Record(rt http.RoundTripper, basepath string) requests.Transport { // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. func Replay(basepath string) requests.Transport { - return requests.Replay(basepath) + return ReplayFS(os.DirFS(basepath)) } +var errNotFound = errors.New("response not found") + // ReplayFile returns an http.RoundTripper that reads its // response from the response file at path. func ReplayFile(path string) requests.Transport { @@ -50,7 +94,42 @@ func ReplayFile(path string) requests.Transport { // Responses are looked up according to a hash of the request. // Response file names may optionally be prefixed with comments for better human organization. func ReplayFS(fsys fs.FS) requests.Transport { - return requests.ReplayFS(fsys) + return requests.RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("problem while replaying transport: %w", err) + } + }() + b, err := httputil.DumpRequest(req, true) + if err != nil { + return nil, err + } + _, name := buildName(b) + glob := "*" + name + matches, err := fs.Glob(fsys, glob) + if err != nil { + return nil, err + } + if len(matches) == 0 { + return nil, fmt.Errorf("%w: no replay file matches %q", errNotFound, glob) + } + if len(matches) > 1 { + return nil, fmt.Errorf("ambiguous response: multiple replay files match %q", glob) + } + b, err = fs.ReadFile(fsys, matches[0]) + if err != nil { + return nil, err + } + r := bufio.NewReader(bytes.NewReader(b)) + return http.ReadResponse(r, req) + }) +} + +func buildName(b []byte) (reqname, resname string) { + h := md5.New() + h.Write(b) + s := base64.URLEncoding.EncodeToString(h.Sum(nil)) + return s[:8] + ".req.txt", s[:8] + ".res.txt" } // Caching returns an http.RoundTripper that attempts to read its @@ -59,5 +138,13 @@ func ReplayFS(fsys fs.FS) requests.Transport { // Requests are named according to a hash of their contents. // Responses are named according to the request that made them. func Caching(rt http.RoundTripper, basepath string) requests.Transport { - return requests.Caching(rt, basepath) + replay := Replay(basepath).RoundTrip + record := Record(rt, basepath).RoundTrip + return requests.RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { + res, err = replay(req) + if errors.Is(err, errNotFound) { + res, err = record(req) + } + return + }) } diff --git a/transport.go b/transport.go index 48bd317..962bfe9 100644 --- a/transport.go +++ b/transport.go @@ -1,12 +1,10 @@ package requests import ( - "bufio" "fmt" "io" "net/http" "regexp" - "strings" "time" ) @@ -23,18 +21,6 @@ func (rtf RoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { var _ Transport = RoundTripFunc(nil) -// ReplayString returns an http.RoundTripper that always responds with a -// request built from rawResponse. It is intended for use in one-off tests. -// -// Deprecated: Use reqtest.ReplayString. -func ReplayString(rawResponse string) Transport { - return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) { - r := bufio.NewReader(strings.NewReader(rawResponse)) - res, err = http.ReadResponse(r, req) - return - }) -} - // UserAgentTransport returns a wrapped http.RoundTripper that sets the User-Agent header on requests to s. func UserAgentTransport(rt http.RoundTripper, s string) Transport { if rt == nil { diff --git a/transport_example_test.go b/transport_example_test.go index 1c326da..bd7f1e1 100644 --- a/transport_example_test.go +++ b/transport_example_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/gophertools/requests" + "github.com/gophertools/requests/reqtest" ) func ExampleReplayString() { @@ -23,7 +24,7 @@ An example response.` const expected = `An example response.` if err := requests. URL("http://response.example"). - Transport(requests.ReplayString(res)). + Transport(reqtest.ReplayString(res)). ToString(&s). Fetch(context.Background()); err != nil { panic(err) From 778cc571eead7f75a1ac095c3cb2ba522e4eb042 Mon Sep 17 00:00:00 2001 From: Carlana Date: Mon, 9 Nov 2026 16:23:06 -0500 Subject: [PATCH 04/14] Some doc links --- reqxml/err.go | 2 +- serializer.go | 4 ++-- validator_handler.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reqxml/err.go b/reqxml/err.go index 6ebc8d7..b28fc50 100644 --- a/reqxml/err.go +++ b/reqxml/err.go @@ -2,7 +2,7 @@ package reqxml import "github.com/gophertools/requests" -// Error is a ValidatorHandler that applies DefaultValidator +// Error is a [ValidatorHandler] that applies DefaultValidator // and decodes the response as an XML object // if the DefaultValidator check fails. func Error(v any) requests.ResponseHandler { diff --git a/serializer.go b/serializer.go index 0b4774b..f904563 100644 --- a/serializer.go +++ b/serializer.go @@ -15,10 +15,10 @@ type Serializer = func(v any) ([]byte, error) type Deserializer = func(data []byte, v any) error var ( - // JSONSerializer is used by BodyJSON and Builder.BodyJSON. + // JSONSerializer is used by [BodyJSON] and [Builder.BodyJSON]. // The default serializer may be changed in a future version of requests. JSONSerializer Serializer = json.Marshal - // JSONDeserializer is used by ToJSON and Builder.ToJSON. + // JSONDeserializer is used by [ToJSON] and [Builder.ToJSON]. // The default deserializer may be changed in a future version of requests. JSONDeserializer Deserializer = json.Unmarshal ) diff --git a/validator_handler.go b/validator_handler.go index 92256c6..27612e1 100644 --- a/validator_handler.go +++ b/validator_handler.go @@ -9,7 +9,7 @@ import ( // ValidatorHandler composes a Validator and a Handler. // If the validation check fails, it triggers the handler. // Any errors from validator or handler will be joined to the error returned. -// If the handler succeeds, the error will matching ErrInvalidHandled. +// If the handler succeeds, the error will match [ErrInvalidHandled]. func ValidatorHandler(v, h ResponseHandler) ResponseHandler { return func(res *http.Response) error { err1 := v(res) @@ -26,7 +26,7 @@ func ValidatorHandler(v, h ResponseHandler) ResponseHandler { var ErrInvalidHandled = errors.New("handled recovery from invalid response") -// ErrorJSON is a ValidatorHandler that applies DefaultValidator +// ErrorJSON is a [ValidatorHandler] that applies DefaultValidator // and decodes the response as a JSON object // if the DefaultValidator check fails. func ErrorJSON(v any) ResponseHandler { From b7052b45b9585de1b8d125754b51252c3a285af0 Mon Sep 17 00:00:00 2001 From: Carlana Date: Mon, 9 Nov 2026 16:38:31 -0500 Subject: [PATCH 05/14] Move test --- builder_example_test.go | 4 ---- setup_test.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 setup_test.go diff --git a/builder_example_test.go b/builder_example_test.go index eeb455f..201975b 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -20,10 +20,6 @@ import ( "github.com/gophertools/requests/reqtest" ) -func init() { - http.DefaultClient.Transport = reqtest.ReplayFS(os.DirFS("testdata")) -} - func Example() { // Simple GET into a string var s string diff --git a/setup_test.go b/setup_test.go new file mode 100644 index 0000000..baab435 --- /dev/null +++ b/setup_test.go @@ -0,0 +1,11 @@ +package requests_test + +import ( + "net/http" + + "github.com/gophertools/requests/reqtest" +) + +func init() { + http.DefaultClient.Transport = reqtest.Replay("testdata") +} From 075dd9ade0d353a5899782ea89ae289ec76c406a Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 11:09:17 -0500 Subject: [PATCH 06/14] docs: Fix comment --- reqxml/body.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reqxml/body.go b/reqxml/body.go index bfc05aa..14bcee1 100644 --- a/reqxml/body.go +++ b/reqxml/body.go @@ -6,7 +6,7 @@ import ( "github.com/gophertools/requests" ) -// Body is a BodyGetter that marshals a XML object. +// Body is a BodyGetter that marshals an XML object. func Body(v any) requests.BodyGetter { return requests.BodySerializer(xml.Marshal, v) } From cca2bd27916cf04c6b084ab56e5fb419fc49937a Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 11:09:36 -0500 Subject: [PATCH 07/14] README: Remove comment about URL --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 82adc8e..7352480 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ Requests also comes with tools for building custom http transports, include a request recorder and replayer for testing. -*[See this note on the canonical project URL.](https://gist.github.com/earthboundkid/8915002ae0e531cecdfc58bc6453ac80)* - ## Features - Simplifies HTTP client usage compared to net/http From 1c600343fa3d41e5f2c128fbed2ad563f809ac45 Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:05:44 -0500 Subject: [PATCH 08/14] Make copyHeaders private --- builder_extras.go | 6 +++--- validator.go | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/builder_extras.go b/builder_extras.go index 4fb6d2a..0e0f626 100644 --- a/builder_extras.go +++ b/builder_extras.go @@ -220,15 +220,15 @@ func (rb *Builder) ToFile(name string) *Builder { // if status code validation is desired. func (rb *Builder) CopyHeaders(h map[string][]string) *Builder { return rb. - AddValidator(CopyHeaders(h)) + AddValidator(copyHeaders(h)) } // ToHeaders sets the method to HEAD and adds a handler which copies the response headers to h. -// To just copy headers, see Builder.CopyHeaders. +// To just copy headers, see [Builder.CopyHeaders]. func (rb *Builder) ToHeaders(h map[string][]string) *Builder { return rb. Head(). - Handle(ChainHandlers(CopyHeaders(h), consumeBody)) + Handle(copyHeaders(h)) } // ErrorJSON adds a validator that applies DefaultValidator diff --git a/validator.go b/validator.go index 0770181..c63b786 100644 --- a/validator.go +++ b/validator.go @@ -92,11 +92,10 @@ func CheckPeek(n int, f func([]byte) error) ResponseHandler { } } -// CopyHeaders copies the response headers to h. -func CopyHeaders(h map[string][]string) ResponseHandler { +// copyHeaders copies the response headers to h. +func copyHeaders(h map[string][]string) ResponseHandler { return func(res *http.Response) error { maps.Copy(h, res.Header) - return nil } } From 69d3a61b4dbf40c32aad34871799d75ef979d1ed Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:06:35 -0500 Subject: [PATCH 09/14] Remove consumeBody, which is unnecessary post-Go 1.27 --- builder_core.go | 4 +++- handler.go | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/builder_core.go b/builder_core.go index d51d376..efbc9a6 100644 --- a/builder_core.go +++ b/builder_core.go @@ -241,6 +241,8 @@ func (rb *Builder) Request(ctx context.Context) (req *http.Request, err error) { return req, nil } +func noOp(res *http.Response) (err error) { return nil } + // Do calls the underlying http.Client and validates and handles any resulting response. The response body is closed after all validators and the handler run. func (rb *Builder) Do(req *http.Request) (err error) { cl := cmp.Or(rb.cl, http.DefaultClient) @@ -255,7 +257,7 @@ func (rb *Builder) Do(req *http.Request) (err error) { } h := minitrue.Cond(rb.handler != nil, rb.handler, - consumeBody) + noOp) code, err := do(cl, req, validators, h) switch code { diff --git a/handler.go b/handler.go index c63d6fa..663df64 100644 --- a/handler.go +++ b/handler.go @@ -28,14 +28,6 @@ func ChainHandlers(handlers ...ResponseHandler) ResponseHandler { } } -func consumeBody(res *http.Response) (err error) { - const maxDiscardSize = 640 * 1 << 10 - if _, err = io.CopyN(io.Discard, res.Body, maxDiscardSize); err == io.EOF { - err = nil - } - return err -} - // ToDeserializer decodes a response into v using a [Deserializer]. func ToDeserializer(d Deserializer, v any) ResponseHandler { return func(res *http.Response) error { From 265d04d2ccd7699eb1912f8a3d7dff554b95782b Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:07:34 -0500 Subject: [PATCH 10/14] reqhtml: Use BodyWriter method --- reqhtml/html.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reqhtml/html.go b/reqhtml/html.go index 9713409..ca2b4aa 100644 --- a/reqhtml/html.go +++ b/reqhtml/html.go @@ -27,9 +27,9 @@ func To(n *html.Node) requests.ResponseHandler { func Body(n *html.Node) requests.Config { return func(rb *requests.Builder) { rb. - Body(requests.BodyWriter(func(w io.Writer) error { + BodyWriter(func(w io.Writer) error { return html.Render(w, n) - })). + }). HeaderOptional("context-type", "text/html") } } From c6db4bae8f5a041c78cd504ac6c6109b3a215874 Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:10:08 -0500 Subject: [PATCH 11/14] Use encoding/json/v2 in places --- builder_example_test.go | 22 +++++++++++++++------- cookies_example_test.go | 2 +- reqtest/json.go | 8 ++++++-- serializer.go | 14 ++++++++++---- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/builder_example_test.go b/builder_example_test.go index 201975b..ca47b83 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -145,7 +145,7 @@ func ExampleBuilder_ToFile() { } type placeholder struct { - ID int `json:"id,omitempty"` + ID int `json:"id,omitzero"` Title string `json:"title"` Body string `json:"body"` UserID int `json:"userId"` @@ -307,8 +307,8 @@ func ExampleBuilder_Bearer() { } // But our response is accepted when we provide a bearer token var res struct { - Authenticated bool - Token string + Authenticated bool `json:"authenticated"` + Token string `json:"token"` } err = requests. URL("http://httpbin.org/bearer"). @@ -542,8 +542,12 @@ func ExampleBuilder_ErrorJSON() { {"x": 1}`) - var goodJSON struct{ X int } - var errJSON struct{ Error string } + var goodJSON struct { + X int `json:"x"` + } + var errJSON struct { + Error string `json:"error"` + } err := requests. URL("http://example.com/"). Transport(trans). @@ -561,8 +565,12 @@ func ExampleBuilder_ErrorJSON() { {"error": "brewing"}`) - var goodJSON struct{ X int } - var errJSON struct{ Error string } + var goodJSON struct { + X int `json:"x"` + } + var errJSON struct { + Error string `json:"error"` + } err := requests. URL("http://example.com/"). Transport(trans). diff --git a/cookies_example_test.go b/cookies_example_test.go index 73b0acc..7a3f5c2 100644 --- a/cookies_example_test.go +++ b/cookies_example_test.go @@ -30,7 +30,7 @@ func ExampleNewCookieJar() { } // And we'll see that they're reused on subsequent requests var cookies struct { - Cookies map[string]string + Cookies map[string]string `json:"cookies"` } err = requests. URL("http://httpbin.org/cookies"). diff --git a/reqtest/json.go b/reqtest/json.go index c3c8c44..c6a6173 100644 --- a/reqtest/json.go +++ b/reqtest/json.go @@ -2,7 +2,9 @@ package reqtest import ( "bytes" - "encoding/json" + jsonv1 "encoding/json" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" "fmt" "io" "net/http" @@ -20,7 +22,9 @@ import ( // wrapped marshaling error. func ReplayJSON(code int, obj any) requests.Transport { return requests.RoundTripFunc(func(req *http.Request) (*http.Response, error) { - data, err := json.MarshalIndent(obj, "", " ") + data, err := jsonv2.Marshal(obj, + jsonv1.DefaultOptionsV1(), + jsontext.WithIndent(" ")) if err != nil { return nil, fmt.Errorf("marshal error in ReplayJSON: %v", err) } diff --git a/serializer.go b/serializer.go index f904563..3baec2e 100644 --- a/serializer.go +++ b/serializer.go @@ -1,7 +1,8 @@ package requests import ( - "encoding/json" + jsonv1 "encoding/json" + jsonv2 "encoding/json/v2" ) // Serializer is a function @@ -15,10 +16,15 @@ type Serializer = func(v any) ([]byte, error) type Deserializer = func(data []byte, v any) error var ( + // JSONV1Serializer is [jsonv1.Marshal]. + JSONV1Serializer Serializer = jsonv1.Marshal // JSONSerializer is used by [BodyJSON] and [Builder.BodyJSON]. - // The default serializer may be changed in a future version of requests. - JSONSerializer Serializer = json.Marshal + JSONSerializer Serializer = func(v any) ([]byte, error) { + return jsonv2.Marshal(v) + } // JSONDeserializer is used by [ToJSON] and [Builder.ToJSON]. // The default deserializer may be changed in a future version of requests. - JSONDeserializer Deserializer = json.Unmarshal + JSONDeserializer Deserializer = func(data []byte, v any) error { + return jsonv2.Unmarshal(data, v) + } ) From 36da0f7926d40a8b4fb153eb3f596567fdd8b413 Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:21:57 -0500 Subject: [PATCH 12/14] Simplify API somewhat --- body.go | 24 ++++++++++++------------ builder_example_test.go | 4 ++-- builder_extras.go | 26 ++++++++++---------------- builder_test.go | 18 ++++++++++++------ handler.go | 12 ++++++------ validator_handler.go | 2 +- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/body.go b/body.go index 1cf3ba1..88bd476 100644 --- a/body.go +++ b/body.go @@ -11,8 +11,8 @@ import ( // BodyGetter provides a Builder with a source for a request body. type BodyGetter = func() (io.ReadCloser, error) -// BodyReader is a BodyGetter that returns an io.Reader. -func BodyReader(r io.Reader) BodyGetter { +// bodyReader is a BodyGetter that returns an io.Reader. +func bodyReader(r io.Reader) BodyGetter { return func() (io.ReadCloser, error) { if rc, ok := r.(io.ReadCloser); ok { return rc, nil @@ -21,8 +21,8 @@ func BodyReader(r io.Reader) BodyGetter { } } -// BodyWriter is a BodyGetter that pipes writes into a request body. -func BodyWriter(f func(w io.Writer) error) BodyGetter { +// bodyWriter is a BodyGetter that pipes writes into a request body. +func bodyWriter(f func(w io.Writer) error) BodyGetter { return func() (io.ReadCloser, error) { r, w := io.Pipe() go func() { @@ -36,8 +36,8 @@ func BodyWriter(f func(w io.Writer) error) BodyGetter { } } -// BodyBytes is a BodyGetter that returns the provided raw bytes. -func BodyBytes(b []byte) BodyGetter { +// bodyBytes is a BodyGetter that returns the provided raw bytes. +func bodyBytes(b []byte) BodyGetter { return func() (io.ReadCloser, error) { return rc(bytes.NewReader(b)), nil } @@ -56,21 +56,21 @@ func BodySerializer(s Serializer, v any) BodyGetter { } } -// BodyJSON is a [BodySerializer] +// bodyJSON is a [BodySerializer] // that uses [JSONSerializer] to marshal the object. -func BodyJSON(v any) BodyGetter { +func bodyJSON(v any) BodyGetter { return BodySerializer(JSONSerializer, v) } -// BodyForm is a BodyGetter that builds an encoded form body. -func BodyForm(data url.Values) BodyGetter { +// bodyForm is a BodyGetter that builds an encoded form body. +func bodyForm(data url.Values) BodyGetter { return func() (r io.ReadCloser, err error) { return rc(strings.NewReader(data.Encode())), nil } } -// BodyFile is a BodyGetter that reads the provided file path. -func BodyFile(name string) BodyGetter { +// bodyFile is a BodyGetter that reads the provided file path. +func bodyFile(name string) BodyGetter { return func() (r io.ReadCloser, err error) { return os.Open(name) } diff --git a/builder_example_test.go b/builder_example_test.go index ca47b83..3eb5630 100644 --- a/builder_example_test.go +++ b/builder_example_test.go @@ -74,12 +74,12 @@ func Example_postJSON() { // {101 foo baz 1} } -func ExampleBuilder_ToBytesBuffer() { +func ExampleToBytesBuffer() { // Simple GET into a buffer var buf bytes.Buffer err := requests. URL("http://example.com"). - ToBytesBuffer(&buf). + Handle(requests.ToBytesBuffer(&buf)). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) diff --git a/builder_extras.go b/builder_extras.go index 0e0f626..e665bd8 100644 --- a/builder_extras.go +++ b/builder_extras.go @@ -1,7 +1,6 @@ package requests import ( - "bytes" "encoding/base64" "fmt" "io" @@ -120,17 +119,17 @@ func (rb *Builder) Bearer(token string) *Builder { // BodyReader sets the Builder's request body to r. func (rb *Builder) BodyReader(r io.Reader) *Builder { - return rb.Body(BodyReader(r)) + return rb.Body(bodyReader(r)) } // BodyWriter pipes writes from w to the Builder's request body. func (rb *Builder) BodyWriter(f func(w io.Writer) error) *Builder { - return rb.Body(BodyWriter(f)) + return rb.Body(bodyWriter(f)) } // BodyBytes sets the Builder's request body to b. func (rb *Builder) BodyBytes(b []byte) *Builder { - return rb.Body(BodyBytes(b)) + return rb.Body(bodyBytes(b)) } // BodySerializer sets the Builder's request body @@ -146,7 +145,7 @@ func (rb *Builder) BodySerializer(s Serializer, v any) *Builder { // if it is not otherwise set. func (rb *Builder) BodyJSON(v any) *Builder { return rb. - Body(BodyJSON(v)). + Body(bodyJSON(v)). HeaderOptional("Content-Type", "application/json") } @@ -154,13 +153,13 @@ func (rb *Builder) BodyJSON(v any) *Builder { // It also sets the ContentType to "application/x-www-form-urlencoded". func (rb *Builder) BodyForm(data url.Values) *Builder { return rb. - Body(BodyForm(data)). + Body(bodyForm(data)). ContentType("application/x-www-form-urlencoded") } // BodyFile sets the Builder's request body to read from the given file path. func (rb *Builder) BodyFile(name string) *Builder { - return rb.Body(BodyFile(name)) + return rb.Body(bodyFile(name)) } // CheckStatus adds a validator for status code of a response. @@ -189,7 +188,7 @@ func (rb *Builder) ToDeserializer(d Deserializer, v any) *Builder { // // It uses [JSONDeserializer] to unmarshal the object. func (rb *Builder) ToJSON(v any) *Builder { - return rb.Handle(ToJSON(v)) + return rb.Handle(toJSON(v)) } // ToString sets the Builder to write the response body to the provided string pointer. @@ -197,21 +196,16 @@ func (rb *Builder) ToString(sp *string) *Builder { return rb.Handle(ToString(sp)) } -// ToBytesBuffer sets the Builder to write the response body to the provided bytes.Buffer. -func (rb *Builder) ToBytesBuffer(buf *bytes.Buffer) *Builder { - return rb.Handle(ToBytesBuffer(buf)) -} - // ToWriter sets the Builder to copy the response body into w. func (rb *Builder) ToWriter(w io.Writer) *Builder { - return rb.Handle(ToWriter(w)) + return rb.Handle(toWriter(w)) } // ToFile sets the Builder to write the response body to the given file name. // The file and its parent directories are created automatically. -// For more advanced use cases, use ToWriter. +// For more advanced use cases, use [Builder.ToWriter]. func (rb *Builder) ToFile(name string) *Builder { - return rb.Handle(ToFile(name)) + return rb.Handle(toFile(name)) } // CopyHeaders adds a validator which copies the response headers to h. diff --git a/builder_test.go b/builder_test.go index 6ecf037..d998fc7 100644 --- a/builder_test.go +++ b/builder_test.go @@ -243,7 +243,9 @@ func TestRequestClosesBodyOnError(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var file *os.File - getter := requests.BodyFile("go.mod") + getter := func() (r io.ReadCloser, err error) { + return os.Open("go.mod") + } req, err := requests.URL("https://example.com"). Method(tc.method). Body(func() (io.ReadCloser, error) { @@ -315,11 +317,15 @@ func TestRequestBodyOwnership(t *testing.T) { func TestRequestErrorStopsBodyWriter(t *testing.T) { t.Parallel() done := make(chan error, 1) - getter := requests.BodyWriter(func(w io.Writer) error { - _, err := io.WriteString(w, "body") - done <- err - return err - }) + getter := func() (io.ReadCloser, error) { + r, w := io.Pipe() + go func() { + var err error + defer func() { w.CloseWithError(err) }() + err = func(w io.Writer) error { _, err := io.WriteString(w, "body"); done <- err; return err }(w) + }() + return r, nil + } req, err := requests.URL("https://example.com"). Method("bad method"). Body(func() (io.ReadCloser, error) { diff --git a/handler.go b/handler.go index 663df64..d418f20 100644 --- a/handler.go +++ b/handler.go @@ -42,10 +42,10 @@ func ToDeserializer(d Deserializer, v any) ResponseHandler { } } -// ToJSON decodes a response as a JSON object. +// toJSON decodes a response as a JSON object. // // It uses [JSONDeserializer] to unmarshal the object. -func ToJSON(v any) ResponseHandler { +func toJSON(v any) ResponseHandler { return ToDeserializer(JSONDeserializer, v) } @@ -83,8 +83,8 @@ func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler { } } -// ToWriter copies the response body to w. -func ToWriter(w io.Writer) ResponseHandler { +// toWriter copies the response body to w. +func toWriter(w io.Writer) ResponseHandler { return ToBufioReader(func(r *bufio.Reader) error { _, err := io.Copy(w, r) @@ -92,9 +92,9 @@ func ToWriter(w io.Writer) ResponseHandler { }) } -// ToFile writes the response body at the provided file path. +// toFile writes the response body at the provided file path. // The file and its parent directories are created automatically. -func ToFile(name string) ResponseHandler { +func toFile(name string) ResponseHandler { return func(res *http.Response) error { _ = os.MkdirAll(filepath.Dir(name), 0777) diff --git a/validator_handler.go b/validator_handler.go index 27612e1..c30b0f0 100644 --- a/validator_handler.go +++ b/validator_handler.go @@ -30,5 +30,5 @@ var ErrInvalidHandled = errors.New("handled recovery from invalid response") // and decodes the response as a JSON object // if the DefaultValidator check fails. func ErrorJSON(v any) ResponseHandler { - return ValidatorHandler(DefaultValidator, ToJSON(v)) + return ValidatorHandler(DefaultValidator, toJSON(v)) } From e3ab2b2161959c447414315b551660641091e569 Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:26:58 -0500 Subject: [PATCH 13/14] Make BodyForm content type optional --- builder_extras.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builder_extras.go b/builder_extras.go index e665bd8..7434fa5 100644 --- a/builder_extras.go +++ b/builder_extras.go @@ -150,11 +150,12 @@ func (rb *Builder) BodyJSON(v any) *Builder { } // BodyForm sets the Builder's request body to the encoded form. -// It also sets the ContentType to "application/x-www-form-urlencoded". +// It also sets the ContentType to "application/x-www-form-urlencoded" +// if it is not otherwise set. func (rb *Builder) BodyForm(data url.Values) *Builder { return rb. Body(bodyForm(data)). - ContentType("application/x-www-form-urlencoded") + HeaderOptional("Content-Type", "application/x-www-form-urlencoded") } // BodyFile sets the Builder's request body to read from the given file path. From a5b38702eacc99d0a68099dddff234383c005aed Mon Sep 17 00:00:00 2001 From: Carlana Date: Tue, 10 Nov 2026 14:30:08 -0500 Subject: [PATCH 14/14] Fix doc --- builder_core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder_core.go b/builder_core.go index efbc9a6..5cf7d08 100644 --- a/builder_core.go +++ b/builder_core.go @@ -59,7 +59,7 @@ import ( // // Set a handler for a response with [Builder.Handle] // or use the built in [Builder.ToHeaders], [Builder.ToJSON], [Builder.ToString], -// [Builder.ToBytesBuffer], or [Builder.ToWriter]. +// or [Builder.ToWriter]. // // [Builder.Fetch] creates an http.Request with [Builder.Request] // and validates and handles it with [Builder.Do].