mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: Support escaped characters in key-value pair parsing (#47901)
* Additional logic to handle quoted values * Simple test with quoted values that include spaces * Update test description * Updating logic to account for nested quotes - Adding additional test for nested quotes * Strip out line breaks and carriage returns pre-processing * Fix typo in test result * Update key-value logic to avoid regexp - Minor changes to account for null values * Correct escaping on test * Additional tests - Test for null values - Test for nested separator characters - Update quoting
This commit is contained in:
parent
459c64fd44
commit
d0b41f882e
@ -14,6 +14,66 @@ describe('Extract fields from text', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('Test key-values with single/double quotes', async () => {
|
||||
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
|
||||
const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="d and 4"');
|
||||
expect(out).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
"x": "y",
|
||||
"z": "d and 4",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('Test key-values with nested single/double quotes', async () => {
|
||||
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
|
||||
const out = extractor.parse(
|
||||
`a="1", "b"=\'2\',c=3 x:y ;\r\nz="dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'"`
|
||||
);
|
||||
|
||||
expect(out).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
"x": "y",
|
||||
"z": "dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('Test key-values with nested separator characters', async () => {
|
||||
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
|
||||
const out = extractor.parse(`a="1", "b"=\'2\',c=3 x:y ;\r\nz="This is; testing& validating, 1=:2"`);
|
||||
|
||||
expect(out).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
"x": "y",
|
||||
"z": "This is; testing& validating, 1=:2",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('Test key-values where some values are null', async () => {
|
||||
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
|
||||
const out = extractor.parse(`a=, "b"=\'2\',c=3 x: `);
|
||||
|
||||
expect(out).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
"x": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('Split key+values', async () => {
|
||||
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
|
||||
const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="7"');
|
||||
|
@ -19,33 +19,99 @@ const extJSON: FieldExtractor = {
|
||||
},
|
||||
};
|
||||
|
||||
// strips quotes and leading/trailing braces in prom labels
|
||||
const stripDecor = /['"]|^\{|\}$/g;
|
||||
// splits on whitespace and other label pair delimiters
|
||||
const splitLines = /[\s,;&]+/g;
|
||||
// splits kv pairs
|
||||
const splitPair = /[=:]/g;
|
||||
function parseKeyValuePairs(raw: string): Record<string, string> {
|
||||
const buff: string[] = []; // array of characters
|
||||
let esc = '';
|
||||
let key = '';
|
||||
const obj: Record<string, string> = {};
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
let c = raw[i];
|
||||
if (c === esc) {
|
||||
esc = '';
|
||||
c = raw[++i];
|
||||
}
|
||||
|
||||
const isEscaped = c === '\\';
|
||||
if (isEscaped) {
|
||||
c = raw[++i];
|
||||
}
|
||||
|
||||
// When escaped just append
|
||||
if (isEscaped || esc.length) {
|
||||
buff.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c === `"` || c === `'`) {
|
||||
esc = c;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case ':':
|
||||
case '=':
|
||||
if (buff.length) {
|
||||
if (key) {
|
||||
obj[key] = '';
|
||||
}
|
||||
key = buff.join('');
|
||||
buff.length = 0; // clear values
|
||||
}
|
||||
break;
|
||||
|
||||
// escape chars
|
||||
case `"`:
|
||||
case `'`:
|
||||
// whitespace
|
||||
case ` `:
|
||||
case `\n`:
|
||||
case `\t`:
|
||||
case `\r`:
|
||||
case `\n`:
|
||||
if (buff.length && key === '') {
|
||||
obj[buff.join('')] = '';
|
||||
buff.length = 0;
|
||||
}
|
||||
// seperators
|
||||
case ',':
|
||||
case ';':
|
||||
case '&':
|
||||
case '{':
|
||||
case '}':
|
||||
if (buff.length) {
|
||||
const val = buff.join('');
|
||||
if (key.length) {
|
||||
obj[key] = val;
|
||||
key = '';
|
||||
} else {
|
||||
key = val;
|
||||
}
|
||||
buff.length = 0; // clear values
|
||||
}
|
||||
break;
|
||||
|
||||
// append our buffer
|
||||
default:
|
||||
buff.push(c);
|
||||
if (i === raw.length - 1) {
|
||||
if (key === '' && buff.length) {
|
||||
obj[buff.join('')] = '';
|
||||
buff.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key.length) {
|
||||
obj[key] = buff.join('');
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const extLabels: FieldExtractor = {
|
||||
id: FieldExtractorID.KeyValues,
|
||||
name: 'Key+value pairs',
|
||||
description: 'Look for a=b, c: d values in the line',
|
||||
parse: (v: string) => {
|
||||
const obj: Record<string, any> = {};
|
||||
|
||||
v.trim()
|
||||
.replace(stripDecor, '')
|
||||
.split(splitLines)
|
||||
.forEach((pair) => {
|
||||
let [k, v] = pair.split(splitPair);
|
||||
|
||||
if (k != null) {
|
||||
obj[k] = v;
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
},
|
||||
parse: parseKeyValuePairs,
|
||||
};
|
||||
|
||||
const fmts = [extJSON, extLabels];
|
||||
|
Loading…
Reference in New Issue
Block a user