Attributes
Introduction
In Go, metadata lives in struct tags and comments (//go:generate, doc text). Vuka adds attributes: @ before a declaration. An attribute is either built in, or any Go type — so its arguments are type-checked Go.
You use attributes to attach checked metadata — a route, a permission, a deprecation notice — that decorators and libraries read back at run time.
Writing Attributes
This sample puts a built-in @doc, a typed @Route and a built-in @deprecated on two functions:
type Route struct{ Method, Path string }
@doc("Lists the users")
@Route{Method: "GET", Path: "/users"}
func listUsers() []User { … }
@deprecated("use the shapes service")
func handler() string { … }Built-in Attributes
Three attributes are built in:
| Built-in | Becomes |
|---|---|
@doc("…") | the declaration's doc comment |
@deprecated("…") | a Deprecated: paragraph |
@export("Name") | a wrapper named Name, for Go code to call |
Typed Attributes
A typed attribute (@Route{…}, @Perm("orders.write")) is checked by Go: a wrong field or type is a compile error on the @ line.
Attributes are readable at run time by decorators through c.Attr(&x), and from anywhere through the registry.
Where Attributes Go
An attribute that names a function rather than a type is a decorator.
Parameters take them before the parameter — func show(@Path("id") id int) — see Parameter attributes; and an attribute type can restrict where it goes: see Targets.
Struct fields take typed attributes too, after the field: Title string @Char{Max: 200}. See Field Attributes & References.
A field of type vuka.File set to a string literal names a file that is checked at compile time and embedded: see Files.
Reading Attributes From a Value
Vuka records, at program start, the typed attributes of every function, method and type that has any (a bundle's count), so a library holding only a value — a method value it was handed as a handler, a reflect.Type — reads what was written on its declaration, without calling it. This sample reads a method's @Gate from a method value and through reflection:
type Gate struct{ Perm string }
@Gate{"board.delete"}
func (b *Board) Clear() { … }
ref, _ := vuka.FuncOf(b.Clear) // a method value, or (*Board).Clear
var g Gate
ref.Attrs.Attr(&g) // g.Perm == "board.delete"; ref.Recv is Board
vuka.MethodAttrs(reflect.TypeFor[Board](), "Clear")
vuka.TypeAttrs(reflect.TypeFor[Board]())The lookup functions are these:
vuka.FuncOf(fn) | the declaration of a function, method expression or method value: Recv and Method for a method, its Attrs; Literal for a function literal, which has none |
vuka.MethodAttrs(t, name) | a method's attributes (its own; its type's are TypeAttrs) |
vuka.TypeAttrs(t) | a type's attributes |
attrs.Attr(&x) | fills x with the attribute of its type, as c.Attr does |
The lookups are a map read (FuncOf caches its answer per function).
What Isn't Recorded
Not recorded: generic functions and types (there is no one function to name), and method values made by reflection (reflect.Value.Method), which name no declaration — look those up with MethodAttrs.
Registering From Go
Go code registers a declaration of its own with vuka.RegisterFunc(fn, attrs…) and vuka.RegisterType[T](attrs…), what the generated code calls. Its vuka.Call also lists every attribute: c.Attrs().
Parameter Attributes
Parameters take typed attributes too, written before the parameter, for code that needs to know something about each argument: which arguments a custom validator should check, for example. This sample marks path, query and body parameters:
type Path string
type Query string
type Body struct{}
type Valid struct{}
func showPet(@Path("pet_id") id int, @Query("format") format string, store *Store) Result[Pet] { … }
@valid
func addPet(@Body @Valid in NewPet, store *Store) Result[Pet] { … }They are values, checked like any typed attribute, and several may precede a parameter; an unnamed parameter takes them too (func f(@Query("q") string)). They go on top-level functions and methods, not on the receiver, results or function literals.
Reading Parameter Attributes
A declarer reads them from d.Params[i].Attrs, or with d.Params[i].Attr(&x); a call decorator with c.ParamAttr(i, &x), i indexing c.Args. This decorator validates every argument marked @Valid:
decorator valid(c) {
for i, arg := range c.Args {
var v Valid
if c.ParamAttr(i, &v) {
if err := arg.(Validator).Validate(); err != nil {
c.SetErr(err)
return
}
}
}
c.Next()
}Restricting Where an Attribute Goes
Some attributes only make sense in one place — a column setting belongs on a field, a route's path source on a parameter. The attribute type can say so, and writing it anywhere else becomes a compile error. Declare the targets with @vuka.Targets on the attribute type:
@vuka.Targets(vuka.OnField)
type Char struct{ Max int }
@vuka.Targets(vuka.OnParam | vuka.OnField)
type Path stringUsing it anywhere else is a compile error at the attribute: @Char can't go on a function; Char is for fields.
The targets are vuka.OnFunc, OnMethod, OnType, OnField, OnParam and OnVar (var and const declarations); a type that declares none goes anywhere. They are checked in every package using the type: Vuka records them in a method of the type, which Go code declares by hand as below.
An attribute written as a call, such as @Size(3), is checked against the targets of the type it returns.