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
261 changes: 222 additions & 39 deletions README.md

Large diffs are not rendered by default.

92 changes: 77 additions & 15 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (c *Client) Raw() *req.Client {
// Get issues a GET request using the provided client.
// @group Requests
//
// Example: fetch GitHub pull requests
// Example: fetch GitHub pull requests (typed)
//
// type PullRequest struct {
// Number int `json:"number"`
Expand All @@ -139,6 +139,12 @@ func (c *Client) Raw() *req.Client {
// return
// }
// godump.Dump(res.Body)
//
// Example: bind to a string body
//
// c2 := httpx.New()
// res2 := httpx.Get[string](c2, "https://httpbin.org/uuid")
// _, _ = res2.Body, res2.Err // Body is string
func Get[T any](client *Client, url string, opts ...Option) Result[T] {
return do[T](client, nil, methodGet, url, nil, opts)
}
Expand All @@ -157,7 +163,7 @@ func Get[T any](client *Client, url string, opts ...Option) Result[T] {
//
// c := httpx.New()
// res := httpx.Post[CreateUser, User](c, "https://api.example.com/users", CreateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func Post[In any, Out any](client *Client, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, nil, methodPost, url, body, opts)
}
Expand All @@ -176,7 +182,7 @@ func Post[In any, Out any](client *Client, url string, body In, opts ...Option)
//
// c := httpx.New()
// res := httpx.Put[UpdateUser, User](c, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func Put[In any, Out any](client *Client, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, nil, methodPut, url, body, opts)
}
Expand All @@ -195,7 +201,7 @@ func Put[In any, Out any](client *Client, url string, body In, opts ...Option) R
//
// c := httpx.New()
// res := httpx.Patch[UpdateUser, User](c, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func Patch[In any, Out any](client *Client, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, nil, methodPatch, url, body, opts)
}
Expand All @@ -211,11 +217,35 @@ func Patch[In any, Out any](client *Client, url string, body In, opts ...Option)
//
// c := httpx.New()
// res := httpx.Delete[DeleteResponse](c, "https://api.example.com/users/1")
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is DeleteResponse
func Delete[T any](client *Client, url string, opts ...Option) Result[T] {
return do[T](client, nil, methodDelete, url, nil, opts)
}

// Head issues a HEAD request using the provided client.
// @group Requests
//
// Example: HEAD request
//
// c := httpx.New()
// res := httpx.Head[string](c, "https://example.com")
// _ = res
func Head[T any](client *Client, url string, opts ...Option) Result[T] {
return do[T](client, nil, methodHead, url, nil, opts)
}

// Options issues an OPTIONS request using the provided client.
// @group Requests
//
// Example: OPTIONS request
//
// c := httpx.New()
// res := httpx.Options[string](c, "https://example.com")
// _ = res
func Options[T any](client *Client, url string, opts ...Option) Result[T] {
return do[T](client, nil, methodOptions, url, nil, opts)
}

// GetCtx issues a GET request using the provided client and context.
// @group Requests (Context)
//
Expand All @@ -228,7 +258,7 @@ func Delete[T any](client *Client, url string, opts ...Option) Result[T] {
// c := httpx.New()
// ctx := context.Background()
// res := httpx.GetCtx[User](c, ctx, "https://api.example.com/users/1")
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func GetCtx[T any](client *Client, ctx context.Context, url string, opts ...Option) Result[T] {
return do[T](client, ctx, methodGet, url, nil, opts)
}
Expand All @@ -248,7 +278,7 @@ func GetCtx[T any](client *Client, ctx context.Context, url string, opts ...Opti
// c := httpx.New()
// ctx := context.Background()
// res := httpx.PostCtx[CreateUser, User](c, ctx, "https://api.example.com/users", CreateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func PostCtx[In any, Out any](client *Client, ctx context.Context, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, ctx, methodPost, url, body, opts)
}
Expand All @@ -268,7 +298,7 @@ func PostCtx[In any, Out any](client *Client, ctx context.Context, url string, b
// c := httpx.New()
// ctx := context.Background()
// res := httpx.PutCtx[UpdateUser, User](c, ctx, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func PutCtx[In any, Out any](client *Client, ctx context.Context, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, ctx, methodPut, url, body, opts)
}
Expand All @@ -288,7 +318,7 @@ func PutCtx[In any, Out any](client *Client, ctx context.Context, url string, bo
// c := httpx.New()
// ctx := context.Background()
// res := httpx.PatchCtx[UpdateUser, User](c, ctx, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is User
func PatchCtx[In any, Out any](client *Client, ctx context.Context, url string, body In, opts ...Option) Result[Out] {
return do[Out](client, ctx, methodPatch, url, body, opts)
}
Expand All @@ -305,11 +335,37 @@ func PatchCtx[In any, Out any](client *Client, ctx context.Context, url string,
// c := httpx.New()
// ctx := context.Background()
// res := httpx.DeleteCtx[DeleteResponse](c, ctx, "https://api.example.com/users/1")
// _, _ = res.Body, res.Err
// _, _ = res.Body, res.Err // Body is DeleteResponse
func DeleteCtx[T any](client *Client, ctx context.Context, url string, opts ...Option) Result[T] {
return do[T](client, ctx, methodDelete, url, nil, opts)
}

// HeadCtx issues a HEAD request using the provided client and context.
// @group Requests (Context)
//
// Example: context-aware HEAD
//
// c := httpx.New()
// ctx := context.Background()
// res := httpx.HeadCtx[string](c, ctx, "https://example.com")
// _ = res
func HeadCtx[T any](client *Client, ctx context.Context, url string, opts ...Option) Result[T] {
return do[T](client, ctx, methodHead, url, nil, opts)
}

// OptionsCtx issues an OPTIONS request using the provided client and context.
// @group Requests (Context)
//
// Example: context-aware OPTIONS
//
// c := httpx.New()
// ctx := context.Background()
// res := httpx.OptionsCtx[string](c, ctx, "https://example.com")
// _ = res
func OptionsCtx[T any](client *Client, ctx context.Context, url string, opts ...Option) Result[T] {
return do[T](client, ctx, methodOptions, url, nil, opts)
}

func do[T any](client *Client, ctx context.Context, method, url string, body any, opts []Option) Result[T] {
var res Result[T]

Expand Down Expand Up @@ -370,11 +426,13 @@ func (c *Client) mapError(resp *req.Response) error {
}

const (
methodGet = "GET"
methodPost = "POST"
methodPut = "PUT"
methodPatch = "PATCH"
methodDelete = "DELETE"
methodGet = "GET"
methodPost = "POST"
methodPut = "PUT"
methodPatch = "PATCH"
methodDelete = "DELETE"
methodHead = "HEAD"
methodOptions = "OPTIONS"
)

func send(r *req.Request, method, url string) (*req.Response, error) {
Expand All @@ -389,6 +447,10 @@ func send(r *req.Request, method, url string) (*req.Response, error) {
return r.Patch(url)
case methodDelete:
return r.Delete(url)
case methodHead:
return r.Head(url)
case methodOptions:
return r.Options(url)
default:
return nil, fmt.Errorf("httpx: unsupported method %s", method)
}
Expand Down
65 changes: 65 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,71 @@ func TestErrorMapper(t *testing.T) {
}
}

func TestHeadRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodHead {
t.Fatalf("method = %s", r.Method)
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

client := New()
res := Head[string](client, server.URL)
if res.Err != nil {
t.Fatalf("unexpected error: %v", res.Err)
}
}

func TestOptionsRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodOptions {
t.Fatalf("method = %s", r.Method)
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

client := New()
res := Options[string](client, server.URL)
if res.Err != nil {
t.Fatalf("unexpected error: %v", res.Err)
}
}

func TestHeadCtxRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodHead {
t.Fatalf("method = %s", r.Method)
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

client := New()
ctx := context.Background()
res := HeadCtx[string](client, ctx, server.URL)
if res.Err != nil {
t.Fatalf("unexpected error: %v", res.Err)
}
}

func TestOptionsCtxRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodOptions {
t.Fatalf("method = %s", r.Method)
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

client := New()
ctx := context.Background()
res := OptionsCtx[string](client, ctx, server.URL)
if res.Err != nil {
t.Fatalf("unexpected error: %v", res.Err)
}
}
func TestOptionsApplied(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/users/123" {
Expand Down
2 changes: 2 additions & 0 deletions docs/examplegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ func writeMain(base string, fd *FuncDoc, importPath string) error {
"httptest.": "net/http/httptest",
"context.": "context",
"regexp.": "regexp",
"cookiejar.": "net/http/cookiejar",
"url.": "net/url",
"redis.": "github.com/redis/go-redis/v9",
"time.": "time",
"gocron": "github.com/go-co-op/gocron/v2",
Expand Down
8 changes: 6 additions & 2 deletions examples/auth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ func main() {
// Auth sets the Authorization header using a scheme and token.

// Example: custom auth scheme
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Auth("Token", "abc123"))
// Apply to all requests
c := httpx.New(httpx.Auth("Token", "abc123"))
httpx.Get[string](c, "https://example.com")

// Apply to a single request
httpx.Get[string](httpx.Default(), "https://example.com", httpx.Auth("Token", "abc123"))
}
8 changes: 6 additions & 2 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ func main() {
// Basic sets HTTP basic authentication headers.

// Example: basic auth
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Basic("user", "pass"))
// Apply to all requests
c := httpx.New(httpx.Basic("user", "pass"))
httpx.Get[string](c, "https://example.com")

// Apply to a single request
httpx.Get[string](httpx.Default(), "https://example.com", httpx.Basic("user", "pass"))
}
8 changes: 6 additions & 2 deletions examples/bearer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ func main() {
// Bearer sets the Authorization header with a bearer token.

// Example: bearer auth
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Bearer("token"))
// Apply to all requests
c := httpx.New(httpx.Bearer("token"))
httpx.Get[string](c, "https://example.com")

// Apply to a single request
httpx.Get[string](httpx.Default(), "https://example.com", httpx.Bearer("token"))
}
24 changes: 24 additions & 0 deletions examples/cookiejar/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build ignore
// +build ignore

package main

import (
"github.com/goforj/httpx"
"net/http"
"net/http/cookiejar"
"net/url"
)

func main() {
// CookieJar sets the cookie jar for the client.

// Example: set cookie jar and seed cookies
jar, _ := cookiejar.New(nil)
u, _ := url.Parse("https://example.com")
jar.SetCookies(u, []*http.Cookie{
{Name: "session", Value: "abc123"},
})
c := httpx.New(httpx.CookieJar(jar))
_ = c
}
2 changes: 1 addition & 1 deletion examples/delete/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ func main() {

c := httpx.New()
res := httpx.Delete[DeleteResponse](c, "https://api.example.com/users/1")
_, _ = res.Body, res.Err
_, _ = res.Body, res.Err // Body is DeleteResponse
}
2 changes: 1 addition & 1 deletion examples/deletectx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ func main() {
c := httpx.New()
ctx := context.Background()
res := httpx.DeleteCtx[DeleteResponse](c, ctx, "https://api.example.com/users/1")
_, _ = res.Body, res.Err
_, _ = res.Body, res.Err // Body is DeleteResponse
}
7 changes: 6 additions & 1 deletion examples/get/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func main() {
// Get issues a GET request using the provided client.

// Example: fetch GitHub pull requests
// Example: fetch GitHub pull requests (typed)
type PullRequest struct {
Number int `json:"number"`
Title string `json:"title"`
Expand All @@ -23,4 +23,9 @@ func main() {
return
}
godump.Dump(res.Body)

// Example: bind to a string body
c2 := httpx.New()
res2 := httpx.Get[string](c2, "https://httpbin.org/uuid")
_, _ = res2.Body, res2.Err // Body is string
}
2 changes: 1 addition & 1 deletion examples/getctx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ func main() {
c := httpx.New()
ctx := context.Background()
res := httpx.GetCtx[User](c, ctx, "https://api.example.com/users/1")
_, _ = res.Body, res.Err
_, _ = res.Body, res.Err // Body is User
}
15 changes: 15 additions & 0 deletions examples/head/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build ignore
// +build ignore

package main

import "github.com/goforj/httpx"

func main() {
// Head issues a HEAD request using the provided client.

// Example: HEAD request
c := httpx.New()
res := httpx.Head[string](c, "https://example.com")
_ = res
}
Loading