Documentation
¶
Overview ¶
Package query provides tag-driven request binding, GORM scopes, pagination, and data access helpers for the API layer.
Tag contract for query structs:
query:"name" query parameter name, required for a field to
participate in Bind; also used to locate the sort_dir
companion field in Scopes
default:"25" value used when the parameter is absent
minimum:"1" lower clamp applied after parsing (int fields)
maximum:"200" upper clamp applied after parsing (int fields)
filter:"column" adds WHERE column = ? when the field is non-empty
search:"a,b" adds (a ILIKE ? OR b ILIKE ?) with %value% when non-empty
sort:"a,b" adds ORDER BY when the value appears in the allowed list
default_sort:"c [asc|desc]"
adds ORDER BY c when the sort value is empty or not in
the allowed list; a missing direction means ASC
The query, default, minimum, and maximum tags match the names and semantics that github.com/danielgtaylor/huma/v2 reads, so the same structs can also be bound by huma. The filter, search, and sort tags are project-specific and only interpreted by Scopes.
An absent query parameter and a parameter present with an empty value are treated the same way: the default tag applies, otherwise the zero value is kept.
Index ¶
- Variables
- func Bind(target any, c *echo.Context) error
- func BodyMap(body any, omit ...string) (map[string]any, error)
- func Delete[T any](db *gorm.DB, column string, value any, hard bool, ...) error
- func Get[T any](db *gorm.DB, column string, value any, scopes ...func(*gorm.DB) *gorm.DB) (*T, error)
- func Insert[T any](db *gorm.DB, item *T) error
- func Restore[T any](db *gorm.DB, column string, value any) (*T, error)
- func Scopes(q any) []func(*gorm.DB) *gorm.DB
- func Update[T any](db *gorm.DB, column string, value any, updates map[string]any, ...) (*T, error)
- type PaginatedResponse
Constants ¶
This section is empty.
Variables ¶
var ErrConflict = errors.New("the value conflicts with an existing record or a database constraint")
ErrConflict is returned by Insert and Update when the database rejects the write because of a unique index or check constraint violation. The underlying GORM error is not preserved, so detect it with errors.Is.
var ErrNotFound = gorm.ErrRecordNotFound
ErrNotFound is returned by the single-record helpers when no matching row exists. It is the gorm.ErrRecordNotFound value itself, so errors.Is works against either sentinel.
Functions ¶
func Bind ¶
Bind fills the exported fields of target (a pointer to a struct) from the request query string, driven by the query/default/minimum/maximum tags.
Supported field kinds are string, bool, and signed int kinds. A parse failure returns an error naming the parameter so handlers can map it to a 400 response. Absent or empty parameters take the default tag value when present, otherwise the field keeps its zero value. Fields without a query tag, unexported fields, unsupported kinds, and unknown query parameters are ignored.
func BodyMap ¶
BodyMap marshals body to JSON and back into a map[string]any, then deletes the "id" key and every key listed in omit. The map form keeps zero values in the update payload, which GORM's struct-based Updates would skip.
func Delete ¶
func Delete[T any](db *gorm.DB, column string, value any, hard bool, scopes ...func(*gorm.DB) *gorm.DB) error
Delete removes the record matching column = value. The record is probed first so a missing row yields ErrNotFound instead of a silent 0-rows delete. When hard is true both the probe and the delete run Unscoped so soft-deleted rows can be targeted and removed permanently. Scopes apply to the probe only.
func Get ¶
func Get[T any](db *gorm.DB, column string, value any, scopes ...func(*gorm.DB) *gorm.DB) (*T, error)
Get fetches a single record of T by matching column = value. Scopes are applied to the lookup. Returns ErrNotFound when no row matches, other errors verbatim.
func Insert ¶
Insert creates a new record. Returns ErrConflict on a duplicate-key or check-constraint violation, other errors verbatim.
func Restore ¶
Restore clears the deleted_at column of a soft-deleted record. The lookup and the update both run Unscoped so the row can be found and the column rewritten to NULL. UpdateColumn (not Update) keeps GORM's BeforeUpdate callbacks from touching updated_at. Returns the re-read record, or ErrNotFound when no row matches.
func Scopes ¶
Scopes converts the filter/search/sort tags of a bound query struct into GORM scopes. String fields only; empty values contribute nothing, and a sort value outside the allowed list falls back to the default_sort tag when one is present.
filter:"column" WHERE column = ?
search:"a,b" (a ILIKE ? OR b ILIKE ?) with %value%
sort:"a,b" ORDER BY <value> ASC|DESC when the value is in the list
default_sort:"c [asc|desc]"
ORDER BY used when sort_by is empty or not in the list;
a missing direction means ASC
The sort direction comes from the string field tagged query:"sort_dir" on the same struct: "desc" (case-insensitive) yields DESC, anything else yields ASC. Missing sort_dir fields default to ASC.
Column names come from developer-written tags and are used verbatim. Scopes panics when q is not a struct or a non-nil pointer to one; that is a developer error at startup.
func Update ¶
func Update[T any](db *gorm.DB, column string, value any, updates map[string]any, scopes ...func(*gorm.DB) *gorm.DB) (*T, error)
Update first verifies the record exists (scopes applied), then applies the updates map with a WHERE column = ? guard so a changed primary key in the payload can never touch unrelated rows. Returns the re-read record, or ErrNotFound, ErrConflict, or other errors verbatim.
Types ¶
type PaginatedResponse ¶
type PaginatedResponse[T any] struct { Items []T `json:"items"` Page int `json:"page"` PageSize int `json:"page_size"` TotalItems int64 `json:"total_items"` TotalPages int `json:"total_pages"` HasNext bool `json:"has_next"` HasPrev bool `json:"has_prev"` }
PaginatedResponse wraps a slice of any model with pagination metadata.
func Paginate ¶
func Paginate[T any](db *gorm.DB, page, pageSize int, scopes ...func(*gorm.DB) *gorm.DB) (PaginatedResponse[T], error)
Paginate runs a paginated list query for model T and returns the page of results plus pagination metadata. page < 1 becomes 1, pageSize < 1 becomes 25, and pageSize > 200 becomes 200.
The count relies on GORM's Count deleting the ORDER BY clause (when there is no GROUP BY), so scopes must be applied eagerly here rather than via .Scopes(). Deferred scopes only run inside Execute and would add their ORDER BY after Count already stripped the clause, which Postgres rejects in a count over an aggregated SELECT. The Find then runs with Offset/Limit on the same chain.