river

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2023 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package river implements a high-level API for decoding and encoding River configuration files. The mapping between River and Go values is described in the documentation for the Unmarshal and Marshal functions.

Lower-level APIs which give more control over configuration evaluation are available in the inner packages. The implementation of this package is minimal and serves as a reference for how to consume the lower-level packages.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoConversion = value.ErrNoConversion

ErrNoConversion is returned by implementations of ConvertibleFromCapsule and ConvertibleToCapsule when a conversion with a specific type is unavailable.

Returning this error causes River to fall back to default conversion rules.

Functions

func Marshal added in v0.29.0

func Marshal(v interface{}) ([]byte, error)

Marshal returns the pretty-printed encoding of v as a River configuration file. v must be a Go struct with river struct tags which determine the structure of the resulting file.

Marshal traverses the value v recursively, encoding each struct field as a River block or River attribute, based on the flags provided to the river struct tag.

When a struct field represents a River block, Marshal creates a new block and recursively encodes the value as the body of the block. The name of the created block is taken from the name specified by the river struct tag.

Struct fields which represent River blocks must be either a Go struct or a slice of Go structs. When the field is a Go struct, its value is encoded as a single block. When the field is a slice of Go structs, a block is created for each element in the slice.

When encoding a block, if the inner Go struct has a struct field representing a River block label, the value of that field is used as the label name for the created block. Fields used for River block labels must be the string type. When specified, there must not be more than one struct field which represents a block label.

The river tag specifies a name, possibly followed by a comma-separated list of options. The name must be empty if the provided options do not support a name being defined. The following provides examples for all supported struct field tags with their meanings:

// Field appears as a block named "example". It will always appear in the
// resulting encoding. When decoding, "example" is treated as a required
// block and must be present in the source text.
Field struct{...} `river:"example,block"`

// Field appears as a set of blocks named "example." It will appear in the
// resulting encoding if there is at least one element in the slice. When
// decoding, "example" is treated as a required block and at least one
// "example" block must be present in the source text.
Field []struct{...} `river:"example,block"`

// Field appears as block named "example." It will always appear in the
// resulting encoding. When decoding, "example" is treated as an optional
// block and can be omitted from the source text.
Field struct{...} `river:"example,block,optional"`

// Field appears as a set of blocks named "example." It will appear in the
// resulting encoding if there is at least one element in the slice. When
// decoding, "example" is treated as an optional block and can be omitted
// from the source text.
Field []struct{...} `river:"example,block,optional"`

// Field appears as an attribute named "example." It will always appear in
// the resulting encoding. When decoding, "example" is treated as a
// required attribute and must be present in the source text.
Field bool `river:"example,attr"`

// Field appears as an attribute named "example." If the field's value is
// the Go zero value, "example" is omitted from the resulting encoding.
// When decoding, "example" is treated as an optional attribute and can be
// omitted from the source text.
Field bool `river:"example,attr,optional"`

// The value of Field appears as the block label for the struct being
// converted into a block. When decoding, a block label must be provided.
Field string `river:",label"`

// The inner attributes and blocks of Field are exposed as top-level
// attributes and blocks of the outer struct.
Field struct{...} `river:",squash"`

// Field appears as a set of blocks starting with "example.". Only the
// first set element in the struct will be encoded. Each field in struct
// must be a block. The name of the block is prepended to the enum name.
// When decoding, enum blocks are treated as optional blocks and can be
// omitted from the source text.
Field []struct{...} `river:"example,enum"`

// Field is equivalent to `river:"example,enum"`.
Field []struct{...} `river:"example,enum,optional"`

If a river tag specifies a required or optional block, the name is permitted to contain period `.` characters.

Marshal will panic if it encounters a struct with invalid river tags.

When a struct field represents a River attribute, Marshal encodes the struct value as a River value. The attribute name will be taken from the name specified by the river struct tag. See MarshalValue for the rules used to convert a Go value into a River value.

Example
package main

import (
	"fmt"

	"github.com/grafana/agent/pkg/river"
)

func main() {
	type Person struct {
		Name     string `river:"name,attr"`
		Age      int    `river:"age,attr"`
		Location string `river:"location,attr,optional"`
	}

	p := Person{
		Name: "John Doe",
		Age:  43,
	}

	bb, err := river.Marshal(p)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(bb))
}
Output:

name = "John Doe"
age  = 43

func MarshalValue added in v0.29.0

func MarshalValue(v interface{}) ([]byte, error)

MarshalValue returns the pretty-printed encoding of v as a River value.

MarshalValue traverses the value v recursively. If an encountered value implements the encoding.TextMarshaler interface, MarshalValue calls its MarshalText method and encodes the result as a River string. If a value implements the Capsule interface, it always encodes as a River capsule value.

Otherwise, MarshalValue uses the following type-dependent default encodings:

Boolean values encode to River bools.

Floating point, integer, and Number values encode to River numbers.

String values encode to River strings.

Array and slice values encode to River arrays, except that []byte is converted into a River string. Nil slices encode as an empty array and nil []byte slices encode as an empty string.

Structs encode to River objects, using Go struct field tags to determine the resulting structure of the River object. Each exported struct field with a river tag becomes an object field, using the tag name as the field name. Other struct fields are ignored. If no struct field has a river tag, the struct encodes to a River capsule instead.

Function values encode to River functions, which appear in the resulting text as strings formatted as "function(GO_TYPE)".

All other Go values encode to River capsules, which appear in the resulting text as strings formatted as "capsule(GO_TYPE)".

The river tag specifies the field name, possibly followed by a comma-separated list of options. The following provides examples for all supported struct field tags with their meanings:

// Field appears as an object field named "my_name". It will always
// appear in the resulting encoding. When decoding, "my_name" is treated
// as a required attribute and must be present in the source text.
Field bool `river:"my_name,attr"`

// Field appears as an object field named "my_name". If the field's value
// is the Go zero value, "example" is omitted from the resulting encoding.
// When decoding, "my_name" is treated as an optional attribute and can be
// omitted from the source text.
Field bool `river:"my_name,attr,optional"`
Example
package main

import (
	"fmt"

	"github.com/grafana/agent/pkg/river"
)

func main() {
	type Person struct {
		Name string `river:"name,attr"`
		Age  int    `river:"age,attr"`
	}

	p := Person{
		Name: "John Doe",
		Age:  43,
	}

	bb, err := river.MarshalValue(p)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(bb))
}
Output:

{
	name = "John Doe",
	age  = 43,
}

func Unmarshal added in v0.29.0

func Unmarshal(in []byte, v interface{}) error

Unmarshal converts the River configuration file specified by in and stores it in the struct value pointed to by v. If v is nil or not a pointer, Unmarshal panics. The configuration specified by in may use expressions to compute values while unmarshaling. Refer to the River language documentation for the list of valid formatting and expression rules.

Unmarshal uses the inverse of the encoding rules that Marshal uses, allocating maps, slices, and pointers as necessary.

To unmarshal a River body into a map[string]T, Unmarshal assigns each attribute to a key in the map, and decodes the attribute's value as the value for the map entry. Only attribute statements are allowed when unmarshaling into a map.

To unmarshal a River body into a struct, Unmarshal matches incoming attributes and blocks to the river struct tags specified by v. Incoming attribute and blocks which do not match to a river struct tag cause a decoding error. Additionally, any attribute or block marked as required by the river struct tag that are not present in the source text will generate a decoding error.

To unmarshal a list of River blocks into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice.

To unmarshal a list of River blocks into a Go array, Unmarshal decodes each block into the corresponding Go array element. If the number of River blocks does not match the length of the Go array, a decoding error is returned.

Unmarshal follows the rules specified by UnmarshalValue when unmarshaling the value of an attribute.

Example
package main

import (
	"fmt"

	"github.com/grafana/agent/pkg/river"
)

func main() {
	// Character is our block type which holds an individual character from a
	// book.
	type Character struct {
		// Name of the character. The name is decoded from the block label.
		Name string `river:",label"`
		// Age of the character. The age is a required attribute within the block,
		// and must be set in the config.
		Age int `river:"age,attr"`
		// Location the character lives in. The location is an optional attribute
		// within the block. Optional attributes do not have to bet set.
		Location string `river:"location,attr,optional"`
	}

	// Book is our overall type where we decode the overall River file into.
	type Book struct {
		// Title of the book (required attribute).
		Title string `river:"title,attr"`
		// List of characters. Each character is a labeled block. The optional tag
		// means that it is valid not provide a character block. Decoding into a
		// slice permits there to be multiple specified character blocks.
		Characters []*Character `river:"character,block,optional"`
	}

	// Create our book with two characters.
	input := `
		title = "Wheel of Time"

		character "Rand" {
			age      = 19
			location = "Two Rivers"
		}

		character "Perrin" {
			age      = 19
			location = "Two Rivers"
		}
	`

	// Unmarshal the config into our Book type and print out the data.
	var b Book
	if err := river.Unmarshal([]byte(input), &b); err != nil {
		panic(err)
	}

	fmt.Printf("%s characters:\n", b.Title)

	for _, c := range b.Characters {
		if c.Location != "" {
			fmt.Printf("\t%s (age %d, location %s)\n", c.Name, c.Age, c.Location)
		} else {
			fmt.Printf("\t%s (age %d)\n", c.Name, c.Age)
		}
	}

}
Output:

Wheel of Time characters:
	Rand (age 19, location Two Rivers)
	Perrin (age 19, location Two Rivers)
Example (Functions)

This example shows how functions may be called within user configurations. We focus on the `env` function from the standard library, which retrieves a value from an environment variable.

package main

import (
	"fmt"
	"os"

	"github.com/grafana/agent/pkg/river"
)

func main() {
	// Set an environment variable to use in the test.
	_ = os.Setenv("EXAMPLE", "Jane Doe")

	type Data struct {
		String string `river:"string,attr"`
	}

	input := `
		string = env("EXAMPLE")
	`

	var d Data
	if err := river.Unmarshal([]byte(input), &d); err != nil {
		panic(err)
	}

	fmt.Println(d.String)
}
Output:

Jane Doe

func UnmarshalValue added in v0.29.0

func UnmarshalValue(in []byte, v interface{}) error

UnmarshalValue converts the River configuration file specified by in and stores it in the value pointed to by v. If v is nil or not a pointer, UnmarshalValue panics. The configuration specified by in may use expressions to compute values while unmarshaling. Refer to the River language documentation for the list of valid formatting and expression rules.

Unmarshal uses the inverse of the encoding rules that MarshalValue uses, allocating maps, slices, and pointers as necessary, with the following additional rules:

After converting a River value into its Go value counterpart, the Go value may be converted into a capsule if the capsule type implements ConvertibleIntoCapsule.

To unmarshal a River object into a struct, UnmarshalValue matches incoming object fields to the river struct tags specified by v. Incoming object fields which do not match to a river struct tag cause a decoding error. Additionally, any object field marked as required by the river struct tag that are not present in the source text will generate a decoding error.

To unmarshal River into an interface value, Unmarshal stores one of the following:

  • bool, for River bools
  • float64, for floating point River numbers and integers which are too big to fit in either of int/int64/uint64
  • int/int64/uint64, in this order of preference, for signed and unsigned River integer numbers, depending on how big they are
  • string, for River strings
  • []interface{}, for River arrays
  • map[string]interface{}, for River objects

Capsule and function types will retain their original type when decoding into an interface value.

To unmarshal a River array into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice.

To unmarshal a River array into a Go array, Unmarshal decodes River array elements into the corresponding Go array element. If the number of River elements does not match the length of the Go array, a decoding error is returned.

To unmarshal a River object into a Map, Unmarshal establishes a map to use. If the map is nil, Unmarshal allocates a new map. Otherwise, Unmarshal reuses the existing map, keeping existing entries. Unmarshal then stores key-value pairs from the River object into the map. The map's key type must be string.

Example
package main

import (
	"fmt"

	"github.com/grafana/agent/pkg/river"
)

func main() {
	input := `3 + 5`

	var num int
	if err := river.UnmarshalValue([]byte(input), &num); err != nil {
		panic(err)
	}

	fmt.Println(num)
}
Output:

8

Types

type Capsule

type Capsule interface {
	// RiverCapsule marks the type as a Capsule. RiverCapsule is never invoked by
	// River.
	RiverCapsule()
}

Capsule is an interface marker which tells River that a type should always be treated as a "capsule type" instead of the default type River would assign.

Capsule types are useful for passing around arbitrary Go values in River expressions and for declaring new synthetic types with custom conversion rules.

By default, only two capsule values of the same underlying Go type are compatible. Types which implement ConvertibleFromCapsule or ConvertibleToCapsule can provide custom logic for conversions from and to other types.

type ConvertibleFromCapsule

type ConvertibleFromCapsule interface {
	Capsule

	// ConvertFrom updates the ConvertibleFromCapsule value based on the value of
	// src. src may be any Go value, not just other capsules.
	//
	// ConvertFrom should return ErrNoConversion if no conversion is available
	// from src. Other errors are treated as a River decoding error.
	ConvertFrom(src interface{}) error
}

ConvertibleFromCapsule is a Capsule which supports custom conversion from any Go type which is not the same as the capsule type.

type ConvertibleIntoCapsule

type ConvertibleIntoCapsule interface {
	Capsule

	// ConvertInto should convert its value and store it into dst. dst will be a
	// pointer to a Go value of any type.
	//
	// ConvertInto should return ErrNoConversion if no conversion into dst is
	// available. Other errors are treated as a River decoding error.
	ConvertInto(dst interface{}) error
}

ConvertibleIntoCapsule is a Capsule which supports custom conversion into any Go type which is not the same as the capsule type.

type Decoder added in v0.29.0

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder reads River configuration from an input stream. Call NewDecoder to create instances of Decoder.

func NewDecoder added in v0.29.0

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a new Decoder which reads configuration from r.

func (*Decoder) Decode added in v0.29.0

func (dec *Decoder) Decode(v interface{}) error

Decode reads the River-encoded file from the Decoder's input and stores it in the value pointed to by v. Data will be read from the Decoder's input until EOF is reached.

See the documentation for Unmarshal for details about the conversion of River configuration into Go values.

func (*Decoder) DecodeValue added in v0.29.0

func (dec *Decoder) DecodeValue(v interface{}) error

DecodeValue reads the River-encoded expression from the Decoder's input and stores it in the value pointed to by v. Data will be read from the Decoder's input until EOF is reached.

See the documentation for UnmarshalValue for details about the conversion of River values into Go values.

type Defaulter added in v0.35.0

type Defaulter interface {
	// SetToDefault is called when evaluating a block or body to set the value
	// to its defaults. SetToDefault will not be called on types which are
	// squashed into the parent struct using `river:",squash"`.
	SetToDefault()
}

The Defaulter interface allows a type to implement default functionality in River evaluation.

type Encoder added in v0.29.0

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder writes River configuration to an output stream. Call NewEncoder to create instances of Encoder.

func NewEncoder added in v0.29.0

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns a new Encoder which writes configuration to w.

func (*Encoder) Encode added in v0.29.0

func (enc *Encoder) Encode(v interface{}) error

Encode converts the value pointed to by v into a River configuration file and writes the result to the Decoder's output stream.

See the documentation for Marshal for details about the conversion of Go values into River configuration.

func (*Encoder) EncodeValue added in v0.29.0

func (enc *Encoder) EncodeValue(v interface{}) error

EncodeValue converts the value pointed to by v into a River value and writes the result to the Decoder's output stream.

See the documentation for MarshalValue for details about the conversion of Go values into River values.

type Unmarshaler

type Unmarshaler interface {
	// UnmarshalRiver is invoked when decoding a River value into a Go value. f
	// should be called with a pointer to a value to decode into. UnmarshalRiver
	// will not be called on types which are squashed into the parent struct
	// using `river:",squash"`.
	UnmarshalRiver(f func(v interface{}) error) error
}

The Unmarshaler interface allows a type to hook into River decoding and decode into another type or provide pre-decoding logic.

type Validator added in v0.35.0

type Validator interface {
	// Validate is called when evaluating a block or body to enforce the
	// value is valid. Validate will not be called on types which are
	// squashed into the parent struct using `river:",squash"`.
	Validate() error
}

The Validator interface allows a type to implement validation functionality in River evaluation.

Directories

Path Synopsis
Package ast exposes AST elements used by River.
Package ast exposes AST elements used by River.
Package diag exposes error types used throughout River and a method to pretty-print them to the screen.
Package diag exposes error types used throughout River and a method to pretty-print them to the screen.
encoding
riverjson
Package riverjson encodes River as JSON.
Package riverjson encodes River as JSON.
internal
rivertags
Package rivertags decodes a struct type into river object and structural tags.
Package rivertags decodes a struct type into river object and structural tags.
stdlib
Package stdlib contains standard library functions exposed to River configs.
Package stdlib contains standard library functions exposed to River configs.
value
Package value holds the internal representation for River values.
Package value holds the internal representation for River values.
Package parser implements utilities for parsing River configuration files.
Package parser implements utilities for parsing River configuration files.
Package printer contains utilities for pretty-printing River ASTs.
Package printer contains utilities for pretty-printing River ASTs.
Package scanner implements a lexical scanner for River source files.
Package scanner implements a lexical scanner for River source files.
Package token defines the lexical elements of a River config and utilities surrounding their position.
Package token defines the lexical elements of a River config and utilities surrounding their position.
builder
Package builder exposes an API to create a River configuration file by constructing a set of tokens.
Package builder exposes an API to create a River configuration file by constructing a set of tokens.
Package vm provides a River expression evaluator.
Package vm provides a River expression evaluator.

Jump to

Keyboard shortcuts

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