feat(api/v2): add shared conditional read helper and document list params
conditionalReadResponse applies the If-Match/If-None-Match/If-Modified-Since precondition (304/412) and returns the shared read envelope. The caller's permission is folded into the ETag so a share/role change invalidates the cache even when the model's modified time is unchanged. Also adds doc: tags to the shared ListParams (q/page/per_page).
This commit is contained in:
parent
72445c4d2f
commit
6836903c5f
|
|
@ -16,6 +16,13 @@
|
||||||
|
|
||||||
package apiv2
|
package apiv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/danielgtaylor/huma/v2/conditional"
|
||||||
|
)
|
||||||
|
|
||||||
// Paginated is the standard list-response envelope for every /api/v2 list operation.
|
// Paginated is the standard list-response envelope for every /api/v2 list operation.
|
||||||
type Paginated[T any] struct {
|
type Paginated[T any] struct {
|
||||||
Items []T `json:"items"`
|
Items []T `json:"items"`
|
||||||
|
|
@ -46,9 +53,9 @@ func NewPaginated[T any](items []T, total int64, page, perPage int) Paginated[T]
|
||||||
|
|
||||||
// ListParams carries the standard (page, per_page, q) query shape for list operations.
|
// ListParams carries the standard (page, per_page, q) query shape for list operations.
|
||||||
type ListParams struct {
|
type ListParams struct {
|
||||||
Page int `query:"page" default:"1" minimum:"1"`
|
Page int `query:"page" default:"1" minimum:"1" doc:"1-based page number."`
|
||||||
PerPage int `query:"per_page" default:"50" minimum:"1" maximum:"1000"`
|
PerPage int `query:"per_page" default:"50" minimum:"1" maximum:"1000" doc:"Items per page (max 1000)."`
|
||||||
Q string `query:"q"`
|
Q string `query:"q" doc:"Search query; filters the list to items matching this string."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// singleBody is the create/update response envelope (no ETag).
|
// singleBody is the create/update response envelope (no ETag).
|
||||||
|
|
@ -62,5 +69,16 @@ type singleReadBody[T any] struct {
|
||||||
Body *T
|
Body *T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permission is folded into the ETag so a share/role change invalidates the cache.
|
||||||
|
func conditionalReadResponse[T any](p *conditional.Params, body *T, modified time.Time, permission int) (*singleReadBody[T], error) {
|
||||||
|
e := fmt.Sprintf("%d-%d", modified.UnixNano(), permission)
|
||||||
|
if p.HasConditionalParams() {
|
||||||
|
if err := p.PreconditionFailed(e, modified); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &singleReadBody[T]{ETag: `"` + e + `"`, Body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// emptyBody marks delete / no-content operations.
|
// emptyBody marks delete / no-content operations.
|
||||||
type emptyBody struct{}
|
type emptyBody struct{}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue