Skip to content

feat: support default values in request templates - #31

Draft
mantono wants to merge 11 commits into
masterfrom
feat/default-values
Draft

feat: support default values in request templates#31
mantono wants to merge 11 commits into
masterfrom
feat/default-values

Conversation

@mantono

@mantono mantono commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • support static {{NAME:value}} and opt-in dynamic {{NAME|command}} template defaults
  • add safe, cross-platform command execution plus dedicated user-facing errors
  • document the syntax and examples

Closes #30

Verification

  • cargo build --all-features
  • cargo test
  • manual Unix fallback/override checks

cargo clippy --all-targets --all-features -- -D warnings remains blocked by seven pre-existing warnings on master; no feature-caused Clippy findings.

Replace the plain-key-only regex scanner in src/templ.rs with a manual
scanner/parser that yields one Reference per occurrence, preserving the
byte span of each match. Supports plain {{NAME}}, static
{{NAME:value}}, and command {{NAME|command}} forms while keeping the
existing NAME rule ([A-Za-z0-9_-], 1-32 chars). Delimiter content after
the first ':' or '|' is treated literally up to the next "}}", so
additional ':'/'|' characters remain part of the fallback. Invalid,
unterminated, or non-name constructs are left as ordinary template
text, matching prior rendering behavior.

find_keys is now implemented on top of scan() and keeps its previous
signature and legacy discovery behavior, so src/template.rs is
unaffected by this change.

Adds parser tests for all three reference forms, literal ':'/'|'
retention in fallback content, repeated names with distinct
fallbacks, name-length boundaries, invalid/unterminated constructs,
and legacy key discovery.
…acks

Refactor substitution() in src/template.rs to resolve each parsed
Reference from templ::scan() individually rather than by distinct key
name only. For every occurrence, resolution now follows: a merged
property by NAME, then that occurrence's static fallback, then its
dynamic (command) result, then an interactive prompt only for a plain
missing occurrence, and finally SubstitutionError::MissingValue.

Extended (static/command) occurrences are rewritten to unique internal
Handlebars keys before strict-mode rendering, so distinct fallback
occurrences of the same NAME stay independent while a supplied value
still overrides every occurrence, including an empty sourced value.
Plain occurrences keep their original {{NAME}} text and shared vars
entry, preserving no_escape, strict-mode failure mapping, and existing
normal-template/legacy-missing-value behavior unchanged.

Command execution itself is out of scope for this task (task 3); a
resolve_dynamic() seam always reports unresolved for now, which
correctly falls through to MissingValue without prompting.

Adds tests for static fallback resolution, sourced-value override from
every Property::Source (including an empty override), per-occurrence
mixed/default values, suppression of interactive prompting by any
fallback occurrence, and legacy missing-value behavior.
Add Args::allow_command_fallbacks(), backed by the opt-in
--allow-command-fallbacks flag, and thread it from main.rs into
substitution(). Command-fallback resolution now returns a distinct
SubstitutionError::CommandFallbackNotAllowed when reached without
permission, instead of silently treating it as a missing value.

Isolate shell invocation behind a small src/runner.rs adapter
(sh -c on unix, cmd /C on windows) that normalizes stdout by
removing only trailing CR/LF and surfaces launch failures,
non-zero exit statuses, and non-UTF-8 stdout as distinct
RunnerError variants.

FireError mapping for the new SubstitutionError variants is
intentionally minimal plumbing; a dedicated variant and exit code
are added by a later task.
… variants

Add FireError::CommandFallbackNotAllowed and
FireError::CommandFallbackFailed(RunnerError), each with a distinct,
secret-free Display message and a stable exit code (12 and 13,
respectively, the next codes free after the existing 1 and 3-11).
main.rs now maps SubstitutionError's two command-fallback variants
directly to these, instead of collapsing them into the generic
FireError::Other/TemplateRendering.

Termination::report is refactored around a private exit_code() method
so tests can assert on the underlying u8 without relying on
ExitCode's opaque representation.

Tests cover both new Display messages (including all three RunnerError
sub-cases), document why 12/13 were chosen, and assert every
FireError variant's exit code stays pairwise unique. RunnerError never
carries resolved fallback values or command stdout, so these messages
cannot leak secrets.
Document static and command defaults, source precedence, occurrence semantics, interactive ordering, command output behavior, platform shells, and the opt-in security warning. Add documentation-only example request files for both forms.
… test

The test used printf's \xff hex escape to produce an invalid UTF-8 byte.
Ubuntu's default /bin/sh (dash) does not support \xff and instead prints
the literal bytes \xff, which is valid UTF-8, causing the test to fail
on CI while passing locally under bash. Replace it with \377, a POSIX
octal escape for 0xFF supported by both dash and bash, preserving the
test's intent of exercising RunnerError::NonUtf8.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support default values for variables in request files

1 participant