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
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ formatters:
extra-rules: true
goimports:
local-prefixes:
- github.com/theopenlane/httpsling
- github.com/theopenlane/httpsling
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ default_language_version:

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: detect-private-key
- repo: https://github.com/google/yamlfmt
rev: v0.15.0
rev: v0.17.2
hooks:
- id: yamlfmt
- repo: https://github.com/crate-ci/typos
rev: v1.29.4
- repo: https://github.com/adhtruong/mirrors-typos
rev: v1.38.1
hooks:
- id: typos
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build status](https://badge.buildkite.com/f74a461120ffcadbf7796d5aac8ae8c03a1cbcfda142220074.svg)](https://buildkite.com/theopenlane/httpsling)
[![Build status](https://badge.buildkite.com/f74a461120ffcadbf7796d5aac8ae8c03a1cbcfda142220074.svg)](https://buildkite.com/theopenlane/httpsling?branch=main)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=theopenlane_httpsling&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=theopenlane_httpsling)
[![Go Report Card](https://goreportcard.com/badge/github.com/theopenlane/httpsling)](https://goreportcard.com/report/github.com/theopenlane/httpsling)
[![Go Reference](https://pkg.go.dev/badge/github.com/theopenlane/httpsling.svg)](https://pkg.go.dev/github.com/theopenlane/httpsling)
Expand Down Expand Up @@ -33,7 +33,7 @@ func main() {
}

// Perform a GET request
var out map[string]interface{}
var out map[string]any
resp, err := requester.Receive(&out, httpsling.Get("resource"))
if err != nil {
log.Fatal(err)
Expand All @@ -56,9 +56,17 @@ RequestWithContext(context.Context, ...Option) (*http.Request, error)
Send(...Option) (*http.Response, error)
SendWithContext(context.Context, ...Option) (*http.Response, error)

// build and send the request and parse the response into an interface
Receive(interface{}, ...Option) (*http.Response, []byte, error)
ReceiveWithContext(context.Context, interface{}, ...Option) (*http.Response, error)
// build and send the request and parse the response into a value
Receive(any, ...Option) (*http.Response, error)
ReceiveWithContext(context.Context, any, ...Option) (*http.Response, error)

// typed helper using generics
ReceiveInto[T any](...Option) (*http.Response, T, error)
ReceiveIntoWithContext[T any](context.Context, ...Option) (*http.Response, T, error)

// stream response body into an io.Writer
ReceiveTo(io.Writer, ...Option) (*http.Response, int64, error)
ReceiveToWithContext(context.Context, io.Writer, ...Option) (*http.Response, int64, error)
```

### Configuring BaseURL
Expand Down Expand Up @@ -130,7 +138,7 @@ The library provides a `Receive` to construct and dispatch HTTP. Here are exampl
```go
resp, err := requester.ReceiveWithContext(context.Background(), &out,
httpsling.Post("/path"),
httpsling.Body(map[string]interface{}{"key": "value"})
httpsling.Body(map[string]any{"key": "value"})
)
```

Expand All @@ -139,7 +147,7 @@ The library provides a `Receive` to construct and dispatch HTTP. Here are exampl
```go
resp, err := requester.ReceiveWithContext(context.Background(), &out,
httpsling.Put("/path/123456"),
httpsling.Body(map[string]interface{}{"key": "newValue"})
httpsling.Body(map[string]any{"key": "newValue"})
)
```

Expand Down Expand Up @@ -187,6 +195,8 @@ log.Printf("Status Code: %d\n", resp.StatusCode)
log.Printf("Response Data: %s\n", out.Data)
```

Note: Receive fully reads the response into memory to unmarshal, and restores `resp.Body` so it remains readable by callers.

### Evaluating Response Success

To assess whether the HTTP request was successful:
Expand All @@ -210,4 +220,4 @@ This library was inspired by and built upon the work of several other HTTP clien

## Contributing

See [contributing](.github/CONTRIBUTING.md) for details.
See [contributing](.github/CONTRIBUTING.md) for details.
2 changes: 1 addition & 1 deletion echo/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ func (a *EchoContextAdapter) Err() error {

// Value implements the Value method of the context.Context interface
// used to retrieve a value associated with a specific key from the context
func (a *EchoContextAdapter) Value(key interface{}) interface{} {
func (a *EchoContextAdapter) Value(key any) any {
return a.c.Get(fmt.Sprintf("%v", key))
}
30 changes: 15 additions & 15 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import (
)

func Example() {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"color":"red"}`))
}))
defer s.Close()

var out map[string]string
resp, _ := Receive(
out,
Get(s.URL),
)

fmt.Println(resp.StatusCode)
fmt.Printf("%s", out)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"color":"red"}`))
}))
defer s.Close()

var out map[string]string
resp, _ := Receive(
&out,
Get(s.URL),
)

fmt.Println(resp.StatusCode)
fmt.Printf("%v", out)
}

func Example_receive() {
Expand Down
2 changes: 1 addition & 1 deletion files.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func MimeTypeValidator(validMimeTypes ...string) ValidationFunc {
}
}

// ChainValidators returns a validator that accepts multiple validating criteras
// ChainValidators returns a validator that accepts multiple validating criteria
func ChainValidators(validators ...ValidationFunc) ValidationFunc {
return func(f File) error {
for _, validator := range validators {
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/theopenlane/httpsling

go 1.23.5
go 1.25.1

require (
github.com/felixge/httpsnoop v1.0.4
github.com/google/go-querystring v1.1.0
github.com/stretchr/testify v1.11.1
github.com/theopenlane/utils v0.4.4
github.com/theopenlane/utils v0.5.2
)

require (
Expand All @@ -20,6 +20,6 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/mazrean/formstream v1.1.2
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/theopenlane/echox v0.2.1
github.com/theopenlane/echox v0.2.4
gopkg.in/yaml.v3 v3.0.1 // indirect
)
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/theopenlane/echox v0.2.1 h1:ZhVkimmWxpKITf67oM57SrLWeIdnV8+dNXlC+VzlRaQ=
github.com/theopenlane/echox v0.2.1/go.mod h1:4j/Hx0uoLk5gVzdA83Qqz7xBEmqpoEP+OnzVaw2p6/o=
github.com/theopenlane/utils v0.4.4 h1:4Xb2T+4bjMtf4OL73bWQ1a8zllTt43ryVflRzVaUgmU=
github.com/theopenlane/utils v0.4.4/go.mod h1:lNzPjqQoDM5565s5FRqkmBGO77twAkY3Hxgd38ESo6I=
github.com/theopenlane/echox v0.2.4 h1:bocz1Dfs7d2fkNa8foQqdmeTtkMTQNwe1v20bIGIDps=
github.com/theopenlane/echox v0.2.4/go.mod h1:0cPOHe4SSQHmqP0/n2LsIEzRSogkxSX653bE+PIOVZ8=
github.com/theopenlane/utils v0.5.2 h1:5Hpg+lgSGxBZwirh9DQumTHCBU9Wgopjp7Oug2FA+1c=
github.com/theopenlane/utils v0.5.2/go.mod h1:d7F811pRS817S9wo9SmsSghS5GDgN32BFn6meMM9PM0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
23 changes: 11 additions & 12 deletions httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,17 @@ func Apply(c *http.Client, opts ...Option) error {
}

func newDefaultTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // nolint: mnd
KeepAlive: 30 * time.Second, // nolint: mnd
DualStack: true,
}).DialContext,
MaxIdleConns: 100, // nolint: mnd
IdleConnTimeout: 90 * time.Second, // nolint: mnd
TLSHandshakeTimeout: 10 * time.Second, // nolint: mnd
ExpectContinueTimeout: 1 * time.Second, // nolint: mnd
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // nolint: mnd
KeepAlive: 30 * time.Second, // nolint: mnd
}).DialContext,
MaxIdleConns: 100, // nolint: mnd
IdleConnTimeout: 90 * time.Second, // nolint: mnd
TLSHandshakeTimeout: 10 * time.Second, // nolint: mnd
ExpectContinueTimeout: 1 * time.Second, // nolint: mnd
}
}

// Option is a configuration option for building an http.Client
Expand Down
4 changes: 2 additions & 2 deletions httptestutil/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func DumpToStdout(ts *httptest.Server) {
Dump(ts, os.Stdout)
}

type logFunc func(a ...interface{})
type logFunc func(a ...any)

// Write implements io.Writer
func (f logFunc) Write(p []byte) (n int, err error) {
Expand All @@ -74,6 +74,6 @@ func (f logFunc) Write(p []byte) (n int, err error) {
}

// DumpToLog writes requests and responses to a logging function
func DumpToLog(ts *httptest.Server, logf func(a ...interface{})) {
func DumpToLog(ts *httptest.Server, logf func(a ...any)) {
Dump(ts, logFunc(logf))
}
28 changes: 14 additions & 14 deletions marshaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ var DefaultUnmarshaler Unmarshaler = NewContentTypeUnmarshaler()

// Marshaler marshals values into a []byte
type Marshaler interface {
Marshal(v interface{}) (data []byte, contentType string, err error)
Marshal(v any) (data []byte, contentType string, err error)
}

// Unmarshaler unmarshals a []byte response body into a value
type Unmarshaler interface {
Unmarshal(data []byte, contentType string, v interface{}) error
Unmarshal(data []byte, contentType string, v any) error
}

// MarshalFunc adapts a function to the Marshaler interface
type MarshalFunc func(v interface{}) ([]byte, string, error)
type MarshalFunc func(v any) ([]byte, string, error)

// Apply implements Option
func (f MarshalFunc) Apply(r *Requester) error {
Expand All @@ -35,12 +35,12 @@ func (f MarshalFunc) Apply(r *Requester) error {
}

// Marshal implements the Marshaler interface
func (f MarshalFunc) Marshal(v interface{}) ([]byte, string, error) {
func (f MarshalFunc) Marshal(v any) ([]byte, string, error) {
return f(v)
}

// UnmarshalFunc adapts a function to the Unmarshaler interface
type UnmarshalFunc func(data []byte, contentType string, v interface{}) error
type UnmarshalFunc func(data []byte, contentType string, v any) error

// Apply implements Option
func (f UnmarshalFunc) Apply(r *Requester) error {
Expand All @@ -49,7 +49,7 @@ func (f UnmarshalFunc) Apply(r *Requester) error {
}

// Unmarshal implements the Unmarshaler interface
func (f UnmarshalFunc) Unmarshal(data []byte, contentType string, v interface{}) error {
func (f UnmarshalFunc) Unmarshal(data []byte, contentType string, v any) error {
return f(data, contentType, v)
}

Expand All @@ -59,12 +59,12 @@ type JSONMarshaler struct {
}

// Unmarshal implements Unmarshaler
func (m *JSONMarshaler) Unmarshal(data []byte, _ string, v interface{}) error {
func (m *JSONMarshaler) Unmarshal(data []byte, _ string, v any) error {
return json.Unmarshal(data, v)
}

// Marshal implements Marshaler
func (m *JSONMarshaler) Marshal(v interface{}) (data []byte, contentType string, err error) {
func (m *JSONMarshaler) Marshal(v any) (data []byte, contentType string, err error) {
if m.Indent {
data, err = json.MarshalIndent(v, "", " ")
} else {
Expand All @@ -87,12 +87,12 @@ type XMLMarshaler struct {
}

// Unmarshal implements Unmarshaler
func (*XMLMarshaler) Unmarshal(data []byte, _ string, v interface{}) error {
func (*XMLMarshaler) Unmarshal(data []byte, _ string, v any) error {
return xml.Unmarshal(data, v)
}

// Marshal implements Marshaler
func (m *XMLMarshaler) Marshal(v interface{}) (data []byte, contentType string, err error) {
func (m *XMLMarshaler) Marshal(v any) (data []byte, contentType string, err error) {
if m.Indent {
data, err = xml.MarshalIndent(v, "", " ")
} else {
Expand All @@ -114,14 +114,14 @@ type TextUnmarshaler struct {
}

// Unmarshal implements Unmarshaler
func (*TextUnmarshaler) Unmarshal(data []byte, _ string, v interface{}) error {
func (*TextUnmarshaler) Unmarshal(data []byte, _ string, v any) error {
*(v.(*string)) = string(data)

return nil
}

// Marshal implements Marshaler
func (m *TextUnmarshaler) Marshal(v interface{}) (data []byte, contentType string, err error) {
func (m *TextUnmarshaler) Marshal(v any) (data []byte, contentType string, err error) {
data = []byte(fmt.Sprintf("%v", v))

return data, ContentTypeTextUTF8, nil
Expand All @@ -137,7 +137,7 @@ func (m *TextUnmarshaler) Apply(r *Requester) error {
type FormMarshaler struct{}

// Marshal implements Marshaler
func (*FormMarshaler) Marshal(v interface{}) (data []byte, contentType string, err error) {
func (*FormMarshaler) Marshal(v any) (data []byte, contentType string, err error) {
switch t := v.(type) {
case map[string][]string:
urlV := url.Values(t)
Expand Down Expand Up @@ -191,7 +191,7 @@ func defaultUnmarshalers() map[string]Unmarshaler {
}

// Unmarshal implements Unmarshaler
func (c *ContentTypeUnmarshaler) Unmarshal(data []byte, contentType string, v interface{}) error {
func (c *ContentTypeUnmarshaler) Unmarshal(data []byte, contentType string, v any) error {
if c.Unmarshalers == nil {
c.Unmarshalers = defaultUnmarshalers()
}
Expand Down
Loading
Loading