Expand description
The ${VAR} / env:VAR reference grammar — the ONE parse chokepoint every
env-reference path in the toolkit shares (credentials, token_secret, and
[backend].base_url).
Deliberately carries NO #[cfg(feature = ...)] gate: the function used to
live inside the feature-gated http module, which made “the toolkit’s
universal chokepoint” true only in http builds.
pub rather than pub(crate) (plan 120-05): the grammar is duplicated by
necessity in pmcp-package — the workspace-excluded leaf crate, which
neither may depend on this one nor be depended on by it — and the two
implementations are held to a shared accept/reject table asserted from an
INTEGRATION test in each crate. An integration test is an external consumer,
so the reference implementation has to be reachable from outside the crate
for that parity claim to be checkable at all.
The ONE place the ${VAR} / env:VAR reference grammar is defined for the
whole toolkit.
Every path that reads an operator-supplied value which MAY be an environment
reference routes through parse_env_ref:
- outgoing credentials (
[backend.auth]api_key / bearer token / basic password / oauth2 client_secret) —crate::http::auth; [code_mode] token_secret—crate::code_mode;[backend] base_url—crate::config::BackendSection::resolved_base_url.
§Why this module is NOT feature-gated
The function previously lived as a private helper inside crate::http::auth,
and the whole http module is #[cfg(feature = "http")]. It compiled
(BackendSection is itself http-gated), but the accompanying claim — “the
single chokepoint every env-reference path shares” — was reachable only in
http builds, so it was architecturally false. Packaging tooling is about to
build a cross-crate grammar-parity claim on top of that statement, so the
statement has to be structurally true rather than aspirational: this module
carries NO #[cfg(feature = ...)] gate and compiles in every feature
configuration.
§Grammar
| Input | Result | Meaning |
|---|---|---|
env:VAR | Some("VAR") | reference |
${VAR} | Some("VAR") | reference (VAR must match [A-Za-z0-9_]+) |
${} | Some("") | MALFORMED reference — a reference to an empty name |
${A}://${B} | Some("") | MALFORMED reference — a multi-placeholder composition |
${VAR | None | unterminated → a plain literal |
plain | None | plain literal |
The malformed-means-empty rule is deliberate: the empty name is the signal
that a value is reference-SHAPED but names nothing, so no caller ships the
literal ${...} text to the wire. A ${...} value is a reference to exactly
ONE variable — the grammar does not interpolate inside a larger string, so a
composition like ${SCHEME}://${HOST} names nothing any environment could
set; compose the full value in one variable instead.
§Callers diverge on UNSET, agree on MALFORMED, never diverge on PARSING
Two different questions, two different answers:
- Unset variable (
Some("VAR"),VARnot exported) — caller policy, and it legitimately differs. A credential resolves it to the empty string so an OPTIONAL credential is omitted; an endpoint errors, because an empty endpoint is not a degraded request but a broken one. - Malformed reference (
Some("")) — NOT caller policy. Every caller refuses it, because no environment could ever satisfy it, so “omit” would mean silently proceeding without a value the operator believed they had supplied. The credential path once applied the unset rule here, which madetoken = "${GITHUB-PAT}"send every backend request unauthenticated with no error and no log line; it is now refused at load time (crate::error::ConfigValidationError::MalformedBackendAuthRef) and at provider-build time, matchingbase_url’scrate::error::ConfigValidationError::MalformedBackendBaseUrlRef.
What must NOT differ is the PARSE: a second ${} parser with slightly
different edge cases is a latent security bug (one parser treating ${VAR as
a reference and another as a literal is exactly how a placeholder reaches the
wire).
Note that pmcp-package deliberately DUPLICATES this grammar rather than
depending on the toolkit — that crate is the workspace-excluded leaf and a
dependency in either direction inverts the layering. The duplication is kept
honest by a package-side grammar-parity table, not by a shared type.
Functions§
- is_
valid_ env_ var_ name - Whether
nameis a variable name a target environment can actually be told to set: non-empty, ASCII alphanumerics and_only — the portable intersection of POSIX shells, env files, and container manifests. - parse_
env_ ref - The single brace/env-ref parse core shared by EVERY credential-resolution path (api_key, bearer token, basic password, oauth2 client_secret).