rest

package
v3.0.0-next.11 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 16 Imported by: 0

README

REST Client

Go Reference

Resilient HTTP client with automatic retries, OpenTelemetry instrumentation, and middleware support built on Resty.

Overview

The rest package provides a production-ready HTTP client with built-in resilience patterns, observability, and extensibility through middleware. Built on top of go-resty, it adds OpenTelemetry tracing, metrics, and customizable request/response processing — while returning library-owned types (rest.Response, typed errors) so callers never depend on resty in their own code.

Features

  • Automatic Retries: Configurable retry logic with exponential backoff (network errors and HTTP 5xx)
  • Library-Owned Response: rest.Response with status predicates — no resty types in the public API
  • Typed Errors: errors.As-friendly error types for 401/403, 404, 5xx, other 4xx, and execution failures
  • OpenTelemetry Integration: Distributed tracing and metrics
  • Middleware System: Extensible request/response processing
  • Timeout Management: Request-level timeout configuration
  • Thread-Safe: Concurrent-safe middleware management

Installation

go get github.com/jasoet/pkg/v3/rest

Quick Start

Basic Usage
package main

import (
    "context"
    "fmt"

    "github.com/jasoet/pkg/v3/rest"
)

func main() {
    // Create client with default config
    client := rest.NewClient()

    // Make request
    ctx := context.Background()
    response, err := client.MakeRequestWithTrace(
        ctx,
        "GET",
        "https://api.example.com/users",
        "",
        nil,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(response.Body)
}
With Custom Configuration
import (
    "time"

    "github.com/jasoet/pkg/v3/rest"
)

config := rest.Config{
    RetryCount:         3,
    RetryWaitTime:      1 * time.Second,
    RetryMaxWaitTime:   5 * time.Second,
    Timeout:            30 * time.Second,
    MaxResponseBodyLog: 2048,
}

client := rest.NewClient(
    rest.WithRestConfig(config),
)
With OpenTelemetry
import (
    "github.com/jasoet/pkg/v3/otel"
    "github.com/jasoet/pkg/v3/rest"
)

// Setup OTel
otelConfig := otel.NewConfig("my-service").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider)

// Create client with OTel
client := rest.NewClient(
    rest.WithOTelConfig(otelConfig),
)

// All requests are automatically traced
response, err := client.MakeRequestWithTrace(ctx, "GET", url, "", nil)

Configuration

Config Struct
type Config struct {
    RetryCount       int           // Number of retry attempts
    RetryWaitTime    time.Duration // Initial retry wait time
    RetryMaxWaitTime time.Duration // Maximum retry wait time
    Timeout          time.Duration // Request timeout

    // Limits bytes of response body stored in logs/errors. 0 = unlimited.
    MaxResponseBodyLog int

    // Optional: Enable OpenTelemetry (nil = disabled)
    OTelConfig       *otel.Config
}
Default Configuration

DefaultRestConfig() returns:

  • RetryCount: 1
  • RetryWaitTime: 2 seconds
  • RetryMaxWaitTime: 10 seconds
  • Timeout: 30 seconds
  • MaxResponseBodyLog: 1024

Response Type

MakeRequest and MakeRequestWithTrace return the library-owned *rest.Response:

type Response struct {
    StatusCode int
    Body       string
    Header     http.Header
}
Status Predicates
resp, err := client.MakeRequest(ctx, "GET", url, "", nil)

resp.IsSuccess()     // 2xx
resp.IsError()       // any status >= 400
resp.IsServerError() // 5xx
resp.IsClientError() // 4xx
resp.IsAuthError()   // 401 or 403
resp.IsNotFound()    // 404

Note: even when a request returns a typed error for a non-2xx status, the *Response is still returned (non-nil) so you can inspect the status, body, and headers.

Error Handling

Non-2xx responses and execution failures produce typed errors. The error types are exported for type switches / errors.As; construction is internal to the package.

Error type Condition Sentinel (errors.Is)
*rest.ExecutionError Network/DNS/timeout failure wraps underlying error
*rest.UnauthorizedError HTTP 401 or 403 rest.ErrUnauthorized
*rest.ResourceNotFoundError HTTP 404 rest.ErrResourceNotFound
*rest.ServerError HTTP 5xx rest.ErrServer
*rest.ResponseError Other HTTP 4xx rest.ErrResponse

Each HTTP error type exposes StatusCode, Msg, and RespBody (truncated to MaxResponseBodyLog).

resp, err := client.MakeRequest(ctx, "GET", url, "", nil)
if err != nil {
    var authErr *rest.UnauthorizedError
    var notFound *rest.ResourceNotFoundError
    var srvErr *rest.ServerError
    var execErr *rest.ExecutionError

    switch {
    case errors.As(err, &authErr):
        log.Printf("auth failed (HTTP %d)", authErr.StatusCode)
    case errors.As(err, &notFound):
        log.Printf("missing resource (HTTP %d)", notFound.StatusCode)
    case errors.As(err, &srvErr):
        log.Printf("server error (HTTP %d): %s", srvErr.StatusCode, srvErr.RespBody)
    case errors.As(err, &execErr):
        log.Printf("request execution failed: %v", execErr.Unwrap())
    default:
        log.Printf("request failed: %v", err)
    }
    return
}

fmt.Println(resp.Body)

Client API

Client Options
// Set configuration
WithRestConfig(config Config)

// Add single middleware
WithMiddleware(middleware Middleware)

// Set multiple middlewares (replaces the chain, including the default LoggingMiddleware)
WithMiddlewares(middlewares ...Middleware)

// Enable OpenTelemetry
WithOTelConfig(cfg *otel.Config)
Methods
// Make HTTP request
MakeRequest(
    ctx context.Context,
    method string,
    url string,
    body string,
    headers map[string]string,
) (*rest.Response, error)

// Make HTTP request with resty trace enabled (populates RequestInfo.TraceInfo for middleware)
MakeRequestWithTrace(
    ctx context.Context,
    method string,
    url string,
    body string,
    headers map[string]string,
) (*rest.Response, error)

// Get current configuration (a copy)
GetRestConfig() *Config

// Middleware management
AddMiddleware(middleware Middleware)
SetMiddlewares(middlewares ...Middleware)
GetMiddlewares() []Middleware
Escape Hatch: GetRestClient

GetRestClient() returns the underlying *resty.Client for advanced use cases the wrapper does not cover — custom TLS configuration, binary request bodies via SetBody(interface{}), automatic result unmarshaling with SetResult, file uploads, etc. Responses from calls made directly through the resty client are resty types and bypass the middleware chain and the typed-error mapping above. Note that GetRestClient() calls still record retry metrics (the retry hook lives on the resty client itself) while bypassing the other middleware telemetry (tracing, logging, request metrics).

client := rest.NewClient()

// Advanced: use resty directly
restyClient := client.GetRestClient()
restyClient.R().
    SetHeader("X-Custom", "value").
    SetQueryParam("page", "1").
    Get("https://api.example.com/users")

Note: mutating the resty client after NewClient returns is not thread-safe for concurrent use with MakeRequest/MakeRequestWithTrace.

Middleware System

Built-in Middleware
LoggingMiddleware

Logs request and response details (method, URL, status code, duration, errors). Added by default when no middleware options are provided.

client := rest.NewClient(
    rest.WithMiddleware(rest.NewLoggingMiddleware()),
)
NoOpMiddleware

Placeholder middleware for testing:

client := rest.NewClient(
    rest.WithMiddleware(rest.NewNoOpMiddleware()),
)
OpenTelemetry Middlewares

Automatically prepended when OTelConfig is provided (the default LoggingMiddleware is dropped in that case, since OTel provides logging):

  1. OTelTracingMiddleware - Distributed tracing
  2. OTelMetricsMiddleware - HTTP client metrics
  3. OTelLoggingMiddleware - Structured logging
Custom Middleware

Implement the Middleware interface:

type Middleware interface {
    BeforeRequest(
        ctx context.Context,
        method string,
        url string,
        body string,
        headers map[string]string,
    ) context.Context

    AfterRequest(ctx context.Context, info RequestInfo)
}

RequestInfo carries method, URL, headers, body, timing, status code, truncated response body, error, and — for MakeRequestWithTrace — a TraceInfo with DNS/TCP/TLS/server/response/total durations.

Example:

type AuthMiddleware struct {
    apiKey string
}

func (m *AuthMiddleware) BeforeRequest(
    ctx context.Context,
    method string,
    url string,
    body string,
    headers map[string]string,
) context.Context {
    headers["Authorization"] = "Bearer " + m.apiKey
    return ctx
}

func (m *AuthMiddleware) AfterRequest(
    ctx context.Context,
    info rest.RequestInfo,
) {
    // Process response
}

// Usage
client := rest.NewClient(
    rest.WithMiddleware(&AuthMiddleware{apiKey: "secret"}),
)

OpenTelemetry Integration

Automatic Tracing

When OTelConfig is provided, all requests are traced:

otelConfig := otel.NewConfig("my-client").
    WithTracerProvider(tracerProvider)

client := rest.NewClient(
    rest.WithOTelConfig(otelConfig),
)

// Creates span for each request
response, _ := client.MakeRequestWithTrace(ctx, "GET", url, "", nil)
Span Attributes

Each HTTP request span (named after the HTTP method, kind=client) includes:

Span Attributes:
  http.request.method: "GET" | "POST" | "PUT" | "DELETE" | ...
  url.full: "https://api.example.com/users"
  http.request.body.size: 42
  http.response.status_code: 200
  http.response.body.size: 1024
  http.request.duration_ms: 150

The span status is Error when the request fails or returns a status >= 400, Ok otherwise. Trace context (W3C TraceContext) is injected into the request headers for distributed tracing. Bodies larger than MaxResponseBodyLog (default 1024 bytes) report the truncated length in the http.client.response.size metric and the http.response.body.size span attribute.

Metrics Collection

Automatic HTTP client metrics:

Metrics:
  http.client.request.count: Counter of total requests ({request})
  http.client.request.duration: Histogram of request durations (ms)
  http.client.request.size: Histogram of request body sizes (By)
  http.client.response.size: Histogram of response body sizes (By)
  http.client.retry.count: Counter of retry attempts ({retry})

Metric Attributes:
  http.request.method: "GET"
  http.response.status_code: 200

http.client.retry.count is wired into resty's retry hook, so it increments on both transport errors and status-based (5xx) retries; it also carries an http.retry.attempt attribute with the resty attempt number. The counter counts failed retryable attempts (retries actually performed), and retries triggered by transport errors lose trace-exemplar correlation because they fall back to context.Background() when no response/request context exists.

Advanced Usage

All HTTP Methods
// GET
response, _ := client.MakeRequest(ctx, "GET", url, "", headers)

// POST
response, _ := client.MakeRequest(ctx, "POST", url, `{"key":"value"}`, headers)

// PUT
response, _ := client.MakeRequest(ctx, "PUT", url, body, headers)

// DELETE
response, _ := client.MakeRequest(ctx, "DELETE", url, "", headers)

// PATCH
response, _ := client.MakeRequest(ctx, "PATCH", url, body, headers)

// HEAD
response, _ := client.MakeRequest(ctx, "HEAD", url, "", headers)

// OPTIONS
response, _ := client.MakeRequest(ctx, "OPTIONS", url, "", headers)

// Custom methods fall back to resty's Execute
response, _ := client.MakeRequest(ctx, "REPORT", url, body, headers)
Custom Headers
headers := map[string]string{
    "Authorization": "Bearer token",
    "Content-Type":  "application/json",
    "X-API-Key":     "secret",
}

response, _ := client.MakeRequest(ctx, "GET", url, "", headers)
Request Body

The body parameter is a string. For binary payloads, use GetRestClient() and build the request directly with resty's SetBody(interface{}).

body := `{
    "name": "John Doe",
    "email": "john@example.com"
}`

response, _ := client.MakeRequest(ctx, "POST", url, body, headers)
Configuration from YAML
import (
    "github.com/jasoet/pkg/v3/config"
    "github.com/jasoet/pkg/v3/rest"
)

type AppConfig struct {
    REST rest.Config `yaml:"rest"`
}

yamlConfig := `
rest:
  retryCount: 3
  retryWaitTime: 1s
  retryMaxWaitTime: 5s
  timeout: 30s
  maxResponseBodyLog: 2048
`

cfg, _ := config.LoadString[AppConfig](yamlConfig)
client := rest.NewClient(rest.WithRestConfig(cfg.REST))

Best Practices

1. Use Context for Cancellation
// ✅ Good: Context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

response, err := client.MakeRequest(ctx, "GET", url, "", nil)
2. Configure Retries Appropriately
// ✅ Good: Reasonable retry config
config := rest.Config{
    RetryCount:       3,               // Retry up to 3 times
    RetryWaitTime:    1 * time.Second, // Start with 1s
    RetryMaxWaitTime: 10 * time.Second, // Cap at 10s
    Timeout:          30 * time.Second,
}

Retries trigger on network errors and HTTP 5xx responses — not on 4xx client errors.

3. Always Enable OTel in Production
// ✅ Good: Observability enabled
client := rest.NewClient(
    rest.WithOTelConfig(otelConfig),
)

// ❌ Bad: No observability
client := rest.NewClient()
4. Reuse Client Instances
// ✅ Good: Singleton client
var httpClient = rest.NewClient(/* config */)

func fetchUser(id string) {
    httpClient.MakeRequest(/* ... */)
}

// ❌ Bad: New client per request
func fetchUser(id string) {
    client := rest.NewClient() // Creates new connection pool
    client.MakeRequest(/* ... */)
}
5. Use Middleware for Cross-Cutting Concerns
// ✅ Good: Centralized auth
type AuthMiddleware struct { /* ... */ }

client := rest.NewClient(
    rest.WithMiddleware(&AuthMiddleware{}),
    rest.WithMiddleware(&RateLimitMiddleware{}),
)

// All requests get auth + rate limiting

Testing

The package ships compile-checked examples (example_test.go) and unit tests backed by httptest servers:

# Run tests
go test ./rest -v

# With coverage
go test ./rest -cover
Test Utilities
import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/jasoet/pkg/v3/rest"
)

func TestMyCode(t *testing.T) {
    // Mock server
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(200)
        w.Write([]byte(`{"status":"ok"}`))
    }))
    defer server.Close()

    // Use no-op middleware for testing
    client := rest.NewClient(
        rest.WithMiddlewares(rest.NewNoOpMiddleware()),
    )

    response, err := client.MakeRequest(
        context.Background(),
        "GET",
        server.URL,
        "",
        nil,
    )

    assert.NoError(t, err)
    assert.Equal(t, 200, response.StatusCode)
    assert.True(t, response.IsSuccess())
}

Troubleshooting

Timeout Errors

Problem: Requests timing out

Solutions:

// 1. Increase timeout
config := rest.Config{
    Timeout: 60 * time.Second, // Longer timeout
    // ...
}

// 2. Use context timeout
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
Retry Not Working

Problem: Client not retrying failed requests

Solutions:

// 1. Check retry configuration
config := rest.Config{
    RetryCount:       3, // Must be > 0
    RetryWaitTime:    1 * time.Second,
    RetryMaxWaitTime: 5 * time.Second,
}

// 2. Verify error is retryable
// The client retries on network errors and 5xx status codes.
// It does NOT retry on 4xx client errors.
OTel Not Tracing

Problem: No spans appearing

Solutions:

// 1. Verify OTel config is provided
client := rest.NewClient(
    rest.WithOTelConfig(otelConfig), // Must be set
)

// 2. Check tracer provider
if otelConfig.IsTracingEnabled() {
    // Tracing is enabled
}

// 3. Ensure context propagation
ctx, span := tracer.Start(ctx, "parent-span")
defer span.End()

client.MakeRequestWithTrace(ctx, /* ... */) // Propagates context

Performance

  • Connection Pooling: Reuses HTTP connections via Resty
  • Low Overhead: Minimal middleware overhead (~microseconds)
  • Efficient Retries: Exponential backoff prevents thundering herd

Examples

See examples/rest/ directory for:

  • Basic HTTP requests
  • OpenTelemetry integration
  • Custom middleware
  • Error handling
  • Retry configuration
  • Authentication patterns

Compile-checked examples also live in example_test.go.

  • otel - OpenTelemetry configuration
  • config - Configuration management
  • server - HTTP server

License

MIT License - see LICENSE for details.

Documentation

Overview

Package rest provides an HTTP client with middleware support, retry logic, and optional OpenTelemetry instrumentation.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrUnauthorized     = errors.New("unauthorized")
	ErrResourceNotFound = errors.New("resource not found")
	ErrServer           = errors.New("server error")
	ErrResponse         = errors.New("response error")
)

Sentinel errors for use with errors.Is.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps a resty HTTP client with middleware and OTel support.

func NewClient

func NewClient(options ...ClientOption) *Client

NewClient creates a new REST client with the given options. For custom TLS configuration, use GetRestClient() to access the underlying resty client and call SetTLSClientConfig().

Example

NewClient builds a client from functional options. Fields not set in the provided Config keep their zero values, so start from DefaultRestConfig when you only want to tweak a few fields.

package main

import (
	"fmt"
	"time"

	"github.com/jasoet/pkg/v3/rest"
)

func main() {
	cfg := rest.DefaultRestConfig()
	cfg.RetryCount = 3
	cfg.Timeout = 10 * time.Second

	client := rest.NewClient(rest.WithRestConfig(*cfg))

	actual := client.GetRestConfig()
	fmt.Println("retryCount:", actual.RetryCount)
	fmt.Println("retryWaitTime:", actual.RetryWaitTime)
	fmt.Println("timeout:", actual.Timeout)
	fmt.Println("maxResponseBodyLog:", actual.MaxResponseBodyLog)

}
Output:
retryCount: 3
retryWaitTime: 2s
timeout: 10s
maxResponseBodyLog: 1024

func (*Client) AddMiddleware

func (c *Client) AddMiddleware(middleware Middleware)

AddMiddleware appends a middleware to the chain.

func (*Client) GetMiddlewares

func (c *Client) GetMiddlewares() []Middleware

GetMiddlewares returns a copy of the current middleware chain.

func (*Client) GetRestClient

func (c *Client) GetRestClient() *resty.Client

GetRestClient returns the underlying resty client. Mutations to this client after NewClient returns are not thread-safe for concurrent use with doRequest.

func (*Client) GetRestConfig

func (c *Client) GetRestConfig() *Config

GetRestConfig returns a copy of the current REST configuration.

func (*Client) MakeRequest

func (c *Client) MakeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) (*Response, error)

MakeRequest executes an HTTP request without resty trace.

The body parameter is a string; for binary payloads, use GetRestClient() and build the request directly with resty's SetBody(interface{}).

Example

MakeRequest returns the library-owned *rest.Response with status predicates. Non-2xx responses also produce a typed error suitable for errors.As.

package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jasoet/pkg/v3/rest"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/missing" {
			w.WriteHeader(http.StatusNotFound)
			_, _ = w.Write([]byte(`{"error":"not found"}`))
			return
		}
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"status":"ok"}`))
	}))
	defer server.Close()

	// Replace the default LoggingMiddleware to keep the example output clean.
	client := rest.NewClient(rest.WithMiddlewares(rest.NewNoOpMiddleware()))
	ctx := context.Background()

	resp, err := client.MakeRequest(ctx, http.MethodGet, server.URL+"/users", "", nil)
	fmt.Println("err:", err)
	fmt.Println("status:", resp.StatusCode)
	fmt.Println("isSuccess:", resp.IsSuccess())
	fmt.Println("isError:", resp.IsError())
	fmt.Println("body:", resp.Body)

	// Non-2xx responses return both the Response and a typed error.
	resp, err = client.MakeRequest(ctx, http.MethodGet, server.URL+"/missing", "", nil)
	var notFound *rest.ResourceNotFoundError
	fmt.Println("notFoundErr:", errors.As(err, &notFound))
	fmt.Println("isNotFound:", resp.IsNotFound())

}
Output:
err: <nil>
status: 200
isSuccess: true
isError: false
body: {"status":"ok"}
notFoundErr: true
isNotFound: true

func (*Client) MakeRequestWithTrace

func (c *Client) MakeRequestWithTrace(ctx context.Context, method string, url string, body string, headers map[string]string) (*Response, error)

MakeRequestWithTrace executes an HTTP request with resty trace enabled.

The body parameter is a string; for binary payloads, use GetRestClient() and build the request directly with resty's SetBody(interface{}).

func (*Client) SetMiddlewares

func (c *Client) SetMiddlewares(middlewares ...Middleware)

SetMiddlewares replaces the entire middleware chain.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client during construction.

func WithMiddleware

func WithMiddleware(middleware Middleware) ClientOption

WithMiddleware appends a single middleware to the existing middleware chain. The lock is not held here because option functions run only during NewClient construction.

func WithMiddlewares

func WithMiddlewares(middlewares ...Middleware) ClientOption

WithMiddlewares replaces the entire middleware chain with the provided middlewares. Use WithMiddleware to append instead. The lock is not held here because option functions run only during NewClient construction.

func WithOTelConfig

func WithOTelConfig(cfg *otel.Config) ClientOption

WithOTelConfig sets the OpenTelemetry configuration for the REST client. When set, adds OTel tracing, metrics, and logging middleware automatically.

func WithRestConfig

func WithRestConfig(restConfig Config) ClientOption

WithRestConfig sets the REST client configuration.

type Config

type Config struct {
	RetryCount       int           `yaml:"retryCount" mapstructure:"retryCount"`
	RetryWaitTime    time.Duration `yaml:"retryWaitTime" mapstructure:"retryWaitTime"`
	RetryMaxWaitTime time.Duration `yaml:"retryMaxWaitTime" mapstructure:"retryMaxWaitTime"`
	Timeout          time.Duration `yaml:"timeout" mapstructure:"timeout"`

	// MaxResponseBodyLog limits the number of bytes of response body stored in logs/errors.
	// 0 means unlimited. Default is 1024.
	MaxResponseBodyLog int `yaml:"maxResponseBodyLog" mapstructure:"maxResponseBodyLog"`

	// OpenTelemetry Configuration (optional - nil disables telemetry)
	OTelConfig *otel.Config `yaml:"-" mapstructure:"-"` // Not serializable from config files
}

Config holds configuration for the REST client.

func DefaultRestConfig

func DefaultRestConfig() *Config

DefaultRestConfig returns a default REST configuration with sensible defaults.

type ExecutionError

type ExecutionError struct {
	Msg string
	Err error
}

ExecutionError represents an error during request execution (e.g. network failure).

func (*ExecutionError) Error

func (e *ExecutionError) Error() string

func (*ExecutionError) Unwrap

func (e *ExecutionError) Unwrap() error

type LoggingMiddleware

type LoggingMiddleware struct{}

LoggingMiddleware logs HTTP requests and responses

func NewLoggingMiddleware

func NewLoggingMiddleware() *LoggingMiddleware

NewLoggingMiddleware creates a new LoggingMiddleware instance

func (*LoggingMiddleware) AfterRequest

func (m *LoggingMiddleware) AfterRequest(ctx context.Context, info RequestInfo)

AfterRequest logs the completion of the request with timing information

func (*LoggingMiddleware) BeforeRequest

func (m *LoggingMiddleware) BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context

BeforeRequest returns the context unchanged; timing is handled via RequestInfo.

type Middleware

type Middleware interface {
	BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context
	AfterRequest(ctx context.Context, info RequestInfo)
}

type NoOpMiddleware

type NoOpMiddleware struct{}

NoOpMiddleware is a middleware that does nothing - useful for testing and as a placeholder

func NewNoOpMiddleware

func NewNoOpMiddleware() *NoOpMiddleware

NewNoOpMiddleware creates a new NoOpMiddleware instance

func (*NoOpMiddleware) AfterRequest

func (m *NoOpMiddleware) AfterRequest(ctx context.Context, info RequestInfo)

AfterRequest does nothing

func (*NoOpMiddleware) BeforeRequest

func (m *NoOpMiddleware) BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context

BeforeRequest does nothing and returns the context unchanged

type OTelLoggingMiddleware

type OTelLoggingMiddleware struct {
	// contains filtered or unexported fields
}

OTelLoggingMiddleware implements structured logging with trace correlation for HTTP client

func NewOTelLoggingMiddleware

func NewOTelLoggingMiddleware(cfg *pkgotel.Config) *OTelLoggingMiddleware

NewOTelLoggingMiddleware creates a new OpenTelemetry logging middleware

func (*OTelLoggingMiddleware) AfterRequest

func (m *OTelLoggingMiddleware) AfterRequest(ctx context.Context, info RequestInfo)

AfterRequest logs the completion of the request with trace correlation

func (*OTelLoggingMiddleware) BeforeRequest

func (m *OTelLoggingMiddleware) BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context

BeforeRequest logs the start of the request

type OTelMetricsMiddleware

type OTelMetricsMiddleware struct {
	// contains filtered or unexported fields
}

OTelMetricsMiddleware implements metrics collection for HTTP client requests

func NewOTelMetricsMiddleware

func NewOTelMetricsMiddleware(cfg *pkgotel.Config) *OTelMetricsMiddleware

NewOTelMetricsMiddleware creates a new OpenTelemetry metrics middleware

func (*OTelMetricsMiddleware) AfterRequest

func (m *OTelMetricsMiddleware) AfterRequest(ctx context.Context, info RequestInfo)

AfterRequest records response metrics

func (*OTelMetricsMiddleware) BeforeRequest

func (m *OTelMetricsMiddleware) BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context

BeforeRequest records request size metrics

type OTelTracingMiddleware

type OTelTracingMiddleware struct {
	// contains filtered or unexported fields
}

OTelTracingMiddleware implements distributed tracing for HTTP client requests

func NewOTelTracingMiddleware

func NewOTelTracingMiddleware(cfg *pkgotel.Config) *OTelTracingMiddleware

NewOTelTracingMiddleware creates a new OpenTelemetry tracing middleware

func (*OTelTracingMiddleware) AfterRequest

func (m *OTelTracingMiddleware) AfterRequest(ctx context.Context, info RequestInfo)

AfterRequest ends the span and records the response status

func (*OTelTracingMiddleware) BeforeRequest

func (m *OTelTracingMiddleware) BeforeRequest(ctx context.Context, method string, url string, body string, headers map[string]string) context.Context

BeforeRequest starts a new span for the HTTP request and injects trace context into headers

type RequestInfo

type RequestInfo struct {
	Method     string
	URL        string
	Headers    map[string]string
	Body       string
	StartTime  time.Time
	EndTime    time.Time
	Duration   time.Duration
	StatusCode int
	Response   string
	Error      error
	TraceInfo  TraceInfo
}

type ResourceNotFoundError

type ResourceNotFoundError struct {
	StatusCode int
	Msg        string
	RespBody   string
}

ResourceNotFoundError represents a 404 Not Found response.

func (*ResourceNotFoundError) Error

func (e *ResourceNotFoundError) Error() string

func (*ResourceNotFoundError) Unwrap

func (e *ResourceNotFoundError) Unwrap() error

type Response

type Response struct {
	StatusCode int
	Body       string
	Header     http.Header
}

Response is the library-owned HTTP response returned by MakeRequest and MakeRequestWithTrace. It decouples callers from the underlying resty types.

func (*Response) IsAuthError

func (r *Response) IsAuthError() bool

IsAuthError returns true for HTTP 401 (Unauthorized) and 403 (Forbidden). Both indicate an access control failure and both map to UnauthorizedError in handleResponse; use StatusCode to distinguish them.

func (*Response) IsClientError

func (r *Response) IsClientError() bool

IsClientError returns true for any HTTP 4xx status code. Note: this overlaps with IsAuthError and IsNotFound; in handleResponse, those are checked first so IsClientError only catches remaining 4xx codes.

func (*Response) IsError

func (r *Response) IsError() bool

IsError returns true for any HTTP status code >= 400.

func (*Response) IsNotFound

func (r *Response) IsNotFound() bool

IsNotFound returns true for HTTP 404 (Not Found).

func (*Response) IsServerError

func (r *Response) IsServerError() bool

IsServerError returns true for HTTP 5xx status codes.

func (*Response) IsSuccess

func (r *Response) IsSuccess() bool

IsSuccess returns true for HTTP 2xx status codes.

type ResponseError

type ResponseError struct {
	StatusCode int
	Msg        string
	RespBody   string
}

ResponseError represents a client-side HTTP error (HTTP 4xx, excluding 401/403/404).

func (*ResponseError) Error

func (e *ResponseError) Error() string

func (*ResponseError) Unwrap

func (e *ResponseError) Unwrap() error

type ServerError

type ServerError struct {
	StatusCode int
	Msg        string
	RespBody   string
}

ServerError represents a server-side failure (HTTP 5xx).

func (*ServerError) Error

func (e *ServerError) Error() string

func (*ServerError) Unwrap

func (e *ServerError) Unwrap() error

type TraceInfo

type TraceInfo struct {
	DNSLookup    time.Duration
	TCPConnTime  time.Duration
	TLSHandshake time.Duration
	ServerTime   time.Duration
	ResponseTime time.Duration
	TotalTime    time.Duration
}

TraceInfo is the library-owned request trace information, populated by MakeRequestWithTrace. It mirrors the duration fields of the underlying transport trace that consumers actually read.

type UnauthorizedError

type UnauthorizedError struct {
	StatusCode int
	Msg        string
	RespBody   string
}

UnauthorizedError represents an authentication or authorization failure (HTTP 401/403).

func (*UnauthorizedError) Error

func (e *UnauthorizedError) Error() string

func (*UnauthorizedError) Unwrap

func (e *UnauthorizedError) Unwrap() error

Jump to

Keyboard shortcuts

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