@neuralegion/nextemplate
Version:
Template parser library used for dynamic values interpolation in [Bright DAST app](https://app.brightsec.com).
314 lines (209 loc) • 7.96 kB
Markdown
# nextemplate
Template parser library used for dynamic values interpolation in [Bright DAST app](https://app.brightsec.com).
## Template syntax
The template syntax in `nextemplate` utilizes double curly braces `{{` and `}}` as delimiters for interpolation expressions.
Two types of interpolation expressions are supported within these curly braces: **variable** expressions and **yield** expressions (prefixed with `$`).
### Variable expressions
> ⚠️ Variable expressions must be prefixed with a context name. Currently, the only supported contexts are `auth_object` and `entrypoint`. Additional supported contexts may be added in the future.
> ⚠️ Each context currently has a limited set of supported references. Under the `auth_object` context, you can currently use stage references and OTP token references. Under the `entrypoint` context, currently only parameter references are supported.
#### Stage references
Example: `{{ auth_object.stages.custom_stage_name1.response.body }}`
A stage reference consists of the following components:
- context (`auth_object`)
- special word (`stages`)
- stage name:
- any word (\w), number (\d) or underscore characters
- or special `any` literal
- source (`request` or `response`)
- location in the source (`url`, `headers`, or `body`).
For data transformation, a pipe operator (`|`) is available, supporting parameters and chaining.
Example: `{{ auth_object.stages.custom_stage_name1.response.headers | get: '/Set-Cookie' | match: /sid=(.+)/ : 1 }}`
#### OTP token references
Example: `{{ auth_object.otps.custom_otp_token_name1 }}`
OTP token references do not support nested references or pipes.
#### Entrypoint parameters references
Example: `{{ entrypoint.params.some_param_name }}`
A parameter name should start with a letter and can be followed by letters, digits, and underscores.
Entrypoint parameters references do not support nested references or pipes.
### Yield expressions
> ⚠️ Yield expressions other than $faker could be supported in the future, but currently parser will produce an error.
#### $faker references
Example: `{{ $faker.datatype.uuid }}`
Inspired by [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker).
The first part is predefined `$faker` literal, the second part - faker dataset name (`datatype` is single supported atm),
the third - function name from given dataset (ony `uuid` and `number` are supported).
### Supported pipes
#### get
Returns the value associated with the XPath, or undefined if there is none.
_Format_: `{{ stage_reference | get : xpath }}`
_Parameters_:
- `xpath` - xpath string
_Example_: `{{ auth_object.stages.custom_stage_name1.response.headers | get: '/Set-Cookie' }}`
#### match
Retrieves the result of matching a string against a regular expression.
_Format_: `{{ stage_reference | match : regexp : group }}`
_Parameters_:
- `regexp` - regular expression,
- `group` - number of the capture group (optional, default 1)
_Example_: `{{ auth_object.stages.custom_stage_name1.response.body | match: /sid=(.+)/ : 1 }}`
#### encode
Encodes the value to some format.
_Format_: `{{ stage_reference | encode : format }}`
_Parameters_:
- `format ` - `base64`, `url`, `html` or `none` (optional, default `none`)
_Example_: `{{ auth_object.stages.custom_stage_name1.response.body | encode: 'base64' }}`
#### decode
Decodes the value from some format.
_Format_: `{{ stage_reference | decode : format }}`
_Parameters_:
- `format ` - `base64`, `url`, `html` or `none` (optional, default `none`)
_Example_: `{{ auth_object.stages.custom_stage_name1.response.body | decode: 'base64' }}`
## Install 🚀
`npm i --save @neuralegion/nextemplate`
## API
Main parser function:
`parse(template: string): ParseResult`
Auxiliary `faker`-like generator that consumes generation template (like `faker.datatype.uuid`) as string or `source` from `ParsedFakerExpression` from parser output:
`fakerFn(fakerTemplate: string | { source: string } & any): string`
where
```ts
type ParseResult = (string | ParsedOtpTokenExpression | ParsedStageVariableExpression | ParsedFakerExpression)[];
interface ParsedExpression {
type: 'variable' | 'yield';
source: string;
raw: string;
}
interface ParsedVariableExpression extends ParsedExpression {
context: 'auth_object' | 'entrypoint';
type: 'variable';
}
interface ParsedOtpTokenExpression extends ParsedVariableExpression {
context: 'auth_object';
}
interface ParsedStageVariableExpression extends ParsedVariableExpression {
context: 'auth_object';
pipes: ParsedPipe[];
}
interface ParsedEntrypointParamsVariableExpression extends ParsedVariableExpression {
context: 'entrypoint';
source: 'params';
name: string;
}
interface ParsedPipe {
name: 'get' | 'match' | 'encode' | 'decode;
args: (string | number)[];
}
interface ParsedYieldExpression extends ParsedExpression {
type: 'yield';
value: string;
}
interface ParsedFakerExpression extends ParsedYieldExpression {
}
```
## Sample output
#### Input template string
`prefix {{ auth_object.stages.custom_stage_name1.response.headers | get : '/Set-Cookie' | match : /sid=(.+)/ | encode : 'base64' }} {{ $faker.datatype.uuid }} {{ auth_object.otps.custom_otp_token_name1 }} {{ entrypoint.params.some_param_name }} suffix`
#### Parser output
```json
[
"prefix ",
{
"context": "auth_object",
"type": "variable",
"source": "stages.custom_stage_name1.response.headers",
"pipes": [
{
"name": "get",
"args": ["/Set-Cookie"]
},
{
"name": "match",
"args": ["/sid=(.+)/", 1]
},
{
"name": "encode",
"args": ["base64"]
}
],
"raw": "{{ auth_object.stages.custom_stage_name1.response.headers | get : '/Set-Cookie' | match : /sid=(.+)/ | encode : 'base64' }}"
},
" ",
{
"type": "yield",
"source": "faker.datatype.uuid",
"value": "c6c27519-acb4-4875-91d9-298b921b5104",
"raw": "{{ $faker.datatype.uuid }}"
},
" ",
{
"context": "auth_object",
"type": "variable",
"source": "otps.custom_otp_token_name1",
"raw": "{{ auth_object.otps.custom_otp_token_name1 }}"
},
" ",
{
"context": "entrypoint",
"type": "variable",
"source": "params",
"name": "some_param_name",
"raw": "{{ entrypoint.params.some_param_name }}"
},
" suffix"
]
```
#### fakerFn usage examples
```
> fakerFn('faker.datatype.uuid')
48b0504d-b146-40f7-8fc2-fe19b7b9dc7b
> fakerFn({ source: 'faker.datatype.uuid' })`
b81b54de-735f-401d-aa77-ebd69d4293c2
```
## Usage
<details>
<summary>ECMAScript 2015, Typescript modules</summary>
```
import { parse } from '@neuralegion/nextemplate';
console.log(parse('some_template'));
```
</details>
<details>
<summary>NodeJS (CommonJS module)</summary>
```
const parser = require('@neuralegion/nextemplate');
console.log(parser.parse('some_template'));
```
</details>
<details>
<summary>NodeJS (experimental ESM support)</summary>
`usage.mjs` file:
```
import parser from '@neuralegion/nextemplate';
console.log(parser.parse('some_template'));
```
Running: `node --experimental-modules ./usage.mjs`
</details>
<details>
<summary>Browser (globals from umd bundle)</summary>
```
<script src="./node_modules/@neuralegion/nextemplate/dist/bundle.umd.js"></script>
<script>
alert(nextemplate.parse('some_template'));
</script>
```
</details>
<details>
<summary>Browser (ES modules)</summary>
```
<script type="module">
import { parse } from './node_modules/@neuralegion/nextemplate/dist/bundle.es.js';
alert(parse('some_template'));
</script>
```
</details>
## Development 🛠
Issues and pull requests are highly welcome. 👍
Please, don't forget to lint (`npm run lint`) and test (`npm t`) the code.
## License
Copyright © 2023 [Bright Security](https://brightsec.com).
This project is licensed under the MIT License - see the [LICENSE file](LICENSE) for details.