https://github.com/en9inerd/go-pkgs
A collection of utility packages, helpers, and experiments written in Go.
https://github.com/en9inerd/go-pkgs
go go-lib go-library golang golang-library
Last synced: 3 months ago
JSON representation
A collection of utility packages, helpers, and experiments written in Go.
- Host: GitHub
- URL: https://github.com/en9inerd/go-pkgs
- Owner: en9inerd
- Created: 2025-09-05T18:54:34.000Z (9 months ago)
- Default Branch: master
- Last Pushed: 2025-09-14T15:17:33.000Z (9 months ago)
- Last Synced: 2025-09-14T17:27:00.544Z (9 months ago)
- Topics: go, go-lib, go-library, golang, golang-library
- Language: Go
- Homepage:
- Size: 23.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# go-pkgs
Go library for personal use.
A collection of utility packages, helpers, and experiments written in Go.
## Usage
```bash
go get github.com/en9inerd/go-pkgs
```
## Documentation
The package documentation is auto-generated using [gomarkdoc](https://github.com/princjef/gomarkdoc).
# httpclient
```go
import "github.com/en9inerd/go-pkgs/httpclient"
```
Package httpclient provides HTTP client utilities for making API requests
## Index
- [func DecodeJSONResponse\(resp \*http.Response, target any\) error](<#DecodeJSONResponse>)
- [type Client](<#Client>)
- [func New\(\) \*Client](<#New>)
- [func NewWithConfig\(cfg Config\) \*Client](<#NewWithConfig>)
- [func \(c \*Client\) Delete\(ctx context.Context, path string\) \(\*http.Response, error\)](<#Client.Delete>)
- [func \(c \*Client\) DeleteJSON\(ctx context.Context, path string, target any\) error](<#Client.DeleteJSON>)
- [func \(c \*Client\) Do\(ctx context.Context, req \*http.Request\) \(\*http.Response, error\)](<#Client.Do>)
- [func \(c \*Client\) Get\(ctx context.Context, path string\) \(\*http.Response, error\)](<#Client.Get>)
- [func \(c \*Client\) GetJSON\(ctx context.Context, path string, target any\) error](<#Client.GetJSON>)
- [func \(c \*Client\) Patch\(ctx context.Context, path string, body any\) \(\*http.Response, error\)](<#Client.Patch>)
- [func \(c \*Client\) PatchJSON\(ctx context.Context, path string, body any, target any\) error](<#Client.PatchJSON>)
- [func \(c \*Client\) Post\(ctx context.Context, path string, body any\) \(\*http.Response, error\)](<#Client.Post>)
- [func \(c \*Client\) PostJSON\(ctx context.Context, path string, body any, target any\) error](<#Client.PostJSON>)
- [func \(c \*Client\) Put\(ctx context.Context, path string, body any\) \(\*http.Response, error\)](<#Client.Put>)
- [func \(c \*Client\) PutJSON\(ctx context.Context, path string, body any, target any\) error](<#Client.PutJSON>)
- [func \(c \*Client\) WithBaseURL\(baseURL string\) \*Client](<#Client.WithBaseURL>)
- [func \(c \*Client\) WithHTTPClient\(client \*http.Client\) \*Client](<#Client.WithHTTPClient>)
- [func \(c \*Client\) WithHeader\(key, value string\) \*Client](<#Client.WithHeader>)
- [func \(c \*Client\) WithHeaders\(headers map\[string\]string\) \*Client](<#Client.WithHeaders>)
- [func \(c \*Client\) WithLogger\(logger \*slog.Logger\) \*Client](<#Client.WithLogger>)
- [func \(c \*Client\) WithTimeout\(timeout time.Duration\) \*Client](<#Client.WithTimeout>)
- [type Config](<#Config>)
```go
func DecodeJSONResponse(resp *http.Response, target any) error
```
DecodeJSONResponse decodes a JSON response from an HTTP response
Client wraps http.Client with additional utilities
```go
type Client struct {
// contains filtered or unexported fields
}
```
```go
func New() *Client
```
New creates a new HTTP client with default settings
```go
func NewWithConfig(cfg Config) *Client
```
NewWithConfig creates a new HTTP client with custom configuration
```go
func (c *Client) Delete(ctx context.Context, path string) (*http.Response, error)
```
Delete performs a DELETE request
### func \(\*Client\) DeleteJSON
```go
func (c *Client) DeleteJSON(ctx context.Context, path string, target any) error
```
DeleteJSON performs a DELETE request and decodes the JSON response
```go
func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)
```
Do executes an HTTP request
```go
func (c *Client) Get(ctx context.Context, path string) (*http.Response, error)
```
Get performs a GET request
```go
func (c *Client) GetJSON(ctx context.Context, path string, target any) error
```
GetJSON performs a GET request and decodes the JSON response
```go
func (c *Client) Patch(ctx context.Context, path string, body any) (*http.Response, error)
```
Patch performs a PATCH request with JSON body
### func \(\*Client\) PatchJSON
```go
func (c *Client) PatchJSON(ctx context.Context, path string, body any, target any) error
```
PatchJSON performs a PATCH request with JSON body and decodes the JSON response
```go
func (c *Client) Post(ctx context.Context, path string, body any) (*http.Response, error)
```
Post performs a POST request with JSON body
### func \(\*Client\) PostJSON
```go
func (c *Client) PostJSON(ctx context.Context, path string, body any, target any) error
```
PostJSON performs a POST request with JSON body and decodes the JSON response
```go
func (c *Client) Put(ctx context.Context, path string, body any) (*http.Response, error)
```
Put performs a PUT request with JSON body
```go
func (c *Client) PutJSON(ctx context.Context, path string, body any, target any) error
```
PutJSON performs a PUT request with JSON body and decodes the JSON response
### func \(\*Client\) WithBaseURL
```go
func (c *Client) WithBaseURL(baseURL string) *Client
```
WithBaseURL sets the base URL for all requests
### func \(\*Client\) WithHTTPClient
```go
func (c *Client) WithHTTPClient(client *http.Client) *Client
```
WithHTTPClient sets a custom HTTP client
### func \(\*Client\) WithHeader
```go
func (c *Client) WithHeader(key, value string) *Client
```
WithHeader sets a header that will be included in all requests
### func \(\*Client\) WithHeaders
```go
func (c *Client) WithHeaders(headers map[string]string) *Client
```
WithHeaders sets multiple headers
### func \(\*Client\) WithLogger
```go
func (c *Client) WithLogger(logger *slog.Logger) *Client
```
WithLogger sets the logger
### func \(\*Client\) WithTimeout
```go
func (c *Client) WithTimeout(timeout time.Duration) *Client
```
WithTimeout sets the request timeout
Config holds client configuration
```go
type Config struct {
Timeout time.Duration
BaseURL string
Headers map[string]string
Logger *slog.Logger
}
```
# httperrors
```go
import "github.com/en9inerd/go-pkgs/httperrors"
```
Package httperrors provides structured error types and utilities for HTTP services
## Index
- [func IsAPIError\(err error\) bool](<#IsAPIError>)
- [func IsHTTPError\(err error\) bool](<#IsHTTPError>)
- [func IsNetworkError\(err error\) bool](<#IsNetworkError>)
- [func IsValidationError\(err error\) bool](<#IsValidationError>)
- [type APIError](<#APIError>)
- [func NewAPIError\(code int, message string\) \*APIError](<#NewAPIError>)
- [func NewAPIErrorWithDetails\(code int, message, details string\) \*APIError](<#NewAPIErrorWithDetails>)
- [func NewAPIErrorWithErr\(code int, message string, err error\) \*APIError](<#NewAPIErrorWithErr>)
- [func \(e \*APIError\) Error\(\) string](<#APIError.Error>)
- [func \(e \*APIError\) Unwrap\(\) error](<#APIError.Unwrap>)
- [func \(e \*APIError\) WriteJSON\(w http.ResponseWriter\)](<#APIError.WriteJSON>)
- [type Error](<#Error>)
- [func NewError\(code int, message string\) \*Error](<#NewError>)
- [func NewErrorWithDetails\(code int, message, details string\) \*Error](<#NewErrorWithDetails>)
- [func NewErrorWithErr\(code int, message string, err error\) \*Error](<#NewErrorWithErr>)
- [func \(e \*Error\) Error\(\) string](<#Error.Error>)
- [func \(e \*Error\) Unwrap\(\) error](<#Error.Unwrap>)
- [func \(e \*Error\) WriteJSON\(w http.ResponseWriter\)](<#Error.WriteJSON>)
- [type NetworkError](<#NetworkError>)
- [func NewNetworkError\(message string, err error\) \*NetworkError](<#NewNetworkError>)
- [func \(e \*NetworkError\) Error\(\) string](<#NetworkError.Error>)
- [func \(e \*NetworkError\) Unwrap\(\) error](<#NetworkError.Unwrap>)
- [type ValidationError](<#ValidationError>)
- [func NewValidationError\(fieldErrors map\[string\]\[\]string, nonFieldErrors \[\]string\) \*ValidationError](<#NewValidationError>)
- [func \(e \*ValidationError\) Error\(\) string](<#ValidationError.Error>)
- [func \(e \*ValidationError\) WriteJSON\(w http.ResponseWriter\)](<#ValidationError.WriteJSON>)
```go
func IsAPIError(err error) bool
```
IsAPIError checks if an error is an APIError
```go
func IsHTTPError(err error) bool
```
IsHTTPError checks if an error is an HTTP Error
```go
func IsNetworkError(err error) bool
```
IsNetworkError checks if an error is a NetworkError
```go
func IsValidationError(err error) bool
```
IsValidationError checks if an error is a ValidationError
APIError represents an error from an external API
```go
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
Err error `json:"-"`
}
```
```go
func NewAPIError(code int, message string) *APIError
```
NewAPIError creates a new API error
### func NewAPIErrorWithDetails
```go
func NewAPIErrorWithDetails(code int, message, details string) *APIError
```
NewAPIErrorWithDetails creates a new API error with details
```go
func NewAPIErrorWithErr(code int, message string, err error) *APIError
```
NewAPIErrorWithErr creates a new API error wrapping an underlying error
```go
func (e *APIError) Error() string
```
Error implements the error interface
### func \(\*APIError\) Unwrap
```go
func (e *APIError) Unwrap() error
```
Unwrap returns the underlying error
### func \(\*APIError\) WriteJSON
```go
func (e *APIError) WriteJSON(w http.ResponseWriter)
```
WriteJSON writes the API error as JSON to the response
Error represents a structured HTTP error
```go
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
Err error `json:"-"`
}
```
```go
func NewError(code int, message string) *Error
```
NewError creates a new HTTP error
```go
func NewErrorWithDetails(code int, message, details string) *Error
```
NewErrorWithDetails creates a new HTTP error with details
```go
func NewErrorWithErr(code int, message string, err error) *Error
```
NewErrorWithErr creates a new HTTP error wrapping an underlying error
```go
func (e *Error) Error() string
```
Error implements the error interface
```go
func (e *Error) Unwrap() error
```
Unwrap returns the underlying error
### func \(\*Error\) WriteJSON
```go
func (e *Error) WriteJSON(w http.ResponseWriter)
```
WriteJSON writes the error as JSON to the response
NetworkError represents a network\-related error
```go
type NetworkError struct {
Message string `json:"message"`
Err error `json:"-"`
}
```
```go
func NewNetworkError(message string, err error) *NetworkError
```
NewNetworkError creates a new network error
### func \(\*NetworkError\) Error
```go
func (e *NetworkError) Error() string
```
Error implements the error interface
### func \(\*NetworkError\) Unwrap
```go
func (e *NetworkError) Unwrap() error
```
Unwrap returns the underlying error
ValidationError represents a validation error
```go
type ValidationError struct {
FieldErrors map[string][]string `json:"fieldErrors"`
NonFieldErrors []string `json:"nonFieldErrors"`
}
```
```go
func NewValidationError(fieldErrors map[string][]string, nonFieldErrors []string) *ValidationError
```
NewValidationError creates a new validation error
### func \(\*ValidationError\) Error
```go
func (e *ValidationError) Error() string
```
Error implements the error interface
### func \(\*ValidationError\) WriteJSON
```go
func (e *ValidationError) WriteJSON(w http.ResponseWriter)
```
WriteJSON writes the validation error as JSON to the response
# httpjson
```go
import "github.com/en9inerd/go-pkgs/httpjson"
```
Package httpjson provides common helpers for JSON\-based HTTP services
## Index
- [func DecodeJSON\[T any\]\(r \*http.Request, target \*T\) error](<#DecodeJSON>)
- [func DecodeJSONWithLimit\[T any\]\(r \*http.Request, target \*T, maxSize int64\) error](<#DecodeJSONWithLimit>)
- [func ParseDateRange\(r \*http.Request\) \(from, to time.Time, err error\)](<#ParseDateRange>)
- [func SendErrorJSON\(w http.ResponseWriter, r \*http.Request, l \*slog.Logger, code int, err error, msg string\)](<#SendErrorJSON>)
- [func WriteJSON\(w http.ResponseWriter, data any\)](<#WriteJSON>)
- [func WriteJSONAllowHTML\(w http.ResponseWriter, v any\) error](<#WriteJSONAllowHTML>)
- [func WriteJSONBytes\(w http.ResponseWriter, data \[\]byte\)](<#WriteJSONBytes>)
- [func WriteJSONWithStatus\(w http.ResponseWriter, code int, data any\)](<#WriteJSONWithStatus>)
- [type JSON](<#JSON>)
```go
func DecodeJSON[T any](r *http.Request, target *T) error
```
DecodeJSON decodes JSON from request body into the given struct. The request body should be limited using SizeLimit middleware or http.MaxBytesReader to prevent DoS attacks via large JSON payloads.
```go
func DecodeJSONWithLimit[T any](r *http.Request, target *T, maxSize int64) error
```
DecodeJSONWithLimit decodes JSON from request body into the given struct with a size limit. This prevents DoS attacks via large JSON payloads. NOTE: ResponseWriter is passed as nil to MaxBytesReader because this function doesn't have access to it. The read still errors on oversized bodies, but the connection won't be flagged for close. Use SizeLimit middleware for that.
```go
func ParseDateRange(r *http.Request) (from, to time.Time, err error)
```
ParseDateRange extracts "from" and "to" query parameters and parses them as time.Time
```go
func SendErrorJSON(w http.ResponseWriter, r *http.Request, l *slog.Logger, code int, err error, msg string)
```
SendErrorJSON logs the error and sends a JSON error response
```go
func WriteJSON(w http.ResponseWriter, data any)
```
WriteJSON encodes and writes JSON to the response with HTTP 200
```go
func WriteJSONAllowHTML(w http.ResponseWriter, v any) error
```
WriteJSONAllowHTML encodes and writes JSON with HTML characters unescaped.
SECURITY WARNING: This function does not escape HTML characters in JSON values. Only use this function if you are certain that:
- The JSON will be properly escaped when rendered in HTML on the client side
- The JSON data does not contain user\-controlled content that could lead to XSS
For most use cases, use WriteJSON instead, which escapes HTML characters by default.
```go
func WriteJSONBytes(w http.ResponseWriter, data []byte)
```
WriteJSONBytes writes pre\-encoded JSON bytes to the response
```go
func WriteJSONWithStatus(w http.ResponseWriter, code int, data any)
```
WriteJSONWithStatus encodes and writes JSON with the given HTTP status code
JSON is a convenience alias for a generic JSON object
```go
type JSON map[string]any
```
# longpoll
```go
import "github.com/en9inerd/go-pkgs/longpoll"
```
Package longpoll provides a generic client for long polling HTTP requests.
Long polling is a technique where the client makes a request to the server, and the server holds the request open until it has data to send or a timeout occurs. Once the server responds, the client immediately makes another request to continue receiving updates.
This package is designed to work with various long polling APIs, including: \- Telegram Bot API getUpdates \- Custom long polling endpoints \- Server\-sent events alternatives
Key features: \- Dynamic URL updates \(e.g., for offset parameters like Telegram Bot API\) \- Support for both GET and POST requests \- Automatic retry with configurable backoff \- Context cancellation support \- Concurrent polling operations
Example usage with static URL:
```
client := longpoll.NewWithConfig(longpoll.Config{
PollTimeout: 60 * time.Second,
RetryDelay: 1 * time.Second,
MaxRetries: -1, // unlimited
})
ctx := context.Background()
err := client.Poll(ctx, "https://api.example.com/events", func(resp *http.Response) (string, bool, error) {
var data map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return "", false, err
}
// Process the data...
fmt.Printf("Received: %+v\n", data)
// Return empty string to reuse URL, true to continue polling
return "", true, nil
})
```
Example with Telegram Bot API \(dynamic URL updates\):
```
client := longpoll.NewWithConfig(longpoll.Config{
PollTimeout: 50 * time.Second, // Telegram max is 50s
})
botToken := "YOUR_BOT_TOKEN"
baseURL := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates", botToken)
offset := 0
err := client.Poll(ctx, fmt.Sprintf("%s?timeout=50&offset=%d", baseURL, offset),
func(resp *http.Response) (string, bool, error) {
var result struct {
OK bool `json:"ok"`
Result []struct {
UpdateID int `json:"update_id"`
} `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", false, err
}
// Process updates...
if len(result.Result) > 0 {
lastUpdateID := result.Result[len(result.Result)-1].UpdateID
offset = lastUpdateID + 1
}
// Return new URL with updated offset
nextURL := fmt.Sprintf("%s?timeout=50&offset=%d", baseURL, offset)
return nextURL, true, nil
})
```
## Index
- [type Client](<#Client>)
- [func New\(\) \*Client](<#New>)
- [func NewWithConfig\(cfg Config\) \*Client](<#NewWithConfig>)
- [func \(c \*Client\) ActiveCount\(\) int](<#Client.ActiveCount>)
- [func \(c \*Client\) Poll\(ctx context.Context, url string, handler ResponseHandler\) error](<#Client.Poll>)
- [func \(c \*Client\) PollSimple\(ctx context.Context, url string, handler SimpleResponseHandler\) error](<#Client.PollSimple>)
- [func \(c \*Client\) StopAll\(\)](<#Client.StopAll>)
- [func \(c \*Client\) WithBodyBuilder\(builder func\(\) \(io.Reader, error\)\) \*Client](<#Client.WithBodyBuilder>)
- [func \(c \*Client\) WithHeader\(key, value string\) \*Client](<#Client.WithHeader>)
- [func \(c \*Client\) WithHeaders\(headers map\[string\]string\) \*Client](<#Client.WithHeaders>)
- [func \(c \*Client\) WithLogger\(logger \*slog.Logger\) \*Client](<#Client.WithLogger>)
- [func \(c \*Client\) WithMethod\(method string\) \*Client](<#Client.WithMethod>)
- [type Config](<#Config>)
- [type ResponseHandler](<#ResponseHandler>)
- [type SimpleResponseHandler](<#SimpleResponseHandler>)
Client is a long polling HTTP client.
```go
type Client struct {
// contains filtered or unexported fields
}
```
```go
func New() *Client
```
New creates a new long polling client with default settings.
```go
func NewWithConfig(cfg Config) *Client
```
NewWithConfig creates a new long polling client with custom configuration.
### func \(\*Client\) ActiveCount
```go
func (c *Client) ActiveCount() int
```
ActiveCount returns the number of active polling operations.
```go
func (c *Client) Poll(ctx context.Context, url string, handler ResponseHandler) error
```
Poll starts a long polling loop that continuously polls the given URL. The handler function is called for each response. Polling continues until: \- The context is cancelled \- The handler returns shouldContinue=false \- The handler returns an error \- MaxRetries is exceeded \(if set\)
The handler can return a new URL for the next request, or an empty string to reuse the same URL. This is useful for APIs like Telegram Bot API that require updating parameters \(e.g., offset\) between requests.
This method blocks until polling stops. To poll in the background, call it in a goroutine.
Example
```go
// Create a long polling client
client := NewWithConfig(Config{
PollTimeout: 60 * time.Second, // Each poll can take up to 60 seconds
RetryDelay: 1 * time.Second, // Wait 1 second between retries
MaxRetries: -1, // Unlimited retries
Logger: slog.Default(),
})
// Start polling
ctx := context.Background()
err := client.Poll(ctx, "https://api.example.com/events", func(resp *http.Response) (string, bool, error) {
// Process the response
var data map[string]any
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return "", false, err // Stop polling on decode error
}
fmt.Printf("Received: %+v\n", data)
// Continue polling with the same URL (empty string = reuse URL)
return "", true, nil
})
if err != nil {
fmt.Printf("Polling stopped with error: %v\n", err)
}
```
Example (Telegram Bot API)
```go
// Example: Using longpoll client with Telegram Bot API getUpdates
client := NewWithConfig(Config{
PollTimeout: 50 * time.Second, // Telegram max timeout is 50 seconds
RetryDelay: 1 * time.Second,
MaxRetries: -1,
})
botToken := "YOUR_BOT_TOKEN"
baseURL := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates", botToken)
offset := 0
ctx := context.Background()
err := client.Poll(ctx, fmt.Sprintf("%s?timeout=50&offset=%d", baseURL, offset),
func(resp *http.Response) (string, bool, error) {
var result struct {
OK bool `json:"ok"`
Result []struct {
UpdateID int `json:"update_id"`
} `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", false, err
}
if !result.OK {
return "", false, fmt.Errorf("telegram API error")
}
// Process updates
for _, update := range result.Result {
fmt.Printf("Received update: %d\n", update.UpdateID)
// Handle the update...
}
// Update offset for next request
if len(result.Result) > 0 {
lastUpdateID := result.Result[len(result.Result)-1].UpdateID
offset = lastUpdateID + 1
}
// Return new URL with updated offset
nextURL := fmt.Sprintf("%s?timeout=50&offset=%d", baseURL, offset)
return nextURL, true, nil
})
if err != nil {
fmt.Printf("Telegram polling error: %v\n", err)
}
```
Example (With Context)
```go
client := New()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Poll with automatic cancellation after 5 minutes
err := client.Poll(ctx, "https://api.example.com/updates", func(resp *http.Response) (string, bool, error) {
// Process response...
// Return empty string to reuse URL, false to stop, true to continue
return "", true, nil
})
if err != nil {
fmt.Printf("Polling error: %v\n", err)
}
```
### func \(\*Client\) PollSimple
```go
func (c *Client) PollSimple(ctx context.Context, url string, handler SimpleResponseHandler) error
```
PollSimple is a convenience method that uses a SimpleResponseHandler. The URL remains constant across all requests.
```go
func (c *Client) StopAll()
```
StopAll stops all active polling operations.
### func \(\*Client\) WithBodyBuilder
```go
func (c *Client) WithBodyBuilder(builder func() (io.Reader, error)) *Client
```
WithBodyBuilder sets a function that builds the request body for each poll.
### func \(\*Client\) WithHeader
```go
func (c *Client) WithHeader(key, value string) *Client
```
WithHeader adds a header that will be included in all polling requests.
### func \(\*Client\) WithHeaders
```go
func (c *Client) WithHeaders(headers map[string]string) *Client
```
WithHeaders sets multiple headers for all polling requests.
### func \(\*Client\) WithLogger
```go
func (c *Client) WithLogger(logger *slog.Logger) *Client
```
WithLogger sets the logger for the client.
### func \(\*Client\) WithMethod
```go
func (c *Client) WithMethod(method string) *Client
```
WithMethod sets the HTTP method for polling requests \(GET, POST, etc.\).
Config holds configuration for the long polling client.
```go
type Config struct {
// PollTimeout is the timeout for each individual poll request.
// Default: 60 seconds
PollTimeout time.Duration
// RetryDelay is the delay between retries when a request fails.
// Default: 1 second
RetryDelay time.Duration
// MaxRetries is the maximum number of consecutive retries before giving up.
// Set to -1 for unlimited retries. Default: -1
MaxRetries int
// HTTPClient is the underlying HTTP client to use.
// If nil, a default client will be created.
HTTPClient *http.Client
// Logger is an optional logger for debugging.
Logger *slog.Logger
// Headers are additional headers to include in each request.
Headers map[string]string
// Method is the HTTP method to use for requests. Default: GET
Method string
// BodyBuilder returns the request body for each poll.
// If nil, no body is sent.
BodyBuilder func() (io.Reader, error)
}
```
ResponseHandler is a function that processes a long polling response. It receives the HTTP response and should return: \- nextURL: the URL to use for the next request \(empty string to reuse the same URL\) \- shouldContinue: true to continue polling, false to stop \- error: an error to stop polling with an error
This allows handlers to dynamically update request parameters \(e.g., offset for Telegram Bot API\).
```go
type ResponseHandler func(*http.Response) (nextURL string, shouldContinue bool, err error)
```
SimpleResponseHandler is a simplified handler that doesn't modify the URL.
```go
type SimpleResponseHandler func(*http.Response) (bool, error)
```
# middleware
```go
import "github.com/en9inerd/go-pkgs/middleware"
```
## Index
- [func CORS\(cfg CORSConfig\) func\(http.Handler\) http.Handler](<#CORS>)
- [func GlobalThrottle\(limit int64\) func\(http.Handler\) http.Handler](<#GlobalThrottle>)
- [func GlobalThrottleWithConfig\(cfg ThrottleConfig\) func\(http.Handler\) http.Handler](<#GlobalThrottleWithConfig>)
- [func Headers\(headers ...string\) func\(http.Handler\) http.Handler](<#Headers>)
- [func Health\(next http.Handler\) http.Handler](<#Health>)
- [func Logger\(logger \*slog.Logger\) func\(http.Handler\) http.Handler](<#Logger>)
- [func RealIP\(h http.Handler\) http.Handler](<#RealIP>)
- [func RealIPWithTrustedProxies\(trustedProxies \[\]string, h http.Handler\) http.Handler](<#RealIPWithTrustedProxies>)
- [func Recoverer\(logger \*slog.Logger, includeStack bool\) func\(http.Handler\) http.Handler](<#Recoverer>)
- [func SizeLimit\(size int64\) func\(http.Handler\) http.Handler](<#SizeLimit>)
- [func StripSlashes\(next http.Handler\) http.Handler](<#StripSlashes>)
- [func Timeout\(timeout time.Duration\) func\(http.Handler\) http.Handler](<#Timeout>)
- [func TimeoutWithMessage\(timeout time.Duration, message string\) func\(http.Handler\) http.Handler](<#TimeoutWithMessage>)
- [type CORSConfig](<#CORSConfig>)
- [type HealthResponse](<#HealthResponse>)
- [type ThrottleConfig](<#ThrottleConfig>)
```go
func CORS(cfg CORSConfig) func(http.Handler) http.Handler
```
CORS returns a middleware that handles cross\-origin requests. When cfg.Origin is empty the middleware is a no\-op.
```go
func GlobalThrottle(limit int64) func(http.Handler) http.Handler
```
GlobalThrottle returns a middleware that limits the total number of in\-flight requests across all routes in the server.
## func GlobalThrottleWithConfig
```go
func GlobalThrottleWithConfig(cfg ThrottleConfig) func(http.Handler) http.Handler
```
GlobalThrottleWithConfig returns a throttle middleware with custom configuration.
```go
func Headers(headers ...string) func(http.Handler) http.Handler
```
Headers middleware adds headers to response. Header values are sanitized to prevent HTTP header injection attacks.
```go
func Health(next http.Handler) http.Handler
```
```go
func Logger(logger *slog.Logger) func(http.Handler) http.Handler
```
Logger middleware logs each request with method, path, client IP, response status code, and duration.
```go
func RealIP(h http.Handler) http.Handler
```
RealIP is a middleware that sets a http.Request's RemoteAddr to the results of parsing either the X\-Forwarded\-For or X\-Real\-IP headers.
This middleware should only be used if you can trust the headers sent with the request. If reverse proxies are configured to pass along arbitrary header values from the client, or if this middleware is used without a reverse proxy, malicious clients could set anything as X\-Forwarded\-For header and attack the server in various ways.
For a secure version that validates proxy IPs, use RealIPWithTrustedProxies.
## func RealIPWithTrustedProxies
```go
func RealIPWithTrustedProxies(trustedProxies []string, h http.Handler) http.Handler
```
RealIPWithTrustedProxies is a secure version of RealIP that only trusts X\-Forwarded\-For and X\-Real\-IP headers when the request comes from a trusted proxy. This prevents IP spoofing attacks by validating that the RemoteAddr is from a trusted source.
trustedProxies can be:
- nil or empty: Only trust headers if RemoteAddr is a private IP \(assumes behind reverse proxy\). This default behavior is safe if:
- Your server is always behind a reverse proxy, AND
- Direct client connections from private networks are not possible.
- List of CIDR blocks: Only trust headers if RemoteAddr matches one of the CIDRs
- List of IP addresses: Only trust headers if RemoteAddr matches one of the IPs
Example usage:
```
// Explicit trusted proxies (most secure)
middleware.RealIPWithTrustedProxies([]string{"10.0.0.1", "10.0.0.2"}, handler)
// Trust all private IPs (safe if behind reverse proxy)
middleware.RealIPWithTrustedProxies(nil, handler)
```
```go
func Recoverer(logger *slog.Logger, includeStack bool) func(http.Handler) http.Handler
```
Recoverer is a middleware that recovers from panics, logs the panic and returns a HTTP 500 status if possible. If includeStack is true, full stack traces are logged. In production, set includeStack to false to prevent information disclosure if logs are exposed.
```go
func SizeLimit(size int64) func(http.Handler) http.Handler
```
SizeLimit middleware rejects requests with bodies larger than size.
```go
func StripSlashes(next http.Handler) http.Handler
```
StripSlashes removes trailing slashes from URLs
```go
func Timeout(timeout time.Duration) func(http.Handler) http.Handler
```
Timeout creates a timeout middleware with the default message "Request timeout"
```go
func TimeoutWithMessage(timeout time.Duration, message string) func(http.Handler) http.Handler
```
TimeoutWithMessage creates a timeout middleware with a custom message
CORSConfig defines the CORS policy applied by the CORS middleware.
```go
type CORSConfig struct {
// Origin is the value for Access-Control-Allow-Origin.
// Use "*" to allow any origin. Empty string disables the middleware.
Origin string
// Methods lists the allowed HTTP methods for preflight requests.
Methods []string
// Headers lists the allowed request headers for preflight requests.
// Defaults to ["Content-Type"] when empty, because Content-Type
// with application/json is not CORS-safelisted and would otherwise
// silently block most JSON API requests.
Headers []string
// ExposedHeaders lists response headers the browser is allowed to read.
// Empty means no extra headers are exposed.
ExposedHeaders []string
// MaxAge is the preflight cache duration in seconds.
// Defaults to 86400 (24 hours) when zero.
MaxAge int
// Credentials sets Access-Control-Allow-Credentials to "true".
// Must not be used with Origin "*".
Credentials bool
}
```
```go
type HealthResponse struct {
Status string `json:"status"`
}
```
ThrottleConfig holds configuration for the throttle middleware
```go
type ThrottleConfig struct {
Limit int64
Message string
}
```
# ratelimit
```go
import "github.com/en9inerd/go-pkgs/ratelimit"
```
Package ratelimit provides rate limiting utilities for API clients
## Index
- [type FixedWindow](<#FixedWindow>)
- [func NewFixedWindow\(limit int, window time.Duration\) \*FixedWindow](<#NewFixedWindow>)
- [func \(fw \*FixedWindow\) Allow\(\) bool](<#FixedWindow.Allow>)
- [func \(fw \*FixedWindow\) Wait\(ctx context.Context\) error](<#FixedWindow.Wait>)
- [type Limiter](<#Limiter>)
- [type TokenBucket](<#TokenBucket>)
- [func NewTokenBucket\(capacity float64, refillRate float64\) \*TokenBucket](<#NewTokenBucket>)
- [func \(tb \*TokenBucket\) Allow\(\) bool](<#TokenBucket.Allow>)
- [func \(tb \*TokenBucket\) Wait\(ctx context.Context\) error](<#TokenBucket.Wait>)
FixedWindow implements a fixed window rate limiter
```go
type FixedWindow struct {
// contains filtered or unexported fields
}
```
```go
func NewFixedWindow(limit int, window time.Duration) *FixedWindow
```
NewFixedWindow creates a new fixed window rate limiter limit: maximum requests per window window: time window duration
### func \(\*FixedWindow\) Allow
```go
func (fw *FixedWindow) Allow() bool
```
Allow checks if a request is allowed without blocking
### func \(\*FixedWindow\) Wait
```go
func (fw *FixedWindow) Wait(ctx context.Context) error
```
Wait blocks until a request is allowed or context is cancelled
Limiter provides rate limiting functionality
```go
type Limiter interface {
// Wait blocks until the limiter allows the request
Wait(ctx context.Context) error
// Allow checks if a request is allowed without blocking
Allow() bool
}
```
TokenBucket implements a token bucket rate limiter
```go
type TokenBucket struct {
// contains filtered or unexported fields
}
```
```go
func NewTokenBucket(capacity float64, refillRate float64) *TokenBucket
```
NewTokenBucket creates a new token bucket limiter capacity: maximum number of tokens refillRate: tokens added per second
### func \(\*TokenBucket\) Allow
```go
func (tb *TokenBucket) Allow() bool
```
Allow checks if a request is allowed without blocking
### func \(\*TokenBucket\) Wait
```go
func (tb *TokenBucket) Wait(ctx context.Context) error
```
Wait blocks until a token is available or context is cancelled
# realip
```go
import "github.com/en9inerd/go-pkgs/realip"
```
## Index
- [func Get\(r \*http.Request\) \(string, error\)](<#Get>)
- [func IsPrivateIP\(ip net.IP\) bool](<#IsPrivateIP>)
```go
func Get(r *http.Request) (string, error)
```
Get extracts the "real" client IP from the request. It prefers the first public IP found scanning headers right\-to\-left, falls back to the first valid IP seen in headers, then to RemoteAddr.
```go
func IsPrivateIP(ip net.IP) bool
```
IsPrivateIP returns true if the IP address is in a private subnet. This is useful for validating that a request came from a trusted proxy.
# retry
```go
import "github.com/en9inerd/go-pkgs/retry"
```
Package retry provides utilities for retrying operations with configurable strategies
## Index
- [func Do\(ctx context.Context, strategy \*Strategy, fn func\(\) error\) error](<#Do>)
- [func DoWithResult\[T any\]\(ctx context.Context, strategy \*Strategy, fn func\(\) \(T, error\)\) \(T, error\)](<#DoWithResult>)
- [func ExponentialBackoff\(attempt int, initialDelay time.Duration, maxDelay time.Duration, multiplier float64\) time.Duration](<#ExponentialBackoff>)
- [func IsRetryableError\(err error\) bool](<#IsRetryableError>)
- [type Strategy](<#Strategy>)
- [func DefaultStrategy\(\) \*Strategy](<#DefaultStrategy>)
```go
func Do(ctx context.Context, strategy *Strategy, fn func() error) error
```
Do executes a function with retry logic
```go
func DoWithResult[T any](ctx context.Context, strategy *Strategy, fn func() (T, error)) (T, error)
```
DoWithResult executes a function that returns a result with retry logic
```go
func ExponentialBackoff(attempt int, initialDelay time.Duration, maxDelay time.Duration, multiplier float64) time.Duration
```
ExponentialBackoff calculates the delay for exponential backoff. This is a utility function for implementing custom retry logic. The delay is calculated as: initialDelay \* \(multiplier ^ attempt\), capped at maxDelay.
```go
func IsRetryableError(err error) bool
```
IsRetryableError checks if an error should be retried. This is a utility function that can be used with Strategy.RetryableErrors. It returns false for context cancellation/timeout errors, true for all others. Common retryable errors: network errors, timeouts, 5xx status codes.
Strategy defines a retry strategy
```go
type Strategy struct {
MaxAttempts int
InitialDelay time.Duration
MaxDelay time.Duration
Multiplier float64
Jitter bool
RetryableErrors func(error) bool
}
```
```go
func DefaultStrategy() *Strategy
```
DefaultStrategy returns a default retry strategy with exponential backoff
# router
```go
import "github.com/en9inerd/go-pkgs/router"
```
Package router provides a simple way to group routes and apply middleware using Go's standard http.ServeMux \(Go 1.22\+\). It supports:
- Grouping routes under a common base path
- Attaching middleware stacks at the root or per group
- Mounting static file handlers
- Registering handlers with or without HTTP method prefixes
- Defining custom NotFound \(404\) handlers
Example usage:
```
mux := http.NewServeMux()
r := router.New(mux)
// global middleware
r.Use(loggingMiddleware)
// mount API group
api := r.Mount("/api")
api.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
// serve
http.ListenAndServe(":8080", r)
```
Middleware added to the root group executes for every request. Middleware added to a subgroup executes only for that group's routes. The order of middleware application is the same as the order they are added, i.e. first added runs outermost.
Route patterns may be plain paths \("/foo"\) or include an HTTP method prefix \("GET /foo"\). Root "/" patterns are normalized to "/\{$\}" to avoid acting as a catch\-all.
Package router provides a way to group routes and apply middleware. Works with Go's standard http.ServeMux \(Go 1.22\+\).
## Index
- [func Wrap\(handler http.Handler, mw1 func\(http.Handler\) http.Handler, mws ...func\(http.Handler\) http.Handler\) http.Handler](<#Wrap>)
- [type Group](<#Group>)
- [func New\(mux \*http.ServeMux\) \*Group](<#New>)
- [func RootGroup\(mux \*http.ServeMux, basePath string\) \*Group](<#RootGroup>)
- [func \(g \*Group\) Group\(\) \*Group](<#Group.Group>)
- [func \(g \*Group\) Handle\(pattern string, handler http.Handler\)](<#Group.Handle>)
- [func \(g \*Group\) HandleFiles\(pattern string, root http.FileSystem\)](<#Group.HandleFiles>)
- [func \(g \*Group\) HandleFunc\(pattern string, handler http.HandlerFunc\)](<#Group.HandleFunc>)
- [func \(g \*Group\) HandleRoot\(method string, handler http.Handler\)](<#Group.HandleRoot>)
- [func \(g \*Group\) HandleRootFunc\(method string, handler http.HandlerFunc\)](<#Group.HandleRootFunc>)
- [func \(g \*Group\) Handler\(r \*http.Request\) \(h http.Handler, pattern string\)](<#Group.Handler>)
- [func \(g \*Group\) Mount\(basePath string\) \*Group](<#Group.Mount>)
- [func \(g \*Group\) NotFoundHandler\(handler http.HandlerFunc\)](<#Group.NotFoundHandler>)
- [func \(g \*Group\) Route\(fn func\(\*Group\)\)](<#Group.Route>)
- [func \(g \*Group\) ServeHTTP\(w http.ResponseWriter, r \*http.Request\)](<#Group.ServeHTTP>)
- [func \(g \*Group\) Use\(mw func\(http.Handler\) http.Handler, more ...func\(http.Handler\) http.Handler\)](<#Group.Use>)
- [func \(g \*Group\) With\(mw func\(http.Handler\) http.Handler, more ...func\(http.Handler\) http.Handler\) \*Group](<#Group.With>)
```go
func Wrap(handler http.Handler, mw1 func(http.Handler) http.Handler, mws ...func(http.Handler) http.Handler) http.Handler
```
Wrap applies middleware\(s\) around a handler.
Group represents a collection of routes with optional middleware.
```go
type Group struct {
// contains filtered or unexported fields
}
```
```go
func New(mux *http.ServeMux) *Group
```
New creates a new root Group bound to the given mux.
```go
func RootGroup(mux *http.ServeMux, basePath string) *Group
```
RootGroup creates a new root Group with a base path bound to the given mux.
```go
func (g *Group) Group() *Group
```
Group creates a new subgroup with the same middleware stack.
```go
func (g *Group) Handle(pattern string, handler http.Handler)
```
Handle registers a route with middlewares applied.
### func \(\*Group\) HandleFiles
```go
func (g *Group) HandleFiles(pattern string, root http.FileSystem)
```
HandleFiles serves static files.
### func \(\*Group\) HandleFunc
```go
func (g *Group) HandleFunc(pattern string, handler http.HandlerFunc)
```
HandleFunc registers a route handler function.
### func \(\*Group\) HandleRoot
```go
func (g *Group) HandleRoot(method string, handler http.Handler)
```
HandleRoot registers a handler for the group's root without redirect.
### func \(\*Group\) HandleRootFunc
```go
func (g *Group) HandleRootFunc(method string, handler http.HandlerFunc)
```
HandleRootFunc registers a root handler func.
```go
func (g *Group) Handler(r *http.Request) (h http.Handler, pattern string)
```
Handler proxies to mux.Handler.
```go
func (g *Group) Mount(basePath string) *Group
```
Mount creates a new subgroup with a base path.
### func \(\*Group\) NotFoundHandler
```go
func (g *Group) NotFoundHandler(handler http.HandlerFunc)
```
NotFoundHandler sets a custom 404 handler on the root group.
```go
func (g *Group) Route(fn func(*Group))
```
Route configures the group inside the provided function.
### func \(\*Group\) ServeHTTP
```go
func (g *Group) ServeHTTP(w http.ResponseWriter, r *http.Request)
```
ServeHTTP implements http.Handler for the group.
```go
func (g *Group) Use(mw func(http.Handler) http.Handler, more ...func(http.Handler) http.Handler)
```
Use appends middleware\(s\) to the group.
```go
func (g *Group) With(mw func(http.Handler) http.Handler, more ...func(http.Handler) http.Handler) *Group
```
With returns a new group with appended middleware\(s\).
# validator
```go
import "github.com/en9inerd/go-pkgs/validator"
```
Package validator provides functionality for validating and sanitizing data.
## Index
- [func Between\(value, min, max int\) bool](<#Between>)
- [func BetweenFloat\(value, min, max float64\) bool](<#BetweenFloat>)
- [func Blank\(value string\) bool](<#Blank>)
- [func InRange\(value, min, max int\) bool](<#InRange>)
- [func InRangeFloat\(value, min, max float64\) bool](<#InRangeFloat>)
- [func IsAlpha\(value string\) bool](<#IsAlpha>)
- [func IsAlphanumeric\(value string\) bool](<#IsAlphanumeric>)
- [func IsEmail\(value string\) bool](<#IsEmail>)
- [func IsHTTPURL\(value string\) bool](<#IsHTTPURL>)
- [func IsNumber\(value string\) bool](<#IsNumber>)
- [func IsNumeric\(value string\) bool](<#IsNumeric>)
- [func IsURL\(value string\) bool](<#IsURL>)
- [func Matches\(value string, pattern \*regexp.Regexp\) bool](<#Matches>)
- [func MaxChars\(value string, n int\) bool](<#MaxChars>)
- [func MaxDuration\(d, maxDuration time.Duration\) bool](<#MaxDuration>)
- [func MaxFloat\(value, max float64\) bool](<#MaxFloat>)
- [func MaxInt\(value, max int\) bool](<#MaxInt>)
- [func MinChars\(value string, n int\) bool](<#MinChars>)
- [func MinDuration\(d, minDuration time.Duration\) bool](<#MinDuration>)
- [func MinFloat\(value, min float64\) bool](<#MinFloat>)
- [func MinInt\(value, min int\) bool](<#MinInt>)
- [func NotBlank\(value string\) bool](<#NotBlank>)
- [func PermittedValue\[T comparable\]\(value T, permittedValues ...T\) bool](<#PermittedValue>)
- [func ValidateRequest\(req Validatable\) error](<#ValidateRequest>)
- [func ValidateRequestWithValidator\(req Validatable, v \*Validator\) error](<#ValidateRequestWithValidator>)
- [type Validatable](<#Validatable>)
- [type Validator](<#Validator>)
- [func \(v \*Validator\) AddFieldError\(key, message string\)](<#Validator.AddFieldError>)
- [func \(v \*Validator\) AddNonFieldError\(message string\)](<#Validator.AddNonFieldError>)
- [func \(v \*Validator\) CheckField\(ok bool, key, message string\)](<#Validator.CheckField>)
- [func \(v \*Validator\) JSON\(\) \[\]byte](<#Validator.JSON>)
- [func \(v \*Validator\) Valid\(\) bool](<#Validator.Valid>)
```go
func Between(value, min, max int) bool
```
Between returns true if the integer value is between min and max \(inclusive\) Alias for InRange for better readability in some contexts
```go
func BetweenFloat(value, min, max float64) bool
```
BetweenFloat returns true if the float value is between min and max \(inclusive\) Alias for InRangeFloat for better readability in some contexts
```go
func Blank(value string) bool
```
Blank returns true if the string is empty or contains only whitespace.
```go
func InRange(value, min, max int) bool
```
InRange returns true if the integer value is between min and max \(inclusive\)
```go
func InRangeFloat(value, min, max float64) bool
```
InRangeFloat returns true if the float value is between min and max \(inclusive\)
```go
func IsAlpha(value string) bool
```
IsAlpha returns true if the string contains only alphabetic characters
```go
func IsAlphanumeric(value string) bool
```
IsAlphanumeric returns true if the string contains only alphanumeric characters
```go
func IsEmail(value string) bool
```
IsEmail returns true if the string is a valid email address
```go
func IsHTTPURL(value string) bool
```
IsHTTPURL returns true if the string is a valid HTTP or HTTPS URL
```go
func IsNumber(value string) bool
```
IsNumber returns true if the string represents a valid integer.
```go
func IsNumeric(value string) bool
```
IsNumeric returns true if the string contains only numeric characters
```go
func IsURL(value string) bool
```
IsURL returns true if the string is a valid URL
```go
func Matches(value string, pattern *regexp.Regexp) bool
```
Matches returns true if the string matches the provided regular expression.
```go
func MaxChars(value string, n int) bool
```
MaxChars returns true if the string contains no more than n characters.
```go
func MaxDuration(d, maxDuration time.Duration) bool
```
MaxDuration returns true if the duration is less than or equal to maxDuration.
```go
func MaxFloat(value, max float64) bool
```
MaxFloat returns true if value is less than or equal to max.
```go
func MaxInt(value, max int) bool
```
MaxInt returns true if value is less than or equal to max.
```go
func MinChars(value string, n int) bool
```
MinChars returns true if the string contains at least n characters.
```go
func MinDuration(d, minDuration time.Duration) bool
```
MinDuration returns true if the duration is greater than or equal to minDuration.
```go
func MinFloat(value, min float64) bool
```
MinFloat returns true if value is greater than or equal to min.
```go
func MinInt(value, min int) bool
```
MinInt returns true if value is greater than or equal to min.
```go
func NotBlank(value string) bool
```
NotBlank returns true if the string is not empty or whitespace.
```go
func PermittedValue[T comparable](value T, permittedValues ...T) bool
```
PermittedValue returns true if value is among the provided permittedValues.
```go
func ValidateRequest(req Validatable) error
```
ValidateRequest validates a request that implements the Validatable interface and returns an error if validation fails
## func ValidateRequestWithValidator
```go
func ValidateRequestWithValidator(req Validatable, v *Validator) error
```
ValidateRequestWithValidator validates a request using a provided validator This allows for custom validation logic or reusing a validator instance
Validatable defines an interface for structs that can validate themselves using a Validator.
```go
type Validatable interface {
Validate(v *Validator)
}
```
Validator collects field and non\-field validation errors.
```go
type Validator struct {
FieldErrors map[string][]string `json:"fieldErrors"`
NonFieldErrors []string `json:"nonFieldErrors"`
}
```
### func \(\*Validator\) AddFieldError
```go
func (v *Validator) AddFieldError(key, message string)
```
AddFieldError adds an error message to a specific field.
### func \(\*Validator\) AddNonFieldError
```go
func (v *Validator) AddNonFieldError(message string)
```
AddNonFieldError adds a general error message not associated with a specific field.
### func \(\*Validator\) CheckField
```go
func (v *Validator) CheckField(ok bool, key, message string)
```
CheckField adds a field error if the provided condition is false.
```go
func (v *Validator) JSON() []byte
```
JSON returns the validation errors as a JSON byte slice.
### func \(\*Validator\) Valid
```go
func (v *Validator) Valid() bool
```
Valid returns true if there are no field or non\-field validation errors.
Generated by [gomarkdoc]()
## License
MIT