resource

package
v0.23.0 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package resource is a dependency-free API-resource / serializer layer for turning domain models into clean JSON representations — the JSON-API equivalent of Laravel's API Resources or Django REST Framework serializers.

The problem it removes is hand-mapping: every endpoint that returns a model otherwise repeats the same "copy these fields, rename that one, hide the password, add a computed URL" boilerplate. A Resource describes that mapping once, and the Item / Collection / Paginated wrappers render it consistently.

Defining a resource

A resource is anything that maps one Model to an output value (typically a Fields map). Implement the interface, or wrap a function with Func:

type User struct {
    ID        int
    Name      string
    Email     string
    Password  string // never serialised
    Admin     bool
    AvatarURL string
}

var UserResource = resource.Func(func(u User) any {
    return resource.Fields{
        "id":     u.ID,
        "name":   u.Name,
        "email":  u.Email,
        // computed field
        "avatar": u.AvatarURL,
    }.
        When(u.Admin, "is_admin", true) // conditional: only admins carry it
})

Rendering

resource.Item(user, UserResource)             // -> single object
resource.Collection(users, UserResource)      // -> {"data": [...]}
resource.Paginated(paginator, UserResource)   // -> {"data": [...], "meta": {...}}

Each returns a plain value (map / struct) ready for an HTTP layer to encode, e.g. c.JSON(200, resource.Item(user, UserResource)). resource is a leaf package: it never imports web, so there is no import cycle.

Web integration

func (h *Handler) Show(c *web.Context) (any, error) {
    u, err := h.users.Find(c.Context(), id)
    if err != nil {
        return nil, err
    }
    return resource.Item(u, UserResource), nil
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Embed

func Embed[Model any](m Model, r Resource[Model]) any

Embed renders a single related model through its own resource, returning the transformed value for nesting under a relation key. A nil related pointer yields nil, so the JSON renders as null rather than panicking.

resource.Fields{
    "id":      post.ID,
    "title":   post.Title,
    "author":  resource.Embed(post.Author, UserResource),
}

func EmbedMany

func EmbedMany[Model any](models []Model, r Resource[Model]) []any

EmbedMany renders a slice of related models through their resource, returning a []any suitable for nesting under a relation key (e.g. a User embedding its Posts). A nil slice yields an empty, non-nil slice so the JSON is [] not null.

resource.Fields{
    "id":    user.ID,
    "posts": resource.EmbedMany(user.Posts, PostResource),
}

func Item

func Item[Model any](m Model, r Resource[Model]) any

Item renders a single model through its resource. The result is the transformed value itself (no envelope), ready for c.JSON(200, ...).

c.JSON(200, resource.Item(user, UserResource))

Types

type CollectionResponse

type CollectionResponse struct {
	Data []any          `json:"data"`
	Meta map[string]any `json:"meta,omitempty"`
}

CollectionResponse is the envelope returned by Collection: a "data" array of transformed models, plus optional top-level "meta". meta is omitted from the JSON when empty.

func Collection

func Collection[Model any](models []Model, r Resource[Model]) CollectionResponse

Collection renders a slice of models through their resource, wrapping the results in {"data": [...]}. A nil slice renders as {"data": []}, never null.

c.JSON(200, resource.Collection(users, UserResource))

func (CollectionResponse) WithMeta

func (c CollectionResponse) WithMeta(meta map[string]any) CollectionResponse

WithMeta attaches top-level meta to a CollectionResponse and returns it for chaining. Repeated calls merge.

type Fields

type Fields map[string]any

Fields is the working output of a resource: an ordered-by-key, JSON-object mapping of response keys to values. encoding/json marshals it like any map[string]any (keys sorted), so output is deterministic.

The methods are chainable and return Fields so a resource body can read as a single expression. Every method is nil-safe: calling on a nil Fields lazily allocates, so the zero value is usable.

func (Fields) Except

func (f Fields) Except(keys ...string) Fields

Except returns a new Fields with the listed keys removed (a blacklist). The receiver is not modified.

func (Fields) Merge

func (f Fields) Merge(other Fields) Fields

Merge copies all keys from other into f (other wins on collisions). Useful for splicing in a shared block of fields.

func (Fields) OmitEmpty

func (f Fields) OmitEmpty(keys ...string) Fields

OmitEmpty deletes keys whose value is a Go zero value (nil, "", 0, false, empty slice/map). Use it to drop optional fields instead of rendering them as null/zero — the manual, opt-in equivalent of the json `,omitempty` tag.

func (Fields) Only

func (f Fields) Only(keys ...string) Fields

Only returns a new Fields containing just the listed keys (a whitelist). Missing keys are skipped. The receiver is not modified.

func (Fields) Rename

func (f Fields) Rename(from, to string) Fields

Rename moves the value at from to to, dropping the old key. It is a no-op if from is absent or from == to.

func (Fields) Set

func (f Fields) Set(key string, val any) Fields

Set adds or overwrites key with val and returns the (possibly newly allocated) Fields for chaining.

func (Fields) Unless

func (f Fields) Unless(cond bool, key string, val any) Fields

Unless is the inverse of When: it sets key to val only when cond is false.

func (Fields) When

func (f Fields) When(cond bool, key string, val any) Fields

When sets key to val only when cond is true. It is the conditional-field helper: omit a field entirely (not null) unless a predicate holds.

Fields{...}.When(user.IsAdmin, "secret_notes", user.Notes)

func (Fields) WhenFunc

func (f Fields) WhenFunc(cond bool, key string, val func() any) Fields

WhenFunc is like When but defers the value to a function, so an expensive or possibly-panicking computation only runs when cond is true.

type Func

type Func[Model any] func(Model) any

Func adapts an ordinary func(Model) any into a Resource. It is the common way to define a resource inline without declaring a named type.

func (Func[Model]) Transform

func (f Func[Model]) Transform(m Model) any

Transform implements Resource by calling the wrapped function.

type Links struct {
	First string `json:"first,omitempty"`
	Last  string `json:"last,omitempty"`
	Prev  string `json:"prev,omitempty"`
	Next  string `json:"next,omitempty"`
}

Links carries the URL helpers a paginated client typically needs. All fields are omitempty, so an unset link is absent rather than null. They are only populated when a caller provides a base URL via PaginatedLinks.

type Meta

type Meta struct {
	Total    int64 `json:"total"`
	Page     int   `json:"page"`
	PerPage  int   `json:"per_page"`
	LastPage int   `json:"last_page"`
	HasMore  bool  `json:"has_more"`
}

Meta describes a paginated result set. The keys mirror orm.Paginator so a client can drive a pager directly from the response.

type PaginatedResponse

type PaginatedResponse struct {
	Data  []any          `json:"data"`
	Meta  Meta           `json:"meta"`
	Links *Links         `json:"links,omitempty"`
	With  map[string]any `json:"with,omitempty"`
}

PaginatedResponse is the envelope returned by Paginated: the page's "data", derived "meta", and optional "links". Both meta and links always render (meta is always meaningful for a page); extra top-level data merges into meta.

func Paginated

func Paginated[Model any](p *orm.Paginator[Model], r Resource[Model]) PaginatedResponse

Paginated renders an orm.Paginator page through a resource, deriving the meta block (total, page, per_page, last_page, has_more) straight from the paginator. A nil paginator yields an empty page with zero-value meta.

page, _ := orm.Query[User](conn).Paginate(ctx, 2, 15)
c.JSON(200, resource.Paginated(page, UserResource))

Importing orm here is cycle-free: orm imports only database, never resource.

func (p PaginatedResponse) WithLinks(base string) PaginatedResponse

WithLinks builds first/last/prev/next links from base (e.g. the request path) using a "?page=N" query and attaches them. base should already include any other query parameters or end clean; the page param is appended with the appropriate separator.

func (PaginatedResponse) WithMeta

func (p PaginatedResponse) WithMeta(extra map[string]any) PaginatedResponse

WithMeta merges extra top-level data into the "with" block and returns the response for chaining.

type Resource

type Resource[Model any] interface {
	Transform(Model) any
}

Resource maps a single Model to its serialisable output. The output is usually a Fields map, but may be any value encoding/json can marshal (including a typed DTO struct).

Jump to

Keyboard shortcuts

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