Request Body Templates¶
Templates let you define JSON request bodies with dynamic placeholders. Each request in a run receives a freshly generated body, pre-computed before any requests fire.
Usage¶
-T / --request-template is mutually exclusive with -B / --body.
File Structure¶
A template file is a standard JSON object with two parts:
| Part | Purpose |
|---|---|
| Body fields | The JSON shape sent in each request. Values that look like {{name}} are substituted at generation time. Non-placeholder values are copied as-is. |
_lmn_metadata_templates |
Defines how each placeholder generates its value. Stripped from the request body automatically. |
Placeholder Syntax¶
| Syntax | Behaviour |
|---|---|
"{{name}}" |
Generate a fresh value for each request |
"{{name:once}}" |
Generate once at startup — same value across all requests in the run |
Placeholders can appear anywhere a JSON string value can appear, including inside nested objects and arrays.
Built-in Placeholders¶
{{ENV:VAR_NAME}}¶
Injects the value of an environment variable at template parse time.
Behaviour:
- No
_lmn_metadata_templatesentry is required or allowed —ENV:placeholders are built-in. - The env var is read once when the template is parsed, before any requests fire (fail-closed).
- If the named env var is not set, lmn exits immediately with an error.
- If the var name is empty (
{{ENV:}}), lmn exits immediately with an error. {{ENV:VAR_NAME:once}}is not supported. The:oncesuffix is reserved for regular generator placeholders. Use{{ENV:VAR_NAME}}— it is already resolved once at startup and the same value is reused across all requests in the run.
Supported Types¶
string¶
Generates a string value. Two strategies are available: choice and generated.
Choice — pick randomly from a fixed list (takes priority over generation constraints):
Generated — build a string from character constraints:
"token": {
"type": "string",
"exact": 12,
"details": {
"uppercase_count": 4,
"lowercase_count": 6,
"special_chars": ["@", "_", "-"]
}
}
| Field | Type | Description |
|---|---|---|
exact |
number | Exact string length |
min |
number | Minimum length (used when exact is absent) |
max |
number | Maximum length (used when exact is absent) |
details.choice |
string[] | If present, picks randomly from this list — all other constraints ignored |
details.uppercase_count |
number | Number of uppercase letters to include |
details.lowercase_count |
number | Number of lowercase letters to include |
details.special_chars |
string[] | Pool of special characters to fill remaining slots. If absent and slots remain, fills with alphanumeric characters |
Constraints: uppercase_count + lowercase_count must not exceed the minimum length. min must not exceed max. Max length is capped at 10,000.
Character positions are shuffled — no ordering is guaranteed within the generated string.
float¶
Generates a floating-point number.
Exact value:
Random in range:
| Field | Type | Description |
|---|---|---|
exact |
number | Always emit this value. If set, min/max are ignored |
min |
number | Lower bound (inclusive) |
max |
number | Upper bound (inclusive) |
details.decimals |
number | Decimal places to round to. Defaults to 2 |
Constraints: min must not exceed max. Both min and max are required when exact is absent.
object¶
Composes other placeholders into a nested JSON object. Each field in composition maps an output key to a placeholder reference.
"money": {
"type": "object",
"composition": {
"amount": "{{amount}}",
"currency": "{{currency}}"
}
}
| Field | Type | Description |
|---|---|---|
composition |
{ field: "{{name}}" } |
Maps output field names to placeholder references |
Constraints: All referenced placeholders must be defined. Circular references are detected and rejected at startup.
:once is supported inside composition values — the referenced placeholder's :once behaviour still applies.
Full Example¶
{
"name": "{{username:once}}",
"payment": "{{money}}",
"_lmn_metadata_templates": {
"username": {
"type": "string",
"details": {
"choice": ["alice", "bob", "carol", "dave"]
}
},
"amount": {
"type": "float",
"min": 1.0,
"max": 500.0,
"details": { "decimals": 2 }
},
"currency": {
"type": "string",
"details": {
"choice": ["EUR", "USD", "GBP"]
}
},
"money": {
"type": "object",
"composition": {
"amount": "{{amount}}",
"currency": "{{currency}}"
}
}
}
}
In this example username is fixed for the entire run (:once), while payment.amount and payment.currency vary per request.
Validation¶
All of the following are checked at startup before any request fires:
- Template file exists and is valid JSON
- Every
{{placeholder}}in the body has a definition in_lmn_metadata_templates - Every
compositionreference points to a defined placeholder - No circular references between
objectcompositions - All numeric constraints are coherent (
min ≤ max,uppercase_count + lowercase_count ≤ min_length, etc.) - String lengths do not exceed the 10,000 character cap
Response Templates¶
Response templates let you track specific fields from response bodies. You define a JSON shape that mirrors the fields you care about — everything else in the response is ignored. Extracted values are aggregated and displayed in the run statistics.
Usage¶
-S / --response-template is optional and independent of -T / --request-template.
File Structure¶
A response template is a JSON object that mirrors the expected response shape. Leaf values are {{TYPE}} placeholders indicating which fields to extract and how to aggregate them.
The template acts as a loose schema — the response may contain additional fields, but any field referenced in the template that is missing from a response is tracked as a mismatch.
Supported Extraction Types¶
{{STRING}}¶
Extracts a string value and tracks the frequency distribution of distinct values.
Stats output: count per unique value.
{{FLOAT}}¶
Extracts a numeric value and tracks aggregate statistics.
Stats output: min, max, avg, percentiles.
Nested Paths¶
The template structure mirrors the response JSON. To track a deeply nested field, nest the template accordingly:
Given a response {"error": {"code": "NOT_FOUND", "message": "..."}, "request_id": "..."}, this extracts error.code as "NOT_FOUND" and ignores error.message and request_id.
Mismatches¶
When a response does not contain a field defined in the template, it is not treated as an error — the request still counts as normal. Instead, mismatches are tracked separately and reported in the statistics, so you can see how many responses did not conform to the expected shape.
A mismatch occurs when:
- A templated field path does not exist in the response
- A templated field has a value type that does not match the extraction type (e.g. {{FLOAT}} on a string value)
Full Example¶
Response template:
CLI:
After the run, the statistics section will include:
- Distribution of error.code values across all responses
- Count of responses where error.code was missing or had an unexpected type
Extending the Template System¶
Adding a new generator type¶
- Add a variant to
RawTemplateDefinlmn-core/src/request_template/definition.rswith its raw serde fields - Add a corresponding validated struct and variant to
TemplateDef - Implement validation in the
validate()function - Implement
Generatefor the new type inlmn-core/src/request_template/generator.rs - Add a match arm in
GeneratorContext::generate_def
Adding a new body format (e.g. XML, form-data)¶
The BodyFormat enum in lmn-core/src/command/run.rs is the extension point:
- Add a new variant to
BodyFormat - Add a corresponding CLI value to the format selector (when introduced)
- Add the
Content-Typemapping in thematch formatarm insiderun_concurrent_requests - Add a
value_parserfor the new format inlmn-cli/src/cli/command.rs(e.g. XML validation)