{"id":31811105,"url":"https://github.com/en9inerd/go-pkgs","last_synced_at":"2026-03-13T07:01:09.258Z","repository":{"id":314761970,"uuid":"1051300968","full_name":"en9inerd/go-pkgs","owner":"en9inerd","description":"A collection of utility packages, helpers, and experiments written in Go.","archived":false,"fork":false,"pushed_at":"2025-09-14T15:17:33.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-14T17:27:00.544Z","etag":null,"topics":["go","go-lib","go-library","golang","golang-library"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/en9inerd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-05T18:54:34.000Z","updated_at":"2025-09-14T16:02:23.000Z","dependencies_parsed_at":"2025-09-14T17:38:29.658Z","dependency_job_id":null,"html_url":"https://github.com/en9inerd/go-pkgs","commit_stats":null,"previous_names":["en9inerd/go-pkgs"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/en9inerd/go-pkgs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/en9inerd%2Fgo-pkgs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/en9inerd%2Fgo-pkgs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/en9inerd%2Fgo-pkgs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/en9inerd%2Fgo-pkgs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/en9inerd","download_url":"https://codeload.github.com/en9inerd/go-pkgs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/en9inerd%2Fgo-pkgs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279006442,"owners_count":26084107,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","go-lib","go-library","golang","golang-library"],"created_at":"2025-10-11T06:22:57.689Z","updated_at":"2026-03-13T07:01:09.141Z","avatar_url":"https://github.com/en9inerd.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-pkgs\n\nGo library for personal use.  \nA collection of utility packages, helpers, and experiments written in Go.\n\n## Usage\n\n```bash\ngo get github.com/en9inerd/go-pkgs\n```\n\n## Documentation\n\nThe package documentation is auto-generated using [gomarkdoc](https://github.com/princjef/gomarkdoc).\n\n\u003c!-- gomarkdoc:embed:start --\u003e\n\n\u003c!-- Code generated by gomarkdoc. DO NOT EDIT --\u003e\n\n# httpclient\n\n```go\nimport \"github.com/en9inerd/go-pkgs/httpclient\"\n```\n\nPackage httpclient provides HTTP client utilities for making API requests\n\n## Index\n\n- [func DecodeJSONResponse\\(resp \\*http.Response, target any\\) error](\u003c#DecodeJSONResponse\u003e)\n- [type Client](\u003c#Client\u003e)\n  - [func New\\(\\) \\*Client](\u003c#New\u003e)\n  - [func NewWithConfig\\(cfg Config\\) \\*Client](\u003c#NewWithConfig\u003e)\n  - [func \\(c \\*Client\\) Delete\\(ctx context.Context, path string\\) \\(\\*http.Response, error\\)](\u003c#Client.Delete\u003e)\n  - [func \\(c \\*Client\\) DeleteJSON\\(ctx context.Context, path string, target any\\) error](\u003c#Client.DeleteJSON\u003e)\n  - [func \\(c \\*Client\\) Do\\(ctx context.Context, req \\*http.Request\\) \\(\\*http.Response, error\\)](\u003c#Client.Do\u003e)\n  - [func \\(c \\*Client\\) Get\\(ctx context.Context, path string\\) \\(\\*http.Response, error\\)](\u003c#Client.Get\u003e)\n  - [func \\(c \\*Client\\) GetJSON\\(ctx context.Context, path string, target any\\) error](\u003c#Client.GetJSON\u003e)\n  - [func \\(c \\*Client\\) Patch\\(ctx context.Context, path string, body any\\) \\(\\*http.Response, error\\)](\u003c#Client.Patch\u003e)\n  - [func \\(c \\*Client\\) PatchJSON\\(ctx context.Context, path string, body any, target any\\) error](\u003c#Client.PatchJSON\u003e)\n  - [func \\(c \\*Client\\) Post\\(ctx context.Context, path string, body any\\) \\(\\*http.Response, error\\)](\u003c#Client.Post\u003e)\n  - [func \\(c \\*Client\\) PostJSON\\(ctx context.Context, path string, body any, target any\\) error](\u003c#Client.PostJSON\u003e)\n  - [func \\(c \\*Client\\) Put\\(ctx context.Context, path string, body any\\) \\(\\*http.Response, error\\)](\u003c#Client.Put\u003e)\n  - [func \\(c \\*Client\\) PutJSON\\(ctx context.Context, path string, body any, target any\\) error](\u003c#Client.PutJSON\u003e)\n  - [func \\(c \\*Client\\) WithBaseURL\\(baseURL string\\) \\*Client](\u003c#Client.WithBaseURL\u003e)\n  - [func \\(c \\*Client\\) WithHTTPClient\\(client \\*http.Client\\) \\*Client](\u003c#Client.WithHTTPClient\u003e)\n  - [func \\(c \\*Client\\) WithHeader\\(key, value string\\) \\*Client](\u003c#Client.WithHeader\u003e)\n  - [func \\(c \\*Client\\) WithHeaders\\(headers map\\[string\\]string\\) \\*Client](\u003c#Client.WithHeaders\u003e)\n  - [func \\(c \\*Client\\) WithLogger\\(logger \\*slog.Logger\\) \\*Client](\u003c#Client.WithLogger\u003e)\n  - [func \\(c \\*Client\\) WithTimeout\\(timeout time.Duration\\) \\*Client](\u003c#Client.WithTimeout\u003e)\n- [type Config](\u003c#Config\u003e)\n\n\n\u003ca name=\"DecodeJSONResponse\"\u003e\u003c/a\u003e\n## func DecodeJSONResponse\n\n```go\nfunc DecodeJSONResponse(resp *http.Response, target any) error\n```\n\nDecodeJSONResponse decodes a JSON response from an HTTP response\n\n\u003ca name=\"Client\"\u003e\u003c/a\u003e\n## type Client\n\nClient wraps http.Client with additional utilities\n\n```go\ntype Client struct {\n    // contains filtered or unexported fields\n}\n```\n\n\u003ca name=\"New\"\u003e\u003c/a\u003e\n### func New\n\n```go\nfunc New() *Client\n```\n\nNew creates a new HTTP client with default settings\n\n\u003ca name=\"NewWithConfig\"\u003e\u003c/a\u003e\n### func NewWithConfig\n\n```go\nfunc NewWithConfig(cfg Config) *Client\n```\n\nNewWithConfig creates a new HTTP client with custom configuration\n\n\u003ca name=\"Client.Delete\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Delete\n\n```go\nfunc (c *Client) Delete(ctx context.Context, path string) (*http.Response, error)\n```\n\nDelete performs a DELETE request\n\n\u003ca name=\"Client.DeleteJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) DeleteJSON\n\n```go\nfunc (c *Client) DeleteJSON(ctx context.Context, path string, target any) error\n```\n\nDeleteJSON performs a DELETE request and decodes the JSON response\n\n\u003ca name=\"Client.Do\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Do\n\n```go\nfunc (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)\n```\n\nDo executes an HTTP request\n\n\u003ca name=\"Client.Get\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Get\n\n```go\nfunc (c *Client) Get(ctx context.Context, path string) (*http.Response, error)\n```\n\nGet performs a GET request\n\n\u003ca name=\"Client.GetJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) GetJSON\n\n```go\nfunc (c *Client) GetJSON(ctx context.Context, path string, target any) error\n```\n\nGetJSON performs a GET request and decodes the JSON response\n\n\u003ca name=\"Client.Patch\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Patch\n\n```go\nfunc (c *Client) Patch(ctx context.Context, path string, body any) (*http.Response, error)\n```\n\nPatch performs a PATCH request with JSON body\n\n\u003ca name=\"Client.PatchJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) PatchJSON\n\n```go\nfunc (c *Client) PatchJSON(ctx context.Context, path string, body any, target any) error\n```\n\nPatchJSON performs a PATCH request with JSON body and decodes the JSON response\n\n\u003ca name=\"Client.Post\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Post\n\n```go\nfunc (c *Client) Post(ctx context.Context, path string, body any) (*http.Response, error)\n```\n\nPost performs a POST request with JSON body\n\n\u003ca name=\"Client.PostJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) PostJSON\n\n```go\nfunc (c *Client) PostJSON(ctx context.Context, path string, body any, target any) error\n```\n\nPostJSON performs a POST request with JSON body and decodes the JSON response\n\n\u003ca name=\"Client.Put\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Put\n\n```go\nfunc (c *Client) Put(ctx context.Context, path string, body any) (*http.Response, error)\n```\n\nPut performs a PUT request with JSON body\n\n\u003ca name=\"Client.PutJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) PutJSON\n\n```go\nfunc (c *Client) PutJSON(ctx context.Context, path string, body any, target any) error\n```\n\nPutJSON performs a PUT request with JSON body and decodes the JSON response\n\n\u003ca name=\"Client.WithBaseURL\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithBaseURL\n\n```go\nfunc (c *Client) WithBaseURL(baseURL string) *Client\n```\n\nWithBaseURL sets the base URL for all requests\n\n\u003ca name=\"Client.WithHTTPClient\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithHTTPClient\n\n```go\nfunc (c *Client) WithHTTPClient(client *http.Client) *Client\n```\n\nWithHTTPClient sets a custom HTTP client\n\n\u003ca name=\"Client.WithHeader\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithHeader\n\n```go\nfunc (c *Client) WithHeader(key, value string) *Client\n```\n\nWithHeader sets a header that will be included in all requests\n\n\u003ca name=\"Client.WithHeaders\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithHeaders\n\n```go\nfunc (c *Client) WithHeaders(headers map[string]string) *Client\n```\n\nWithHeaders sets multiple headers\n\n\u003ca name=\"Client.WithLogger\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithLogger\n\n```go\nfunc (c *Client) WithLogger(logger *slog.Logger) *Client\n```\n\nWithLogger sets the logger\n\n\u003ca name=\"Client.WithTimeout\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithTimeout\n\n```go\nfunc (c *Client) WithTimeout(timeout time.Duration) *Client\n```\n\nWithTimeout sets the request timeout\n\n\u003ca name=\"Config\"\u003e\u003c/a\u003e\n## type Config\n\nConfig holds client configuration\n\n```go\ntype Config struct {\n    Timeout time.Duration\n    BaseURL string\n    Headers map[string]string\n    Logger  *slog.Logger\n}\n```\n\n# httperrors\n\n```go\nimport \"github.com/en9inerd/go-pkgs/httperrors\"\n```\n\nPackage httperrors provides structured error types and utilities for HTTP services\n\n## Index\n\n- [func IsAPIError\\(err error\\) bool](\u003c#IsAPIError\u003e)\n- [func IsHTTPError\\(err error\\) bool](\u003c#IsHTTPError\u003e)\n- [func IsNetworkError\\(err error\\) bool](\u003c#IsNetworkError\u003e)\n- [func IsValidationError\\(err error\\) bool](\u003c#IsValidationError\u003e)\n- [type APIError](\u003c#APIError\u003e)\n  - [func NewAPIError\\(code int, message string\\) \\*APIError](\u003c#NewAPIError\u003e)\n  - [func NewAPIErrorWithDetails\\(code int, message, details string\\) \\*APIError](\u003c#NewAPIErrorWithDetails\u003e)\n  - [func NewAPIErrorWithErr\\(code int, message string, err error\\) \\*APIError](\u003c#NewAPIErrorWithErr\u003e)\n  - [func \\(e \\*APIError\\) Error\\(\\) string](\u003c#APIError.Error\u003e)\n  - [func \\(e \\*APIError\\) Unwrap\\(\\) error](\u003c#APIError.Unwrap\u003e)\n  - [func \\(e \\*APIError\\) WriteJSON\\(w http.ResponseWriter\\)](\u003c#APIError.WriteJSON\u003e)\n- [type Error](\u003c#Error\u003e)\n  - [func NewError\\(code int, message string\\) \\*Error](\u003c#NewError\u003e)\n  - [func NewErrorWithDetails\\(code int, message, details string\\) \\*Error](\u003c#NewErrorWithDetails\u003e)\n  - [func NewErrorWithErr\\(code int, message string, err error\\) \\*Error](\u003c#NewErrorWithErr\u003e)\n  - [func \\(e \\*Error\\) Error\\(\\) string](\u003c#Error.Error\u003e)\n  - [func \\(e \\*Error\\) Unwrap\\(\\) error](\u003c#Error.Unwrap\u003e)\n  - [func \\(e \\*Error\\) WriteJSON\\(w http.ResponseWriter\\)](\u003c#Error.WriteJSON\u003e)\n- [type NetworkError](\u003c#NetworkError\u003e)\n  - [func NewNetworkError\\(message string, err error\\) \\*NetworkError](\u003c#NewNetworkError\u003e)\n  - [func \\(e \\*NetworkError\\) Error\\(\\) string](\u003c#NetworkError.Error\u003e)\n  - [func \\(e \\*NetworkError\\) Unwrap\\(\\) error](\u003c#NetworkError.Unwrap\u003e)\n- [type ValidationError](\u003c#ValidationError\u003e)\n  - [func NewValidationError\\(fieldErrors map\\[string\\]\\[\\]string, nonFieldErrors \\[\\]string\\) \\*ValidationError](\u003c#NewValidationError\u003e)\n  - [func \\(e \\*ValidationError\\) Error\\(\\) string](\u003c#ValidationError.Error\u003e)\n  - [func \\(e \\*ValidationError\\) WriteJSON\\(w http.ResponseWriter\\)](\u003c#ValidationError.WriteJSON\u003e)\n\n\n\u003ca name=\"IsAPIError\"\u003e\u003c/a\u003e\n## func IsAPIError\n\n```go\nfunc IsAPIError(err error) bool\n```\n\nIsAPIError checks if an error is an APIError\n\n\u003ca name=\"IsHTTPError\"\u003e\u003c/a\u003e\n## func IsHTTPError\n\n```go\nfunc IsHTTPError(err error) bool\n```\n\nIsHTTPError checks if an error is an HTTP Error\n\n\u003ca name=\"IsNetworkError\"\u003e\u003c/a\u003e\n## func IsNetworkError\n\n```go\nfunc IsNetworkError(err error) bool\n```\n\nIsNetworkError checks if an error is a NetworkError\n\n\u003ca name=\"IsValidationError\"\u003e\u003c/a\u003e\n## func IsValidationError\n\n```go\nfunc IsValidationError(err error) bool\n```\n\nIsValidationError checks if an error is a ValidationError\n\n\u003ca name=\"APIError\"\u003e\u003c/a\u003e\n## type APIError\n\nAPIError represents an error from an external API\n\n```go\ntype APIError struct {\n    Code    int    `json:\"code\"`\n    Message string `json:\"message\"`\n    Details string `json:\"details,omitempty\"`\n    Err     error  `json:\"-\"`\n}\n```\n\n\u003ca name=\"NewAPIError\"\u003e\u003c/a\u003e\n### func NewAPIError\n\n```go\nfunc NewAPIError(code int, message string) *APIError\n```\n\nNewAPIError creates a new API error\n\n\u003ca name=\"NewAPIErrorWithDetails\"\u003e\u003c/a\u003e\n### func NewAPIErrorWithDetails\n\n```go\nfunc NewAPIErrorWithDetails(code int, message, details string) *APIError\n```\n\nNewAPIErrorWithDetails creates a new API error with details\n\n\u003ca name=\"NewAPIErrorWithErr\"\u003e\u003c/a\u003e\n### func NewAPIErrorWithErr\n\n```go\nfunc NewAPIErrorWithErr(code int, message string, err error) *APIError\n```\n\nNewAPIErrorWithErr creates a new API error wrapping an underlying error\n\n\u003ca name=\"APIError.Error\"\u003e\u003c/a\u003e\n### func \\(\\*APIError\\) Error\n\n```go\nfunc (e *APIError) Error() string\n```\n\nError implements the error interface\n\n\u003ca name=\"APIError.Unwrap\"\u003e\u003c/a\u003e\n### func \\(\\*APIError\\) Unwrap\n\n```go\nfunc (e *APIError) Unwrap() error\n```\n\nUnwrap returns the underlying error\n\n\u003ca name=\"APIError.WriteJSON\"\u003e\u003c/a\u003e\n### func \\(\\*APIError\\) WriteJSON\n\n```go\nfunc (e *APIError) WriteJSON(w http.ResponseWriter)\n```\n\nWriteJSON writes the API error as JSON to the response\n\n\u003ca name=\"Error\"\u003e\u003c/a\u003e\n## type Error\n\nError represents a structured HTTP error\n\n```go\ntype Error struct {\n    Code    int    `json:\"code\"`\n    Message string `json:\"message\"`\n    Details string `json:\"details,omitempty\"`\n    Err     error  `json:\"-\"`\n}\n```\n\n\u003ca name=\"NewError\"\u003e\u003c/a\u003e\n### func NewError\n\n```go\nfunc NewError(code int, message string) *Error\n```\n\nNewError creates a new HTTP error\n\n\u003ca name=\"NewErrorWithDetails\"\u003e\u003c/a\u003e\n### func NewErrorWithDetails\n\n```go\nfunc NewErrorWithDetails(code int, message, details string) *Error\n```\n\nNewErrorWithDetails creates a new HTTP error with details\n\n\u003ca name=\"NewErrorWithErr\"\u003e\u003c/a\u003e\n### func NewErrorWithErr\n\n```go\nfunc NewErrorWithErr(code int, message string, err error) *Error\n```\n\nNewErrorWithErr creates a new HTTP error wrapping an underlying error\n\n\u003ca name=\"Error.Error\"\u003e\u003c/a\u003e\n### func \\(\\*Error\\) Error\n\n```go\nfunc (e *Error) Error() string\n```\n\nError implements the error interface\n\n\u003ca name=\"Error.Unwrap\"\u003e\u003c/a\u003e\n### func \\(\\*Error\\) Unwrap\n\n```go\nfunc (e *Error) Unwrap() error\n```\n\nUnwrap returns the underlying error\n\n\u003ca name=\"Error.WriteJSON\"\u003e\u003c/a\u003e\n### func \\(\\*Error\\) WriteJSON\n\n```go\nfunc (e *Error) WriteJSON(w http.ResponseWriter)\n```\n\nWriteJSON writes the error as JSON to the response\n\n\u003ca name=\"NetworkError\"\u003e\u003c/a\u003e\n## type NetworkError\n\nNetworkError represents a network\\-related error\n\n```go\ntype NetworkError struct {\n    Message string `json:\"message\"`\n    Err     error  `json:\"-\"`\n}\n```\n\n\u003ca name=\"NewNetworkError\"\u003e\u003c/a\u003e\n### func NewNetworkError\n\n```go\nfunc NewNetworkError(message string, err error) *NetworkError\n```\n\nNewNetworkError creates a new network error\n\n\u003ca name=\"NetworkError.Error\"\u003e\u003c/a\u003e\n### func \\(\\*NetworkError\\) Error\n\n```go\nfunc (e *NetworkError) Error() string\n```\n\nError implements the error interface\n\n\u003ca name=\"NetworkError.Unwrap\"\u003e\u003c/a\u003e\n### func \\(\\*NetworkError\\) Unwrap\n\n```go\nfunc (e *NetworkError) Unwrap() error\n```\n\nUnwrap returns the underlying error\n\n\u003ca name=\"ValidationError\"\u003e\u003c/a\u003e\n## type ValidationError\n\nValidationError represents a validation error\n\n```go\ntype ValidationError struct {\n    FieldErrors    map[string][]string `json:\"fieldErrors\"`\n    NonFieldErrors []string            `json:\"nonFieldErrors\"`\n}\n```\n\n\u003ca name=\"NewValidationError\"\u003e\u003c/a\u003e\n### func NewValidationError\n\n```go\nfunc NewValidationError(fieldErrors map[string][]string, nonFieldErrors []string) *ValidationError\n```\n\nNewValidationError creates a new validation error\n\n\u003ca name=\"ValidationError.Error\"\u003e\u003c/a\u003e\n### func \\(\\*ValidationError\\) Error\n\n```go\nfunc (e *ValidationError) Error() string\n```\n\nError implements the error interface\n\n\u003ca name=\"ValidationError.WriteJSON\"\u003e\u003c/a\u003e\n### func \\(\\*ValidationError\\) WriteJSON\n\n```go\nfunc (e *ValidationError) WriteJSON(w http.ResponseWriter)\n```\n\nWriteJSON writes the validation error as JSON to the response\n\n# httpjson\n\n```go\nimport \"github.com/en9inerd/go-pkgs/httpjson\"\n```\n\nPackage httpjson provides common helpers for JSON\\-based HTTP services\n\n## Index\n\n- [func DecodeJSON\\[T any\\]\\(r \\*http.Request, target \\*T\\) error](\u003c#DecodeJSON\u003e)\n- [func DecodeJSONWithLimit\\[T any\\]\\(r \\*http.Request, target \\*T, maxSize int64\\) error](\u003c#DecodeJSONWithLimit\u003e)\n- [func ParseDateRange\\(r \\*http.Request\\) \\(from, to time.Time, err error\\)](\u003c#ParseDateRange\u003e)\n- [func SendErrorJSON\\(w http.ResponseWriter, r \\*http.Request, l \\*slog.Logger, code int, err error, msg string\\)](\u003c#SendErrorJSON\u003e)\n- [func WriteJSON\\(w http.ResponseWriter, data any\\)](\u003c#WriteJSON\u003e)\n- [func WriteJSONAllowHTML\\(w http.ResponseWriter, v any\\) error](\u003c#WriteJSONAllowHTML\u003e)\n- [func WriteJSONBytes\\(w http.ResponseWriter, data \\[\\]byte\\)](\u003c#WriteJSONBytes\u003e)\n- [func WriteJSONWithStatus\\(w http.ResponseWriter, code int, data any\\)](\u003c#WriteJSONWithStatus\u003e)\n- [type JSON](\u003c#JSON\u003e)\n\n\n\u003ca name=\"DecodeJSON\"\u003e\u003c/a\u003e\n## func DecodeJSON\n\n```go\nfunc DecodeJSON[T any](r *http.Request, target *T) error\n```\n\nDecodeJSON 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.\n\n\u003ca name=\"DecodeJSONWithLimit\"\u003e\u003c/a\u003e\n## func DecodeJSONWithLimit\n\n```go\nfunc DecodeJSONWithLimit[T any](r *http.Request, target *T, maxSize int64) error\n```\n\nDecodeJSONWithLimit 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.\n\n\u003ca name=\"ParseDateRange\"\u003e\u003c/a\u003e\n## func ParseDateRange\n\n```go\nfunc ParseDateRange(r *http.Request) (from, to time.Time, err error)\n```\n\nParseDateRange extracts \"from\" and \"to\" query parameters and parses them as time.Time\n\n\u003ca name=\"SendErrorJSON\"\u003e\u003c/a\u003e\n## func SendErrorJSON\n\n```go\nfunc SendErrorJSON(w http.ResponseWriter, r *http.Request, l *slog.Logger, code int, err error, msg string)\n```\n\nSendErrorJSON logs the error and sends a JSON error response\n\n\u003ca name=\"WriteJSON\"\u003e\u003c/a\u003e\n## func WriteJSON\n\n```go\nfunc WriteJSON(w http.ResponseWriter, data any)\n```\n\nWriteJSON encodes and writes JSON to the response with HTTP 200\n\n\u003ca name=\"WriteJSONAllowHTML\"\u003e\u003c/a\u003e\n## func WriteJSONAllowHTML\n\n```go\nfunc WriteJSONAllowHTML(w http.ResponseWriter, v any) error\n```\n\nWriteJSONAllowHTML encodes and writes JSON with HTML characters unescaped.\n\nSECURITY WARNING: This function does not escape HTML characters in JSON values. Only use this function if you are certain that:\n\n- The JSON will be properly escaped when rendered in HTML on the client side\n- The JSON data does not contain user\\-controlled content that could lead to XSS\n\nFor most use cases, use WriteJSON instead, which escapes HTML characters by default.\n\n\u003ca name=\"WriteJSONBytes\"\u003e\u003c/a\u003e\n## func WriteJSONBytes\n\n```go\nfunc WriteJSONBytes(w http.ResponseWriter, data []byte)\n```\n\nWriteJSONBytes writes pre\\-encoded JSON bytes to the response\n\n\u003ca name=\"WriteJSONWithStatus\"\u003e\u003c/a\u003e\n## func WriteJSONWithStatus\n\n```go\nfunc WriteJSONWithStatus(w http.ResponseWriter, code int, data any)\n```\n\nWriteJSONWithStatus encodes and writes JSON with the given HTTP status code\n\n\u003ca name=\"JSON\"\u003e\u003c/a\u003e\n## type JSON\n\nJSON is a convenience alias for a generic JSON object\n\n```go\ntype JSON map[string]any\n```\n\n# longpoll\n\n```go\nimport \"github.com/en9inerd/go-pkgs/longpoll\"\n```\n\nPackage longpoll provides a generic client for long polling HTTP requests.\n\nLong 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.\n\nThis package is designed to work with various long polling APIs, including: \\- Telegram Bot API getUpdates \\- Custom long polling endpoints \\- Server\\-sent events alternatives\n\nKey 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\n\nExample usage with static URL:\n\n```\nclient := longpoll.NewWithConfig(longpoll.Config{\n\tPollTimeout: 60 * time.Second,\n\tRetryDelay:  1 * time.Second,\n\tMaxRetries: -1, // unlimited\n})\n\nctx := context.Background()\nerr := client.Poll(ctx, \"https://api.example.com/events\", func(resp *http.Response) (string, bool, error) {\n\tvar data map[string]interface{}\n\tif err := json.NewDecoder(resp.Body).Decode(\u0026data); err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\t// Process the data...\n\tfmt.Printf(\"Received: %+v\\n\", data)\n\n\t// Return empty string to reuse URL, true to continue polling\n\treturn \"\", true, nil\n})\n```\n\nExample with Telegram Bot API \\(dynamic URL updates\\):\n\n```\nclient := longpoll.NewWithConfig(longpoll.Config{\n\tPollTimeout: 50 * time.Second, // Telegram max is 50s\n})\n\nbotToken := \"YOUR_BOT_TOKEN\"\nbaseURL := fmt.Sprintf(\"https://api.telegram.org/bot%s/getUpdates\", botToken)\noffset := 0\n\nerr := client.Poll(ctx, fmt.Sprintf(\"%s?timeout=50\u0026offset=%d\", baseURL, offset),\n\tfunc(resp *http.Response) (string, bool, error) {\n\t\tvar result struct {\n\t\t\tOK     bool `json:\"ok\"`\n\t\t\tResult []struct {\n\t\t\t\tUpdateID int `json:\"update_id\"`\n\t\t\t} `json:\"result\"`\n\t\t}\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(\u0026result); err != nil {\n\t\t\treturn \"\", false, err\n\t\t}\n\n\t\t// Process updates...\n\t\tif len(result.Result) \u003e 0 {\n\t\t\tlastUpdateID := result.Result[len(result.Result)-1].UpdateID\n\t\t\toffset = lastUpdateID + 1\n\t\t}\n\n\t\t// Return new URL with updated offset\n\t\tnextURL := fmt.Sprintf(\"%s?timeout=50\u0026offset=%d\", baseURL, offset)\n\t\treturn nextURL, true, nil\n\t})\n```\n\n## Index\n\n- [type Client](\u003c#Client\u003e)\n  - [func New\\(\\) \\*Client](\u003c#New\u003e)\n  - [func NewWithConfig\\(cfg Config\\) \\*Client](\u003c#NewWithConfig\u003e)\n  - [func \\(c \\*Client\\) ActiveCount\\(\\) int](\u003c#Client.ActiveCount\u003e)\n  - [func \\(c \\*Client\\) Poll\\(ctx context.Context, url string, handler ResponseHandler\\) error](\u003c#Client.Poll\u003e)\n  - [func \\(c \\*Client\\) PollSimple\\(ctx context.Context, url string, handler SimpleResponseHandler\\) error](\u003c#Client.PollSimple\u003e)\n  - [func \\(c \\*Client\\) StopAll\\(\\)](\u003c#Client.StopAll\u003e)\n  - [func \\(c \\*Client\\) WithBodyBuilder\\(builder func\\(\\) \\(io.Reader, error\\)\\) \\*Client](\u003c#Client.WithBodyBuilder\u003e)\n  - [func \\(c \\*Client\\) WithHeader\\(key, value string\\) \\*Client](\u003c#Client.WithHeader\u003e)\n  - [func \\(c \\*Client\\) WithHeaders\\(headers map\\[string\\]string\\) \\*Client](\u003c#Client.WithHeaders\u003e)\n  - [func \\(c \\*Client\\) WithLogger\\(logger \\*slog.Logger\\) \\*Client](\u003c#Client.WithLogger\u003e)\n  - [func \\(c \\*Client\\) WithMethod\\(method string\\) \\*Client](\u003c#Client.WithMethod\u003e)\n- [type Config](\u003c#Config\u003e)\n- [type ResponseHandler](\u003c#ResponseHandler\u003e)\n- [type SimpleResponseHandler](\u003c#SimpleResponseHandler\u003e)\n\n\n\u003ca name=\"Client\"\u003e\u003c/a\u003e\n## type Client\n\nClient is a long polling HTTP client.\n\n```go\ntype Client struct {\n    // contains filtered or unexported fields\n}\n```\n\n\u003ca name=\"New\"\u003e\u003c/a\u003e\n### func New\n\n```go\nfunc New() *Client\n```\n\nNew creates a new long polling client with default settings.\n\n\u003ca name=\"NewWithConfig\"\u003e\u003c/a\u003e\n### func NewWithConfig\n\n```go\nfunc NewWithConfig(cfg Config) *Client\n```\n\nNewWithConfig creates a new long polling client with custom configuration.\n\n\u003ca name=\"Client.ActiveCount\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) ActiveCount\n\n```go\nfunc (c *Client) ActiveCount() int\n```\n\nActiveCount returns the number of active polling operations.\n\n\u003ca name=\"Client.Poll\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) Poll\n\n```go\nfunc (c *Client) Poll(ctx context.Context, url string, handler ResponseHandler) error\n```\n\nPoll 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\\)\n\nThe 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.\n\nThis method blocks until polling stops. To poll in the background, call it in a goroutine.\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\n\u003cp\u003e\n\n\n\n```go\n// Create a long polling client\nclient := NewWithConfig(Config{\n\tPollTimeout: 60 * time.Second, // Each poll can take up to 60 seconds\n\tRetryDelay:  1 * time.Second,  // Wait 1 second between retries\n\tMaxRetries:  -1,               // Unlimited retries\n\tLogger:      slog.Default(),\n})\n\n// Start polling\nctx := context.Background()\nerr := client.Poll(ctx, \"https://api.example.com/events\", func(resp *http.Response) (string, bool, error) {\n\t// Process the response\n\tvar data map[string]any\n\tif err := json.NewDecoder(resp.Body).Decode(\u0026data); err != nil {\n\t\treturn \"\", false, err // Stop polling on decode error\n\t}\n\n\tfmt.Printf(\"Received: %+v\\n\", data)\n\n\t// Continue polling with the same URL (empty string = reuse URL)\n\treturn \"\", true, nil\n})\n\nif err != nil {\n\tfmt.Printf(\"Polling stopped with error: %v\\n\", err)\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eExample (Telegram Bot API)\u003c/summary\u003e\n\u003cp\u003e\n\n\n\n```go\n// Example: Using longpoll client with Telegram Bot API getUpdates\nclient := NewWithConfig(Config{\n\tPollTimeout: 50 * time.Second, // Telegram max timeout is 50 seconds\n\tRetryDelay:  1 * time.Second,\n\tMaxRetries:  -1,\n})\n\nbotToken := \"YOUR_BOT_TOKEN\"\nbaseURL := fmt.Sprintf(\"https://api.telegram.org/bot%s/getUpdates\", botToken)\noffset := 0\n\nctx := context.Background()\nerr := client.Poll(ctx, fmt.Sprintf(\"%s?timeout=50\u0026offset=%d\", baseURL, offset),\n\tfunc(resp *http.Response) (string, bool, error) {\n\t\tvar result struct {\n\t\t\tOK     bool `json:\"ok\"`\n\t\t\tResult []struct {\n\t\t\t\tUpdateID int `json:\"update_id\"`\n\t\t\t} `json:\"result\"`\n\t\t}\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(\u0026result); err != nil {\n\t\t\treturn \"\", false, err\n\t\t}\n\n\t\tif !result.OK {\n\t\t\treturn \"\", false, fmt.Errorf(\"telegram API error\")\n\t\t}\n\n\t\t// Process updates\n\t\tfor _, update := range result.Result {\n\t\t\tfmt.Printf(\"Received update: %d\\n\", update.UpdateID)\n\t\t\t// Handle the update...\n\t\t}\n\n\t\t// Update offset for next request\n\t\tif len(result.Result) \u003e 0 {\n\t\t\tlastUpdateID := result.Result[len(result.Result)-1].UpdateID\n\t\t\toffset = lastUpdateID + 1\n\t\t}\n\n\t\t// Return new URL with updated offset\n\t\tnextURL := fmt.Sprintf(\"%s?timeout=50\u0026offset=%d\", baseURL, offset)\n\t\treturn nextURL, true, nil\n\t})\n\nif err != nil {\n\tfmt.Printf(\"Telegram polling error: %v\\n\", err)\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eExample (With Context)\u003c/summary\u003e\n\u003cp\u003e\n\n\n\n```go\nclient := New()\n\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\n\n// Poll with automatic cancellation after 5 minutes\nerr := client.Poll(ctx, \"https://api.example.com/updates\", func(resp *http.Response) (string, bool, error) {\n\t// Process response...\n\t// Return empty string to reuse URL, false to stop, true to continue\n\treturn \"\", true, nil\n})\n\nif err != nil {\n\tfmt.Printf(\"Polling error: %v\\n\", err)\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"Client.PollSimple\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) PollSimple\n\n```go\nfunc (c *Client) PollSimple(ctx context.Context, url string, handler SimpleResponseHandler) error\n```\n\nPollSimple is a convenience method that uses a SimpleResponseHandler. The URL remains constant across all requests.\n\n\u003ca name=\"Client.StopAll\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) StopAll\n\n```go\nfunc (c *Client) StopAll()\n```\n\nStopAll stops all active polling operations.\n\n\u003ca name=\"Client.WithBodyBuilder\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithBodyBuilder\n\n```go\nfunc (c *Client) WithBodyBuilder(builder func() (io.Reader, error)) *Client\n```\n\nWithBodyBuilder sets a function that builds the request body for each poll.\n\n\u003ca name=\"Client.WithHeader\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithHeader\n\n```go\nfunc (c *Client) WithHeader(key, value string) *Client\n```\n\nWithHeader adds a header that will be included in all polling requests.\n\n\u003ca name=\"Client.WithHeaders\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithHeaders\n\n```go\nfunc (c *Client) WithHeaders(headers map[string]string) *Client\n```\n\nWithHeaders sets multiple headers for all polling requests.\n\n\u003ca name=\"Client.WithLogger\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithLogger\n\n```go\nfunc (c *Client) WithLogger(logger *slog.Logger) *Client\n```\n\nWithLogger sets the logger for the client.\n\n\u003ca name=\"Client.WithMethod\"\u003e\u003c/a\u003e\n### func \\(\\*Client\\) WithMethod\n\n```go\nfunc (c *Client) WithMethod(method string) *Client\n```\n\nWithMethod sets the HTTP method for polling requests \\(GET, POST, etc.\\).\n\n\u003ca name=\"Config\"\u003e\u003c/a\u003e\n## type Config\n\nConfig holds configuration for the long polling client.\n\n```go\ntype Config struct {\n    // PollTimeout is the timeout for each individual poll request.\n    // Default: 60 seconds\n    PollTimeout time.Duration\n\n    // RetryDelay is the delay between retries when a request fails.\n    // Default: 1 second\n    RetryDelay time.Duration\n\n    // MaxRetries is the maximum number of consecutive retries before giving up.\n    // Set to -1 for unlimited retries. Default: -1\n    MaxRetries int\n\n    // HTTPClient is the underlying HTTP client to use.\n    // If nil, a default client will be created.\n    HTTPClient *http.Client\n\n    // Logger is an optional logger for debugging.\n    Logger *slog.Logger\n\n    // Headers are additional headers to include in each request.\n    Headers map[string]string\n\n    // Method is the HTTP method to use for requests. Default: GET\n    Method string\n\n    // BodyBuilder returns the request body for each poll.\n    // If nil, no body is sent.\n    BodyBuilder func() (io.Reader, error)\n}\n```\n\n\u003ca name=\"ResponseHandler\"\u003e\u003c/a\u003e\n## type ResponseHandler\n\nResponseHandler 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\n\nThis allows handlers to dynamically update request parameters \\(e.g., offset for Telegram Bot API\\).\n\n```go\ntype ResponseHandler func(*http.Response) (nextURL string, shouldContinue bool, err error)\n```\n\n\u003ca name=\"SimpleResponseHandler\"\u003e\u003c/a\u003e\n## type SimpleResponseHandler\n\nSimpleResponseHandler is a simplified handler that doesn't modify the URL.\n\n```go\ntype SimpleResponseHandler func(*http.Response) (bool, error)\n```\n\n# middleware\n\n```go\nimport \"github.com/en9inerd/go-pkgs/middleware\"\n```\n\n## Index\n\n- [func CORS\\(cfg CORSConfig\\) func\\(http.Handler\\) http.Handler](\u003c#CORS\u003e)\n- [func GlobalThrottle\\(limit int64\\) func\\(http.Handler\\) http.Handler](\u003c#GlobalThrottle\u003e)\n- [func GlobalThrottleWithConfig\\(cfg ThrottleConfig\\) func\\(http.Handler\\) http.Handler](\u003c#GlobalThrottleWithConfig\u003e)\n- [func Headers\\(headers ...string\\) func\\(http.Handler\\) http.Handler](\u003c#Headers\u003e)\n- [func Health\\(next http.Handler\\) http.Handler](\u003c#Health\u003e)\n- [func Logger\\(logger \\*slog.Logger\\) func\\(http.Handler\\) http.Handler](\u003c#Logger\u003e)\n- [func RealIP\\(h http.Handler\\) http.Handler](\u003c#RealIP\u003e)\n- [func RealIPWithTrustedProxies\\(trustedProxies \\[\\]string, h http.Handler\\) http.Handler](\u003c#RealIPWithTrustedProxies\u003e)\n- [func Recoverer\\(logger \\*slog.Logger, includeStack bool\\) func\\(http.Handler\\) http.Handler](\u003c#Recoverer\u003e)\n- [func SizeLimit\\(size int64\\) func\\(http.Handler\\) http.Handler](\u003c#SizeLimit\u003e)\n- [func StripSlashes\\(next http.Handler\\) http.Handler](\u003c#StripSlashes\u003e)\n- [func Timeout\\(timeout time.Duration\\) func\\(http.Handler\\) http.Handler](\u003c#Timeout\u003e)\n- [func TimeoutWithMessage\\(timeout time.Duration, message string\\) func\\(http.Handler\\) http.Handler](\u003c#TimeoutWithMessage\u003e)\n- [type CORSConfig](\u003c#CORSConfig\u003e)\n- [type HealthResponse](\u003c#HealthResponse\u003e)\n- [type ThrottleConfig](\u003c#ThrottleConfig\u003e)\n\n\n\u003ca name=\"CORS\"\u003e\u003c/a\u003e\n## func CORS\n\n```go\nfunc CORS(cfg CORSConfig) func(http.Handler) http.Handler\n```\n\nCORS returns a middleware that handles cross\\-origin requests. When cfg.Origin is empty the middleware is a no\\-op.\n\n\u003ca name=\"GlobalThrottle\"\u003e\u003c/a\u003e\n## func GlobalThrottle\n\n```go\nfunc GlobalThrottle(limit int64) func(http.Handler) http.Handler\n```\n\nGlobalThrottle returns a middleware that limits the total number of in\\-flight requests across all routes in the server.\n\n\u003ca name=\"GlobalThrottleWithConfig\"\u003e\u003c/a\u003e\n## func GlobalThrottleWithConfig\n\n```go\nfunc GlobalThrottleWithConfig(cfg ThrottleConfig) func(http.Handler) http.Handler\n```\n\nGlobalThrottleWithConfig returns a throttle middleware with custom configuration.\n\n\u003ca name=\"Headers\"\u003e\u003c/a\u003e\n## func Headers\n\n```go\nfunc Headers(headers ...string) func(http.Handler) http.Handler\n```\n\nHeaders middleware adds headers to response. Header values are sanitized to prevent HTTP header injection attacks.\n\n\u003ca name=\"Health\"\u003e\u003c/a\u003e\n## func Health\n\n```go\nfunc Health(next http.Handler) http.Handler\n```\n\n\n\n\u003ca name=\"Logger\"\u003e\u003c/a\u003e\n## func Logger\n\n```go\nfunc Logger(logger *slog.Logger) func(http.Handler) http.Handler\n```\n\nLogger middleware logs each request with method, path, client IP, response status code, and duration.\n\n\u003ca name=\"RealIP\"\u003e\u003c/a\u003e\n## func RealIP\n\n```go\nfunc RealIP(h http.Handler) http.Handler\n```\n\nRealIP 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.\n\nThis 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.\n\nFor a secure version that validates proxy IPs, use RealIPWithTrustedProxies.\n\n\u003ca name=\"RealIPWithTrustedProxies\"\u003e\u003c/a\u003e\n## func RealIPWithTrustedProxies\n\n```go\nfunc RealIPWithTrustedProxies(trustedProxies []string, h http.Handler) http.Handler\n```\n\nRealIPWithTrustedProxies 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.\n\ntrustedProxies can be:\n\n- nil or empty: Only trust headers if RemoteAddr is a private IP \\(assumes behind reverse proxy\\). This default behavior is safe if:\n- Your server is always behind a reverse proxy, AND\n- Direct client connections from private networks are not possible.\n- List of CIDR blocks: Only trust headers if RemoteAddr matches one of the CIDRs\n- List of IP addresses: Only trust headers if RemoteAddr matches one of the IPs\n\nExample usage:\n\n```\n// Explicit trusted proxies (most secure)\nmiddleware.RealIPWithTrustedProxies([]string{\"10.0.0.1\", \"10.0.0.2\"}, handler)\n\n// Trust all private IPs (safe if behind reverse proxy)\nmiddleware.RealIPWithTrustedProxies(nil, handler)\n```\n\n\u003ca name=\"Recoverer\"\u003e\u003c/a\u003e\n## func Recoverer\n\n```go\nfunc Recoverer(logger *slog.Logger, includeStack bool) func(http.Handler) http.Handler\n```\n\nRecoverer 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.\n\n\u003ca name=\"SizeLimit\"\u003e\u003c/a\u003e\n## func SizeLimit\n\n```go\nfunc SizeLimit(size int64) func(http.Handler) http.Handler\n```\n\nSizeLimit middleware rejects requests with bodies larger than size.\n\n\u003ca name=\"StripSlashes\"\u003e\u003c/a\u003e\n## func StripSlashes\n\n```go\nfunc StripSlashes(next http.Handler) http.Handler\n```\n\nStripSlashes removes trailing slashes from URLs\n\n\u003ca name=\"Timeout\"\u003e\u003c/a\u003e\n## func Timeout\n\n```go\nfunc Timeout(timeout time.Duration) func(http.Handler) http.Handler\n```\n\nTimeout creates a timeout middleware with the default message \"Request timeout\"\n\n\u003ca name=\"TimeoutWithMessage\"\u003e\u003c/a\u003e\n## func TimeoutWithMessage\n\n```go\nfunc TimeoutWithMessage(timeout time.Duration, message string) func(http.Handler) http.Handler\n```\n\nTimeoutWithMessage creates a timeout middleware with a custom message\n\n\u003ca name=\"CORSConfig\"\u003e\u003c/a\u003e\n## type CORSConfig\n\nCORSConfig defines the CORS policy applied by the CORS middleware.\n\n```go\ntype CORSConfig struct {\n    // Origin is the value for Access-Control-Allow-Origin.\n    // Use \"*\" to allow any origin. Empty string disables the middleware.\n    Origin string\n\n    // Methods lists the allowed HTTP methods for preflight requests.\n    Methods []string\n\n    // Headers lists the allowed request headers for preflight requests.\n    // Defaults to [\"Content-Type\"] when empty, because Content-Type\n    // with application/json is not CORS-safelisted and would otherwise\n    // silently block most JSON API requests.\n    Headers []string\n\n    // ExposedHeaders lists response headers the browser is allowed to read.\n    // Empty means no extra headers are exposed.\n    ExposedHeaders []string\n\n    // MaxAge is the preflight cache duration in seconds.\n    // Defaults to 86400 (24 hours) when zero.\n    MaxAge int\n\n    // Credentials sets Access-Control-Allow-Credentials to \"true\".\n    // Must not be used with Origin \"*\".\n    Credentials bool\n}\n```\n\n\u003ca name=\"HealthResponse\"\u003e\u003c/a\u003e\n## type HealthResponse\n\n\n\n```go\ntype HealthResponse struct {\n    Status string `json:\"status\"`\n}\n```\n\n\u003ca name=\"ThrottleConfig\"\u003e\u003c/a\u003e\n## type ThrottleConfig\n\nThrottleConfig holds configuration for the throttle middleware\n\n```go\ntype ThrottleConfig struct {\n    Limit   int64\n    Message string\n}\n```\n\n# ratelimit\n\n```go\nimport \"github.com/en9inerd/go-pkgs/ratelimit\"\n```\n\nPackage ratelimit provides rate limiting utilities for API clients\n\n## Index\n\n- [type FixedWindow](\u003c#FixedWindow\u003e)\n  - [func NewFixedWindow\\(limit int, window time.Duration\\) \\*FixedWindow](\u003c#NewFixedWindow\u003e)\n  - [func \\(fw \\*FixedWindow\\) Allow\\(\\) bool](\u003c#FixedWindow.Allow\u003e)\n  - [func \\(fw \\*FixedWindow\\) Wait\\(ctx context.Context\\) error](\u003c#FixedWindow.Wait\u003e)\n- [type Limiter](\u003c#Limiter\u003e)\n- [type TokenBucket](\u003c#TokenBucket\u003e)\n  - [func NewTokenBucket\\(capacity float64, refillRate float64\\) \\*TokenBucket](\u003c#NewTokenBucket\u003e)\n  - [func \\(tb \\*TokenBucket\\) Allow\\(\\) bool](\u003c#TokenBucket.Allow\u003e)\n  - [func \\(tb \\*TokenBucket\\) Wait\\(ctx context.Context\\) error](\u003c#TokenBucket.Wait\u003e)\n\n\n\u003ca name=\"FixedWindow\"\u003e\u003c/a\u003e\n## type FixedWindow\n\nFixedWindow implements a fixed window rate limiter\n\n```go\ntype FixedWindow struct {\n    // contains filtered or unexported fields\n}\n```\n\n\u003ca name=\"NewFixedWindow\"\u003e\u003c/a\u003e\n### func NewFixedWindow\n\n```go\nfunc NewFixedWindow(limit int, window time.Duration) *FixedWindow\n```\n\nNewFixedWindow creates a new fixed window rate limiter limit: maximum requests per window window: time window duration\n\n\u003ca name=\"FixedWindow.Allow\"\u003e\u003c/a\u003e\n### func \\(\\*FixedWindow\\) Allow\n\n```go\nfunc (fw *FixedWindow) Allow() bool\n```\n\nAllow checks if a request is allowed without blocking\n\n\u003ca name=\"FixedWindow.Wait\"\u003e\u003c/a\u003e\n### func \\(\\*FixedWindow\\) Wait\n\n```go\nfunc (fw *FixedWindow) Wait(ctx context.Context) error\n```\n\nWait blocks until a request is allowed or context is cancelled\n\n\u003ca name=\"Limiter\"\u003e\u003c/a\u003e\n## type Limiter\n\nLimiter provides rate limiting functionality\n\n```go\ntype Limiter interface {\n    // Wait blocks until the limiter allows the request\n    Wait(ctx context.Context) error\n    // Allow checks if a request is allowed without blocking\n    Allow() bool\n}\n```\n\n\u003ca name=\"TokenBucket\"\u003e\u003c/a\u003e\n## type TokenBucket\n\nTokenBucket implements a token bucket rate limiter\n\n```go\ntype TokenBucket struct {\n    // contains filtered or unexported fields\n}\n```\n\n\u003ca name=\"NewTokenBucket\"\u003e\u003c/a\u003e\n### func NewTokenBucket\n\n```go\nfunc NewTokenBucket(capacity float64, refillRate float64) *TokenBucket\n```\n\nNewTokenBucket creates a new token bucket limiter capacity: maximum number of tokens refillRate: tokens added per second\n\n\u003ca name=\"TokenBucket.Allow\"\u003e\u003c/a\u003e\n### func \\(\\*TokenBucket\\) Allow\n\n```go\nfunc (tb *TokenBucket) Allow() bool\n```\n\nAllow checks if a request is allowed without blocking\n\n\u003ca name=\"TokenBucket.Wait\"\u003e\u003c/a\u003e\n### func \\(\\*TokenBucket\\) Wait\n\n```go\nfunc (tb *TokenBucket) Wait(ctx context.Context) error\n```\n\nWait blocks until a token is available or context is cancelled\n\n# realip\n\n```go\nimport \"github.com/en9inerd/go-pkgs/realip\"\n```\n\n## Index\n\n- [func Get\\(r \\*http.Request\\) \\(string, error\\)](\u003c#Get\u003e)\n- [func IsPrivateIP\\(ip net.IP\\) bool](\u003c#IsPrivateIP\u003e)\n\n\n\u003ca name=\"Get\"\u003e\u003c/a\u003e\n## func Get\n\n```go\nfunc Get(r *http.Request) (string, error)\n```\n\nGet 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.\n\n\u003ca name=\"IsPrivateIP\"\u003e\u003c/a\u003e\n## func IsPrivateIP\n\n```go\nfunc IsPrivateIP(ip net.IP) bool\n```\n\nIsPrivateIP returns true if the IP address is in a private subnet. This is useful for validating that a request came from a trusted proxy.\n\n# retry\n\n```go\nimport \"github.com/en9inerd/go-pkgs/retry\"\n```\n\nPackage retry provides utilities for retrying operations with configurable strategies\n\n## Index\n\n- [func Do\\(ctx context.Context, strategy \\*Strategy, fn func\\(\\) error\\) error](\u003c#Do\u003e)\n- [func DoWithResult\\[T any\\]\\(ctx context.Context, strategy \\*Strategy, fn func\\(\\) \\(T, error\\)\\) \\(T, error\\)](\u003c#DoWithResult\u003e)\n- [func ExponentialBackoff\\(attempt int, initialDelay time.Duration, maxDelay time.Duration, multiplier float64\\) time.Duration](\u003c#ExponentialBackoff\u003e)\n- [func IsRetryableError\\(err error\\) bool](\u003c#IsRetryableError\u003e)\n- [type Strategy](\u003c#Strategy\u003e)\n  - [func DefaultStrategy\\(\\) \\*Strategy](\u003c#DefaultStrategy\u003e)\n\n\n\u003ca name=\"Do\"\u003e\u003c/a\u003e\n## func Do\n\n```go\nfunc Do(ctx context.Context, strategy *Strategy, fn func() error) error\n```\n\nDo executes a function with retry logic\n\n\u003ca name=\"DoWithResult\"\u003e\u003c/a\u003e\n## func DoWithResult\n\n```go\nfunc DoWithResult[T any](ctx context.Context, strategy *Strategy, fn func() (T, error)) (T, error)\n```\n\nDoWithResult executes a function that returns a result with retry logic\n\n\u003ca name=\"ExponentialBackoff\"\u003e\u003c/a\u003e\n## func ExponentialBackoff\n\n```go\nfunc ExponentialBackoff(attempt int, initialDelay time.Duration, maxDelay time.Duration, multiplier float64) time.Duration\n```\n\nExponentialBackoff 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.\n\n\u003ca name=\"IsRetryableError\"\u003e\u003c/a\u003e\n## func IsRetryableError\n\n```go\nfunc IsRetryableError(err error) bool\n```\n\nIsRetryableError 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.\n\n\u003ca name=\"Strategy\"\u003e\u003c/a\u003e\n## type Strategy\n\nStrategy defines a retry strategy\n\n```go\ntype Strategy struct {\n    MaxAttempts     int\n    InitialDelay    time.Duration\n    MaxDelay        time.Duration\n    Multiplier      float64\n    Jitter          bool\n    RetryableErrors func(error) bool\n}\n```\n\n\u003ca name=\"DefaultStrategy\"\u003e\u003c/a\u003e\n### func DefaultStrategy\n\n```go\nfunc DefaultStrategy() *Strategy\n```\n\nDefaultStrategy returns a default retry strategy with exponential backoff\n\n# router\n\n```go\nimport \"github.com/en9inerd/go-pkgs/router\"\n```\n\nPackage router provides a simple way to group routes and apply middleware using Go's standard http.ServeMux \\(Go 1.22\\+\\). It supports:\n\n- Grouping routes under a common base path\n- Attaching middleware stacks at the root or per group\n- Mounting static file handlers\n- Registering handlers with or without HTTP method prefixes\n- Defining custom NotFound \\(404\\) handlers\n\nExample usage:\n\n```\nmux := http.NewServeMux()\nr := router.New(mux)\n\n// global middleware\nr.Use(loggingMiddleware)\n\n// mount API group\napi := r.Mount(\"/api\")\napi.HandleFunc(\"/ping\", func(w http.ResponseWriter, r *http.Request) {\n    w.Write([]byte(\"pong\"))\n})\n\n// serve\nhttp.ListenAndServe(\":8080\", r)\n```\n\nMiddleware 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.\n\nRoute 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.\n\nPackage router provides a way to group routes and apply middleware. Works with Go's standard http.ServeMux \\(Go 1.22\\+\\).\n\n## Index\n\n- [func Wrap\\(handler http.Handler, mw1 func\\(http.Handler\\) http.Handler, mws ...func\\(http.Handler\\) http.Handler\\) http.Handler](\u003c#Wrap\u003e)\n- [type Group](\u003c#Group\u003e)\n  - [func New\\(mux \\*http.ServeMux\\) \\*Group](\u003c#New\u003e)\n  - [func RootGroup\\(mux \\*http.ServeMux, basePath string\\) \\*Group](\u003c#RootGroup\u003e)\n  - [func \\(g \\*Group\\) Group\\(\\) \\*Group](\u003c#Group.Group\u003e)\n  - [func \\(g \\*Group\\) Handle\\(pattern string, handler http.Handler\\)](\u003c#Group.Handle\u003e)\n  - [func \\(g \\*Group\\) HandleFiles\\(pattern string, root http.FileSystem\\)](\u003c#Group.HandleFiles\u003e)\n  - [func \\(g \\*Group\\) HandleFunc\\(pattern string, handler http.HandlerFunc\\)](\u003c#Group.HandleFunc\u003e)\n  - [func \\(g \\*Group\\) HandleRoot\\(method string, handler http.Handler\\)](\u003c#Group.HandleRoot\u003e)\n  - [func \\(g \\*Group\\) HandleRootFunc\\(method string, handler http.HandlerFunc\\)](\u003c#Group.HandleRootFunc\u003e)\n  - [func \\(g \\*Group\\) Handler\\(r \\*http.Request\\) \\(h http.Handler, pattern string\\)](\u003c#Group.Handler\u003e)\n  - [func \\(g \\*Group\\) Mount\\(basePath string\\) \\*Group](\u003c#Group.Mount\u003e)\n  - [func \\(g \\*Group\\) NotFoundHandler\\(handler http.HandlerFunc\\)](\u003c#Group.NotFoundHandler\u003e)\n  - [func \\(g \\*Group\\) Route\\(fn func\\(\\*Group\\)\\)](\u003c#Group.Route\u003e)\n  - [func \\(g \\*Group\\) ServeHTTP\\(w http.ResponseWriter, r \\*http.Request\\)](\u003c#Group.ServeHTTP\u003e)\n  - [func \\(g \\*Group\\) Use\\(mw func\\(http.Handler\\) http.Handler, more ...func\\(http.Handler\\) http.Handler\\)](\u003c#Group.Use\u003e)\n  - [func \\(g \\*Group\\) With\\(mw func\\(http.Handler\\) http.Handler, more ...func\\(http.Handler\\) http.Handler\\) \\*Group](\u003c#Group.With\u003e)\n\n\n\u003ca name=\"Wrap\"\u003e\u003c/a\u003e\n## func Wrap\n\n```go\nfunc Wrap(handler http.Handler, mw1 func(http.Handler) http.Handler, mws ...func(http.Handler) http.Handler) http.Handler\n```\n\nWrap applies middleware\\(s\\) around a handler.\n\n\u003ca name=\"Group\"\u003e\u003c/a\u003e\n## type Group\n\nGroup represents a collection of routes with optional middleware.\n\n```go\ntype Group struct {\n    // contains filtered or unexported fields\n}\n```\n\n\u003ca name=\"New\"\u003e\u003c/a\u003e\n### func New\n\n```go\nfunc New(mux *http.ServeMux) *Group\n```\n\nNew creates a new root Group bound to the given mux.\n\n\u003ca name=\"RootGroup\"\u003e\u003c/a\u003e\n### func RootGroup\n\n```go\nfunc RootGroup(mux *http.ServeMux, basePath string) *Group\n```\n\nRootGroup creates a new root Group with a base path bound to the given mux.\n\n\u003ca name=\"Group.Group\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Group\n\n```go\nfunc (g *Group) Group() *Group\n```\n\nGroup creates a new subgroup with the same middleware stack.\n\n\u003ca name=\"Group.Handle\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Handle\n\n```go\nfunc (g *Group) Handle(pattern string, handler http.Handler)\n```\n\nHandle registers a route with middlewares applied.\n\n\u003ca name=\"Group.HandleFiles\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) HandleFiles\n\n```go\nfunc (g *Group) HandleFiles(pattern string, root http.FileSystem)\n```\n\nHandleFiles serves static files.\n\n\u003ca name=\"Group.HandleFunc\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) HandleFunc\n\n```go\nfunc (g *Group) HandleFunc(pattern string, handler http.HandlerFunc)\n```\n\nHandleFunc registers a route handler function.\n\n\u003ca name=\"Group.HandleRoot\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) HandleRoot\n\n```go\nfunc (g *Group) HandleRoot(method string, handler http.Handler)\n```\n\nHandleRoot registers a handler for the group's root without redirect.\n\n\u003ca name=\"Group.HandleRootFunc\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) HandleRootFunc\n\n```go\nfunc (g *Group) HandleRootFunc(method string, handler http.HandlerFunc)\n```\n\nHandleRootFunc registers a root handler func.\n\n\u003ca name=\"Group.Handler\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Handler\n\n```go\nfunc (g *Group) Handler(r *http.Request) (h http.Handler, pattern string)\n```\n\nHandler proxies to mux.Handler.\n\n\u003ca name=\"Group.Mount\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Mount\n\n```go\nfunc (g *Group) Mount(basePath string) *Group\n```\n\nMount creates a new subgroup with a base path.\n\n\u003ca name=\"Group.NotFoundHandler\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) NotFoundHandler\n\n```go\nfunc (g *Group) NotFoundHandler(handler http.HandlerFunc)\n```\n\nNotFoundHandler sets a custom 404 handler on the root group.\n\n\u003ca name=\"Group.Route\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Route\n\n```go\nfunc (g *Group) Route(fn func(*Group))\n```\n\nRoute configures the group inside the provided function.\n\n\u003ca name=\"Group.ServeHTTP\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) ServeHTTP\n\n```go\nfunc (g *Group) ServeHTTP(w http.ResponseWriter, r *http.Request)\n```\n\nServeHTTP implements http.Handler for the group.\n\n\u003ca name=\"Group.Use\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) Use\n\n```go\nfunc (g *Group) Use(mw func(http.Handler) http.Handler, more ...func(http.Handler) http.Handler)\n```\n\nUse appends middleware\\(s\\) to the group.\n\n\u003ca name=\"Group.With\"\u003e\u003c/a\u003e\n### func \\(\\*Group\\) With\n\n```go\nfunc (g *Group) With(mw func(http.Handler) http.Handler, more ...func(http.Handler) http.Handler) *Group\n```\n\nWith returns a new group with appended middleware\\(s\\).\n\n# validator\n\n```go\nimport \"github.com/en9inerd/go-pkgs/validator\"\n```\n\nPackage validator provides functionality for validating and sanitizing data.\n\n## Index\n\n- [func Between\\(value, min, max int\\) bool](\u003c#Between\u003e)\n- [func BetweenFloat\\(value, min, max float64\\) bool](\u003c#BetweenFloat\u003e)\n- [func Blank\\(value string\\) bool](\u003c#Blank\u003e)\n- [func InRange\\(value, min, max int\\) bool](\u003c#InRange\u003e)\n- [func InRangeFloat\\(value, min, max float64\\) bool](\u003c#InRangeFloat\u003e)\n- [func IsAlpha\\(value string\\) bool](\u003c#IsAlpha\u003e)\n- [func IsAlphanumeric\\(value string\\) bool](\u003c#IsAlphanumeric\u003e)\n- [func IsEmail\\(value string\\) bool](\u003c#IsEmail\u003e)\n- [func IsHTTPURL\\(value string\\) bool](\u003c#IsHTTPURL\u003e)\n- [func IsNumber\\(value string\\) bool](\u003c#IsNumber\u003e)\n- [func IsNumeric\\(value string\\) bool](\u003c#IsNumeric\u003e)\n- [func IsURL\\(value string\\) bool](\u003c#IsURL\u003e)\n- [func Matches\\(value string, pattern \\*regexp.Regexp\\) bool](\u003c#Matches\u003e)\n- [func MaxChars\\(value string, n int\\) bool](\u003c#MaxChars\u003e)\n- [func MaxDuration\\(d, maxDuration time.Duration\\) bool](\u003c#MaxDuration\u003e)\n- [func MaxFloat\\(value, max float64\\) bool](\u003c#MaxFloat\u003e)\n- [func MaxInt\\(value, max int\\) bool](\u003c#MaxInt\u003e)\n- [func MinChars\\(value string, n int\\) bool](\u003c#MinChars\u003e)\n- [func MinDuration\\(d, minDuration time.Duration\\) bool](\u003c#MinDuration\u003e)\n- [func MinFloat\\(value, min float64\\) bool](\u003c#MinFloat\u003e)\n- [func MinInt\\(value, min int\\) bool](\u003c#MinInt\u003e)\n- [func NotBlank\\(value string\\) bool](\u003c#NotBlank\u003e)\n- [func PermittedValue\\[T comparable\\]\\(value T, permittedValues ...T\\) bool](\u003c#PermittedValue\u003e)\n- [func ValidateRequest\\(req Validatable\\) error](\u003c#ValidateRequest\u003e)\n- [func ValidateRequestWithValidator\\(req Validatable, v \\*Validator\\) error](\u003c#ValidateRequestWithValidator\u003e)\n- [type Validatable](\u003c#Validatable\u003e)\n- [type Validator](\u003c#Validator\u003e)\n  - [func \\(v \\*Validator\\) AddFieldError\\(key, message string\\)](\u003c#Validator.AddFieldError\u003e)\n  - [func \\(v \\*Validator\\) AddNonFieldError\\(message string\\)](\u003c#Validator.AddNonFieldError\u003e)\n  - [func \\(v \\*Validator\\) CheckField\\(ok bool, key, message string\\)](\u003c#Validator.CheckField\u003e)\n  - [func \\(v \\*Validator\\) JSON\\(\\) \\[\\]byte](\u003c#Validator.JSON\u003e)\n  - [func \\(v \\*Validator\\) Valid\\(\\) bool](\u003c#Validator.Valid\u003e)\n\n\n\u003ca name=\"Between\"\u003e\u003c/a\u003e\n## func Between\n\n```go\nfunc Between(value, min, max int) bool\n```\n\nBetween returns true if the integer value is between min and max \\(inclusive\\) Alias for InRange for better readability in some contexts\n\n\u003ca name=\"BetweenFloat\"\u003e\u003c/a\u003e\n## func BetweenFloat\n\n```go\nfunc BetweenFloat(value, min, max float64) bool\n```\n\nBetweenFloat returns true if the float value is between min and max \\(inclusive\\) Alias for InRangeFloat for better readability in some contexts\n\n\u003ca name=\"Blank\"\u003e\u003c/a\u003e\n## func Blank\n\n```go\nfunc Blank(value string) bool\n```\n\nBlank returns true if the string is empty or contains only whitespace.\n\n\u003ca name=\"InRange\"\u003e\u003c/a\u003e\n## func InRange\n\n```go\nfunc InRange(value, min, max int) bool\n```\n\nInRange returns true if the integer value is between min and max \\(inclusive\\)\n\n\u003ca name=\"InRangeFloat\"\u003e\u003c/a\u003e\n## func InRangeFloat\n\n```go\nfunc InRangeFloat(value, min, max float64) bool\n```\n\nInRangeFloat returns true if the float value is between min and max \\(inclusive\\)\n\n\u003ca name=\"IsAlpha\"\u003e\u003c/a\u003e\n## func IsAlpha\n\n```go\nfunc IsAlpha(value string) bool\n```\n\nIsAlpha returns true if the string contains only alphabetic characters\n\n\u003ca name=\"IsAlphanumeric\"\u003e\u003c/a\u003e\n## func IsAlphanumeric\n\n```go\nfunc IsAlphanumeric(value string) bool\n```\n\nIsAlphanumeric returns true if the string contains only alphanumeric characters\n\n\u003ca name=\"IsEmail\"\u003e\u003c/a\u003e\n## func IsEmail\n\n```go\nfunc IsEmail(value string) bool\n```\n\nIsEmail returns true if the string is a valid email address\n\n\u003ca name=\"IsHTTPURL\"\u003e\u003c/a\u003e\n## func IsHTTPURL\n\n```go\nfunc IsHTTPURL(value string) bool\n```\n\nIsHTTPURL returns true if the string is a valid HTTP or HTTPS URL\n\n\u003ca name=\"IsNumber\"\u003e\u003c/a\u003e\n## func IsNumber\n\n```go\nfunc IsNumber(value string) bool\n```\n\nIsNumber returns true if the string represents a valid integer.\n\n\u003ca name=\"IsNumeric\"\u003e\u003c/a\u003e\n## func IsNumeric\n\n```go\nfunc IsNumeric(value string) bool\n```\n\nIsNumeric returns true if the string contains only numeric characters\n\n\u003ca name=\"IsURL\"\u003e\u003c/a\u003e\n## func IsURL\n\n```go\nfunc IsURL(value string) bool\n```\n\nIsURL returns true if the string is a valid URL\n\n\u003ca name=\"Matches\"\u003e\u003c/a\u003e\n## func Matches\n\n```go\nfunc Matches(value string, pattern *regexp.Regexp) bool\n```\n\nMatches returns true if the string matches the provided regular expression.\n\n\u003ca name=\"MaxChars\"\u003e\u003c/a\u003e\n## func MaxChars\n\n```go\nfunc MaxChars(value string, n int) bool\n```\n\nMaxChars returns true if the string contains no more than n characters.\n\n\u003ca name=\"MaxDuration\"\u003e\u003c/a\u003e\n## func MaxDuration\n\n```go\nfunc MaxDuration(d, maxDuration time.Duration) bool\n```\n\nMaxDuration returns true if the duration is less than or equal to maxDuration.\n\n\u003ca name=\"MaxFloat\"\u003e\u003c/a\u003e\n## func MaxFloat\n\n```go\nfunc MaxFloat(value, max float64) bool\n```\n\nMaxFloat returns true if value is less than or equal to max.\n\n\u003ca name=\"MaxInt\"\u003e\u003c/a\u003e\n## func MaxInt\n\n```go\nfunc MaxInt(value, max int) bool\n```\n\nMaxInt returns true if value is less than or equal to max.\n\n\u003ca name=\"MinChars\"\u003e\u003c/a\u003e\n## func MinChars\n\n```go\nfunc MinChars(value string, n int) bool\n```\n\nMinChars returns true if the string contains at least n characters.\n\n\u003ca name=\"MinDuration\"\u003e\u003c/a\u003e\n## func MinDuration\n\n```go\nfunc MinDuration(d, minDuration time.Duration) bool\n```\n\nMinDuration returns true if the duration is greater than or equal to minDuration.\n\n\u003ca name=\"MinFloat\"\u003e\u003c/a\u003e\n## func MinFloat\n\n```go\nfunc MinFloat(value, min float64) bool\n```\n\nMinFloat returns true if value is greater than or equal to min.\n\n\u003ca name=\"MinInt\"\u003e\u003c/a\u003e\n## func MinInt\n\n```go\nfunc MinInt(value, min int) bool\n```\n\nMinInt returns true if value is greater than or equal to min.\n\n\u003ca name=\"NotBlank\"\u003e\u003c/a\u003e\n## func NotBlank\n\n```go\nfunc NotBlank(value string) bool\n```\n\nNotBlank returns true if the string is not empty or whitespace.\n\n\u003ca name=\"PermittedValue\"\u003e\u003c/a\u003e\n## func PermittedValue\n\n```go\nfunc PermittedValue[T comparable](value T, permittedValues ...T) bool\n```\n\nPermittedValue returns true if value is among the provided permittedValues.\n\n\u003ca name=\"ValidateRequest\"\u003e\u003c/a\u003e\n## func ValidateRequest\n\n```go\nfunc ValidateRequest(req Validatable) error\n```\n\nValidateRequest validates a request that implements the Validatable interface and returns an error if validation fails\n\n\u003ca name=\"ValidateRequestWithValidator\"\u003e\u003c/a\u003e\n## func ValidateRequestWithValidator\n\n```go\nfunc ValidateRequestWithValidator(req Validatable, v *Validator) error\n```\n\nValidateRequestWithValidator validates a request using a provided validator This allows for custom validation logic or reusing a validator instance\n\n\u003ca name=\"Validatable\"\u003e\u003c/a\u003e\n## type Validatable\n\nValidatable defines an interface for structs that can validate themselves using a Validator.\n\n```go\ntype Validatable interface {\n    Validate(v *Validator)\n}\n```\n\n\u003ca name=\"Validator\"\u003e\u003c/a\u003e\n## type Validator\n\nValidator collects field and non\\-field validation errors.\n\n```go\ntype Validator struct {\n    FieldErrors    map[string][]string `json:\"fieldErrors\"`\n    NonFieldErrors []string            `json:\"nonFieldErrors\"`\n}\n```\n\n\u003ca name=\"Validator.AddFieldError\"\u003e\u003c/a\u003e\n### func \\(\\*Validator\\) AddFieldError\n\n```go\nfunc (v *Validator) AddFieldError(key, message string)\n```\n\nAddFieldError adds an error message to a specific field.\n\n\u003ca name=\"Validator.AddNonFieldError\"\u003e\u003c/a\u003e\n### func \\(\\*Validator\\) AddNonFieldError\n\n```go\nfunc (v *Validator) AddNonFieldError(message string)\n```\n\nAddNonFieldError adds a general error message not associated with a specific field.\n\n\u003ca name=\"Validator.CheckField\"\u003e\u003c/a\u003e\n### func \\(\\*Validator\\) CheckField\n\n```go\nfunc (v *Validator) CheckField(ok bool, key, message string)\n```\n\nCheckField adds a field error if the provided condition is false.\n\n\u003ca name=\"Validator.JSON\"\u003e\u003c/a\u003e\n### func \\(\\*Validator\\) JSON\n\n```go\nfunc (v *Validator) JSON() []byte\n```\n\nJSON returns the validation errors as a JSON byte slice.\n\n\u003ca name=\"Validator.Valid\"\u003e\u003c/a\u003e\n### func \\(\\*Validator\\) Valid\n\n```go\nfunc (v *Validator) Valid() bool\n```\n\nValid returns true if there are no field or non\\-field validation errors.\n\nGenerated by [gomarkdoc](\u003chttps://github.com/princjef/gomarkdoc\u003e)\n\n\n\u003c!-- gomarkdoc:embed:end --\u003e\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fen9inerd%2Fgo-pkgs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fen9inerd%2Fgo-pkgs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fen9inerd%2Fgo-pkgs/lists"}