Sort out objectization in variable substitution (#162382)

Closes #162381

## Summary

This PR is,,,

1. Adding documentation about objectization in variable substitution.
2. Fixing a glitch in the illegal double quotes `""`.
    <details open="true"><summary>details</summary>
    
For example, `""${ZERO}""` may have substituted `"0"` but this
substitution must not occur because no quotes surround the `${ZERO}` in
the context of JSON syntax. `""${ZERO}""` is jut `${ZERO}` with forward
and training `""`.</details>
3. Promoting triple quotes `"""` as an alternative to the illegal double
quotes `""`.
    <details open="true"><summary>details</summary>
    
Now `"""${ZERO}"""` is a way to substitute `"""0"""` rather than `0`
that `"${ZERO}"` may substitute. The same as before, these single and
triple quotes work only in the request body.</details>

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

Note: Regex negative lookahead `x(?!y)` and ~~lookbehind `(?<!y)x`~~
(_EDIT: no lookbehind anymore_) assertions
([cf.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Assertions#other_assertions))
are used in this PR. So, I've checked the following steps on each
browser.

Browsers:
- Chrome 115.0.5790.102
- Firefox 115.0.2
- Edge 115.0.1901.183
- Safari 16.3 (18614.4.6.1.6) ~~CAN'T CHECK~~ - Safari has recently
added support for the negative lookbehind as per this 16.4 release note.

>
https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes
> Added support for RegExp lookbehind assertions.

Steps:
1. Go to Dev Tools > Console.
2. Click `Variables` on the top.
4. Define variable `ZERO` with `0`.
6. Run the following command.

```http
POST test/_doc
{
  "field": "${ZERO}"
}
```

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

## Release note

Improves a way of variable substitution and its documentation

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Youhei Sakurai 2023-07-27 09:30:52 +09:00 committed by GitHub
parent c37b78ef68
commit ceb7ad761d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 10 deletions

View file

@ -122,6 +122,34 @@ GET ${pathVariable}
}
----------------------------------
By default, variables in the body may be substituted as a boolean, number, array, or
object by removing nearby quotes instead of a string with surrounding quotes. Triple
quotes overwrite this default behavior and enforce simple replacement as a string.
[source,js]
----------------------------------
GET /locations/_search
{
"query": {
"bool": {
"must": {
"match": {
// ${shopName} shall be replaced as a string if the variable exists.
"shop.name": """${shopName}"""
}
},
"filter": {
"geo_distance": {
"distance": "12km",
// "${pinLocation}" may be substituted with an array such as [-70, 40].
"pin.location": "${pinLocation}"
}
}
}
}
}
----------------------------------
[float]
[[auto-formatting]]
==== Auto-formatting

View file

@ -117,13 +117,18 @@ export const replaceVariables = (
requests: RequestArgs['requests'],
variables: DevToolsVariable[]
) => {
const urlRegex = /(\${\w+})/g;
const bodyRegex = /("\${\w+}")/g;
const urlRegex = /\${(\w+)}/g;
// The forward part '([\\"]?)"' of regex matches '\\"', '""', and '"', but the only
// last match is preferable. The unwanted ones can be filtered out by checking whether
// the first capturing group is empty. This functionality is identical to the one
// achievable by negative lookbehind assertion - i.e. '(?<![\\"])"'
const bodyRegexSingleQuote = /([\\"]?)"\${(\w+)}"(?!")/g;
const bodyRegexTripleQuotes = /([\\"]?)"""\${(\w+)}"""(?!")/g;
return requests.map((req) => {
if (urlRegex.test(req.url)) {
req.url = req.url.replaceAll(urlRegex, (match) => {
// Sanitize variable name
const key = match.replace('${', '').replace('}', '');
req.url = req.url.replaceAll(urlRegex, (match, key) => {
const variable = variables.find(({ name }) => name === key);
return variable?.value ?? match;
@ -131,13 +136,11 @@ export const replaceVariables = (
}
if (req.data && req.data.length) {
if (bodyRegex.test(req.data[0])) {
const data = req.data[0].replaceAll(bodyRegex, (match) => {
// Sanitize variable name
const key = match.replace('"${', '').replace('}"', '');
if (bodyRegexSingleQuote.test(req.data[0])) {
const data = req.data[0].replaceAll(bodyRegexSingleQuote, (match, lookbehind, key) => {
const variable = variables.find(({ name }) => name === key);
if (variable) {
if (!lookbehind && variable) {
// All values must be stringified to send a successful request to ES.
const { value } = variable;
@ -171,6 +174,17 @@ export const replaceVariables = (
});
req.data = [data];
}
if (bodyRegexTripleQuotes.test(req.data[0])) {
const data = req.data[0].replaceAll(bodyRegexTripleQuotes, (match, lookbehind, key) => {
const variable = variables.find(({ name }) => name === key);
return !lookbehind && variable?.value
? '""' + JSON.stringify(variable?.value) + '""'
: match;
});
req.data = [data];
}
}
return req;

View file

@ -263,5 +263,60 @@ describe('Utils class', () => {
{ url: 'test', data: ['{\n "f": "9893617a-a08f-4e5c-bc41-95610dc2ded8"\n}'] }
);
});
it('with illegal double quotes should not replace variables in body', () => {
testVariables(
{ url: 'test/_doc/${v8}', data: ['{\n "f": ""${v8}""\n}'] },
{ name: 'v8', value: '0' },
{
url: 'test/_doc/0',
data: ['{\n "f": ""${v8}""\n}'],
}
);
});
it('with heredoc triple quotes should replace variables as strings in body', () => {
testVariables(
{ url: 'test/_doc/${v9}', data: ['{\n "f": """${v9}"""\n}'] },
{ name: 'v9', value: '0' },
{
url: 'test/_doc/0',
data: ['{\n "f": """0"""\n}'],
}
);
});
it('with illegal quadruple quotes should not replace variables in body', () => {
testVariables(
{ url: 'test/_doc/${v10}', data: ['{\n "f": """"${v10}""""\n}'] },
{ name: 'v10', value: '0' },
{
url: 'test/_doc/0',
data: ['{\n "f": """"${v10}""""\n}'],
}
);
});
it('with escaped pre quote should not replace variables in body', () => {
testVariables(
{ url: 'test/_doc/${v11}', data: ['{\n "f": "\\"${v11}"\n}'] },
{ name: 'v11', value: '0' },
{
url: 'test/_doc/0',
data: ['{\n "f": "\\"${v11}"\n}'],
}
);
});
it('with escaped pre triple quotes should not replace variables in body', () => {
testVariables(
{ url: 'test/_doc/${v12}', data: ['{\n "f": "\\"""${v12}"""\n}'] },
{ name: 'v12', value: '0' },
{
url: 'test/_doc/0',
data: ['{\n "f": "\\"""${v12}"""\n}'],
}
);
});
});
});