wrap

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2026 License: MIT Imports: 16 Imported by: 0

README

wrap-http

A thin, opinionated wrapper around imroc/req that adds structured error handling, content-encoding-aware decompression, typed sentinel errors for every HTTP status code, and optional easyjson unmarshalling.

Provided as-is, without any warranty or support.

Installation

go get github.com/mightatnight/wrap-http

Quick start

client := wrap.NewReqClient()
defer client.Close()

ctx := context.Background()

// Simple GET
resp := client.Get(ctx, "https://api.example.com/items", nil)
if resp.Err != nil {
    log.Fatal(resp.Err)
}

// POST with JSON body, unmarshal response
var result MyResponse
resp = client.DoWithResult(ctx, http.MethodPost, "https://api.example.com/items",
    map[string]string{"Content-Type": "application/json"},
    MyRequest{Name: "foo"},
    &result,
)
if resp.Err != nil {
    log.Fatal(resp.Err)
}

Core methods

Method Description
Do Execute a request; wraps connection errors with ErrConn.
DoWithErrorHandling Same as Do, plus wraps 4xx/5xx responses with ErrStatus and the matching HTTP sentinel error.
DoWithResult Same as DoWithErrorHandling, plus deserialises the response body into resultTo.
DoWithRequest Execute a pre-built *req.Request.
DoWithHTTPRequest Execute a standard *http.Request and return a *http.Response.

Convenience shorthands — Get, Post, Put, Patch, Delete, Head, Options, Trace, Connect — all delegate to Do.

Error handling

Every error returned through resp.Err is composed with errors.Join, so callers can use errors.Is to check for any layer:

if errors.Is(resp.Err, wrap.ErrStatus) {
    if errors.Is(resp.Err, wrap.ErrNotFound) {
        // 404
    }
}
if errors.Is(resp.Err, wrap.ErrUnmarshal) { /* decode failed */ }
if errors.Is(resp.Err, wrap.ErrConn)      { /* transport error */ }

Sentinel errors exist for every standard 4xx and 5xx status code. Use wrap.CodeToErr(code) to map an arbitrary integer status code to its sentinel (returns ErrUnknownCode for unrecognised codes).

Unmarshalling (DoWithResult)

resultTo accepts two forms:

  • Any pointer (*MyStruct) — standard encoding/json unmarshalling.
  • UnmarshallerFactory — a func() easyjson.Unmarshaler factory; the factory is only called on a successful response, avoiding allocations on error paths.
// Standard JSON
var result MyStruct
client.DoWithResult(ctx, "GET", url, nil, nil, &result)

// EasyJSON (zero allocation on failure)
client.DoWithResult(ctx, "GET", url, nil, nil,
    wrap.UnmarshallerFactory(func() easyjson.Unmarshaler { return &MyStruct{} }),
)

Configuration helpers

Headers
client.SetHeaders(map[string]string{"Authorization": "Bearer token"})
client.SetHeaders(httpHeader)  // http.Header
client.SetHeaders(nil)         // clears all common headers
Cookies
client.SetCookiesArray([]*http.Cookie{{Name: "session", Value: "abc"}})
client.SetCookiesArray(map[string]string{"session": "abc"})
client.SetCookiesArray(nil) // clears all cookies
Proxy
// checkURL is optional; when non-empty a GET is issued to verify connectivity.
err := client.SetProxy("http://proxy.example.com:8080", "https://api.example.com")
Body transformer (decompression)

Calling SetResponseBodyTransformer(nil) installs a default transformer that decodes the response body based on the Content-Encoding response header:

Content-Encoding Algorithm
gzip gzip
deflate zlib (RFC 1950), falling back to raw DEFLATE (RFC 1951)
zlib zlib
br Brotli
zstd Zstandard
identity / absent pass-through

When no Content-Encoding header is present (or it carries an unrecognised value), the transformer probes each format in order of prevalence and returns the raw body unchanged if none match.

A custom function can be provided instead:

client.SetResponseBodyTransformer(func(raw []byte, req *req.Request, resp *req.Response) ([]byte, error) {
    // custom transformation
    return raw, nil
})
Escape hatch
client.Client() // returns the underlying *req.Client for direct configuration

Documentation

Overview

Package wrap provides sentinel errors for HTTP client operations, covering connection failures, response unmarshaling, proxy configuration, and the full range of 4xx/5xx HTTP status codes.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrConn is returned when a connection cannot be established or maintained.
	ErrConn = errors.New("connection")

	// ErrStatus is returned when the server responds with a non-successful HTTP status code.
	ErrStatus = errors.New("status")

	// ErrUnknownCode is returned by [CodeToErr] when the given HTTP status code has no
	// corresponding sentinel error defined in this package.
	ErrUnknownCode = errors.New("unknown status code")

	// ErrUnmarshal is returned when the response body cannot be decoded into the target type.
	ErrUnmarshal = errors.New("unmarshal")

	// ErrInvalidProxy is returned when the provided proxy value is not usable.
	ErrInvalidProxy = errors.New("invalid proxy")

	// ErrInvalidProxyURL is returned when the proxy URL is malformed or cannot be parsed.
	ErrInvalidProxyURL = errors.New("invalid proxy url")

	// ErrInvalidProxyCheckURL is returned when the URL used to verify proxy connectivity is
	// malformed or cannot be parsed.
	ErrInvalidProxyCheckURL = errors.New("invalid proxy check url")
)
View Source
var (
	// ErrInvalidHeadersType is returned when the value supplied for request headers is not
	// a supported type (e.g. map[string]string or http.Header).
	ErrInvalidHeadersType = errors.New("invalid headers type")

	// ErrInvalidCookiesType is returned when the value supplied for request cookies is not
	// a supported type.
	ErrInvalidCookiesType = errors.New("invalid cookies type")
)
View Source
var (

	// ErrBadRequest corresponds to HTTP 400 Bad Request.
	ErrBadRequest = errors.New("bad request")
	// ErrUnauthorized corresponds to HTTP 401 Unauthorized.
	ErrUnauthorized = errors.New("unauthorized")
	// ErrPaymentRequired corresponds to HTTP 402 Payment Required.
	ErrPaymentRequired = errors.New("payment required")
	// ErrForbidden corresponds to HTTP 403 Forbidden.
	ErrForbidden = errors.New("forbidden")
	// ErrNotFound corresponds to HTTP 404 Not Found.
	ErrNotFound = errors.New("not found")
	// ErrMethodNotAllowed corresponds to HTTP 405 Method Not Allowed.
	ErrMethodNotAllowed = errors.New("method not allowed")
	// ErrNotAcceptable corresponds to HTTP 406 Not Acceptable.
	ErrNotAcceptable = errors.New("not acceptable")
	// ErrProxyAuthRequired corresponds to HTTP 407 Proxy Authentication Required.
	ErrProxyAuthRequired = errors.New("proxy authentication required")
	// ErrRequestTimeout corresponds to HTTP 408 Request Timeout.
	ErrRequestTimeout = errors.New("request timeout")
	// ErrConflict corresponds to HTTP 409 Conflict.
	ErrConflict = errors.New("conflict")
	// ErrGone corresponds to HTTP 410 Gone.
	ErrGone = errors.New("gone")
	// ErrLengthRequired corresponds to HTTP 411 Length Required.
	ErrLengthRequired = errors.New("length required")
	// ErrPreconditionFailed corresponds to HTTP 412 Precondition Failed.
	ErrPreconditionFailed = errors.New("precondition failed")
	// ErrRequestEntityTooLarge corresponds to HTTP 413 Request Entity Too Large.
	ErrRequestEntityTooLarge = errors.New("request entity too large")
	// ErrRequestURITooLong corresponds to HTTP 414 Request-URI Too Long.
	ErrRequestURITooLong = errors.New("request uri too long")
	// ErrUnsupportedMediaType corresponds to HTTP 415 Unsupported Media Type.
	ErrUnsupportedMediaType = errors.New("unsupported media type")
	// ErrRequestedRangeNotSatisfiable corresponds to HTTP 416 Requested Range Not Satisfiable.
	ErrRequestedRangeNotSatisfiable = errors.New("requested range not satisfiable")
	// ErrExpectationFailed corresponds to HTTP 417 Expectation Failed.
	ErrExpectationFailed = errors.New("expectation failed")
	// ErrTeapot corresponds to HTTP 418 I'm a Teapot.
	ErrTeapot = errors.New("i'm a teapot")
	// ErrMisdirectedRequest corresponds to HTTP 421 Misdirected Request.
	ErrMisdirectedRequest = errors.New("misdirected request")
	// ErrUnprocessableEntity corresponds to HTTP 422 Unprocessable Entity.
	ErrUnprocessableEntity = errors.New("unprocessable entity")
	// ErrLocked corresponds to HTTP 423 Locked.
	ErrLocked = errors.New("locked")
	// ErrFailedDependency corresponds to HTTP 424 Failed Dependency.
	ErrFailedDependency = errors.New("failed dependency")
	// ErrTooEarly corresponds to HTTP 425 Too Early.
	ErrTooEarly = errors.New("too early")
	// ErrUpgradeRequired corresponds to HTTP 426 Upgrade Required.
	ErrUpgradeRequired = errors.New("upgrade required")
	// ErrPreconditionRequired corresponds to HTTP 428 Precondition Required.
	ErrPreconditionRequired = errors.New("precondition required")
	// ErrTooManyRequests corresponds to HTTP 429 Too Many Requests.
	ErrTooManyRequests = errors.New("too many requests")
	// ErrRequestHeaderFieldsTooLarge corresponds to HTTP 431 Request Header Fields Too Large.
	ErrRequestHeaderFieldsTooLarge = errors.New("request header fields too large")
	// ErrUnavailableForLegalReasons corresponds to HTTP 451 Unavailable For Legal Reasons.
	ErrUnavailableForLegalReasons = errors.New("unavailable for legal reasons")

	// ErrInternalServerError corresponds to HTTP 500 Internal Server Error.
	ErrInternalServerError = errors.New("internal server error")
	// ErrNotImplemented corresponds to HTTP 501 Not Implemented.
	ErrNotImplemented = errors.New("not implemented")
	// ErrBadGateway corresponds to HTTP 502 Bad Gateway.
	ErrBadGateway = errors.New("bad gateway")
	// ErrServiceUnavailable corresponds to HTTP 503 Service Unavailable.
	ErrServiceUnavailable = errors.New("service unavailable")
	// ErrGatewayTimeout corresponds to HTTP 504 Gateway Timeout.
	ErrGatewayTimeout = errors.New("gateway timeout")
	// ErrHTTPVersionNotSupported corresponds to HTTP 505 HTTP Version Not Supported.
	ErrHTTPVersionNotSupported = errors.New("http version not supported")
	// ErrVariantAlsoNegotiates corresponds to HTTP 506 Variant Also Negotiates.
	ErrVariantAlsoNegotiates = errors.New("variant also negotiates")
	// ErrInsufficientStorage corresponds to HTTP 507 Insufficient Storage.
	ErrInsufficientStorage = errors.New("insufficient storage")
	// ErrLoopDetected corresponds to HTTP 508 Loop Detected.
	ErrLoopDetected = errors.New("loop detected")
	// ErrNotExtended corresponds to HTTP 510 Not Extended.
	ErrNotExtended = errors.New("not extended")
	// ErrNetworkAuthenticationRequired corresponds to HTTP 511 Network Authentication Required.
	ErrNetworkAuthenticationRequired = errors.New("network authentication required")
)

Functions

func CodeToErr

func CodeToErr(code int) error

CodeToErr maps an HTTP status code to its corresponding sentinel error. It returns ErrUnknownCode if the status code is not covered by this package.

func NewReqClient

func NewReqClient() *httpClient

Types

type Client

type Client interface {
	// Do execute an HTTP request with the specified method, URL, headers, and body.
	// - ctx: Context for request cancellation and timeout.
	// - method: HTTP method (e.g., "GET", "POST").
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// Returns the HTTP response.
	Do(ctx context.Context, method, url string, headers map[string]string, body any) *req.Response

	// DoWithErrorHandling execute an HTTP request with the specified method, URL, headers, and body.
	// - ctx: Context for request cancellation and timeout.
	// - method: HTTP method (e.g., "GET", "POST").
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// Returns the HTTP response.
	// Does additional checks on the response status code and returns an error if it's not successful.
	DoWithErrorHandling(ctx context.Context, method, url string, headers map[string]string, body any) *req.Response

	// DoWithResult execute an HTTP request with the specified method, URL, headers, and body.
	// - ctx: Context for request cancellation and timeout.
	// - method: HTTP method (e.g., "GET", "POST").
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// - resultTo: Pointer to the variable where we unmarshal the response body OR EasyJSON Unmarshal Factory(to avoid memory allocations when response is unsuccesful).
	// Returns the HTTP response and any error encountered.
	DoWithResult(ctx context.Context, method, url string, headers map[string]string, body any, resultTo any) *req.Response

	// DoWithRequest execute the given req.Request.
	// - request: The req.Request to be executed.
	// Returns the HTTP response.
	DoWithRequest(request *req.Request) *req.Response

	// DoWithRequest execute the given http.Request.
	// - request: The http.Request to be executed.
	// Returns the HTTP response and any error encountered.
	DoWithHTTPRequest(request *http.Request) (*http.Response, error)

	// SetHeaders Sets the headers for the request
	// headers can be a map[string]string, http.Header or nil if you want to delete all headers
	SetHeaders(headers any) error

	// SetCookies Sets cookies for all requests.
	// Cookies can be []*http.Cookie, []http.Cookie ,map[string]string, or nil to remove all cookies.
	SetCookiesArray(cookies any) error

	// SetProxy Sets proxy for all requests.
	// proxy can be a string, *url.URL, or nil to remove the proxy
	// Also give the checkURL to check if the proxy is working
	SetProxy(proxyURL string, checkURL string) error

	// SetResponseBodyTransformer Sets the response body transformer.
	// fn is a function that takes the raw response body, request, and response as input and returns the transformed response body and an error.
	SetResponseBodyTransformer(fn func(rawBody []byte, req *req.Request, resp *req.Response) (transformedBody []byte, err error))

	// Client returns the underlying req.Client used by the Client.
	Client() *req.Client

	// Get sends an HTTP GET request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Get(ctx context.Context, url string, headers map[string]string) *req.Response

	// Post sends an HTTP POST request to the specified URL with optional headers and a request body.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// Returns the HTTP response and any error encountered.
	Post(ctx context.Context, url string, headers map[string]string, body any) *req.Response

	// Put sends an HTTP PUT request to the specified URL with optional headers and a request body.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// Returns the HTTP response and any error encountered.
	Put(ctx context.Context, url string, headers map[string]string, body any) *req.Response

	// Delete sends an HTTP DELETE request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Delete(ctx context.Context, url string, headers map[string]string) *req.Response

	// Patch sends an HTTP PATCH request to the specified URL with optional headers and a request body.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// - body: Optional request body (can be nil).
	// Returns the HTTP response and any error encountered.
	Patch(ctx context.Context, url string, headers map[string]string, body any) *req.Response

	// Head sends an HTTP HEAD request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Head(ctx context.Context, url string, headers map[string]string) *req.Response

	// Options sends an HTTP OPTIONS request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Options(ctx context.Context, url string, headers map[string]string) *req.Response

	// Trace sends an HTTP TRACE request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Trace(ctx context.Context, url string, headers map[string]string) *req.Response

	// Connect sends an HTTP CONNECT request to the specified URL with optional headers.
	// - ctx: Context for request cancellation and timeout.
	// - url: The request URL.
	// - headers: Optional headers to include in the request (map of header name to value).
	// Returns the HTTP response and any error encountered.
	Connect(ctx context.Context, url string, headers map[string]string) *req.Response

	// CloseIdleConnection closes all idle connections, goroutine safe
	CloseIdleConnections()

	// Close closes the HTTP client, it's forbidden to use the client after this method is called.
	Close()
}

type UnmarshallerFactory

type UnmarshallerFactory func() easyjson.Unmarshaler

UnmarshallerFactory is a function that creates a new instance of an easyjson.Unmarshaler Used to avoid unnecessary allocations when response is unsuccessful

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL