steps

package
v0.1.23 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Declarative transformation steps, for any message format.

Why this is shared

The step vocabulary - set, copy, clear, map, replace, trim, case, each with a description and an optional condition - has nothing to do with any particular format. Only three things differ between formats: how a path is written, how a value is read, and how it is written back.

This is the same conclusion the filter grammar reached in internal/expr, and for the same reason. X12 had a step engine and NCPDP needed one; copying it would have produced a second four-hundred-line engine to keep in step by hand, and then a third for delimited. The behaviour worth having is subtle enough that a copy would not keep it - the reason the result is re-read after every write, for instance, took a real defect to discover.

What a format supplies

An Accessor: a path compiler, a condition compiler, and an optional veto on steps it cannot honour. Nothing else.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Accessor

type Accessor[M any] interface {
	// Path compiles one path, or reports why it cannot.
	Path(raw string) (Path[M], error)

	// Condition compiles a when expression over this format.
	Condition(src string) (expr.Expr[M], error)

	// CheckStep refuses a step this format cannot honour, and returns nil for everything it can.
	//
	// For the refusals that are real but specific: a trim on an X12 ISA element can never have an effect, because ISA is
	// fixed width and the padding is put straight back. Refusing at load beats a step that runs forever and does nothing.
	CheckStep(s Step) error
}

Accessor is how one message format addresses, reads and writes values.

type CaseStep

type CaseStep struct {
	Path string `yaml:"path"`

	// To is "upper" or "lower".
	To string `yaml:"to"`
}

CaseStep changes the case of a value.

type Change

type Change struct {
	// Path is where it happened, in canonical form.
	Path string
	// Description is the step's own explanation, when it gave one.
	Description string
	// From and To are the values before and after.
	From string
	To   string
}

Change records one alteration, for the trace and the report.

type ClearStep

type ClearStep struct {
	Path string `yaml:"path"`
}

ClearStep empties a field.

type CopyStep

type CopyStep struct {
	From string `yaml:"from"`
	To   string `yaml:"to"`
}

CopyStep moves a value from one path to another.

type MapStep

type MapStep struct {
	Path string `yaml:"path"`

	// Table names a code set loaded with the channel.
	Table string `yaml:"table"`

	// Default is written when the value is not in the table. Optional.
	Default string `yaml:"default,omitempty"`

	// OnMissing decides what happens when the value is absent from the table and no default is given.
	//
	// Defaults to "keep", which leaves the value alone. The alternative is "fail", which stops the message. There is no
	// option that silently blanks it, because a claim with an empty payer identifier is rejected downstream in a way
	// that points at the wrong system.
	OnMissing string `yaml:"on_missing,omitempty"`
}

MapStep translates a value through a named table.

type Path

type Path[M any] struct {
	// Canonical is the path in canonical form, for change records and error messages.
	Canonical string

	// Read returns the value at this path and whether it was present.
	//
	// The bool distinguishes absent from empty, which every format here treats as different things: a blank field was
	// sent deliberately and an absent one was never sent.
	Read func(M) (string, bool)

	// Write returns a message with the value written. It must not modify its receiver.
	Write func(M, string) (M, error)
}

Path is a path compiled against one message format.

Closures rather than a string handed back to the accessor on every access, so a path is parsed once at load. The filter grammar measured this and it was worth roughly a third of the cost of an evaluation.

type ReplaceStep

type ReplaceStep struct {
	Path    string `yaml:"path"`
	Pattern string `yaml:"pattern"`
	With    string `yaml:"with"`
}

ReplaceStep performs a regular-expression substitution.

type SetStep

type SetStep struct {
	Path  string `yaml:"path"`
	Value string `yaml:"value"`
}

SetStep writes a literal value.

type Step

type Step struct {
	// Description is free text explaining why this step exists. Optional, shown in the interface, and worth writing: the
	// next person to read this channel is trying to work out whether the step is still needed.
	Description string `yaml:"description,omitempty"`

	// When is an optional condition. The step is skipped unless it holds.
	//
	// The same grammar as any filter in this server - and, or, not, ==, !=, =~, exists, empty, in - because the grammar
	// is generic over the format. The only thing that changes between formats is how the path is written.
	When string `yaml:"when,omitempty"`

	// Set writes a value at a path.
	Set *SetStep `yaml:"set,omitempty"`

	// Copy takes the value at one path and writes it to another.
	Copy *CopyStep `yaml:"copy,omitempty"`

	// Clear empties a field, leaving it present.
	//
	// Not the same as removing it, and these formats make that distinction meaningful: a blank field was sent
	// deliberately while an absent one was never sent. There is no remove step for exactly that reason - the honest
	// options are to blank the field or to leave it alone.
	Clear *ClearStep `yaml:"clear,omitempty"`

	// Map translates a value through a lookup table.
	Map *MapStep `yaml:"map,omitempty"`

	// Replace performs a regular-expression substitution.
	Replace *ReplaceStep `yaml:"replace,omitempty"`

	// Trim removes surrounding whitespace.
	//
	// Worth having as its own step rather than a replace: these formats pad fixed-width fields with spaces, so this is
	// one of the most common changes an interface needs, and a named step is readable where a pattern is not.
	Trim *TrimStep `yaml:"trim,omitempty"`

	// Case changes a value to upper or lower case.
	Case *CaseStep `yaml:"case,omitempty"`
}

Step is one declarative change.

Exactly one action per step, checked at load. Two actions in one step have no defined order, and a configuration whose meaning depends on which the reader assumes is worse than one that refuses to load.

func (Step) TargetPaths

func (s Step) TargetPaths() map[string]string

TargetPaths returns the paths this step writes to, keyed by the configuration key that named each one.

For an Accessor's CheckStep, which usually needs to veto a step by what it writes rather than by which action it is: a format with a derived or fixed-width field cares that something writes to it, not whether the step was a set or a trim.

copy.from is deliberately absent. Reading a field is always allowed; it is writing that a format restricts.

type Steps

type Steps[M any] struct {
	// contains filtered or unexported fields
}

Steps is a compiled sequence, ready to run.

func Compile

func Compile[M any](in []Step, tables *codeset.Set, a Accessor[M]) (*Steps[M], error)

Compile prepares a sequence for running.

Everything checkable without a message is checked here: paths parse, patterns compile, tables are named, case directions are recognised, exactly one action per step. A channel with a bad step must refuse to start rather than fail on the first message, because the operator who deployed it is watching at that moment and will not be watching at three the following morning.

func (*Steps[M]) Apply

func (s *Steps[M]) Apply(m M) (M, []Change, error)

Apply runs the sequence, returning the result and what changed.

The message given is not modified. Every step works on the output of the last, so a later step sees earlier changes - which is what a reader of the file expects, and the alternative would make a sequence of steps behave differently from the same steps in separate channels.

func (*Steps[M]) Len

func (s *Steps[M]) Len() int

Len reports how many steps there are.

type TrimStep

type TrimStep struct {
	Path string `yaml:"path"`
}

TrimStep removes surrounding whitespace.

Jump to

Keyboard shortcuts

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