tiq

package module
v0.0.0-...-83b533a Latest Latest
Warning

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

Go to latest
Published: Nov 20, 2025 License: MIT Imports: 7 Imported by: 0

README

tiq

Modular Golang Struct tags parser that's actually useful.

type Config struct {
	Url  string `env:"name=URL, type=string"`
	Port int    `env:"name=PORT, type=port"`
}

type EnvSchema struct {
	Name string `tag:"env | get('name')"`
	Type string `tag:"env | get('type')"`
}

conf := Config{}

inspector, err := tiq.Inspect(&conf)
for _, field := range inspector.Fields() {
    env, err := tiq.Parse[EnvSchema](field)

    value := os.Getenv(env.Name)
    field.Set(validate(env.Type, value))
}

conf.Url // now set to the value of the URL env var
conf.Port // now set to the value of the PORT env var

Installation

go get github.com/AnatoleLucet/tiq

Usage

tiq is a modular Golang Struct tags parser. It offers a very simple DSL to extract what you need from tags, and multiple APIs to inspect and update user defined structs.

The DSL is designed to be as straightforward as possible for you to pick up and get what's happening at a glance, even if you've never used tiq before.

import (
    "github.com/AnatoleLucet/tiq"
)

// User defined struct with tags that can by parsed by the `load()` function.
type Config struct {
	Url  string `env:"name=URL, type=string, optional"`
	Port int    `env:"name=PORT, type=port, oneof=8080|3000|5000"`
}

func main() {
    conf, err := load(&Config{})

    conf.Url // Now set to the value of the URL env var.
    conf.Port // Now set to the value of the PORT env var.
}


// Define your schema and how to parse tags
type EnvSchema struct {
    // Each field containing a `tag:""` will be evaluated by tiq's DSL to
    // extract what you need from the user defined tags.
    // See "The DSL" section of the README to learn more.
	Name     string   `tag:"env | get('name')"`
	Type     string   `tag:"env | get('type')"`
	Optional bool     `tag:"env | has('optional')"`
	Oneof    []string `tag:"env | get('oneof') | split('|')"`
}

func load[T any](conf *T) (*T, error) {
    inspector, err := tiq.Inspect(conf)

    for _, field := range inspector.Fields() {
        env, err := tiq.Parse[EnvSchema](field)

        value := os.Getenv(env.Name)

        // Ideally you'd call a function to validate if the value
        // is correct according what was parsed in the `env` variable:
        // validate(env, value)

        // .SetFrom() will convert the value to the target field's type using `github.com/AnatoleLucet/as`.
        // Alternatively you could use .Set() to try and set the field's value directly without conversion.
        field.SetFrom(value)
    }

    return conf
}
Real world example

If you want to see tiq in action on a real project, checkout environ, another project of mine, powered by tiq!

The DSL

The DSL is based on ExprLang, a simple but powerful expression language.

Don't try to use functions from the official ExprLang docs, they probably won't work. Instead, take a look at tiq's function set to find what you need!

Basic syntax
# The most simple expression would look like this:
`tag:"123"`
# where `tag:"..."` is the Golang tag tiq will pick up for evaluation,
# and `123` is the DSL expression tiq will evaluate.

# To get a tag's value, simply use the name of the tag you want to get:
`tag:"mytag"`
# it will return `mytag`'s content unaltered (e.g. if given `mytag:"content"`, the expression above will return `content`).

# Once you have the value you want to parse, you can use tiq's function set to extract entries and values from it:
`tag:"get(mytag, 'foo')"`
# here we pass `mytag`'s content to the `get()` function, and try to get the `foo` entry's value from it.
# So when given `mytag:"foo=bar"`, the expression above will return `bar` (the value of the `foo` entry).

# To chain one or more functions together, you can use ExprLang's pipe operator:
`tag:"mytag | get("foo") | default("baz")"`
# the pipe operator will pass the left operand's value as the first parameter the right operand.
# What this means is that `"foo=bar" | get("foo")` is equivalent to `get("foo=bar", "foo")`.

# To learn more about tiq's syntax, check out ExprLang's docs at https://expr-lang.org/docs/getting-started.
# But remember most functions from ExprLang won't work because tiq uses its own functions set (described below).
Functions
Name Description Usage
get() Gets an entry's value from a comma-separated key-value list. get("foo=1, bar=2", "foo") -> 1
first() Gets the first entry's value (or key if there's no value) from a comma-separated key-value list. first("foo=1, bar=2") -> 1
last() Gets the last entry's value (or key if there's no value) from a comma-separated key-value list. last("foo=1, bar=2") -> 2
nth() Gets the nth entry's value (or key if there's no value) from a comma-separated key-value list. nth("foo=1, bar=2", 0) -> 1
has() Returns true or false if the entry is present in a comma-separated key-value list. has("foo=1, bar=2", "bar") -> true
split() Splits a string with the given separator. split("1|2|3|4", "|") -> [1 2 3 4]
default() Returns a default value if the given value if nil. default(nil, "foo") -> "foo"

tiq.Inspect

The inspector helps you crawl through a struct's fields, read tags from them, and update values accordingly.

inspector, err := tiq.Inspect(&mystruct)

// Get a field by name
field, ok := inspector.Field("Name")

field.Set("value") // update the field's value
field.SetFrom("value") // same as .Set() but converts the value to the field's type if necessary
field.Tag("mytag") // returns the content of `mytag:"content"`
field.Tags() // returns every tags of the field in a map[string]string

// Alternatively you could loop through every field on the struct:
for _, field := range inspector.Fields() {
    // field.Set("value")
}

tiq.Parse

The parser is how you retrieve what you want from tags with tiq. It takes a schema and a tiq.Field to parse tags on.

type EnvSchema struct {
	Name     string   `tag:"env | get('name')"`
	Optional bool     `tag:"env | has('optional')"`
	Oneof    []string `tag:"env | get('oneof') | split('|')"`
}

env, err := tiq.Parse[EnvSchema](field) // field is usually retrieved via tiq.Inspect

env.Name // if `field` has a tag `env:"name=foo"`, this will be set to "foo", else ""
env.Optional // if `field` has a tag `env:"optional"`, this will be set to true, else false
env.Oneof // if `field` has a tag `env:"oneof=one|two|three"`, this will be set to [one two three], else []

tiq.Get

A simple static function to get a tag's content from anywhere.

type User struct {
    Name string `json:"name,omitempty"`
}

var user User
json, err := tiq.Get(&user, "Name", "json")

json // "name,omitempty"

tiq.Set

A simple static function to set a field's content from anywhere.

type User struct {
    Name string `json:"name,omitempty"`
}

var user User
err := tiq.Set(&user, "Name", "Bob")

user.Name // "Bob"

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNilValue      = errors.New("nil value provided")
	ErrNotAStruct    = errors.New("provided value is not a struct or pointer to struct")
	ErrCannotConvert = errors.New("cannot convert value")

	ErrFieldNotFound    = errors.New("field not found")
	ErrFieldNotSettable = errors.New("field is not settable")

	ErrCompileTag = errors.New("cannot compile tag")
)

Functions

func Get

func Get(value any, field, tag string) (string, bool)

func Parse

func Parse[Schema any](field *Field) (*Schema, error)

func Set

func Set(value any, field string, newValue any) error

Types

type Field

type Field struct {
	reflect.Value
	reflect.StructField
}

func (*Field) Set

func (f *Field) Set(value any) error

Set updates the field's value to the provided value.

func (*Field) SetFrom

func (f *Field) SetFrom(value any) error

SetFrom updates the field's value to the provided value after converting it to the appropriate type. See as.Type for supported conversions.

func (*Field) Tag

func (f *Field) Tag(name string) (string, bool)

Tag returns the tag value of the given name and whether it was found or not.

func (*Field) Tags

func (f *Field) Tags() (map[string]string, error)

Tags parses and returns every tag of the field as a map.

type Inspector

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

func Inspect

func Inspect(value any) (*Inspector, error)

Inspect takes a struct or pointer to struct and returns an Inspector that can be used to inspect the struct's fields and tags.

func (*Inspector) Field

func (i *Inspector) Field(name string) (*Field, bool)

Field returns the field with the given name, or nil if it doesn't exist.

func (*Inspector) Fields

func (i *Inspector) Fields() []*Field

Fields returns every fields of the struct.

Jump to

Keyboard shortcuts

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