Ref interpolation

${VAR} in a source tag is substituted from variables you pass in, so one struct can serve several environments or regions.

type Config struct {
	DBPassword secret.String `source:"aws-sm://${ENV}/db#password"`
	Bucket     string        `source:"s3://backups-${REGION}/nightly"`
}

cfg, err := mamori.Load[Config](ctx,
	mamori.WithRefVars(map[string]string{"ENV": "prod", "REGION": "eu-west-1"}),
)

Only the braced form is substituted. A bare $VAR passes through untouched, so a password or an exec: command containing a literal $ is safe. $$ collapses to one $.

Variables come only from WithRefVars

mamori never reads the ambient environment for ${VAR}. This is the rule to remember, because a ref decides which secret a process reads. If expansion used os.Getenv, anything that could set an environment variable in your process could redirect that read to a secret of its own choosing.

When a value genuinely should come from the environment, name it:

mamori.WithRefVars(mamori.EnvVars("ENVIRONMENT", "REGION"))

EnvVars takes names on purpose, so what can influence a ref stays greppable at the call site. A name that is not set is omitted rather than mapped to "", so it still fails as undefined instead of expanding to nothing.

Calling WithRefVars more than once merges, with later calls winning per key.

Where variables can appear

Expansion runs over the whole tag before it is parsed, so a variable can supply a scheme, a path segment, a fragment, or an option value:

Endpoint string `source:"${SCHEME}://prod/db#${KEY}?region=${REGION}"`

It is not recursive. A value containing ${B} is inserted verbatim and never rescanned, which rules out loops from self-referencing values.

Errors

Three shapes fail at Load, Watch, or Doctor time rather than expanding to nothing, all wrapping ErrInvalid:

SituationExample
Undefined variable${NOPE} with no NOPE key
Unterminated${NOPE with no }
Empty name${}

Silently expanding to nothing would give you aws-sm:///db, which resolves not-found and quietly takes the field’s default:. A forgotten ENV would become a wrong value instead of an error.

Keep delimiters out of variable values

A value containing #, ?, or , re-cuts the ref, and the result is usually a valid ref pointing somewhere else rather than an error:

DBUser string `source:"aws-sm://${ENV}/db#/credentials/user"`
ENVWhat actually resolves
prodpath prod/db, pointer /credentials/user, as written
prod?xpath prod, no fragment, the rest swallowed into the query
prod#ypath prod, fragment is now the literal key y/db#/credentials/user

The last two resolve not-found and fall through to default:. A comma can similarly split one ref into a two-element precedence chain.

mamori does not escape variable values, because supplying a scheme or fragment is an advertised use of ${VAR} and no rule could tell an intended ? from an accidental one. The rule to hold instead: variable values are operator-supplied identifiers such as environment names, regions, and tenants. If one is ever assembled from something less trusted, validate it there.

Expanded refs are visible, so values must not be secrets

After expansion the ref shows up as resolved, variable values included, in Status(), the admin endpoint, and mamori doctor.

Use WithRefVars for anything you would be comfortable seeing on a status page. A secret belongs in the value a ref resolves to, never in the ref’s own text.

See also