Documentation
¶
Overview ¶
Package tag expands JaWS dependency tags into keys used to associate Elements with application state and logical signals.
Tags identify dependencies; they do not observe state or schedule work. Register them with github.com/linkdata/jaws.Request.Tag or github.com/linkdata/jaws.Element.Tag, mutate the authoritative state, then pass the corresponding keys to github.com/linkdata/jaws.Request.Dirty or use them as github.com/linkdata/jaws.Jaws.Broadcast destinations.
Choosing tags ¶
Prefer stable identities derived from the data being rendered. Use a pointer when output depends on an object as a whole, a distinct comparable wrapper type for an independently changing part, or a named empty struct for a shared signal. Name a tag for the dependency, not the event that changes it. Plain strings are not tag keys; use Tag for a string-named logical signal.
Register an item with its own key and a separate group key when both narrow and broad updates are needed. Do not include the group key in the item's TagGetter.JawsGetTag result: dirtying the item would then expand to the group and refresh every group listener.
Registration and targeting ¶
Registration is additive and lasts until the Element is removed or its Request ends. Adding a tag neither schedules an update nor changes targets already selected. Register known dependencies during initial rendering; add a later-discovered dependency only when it remains valid for the Element's remaining lifetime.
Ordinary keys passed through github.com/linkdata/jaws.Request.Dirty match Elements across all live Requests. A non-nil pointer to github.com/linkdata/jaws.Element is the exact-target exception after expansion. TagExpand defines accepted keys and validation, TagGetter defines stable identity and concurrency requirements, and github.com/linkdata/jaws.Request.TagsOf reports the keys actually registered on an Element.
Index ¶
- Constants
- Variables
- func FindTagGetter(x any) (path string, tgType reflect.Type, found bool)
- func NewErrNotComparable(x any) error
- func NewErrNotUsableAsTag(x any) error
- func TagExpand(tagValue any) (result []any, err error)
- func TagString(tag any) string
- func TagStringDebug(tag any) string
- func TagStringRelease(tag any) string
- func TagsString(tags []any) string
- func TagsStringDebug(tags []any) string
- func TagsStringRelease(tags []any) string
- type Tag
- type TagGetter
Examples ¶
Constants ¶
const DebugRender = false
DebugRender reports whether TagString and TagsString render tags in full.
It is false in the default (production) build, where they forward to the bounded, crash-safe TagStringRelease/TagsStringRelease. Build with -tags debug or -race to make it true.
Variables ¶
var ErrIllegalTagType errIllegalTagType
ErrIllegalTagType is returned when a UI tag type is disallowed.
var ErrNotComparable errNotComparable
ErrNotComparable is returned when a UI object or tag is not comparable.
var ErrNotUsableAsTag errNotUsableAsTag
ErrNotUsableAsTag is returned when a value cannot be used as a tag.
A tag key must be comparable at runtime and equal to itself so later dirtying, broadcasts and event routing can match it reliably. This error also matches ErrNotComparable via errors.Is.
var ErrTooManyTags errTooManyTags
ErrTooManyTags is returned when tag expansion exceeds the recursion depth (maxTagDepth) or result count (maxTagCount) limits.
Functions ¶
func FindTagGetter ¶
FindTagGetter searches x recursively for a nested TagGetter.
The search is bounded: it follows at most maxTagDepth levels of nesting and scans only the first maxHintScan elements of any array or slice. It is used only to enrich the ErrNotUsableAsTag diagnostic, so these bounds trade completeness for a cheap, terminating search.
It is a best-effort diagnostic aid: the maxHintScan and maxTagDepth bounds may change, and a negative result is not authoritative, so callers should not rely on it for non-diagnostic purposes.
func NewErrNotComparable ¶
NewErrNotComparable returns ErrNotComparable if x is not comparable.
func NewErrNotUsableAsTag ¶
NewErrNotUsableAsTag returns ErrNotUsableAsTag for an unusable tag key.
It returns nil for nil and for values that are comparable at runtime and equal to themselves. It only validates key usability; it does not apply TagExpand's tag-type policy, so a value may pass this check and still be rejected with ErrIllegalTagType.
func TagExpand ¶
TagExpand expands tagValue into a flat list of unique, usable tag keys.
tagValue may be nil, a Tag, []Tag, []any, a TagGetter, or another value that is comparable at runtime and equals itself. A nil interface contributes no keys. A typed nil is a non-nil interface and follows the normal rules for its dynamic type, including dispatch to TagGetter.JawsGetTag when it implements TagGetter.
The predeclared string, bool, signed integer, unsigned integer other than uintptr, and floating-point types are rejected with ErrIllegalTagType, as are template.HTML, template.HTMLAttr, jid.Jid and key.Key. An unusable expanded key is rejected with ErrNotUsableAsTag, which also matches ErrNotComparable under errors.Is. Expansion that exceeds the nesting-depth or total-count limits is rejected with ErrTooManyTags.
On error, result contains the tags expanded before the failure. If an expanded value is not usable as a tag key, result is nil and err matches ErrNotUsableAsTag.
A single call may invoke a TagGetter more than once, and later calls may expand the same value again. When expansion encounters a cyclic TagGetter graph, it uses the participating TagGetter values themselves as keys; they must therefore be usable as tags. Implementations must satisfy TagGetter's stable-identity contract.
TagExpand does not copy input slices or slices returned by TagGetter.JawsGetTag before traversing them. They must not be mutated concurrently with expansion.
Errors are returned rather than logged. Use github.com/linkdata/jaws.Jaws.MustTagExpand to report them through a configured logger instead.
Example (ErrorsIs) ¶
package main
import (
"errors"
"fmt"
"github.com/linkdata/jaws/lib/tag"
)
func main() {
_, err := tag.TagExpand([]int{1})
fmt.Println(errors.Is(err, tag.ErrNotUsableAsTag))
fmt.Println(errors.Is(err, tag.ErrNotComparable))
}
Output: true true
Example (TagGetter) ¶
package main
import (
"fmt"
"github.com/linkdata/jaws/lib/tag"
)
type exampleItem struct {
Name string
}
func (item *exampleItem) JawsGetTag() any {
return item
}
func main() {
item := &exampleItem{Name: "row"}
tags, err := tag.TagExpand([]any{item, tag.Tag("list")})
if err != nil {
panic(err)
}
fmt.Println(len(tags), tags[0] == item, tags[1] == tag.Tag("list"))
}
Output: 2 true true
func TagString ¶
TagString returns a debug string for tag.
In the default build it forwards to the crash-safe TagStringRelease; see DebugRender.
func TagStringDebug ¶ added in v0.700.0
TagStringDebug renders tag in full: a pointer as its type and address, a fmt.Stringer as its type and String(), and every other value through "%#v".
It is the most informative form, but because it descends into the value it can overflow the stack or exhaust memory on a self-referential or oversized tag, so it is unsuitable for untrusted input.
TagString forwards here in the debug and -race builds.
func TagStringRelease ¶ added in v0.700.0
TagStringRelease renders tag showing only its type, plus its address when tag is a pointer whose address can be read without panicking.
It reads the type via reflection, never hands tag itself to a formatting verb, and never invokes a String/GoString/Format/Error method or touches the pointed-to memory; the address is read under a recover (see pointerAddr) because reflect.Value.Pointer can panic on some cgo / not-in-heap pointers, and an over-long type name is truncated. It therefore cannot recurse, overflow the stack, exhaust memory, or panic no matter what tag contains — at the cost of not showing the tag's contents.
TagString forwards here in the default build.
func TagsString ¶ added in v0.700.0
TagsString returns a debug string for a slice of tags.
In the default build it forwards to the crash-safe TagsStringRelease; see DebugRender.
func TagsStringDebug ¶ added in v0.700.0
TagsStringDebug renders a slice of tags in full, each with TagStringDebug.
It applies no aggregate size limit, matching the informative (unsafe) debug contract. TagsString forwards here in the debug and -race builds.
func TagsStringRelease ¶ added in v0.700.0
TagsStringRelease renders a slice of tags, each with TagStringRelease.
It stops once the output reaches maxTagString, so it inherits the per-element crash-safety and stays bounded however long the slice is. TagsString forwards here in the default build.
Types ¶
type TagGetter ¶
type TagGetter interface {
// JawsGetTag returns the value that [TagExpand] interprets as the object's tags.
JawsGetTag() any
}
TagGetter exposes an object's tags to TagExpand.
github.com/linkdata/jaws.Element.ApplyGetter, TagExpand, and APIs that use TagExpand for registration, dirtying, or broadcast destinations may call JawsGetTag before or after rendering and more than once. Application code may also call it directly. The method receives no Request or rendering context. Callers needing flattened, validated keys should use TagExpand rather than interpret the raw return value.
An explicitly documented initialization phase may return a nil interface, which expands to no keys. A typed nil is a non-nil interface and follows TagExpand's normal rules for its dynamic type. Initialization is not retroactive: it does not affect earlier registration, dirtying, or broadcasts. After its first non-nil result, every call must return a value that TagExpand expands to the same set of keys. Previously returned containers must continue expanding to the key set they produced when returned and must be treated as read-only. Fresh containers and equivalent representations are allowed.
JaWS does not serialize JawsGetTag calls. A getter used concurrently must synchronize its state and safely publish any returned containers.
Example ¶
ExampleTagGetter shows the two supported ways to read an object's tags: JawsGetTag directly, which returns the raw value, and TagExpand, which flattens and validates it into keys. Both are stable for as long as the getter is idempotent.
package main
import (
"fmt"
"github.com/linkdata/jaws/lib/tag"
)
type exampleItem struct {
Name string
}
func (item *exampleItem) JawsGetTag() any {
return item
}
func main() {
group := &exampleItem{Name: "group"}
item := &exampleItem{Name: "row"}
// JawsGetTag is the canonical public accessor and may be called directly.
fmt.Println(item.JawsGetTag() == item)
// TagExpand is how to obtain flattened, validated keys.
keys, err := tag.TagExpand([]any{item, group})
if err != nil {
panic(err)
}
fmt.Println(len(keys), keys[0] == item, keys[1] == group)
}
Output: true 2 true true