Skip to content

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:

vuka
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-inBecomes
@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:

vuka
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:

vuka
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:

vuka
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
@vuka.Targets(vuka.OnField)
type Char struct{ Max int }

@vuka.Targets(vuka.OnParam | vuka.OnField)
type Path string

Using 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.

Released under the MIT License. Vuka builds on Go and its tools, which are made by the Go Authors. Vuka is not affiliated with Google or the Go project. Go is a trademark of Google LLC.