parser API

parser

package

API reference for the parser package.

S
struct

Config

Config holds the parsed FSM definition from a tag.

pkg/fsm/parser/parser.go:12-17
type Config struct

Fields

Name Type Description
InitialState string
Transitions map[string][]string
Wildcards []string
Timeouts map[string]TimeoutRule
S
struct

TimeoutRule

TimeoutRule defines an automatic transition after a duration.

pkg/fsm/parser/parser.go:20-24
type TimeoutRule struct

Fields

Name Type Description
FromState string
ToState string
Duration time.Duration
F
function

Parse

Parse parses an FSM tag string into a Config.

Parameters

tag
string

Returns

error
pkg/fsm/parser/parser.go:29-86
func Parse(tag string) (*Config, error)

{
	cfg := &Config{
		Transitions: make(map[string][]string),
		Timeouts:    make(map[string]TimeoutRule),
	}

	parsed := tagParser.Parse(tag)

	if initial, ok := parsed["initial"]; ok && len(initial) > 0 {
		cfg.InitialState = initial[0]
	}

	for key := range parsed {
		if key == "initial" {
			continue
		}

		part := key
		var timeoutDuration time.Duration

		if startBracket := strings.Index(part, "["); startBracket != -1 {
			endBracket := strings.Index(part, "]")
			if endBracket > startBracket {
				durStr := part[startBracket+1 : endBracket]
				var err error
				timeoutDuration, err = time.ParseDuration(durStr)
				if err != nil {
					return nil, fmt.Errorf("invalid timeout duration: %s", durStr)
				}
				part = strings.TrimSpace(part[:startBracket])
			}
		}

		before, after, ok := strings.Cut(part, "->")
		if !ok {
			continue
		}

		src := strings.TrimSpace(before)
		dst := strings.TrimSpace(after)

		if src == "*" {
			cfg.Wildcards = append(cfg.Wildcards, dst)
		} else {
			cfg.Transitions[src] = append(cfg.Transitions[src], dst)
		}

		if timeoutDuration > 0 {
			cfg.Timeouts[src] = TimeoutRule{
				FromState: src,
				ToState:   dst,
				Duration:  timeoutDuration,
			}
		}
	}

	return cfg, nil
}
F
function

TestParse_BasicTransition

Parameters

pkg/fsm/parser/parser_test.go:8-25
func TestParse_BasicTransition(t *testing.T)

{
	cfg, err := Parse("initial:draft; draft->paid; paid->shipped")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if cfg.InitialState != "draft" {
		t.Errorf("initial = %q, want %q", cfg.InitialState, "draft")
	}
	if len(cfg.Transitions) != 2 {
		t.Fatalf("expected 2 transitions, got %d", len(cfg.Transitions))
	}
	if !contains(cfg.Transitions["draft"], "paid") {
		t.Error("expected draft->paid")
	}
	if !contains(cfg.Transitions["paid"], "shipped") {
		t.Error("expected paid->shipped")
	}
}
F
function

TestParse_Wildcard

Parameters

pkg/fsm/parser/parser_test.go:27-35
func TestParse_Wildcard(t *testing.T)

{
	cfg, err := Parse("initial:active; *->cancelled")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if len(cfg.Wildcards) != 1 || cfg.Wildcards[0] != "cancelled" {
		t.Errorf("wildcards = %v, want [cancelled]", cfg.Wildcards)
	}
}
F
function

TestParse_Timeout

Parameters

pkg/fsm/parser/parser_test.go:37-52
func TestParse_Timeout(t *testing.T)

{
	cfg, err := Parse("initial:pending; pending->expired [500ms]")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	rule, ok := cfg.Timeouts["pending"]
	if !ok {
		t.Fatal("expected timeout for 'pending'")
	}
	if rule.ToState != "expired" {
		t.Errorf("toState = %q, want %q", rule.ToState, "expired")
	}
	if rule.Duration != 500*time.Millisecond {
		t.Errorf("duration = %v, want %v", rule.Duration, 500*time.Millisecond)
	}
}
F
function

TestParse_TimeoutCustomDuration

Parameters

pkg/fsm/parser/parser_test.go:54-66
func TestParse_TimeoutCustomDuration(t *testing.T)

{
	cfg, err := Parse("a->b [2s]")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	rule, ok := cfg.Timeouts["a"]
	if !ok {
		t.Fatal("expected timeout for 'a'")
	}
	if rule.Duration != 2*time.Second {
		t.Errorf("duration = %v, want %v", rule.Duration, 2*time.Second)
	}
}
F
function

TestParse_MultipleTransitionsFromSameState

Parameters

pkg/fsm/parser/parser_test.go:68-76
func TestParse_MultipleTransitionsFromSameState(t *testing.T)

{
	cfg, err := Parse("a->b; a->c; b->d")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if len(cfg.Transitions["a"]) != 2 {
		t.Fatalf("expected 2 transitions from 'a', got %d", len(cfg.Transitions["a"]))
	}
}
F
function

TestParse_NoInitial

Parameters

pkg/fsm/parser/parser_test.go:78-86
func TestParse_NoInitial(t *testing.T)

{
	cfg, err := Parse("a->b")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if cfg.InitialState != "" {
		t.Errorf("expected empty initial, got %q", cfg.InitialState)
	}
}
F
function

TestParse_InvalidDuration

Parameters

pkg/fsm/parser/parser_test.go:88-93
func TestParse_InvalidDuration(t *testing.T)

{
	_, err := Parse("a->b [notaduration]")
	if err == nil {
		t.Fatal("expected error for invalid duration")
	}
}
F
function

TestParse_TrimsWhitespace

Parameters

pkg/fsm/parser/parser_test.go:95-106
func TestParse_TrimsWhitespace(t *testing.T)

{
	cfg, err := Parse("  initial:  draft  ;  draft  ->  paid  ")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if cfg.InitialState != "draft" {
		t.Errorf("initial = %q, want %q", cfg.InitialState, "draft")
	}
	if !contains(cfg.Transitions["draft"], "paid") {
		t.Error("expected draft->paid")
	}
}
F
function

TestParse_EmptyTag

Parameters

pkg/fsm/parser/parser_test.go:108-116
func TestParse_EmptyTag(t *testing.T)

{
	cfg, err := Parse("")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if cfg.InitialState != "" || len(cfg.Transitions) != 0 {
		t.Error("expected empty config for empty tag")
	}
}
F
function

TestParse_MultipleWildcards

Parameters

pkg/fsm/parser/parser_test.go:118-126
func TestParse_MultipleWildcards(t *testing.T)

{
	cfg, err := Parse("*->cancelled; *->archived")
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	if len(cfg.Wildcards) != 2 {
		t.Fatalf("expected 2 wildcards, got %v", cfg.Wildcards)
	}
}
F
function

contains

Parameters

slice
[]string
s
string

Returns

bool
pkg/fsm/parser/parser_test.go:128-135
func contains(slice []string, s string) bool

{
	for _, v := range slice {
		if v == s {
			return true
		}
	}
	return false
}
S
struct

Policy

Policy represents the security rules extracted from a struct tag.

pkg/guard/parser/parser.go:8-12
type Policy struct

Fields

Name Type Description
Roles []string
Permissions map[string][]string
UseValueAsRole bool
F
function

Parse

Parse extracts policy information from a ‘guard’ struct tag.

Parameters

tag
string

Returns

pkg/guard/parser/parser.go:17-38
func Parse(tag string) *Policy

{
	p := &Policy{
		Permissions: make(map[string][]string),
	}

	parsed := tagParser.Parse(tag)

	for key, values := range parsed {
		if key == "role" {
			if len(values) == 1 && values[0] == "*" {
				p.UseValueAsRole = true
			} else {
				p.Roles = append(p.Roles, values...)
			}
			continue
		}

		p.Permissions[key] = values
	}

	return p
}