Skip to content

Commit

Permalink
feat: add escape sequence for terminal characters
Browse files Browse the repository at this point in the history
allows filtering properties that contains special characters `,*()/`

use `\` (backslash) for escaping such characters:

foo\/bar, will filter JSON property that matches foo/bar.
  • Loading branch information
mochja committed May 4, 2022
1 parent 9d1ad2e commit 5cd8b74
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 15 deletions.
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ Take a look at `test/index-test.js` for examples of all of these and more.
## Grammar

```
Props ::= Prop | Prop "," Props
Prop ::= Object | Array
Object ::= NAME | NAME "/" Prop
Array ::= NAME "(" Props ")"
NAME ::= ? all visible characters ?
Props ::= Prop | Prop "," Props
Prop ::= Object | Array
Object ::= NAME | NAME "/" Prop
Array ::= NAME "(" Props ")"
NAME ::= ? all visible characters ? | EscapeSeq | Wildcard
Wildcard ::= "*"
EscapeSeq ::= "\" ("," | "*" | "/" | "(" | ")")
```

## Examples
Expand Down Expand Up @@ -112,6 +114,28 @@ var maskedObj = mask(originalObj, fields);
assert.deepEqual(maskedObj, expectObj);
```

### Escaping

It is also possible to get keys that contain `,*()/` using `\` (backslash) as escape character.

```json
{
"metadata": {
"labels": {
"app.kubernetes.io/name": "mysql",
"location": "WH1"
}
}
}
```

You can filter out the location property by `metadata(labels(app.kubernetes.io\/name))` mask.

NOTE: In [JavaScript String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences) you must escape backslash with another backslash:
```js
var fields = 'metadata(labels(app.kubernetes.io\\/name))'
```

### Partial Responses Server Example

Here's an example of using `json-mask` to implement the
Expand Down
24 changes: 18 additions & 6 deletions lib/compiler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var util = require('./util')
var TERMINALS = { ',': 1, '/': 2, '(': 3, ')': 4 }
var TERMINALS = { ',': 1, '/': 2, '(': 3, ')': 4, '*': 5 }
var ESCAPE_CHAR = '\\'

module.exports = compile

Expand All @@ -11,14 +12,17 @@ module.exports = compile
* Prop ::= Object | Array
* Object ::= NAME | NAME "/" Prop
* Array ::= NAME "(" Props ")"
* NAME ::= ? all visible characters ?
* NAME ::= ? all visible characters ? | EscapeSeq | Wildcard
* Wildcard ::= "*"
* EscapeSeq ::= "\" ("," | "*" | "/" | "(" | ")")
*
* Examples:
* a
* a,d,g
* a/b/c
* a(b)
* ob,a(k,z(f,g/d)),d
* a\/b/c
*/

function compile (text) {
Expand All @@ -31,6 +35,7 @@ function scan (text) {
var len = text.length
var tokens = []
var name = ''
var escape = false
var ch

function maybePushName () {
Expand All @@ -41,12 +46,17 @@ function scan (text) {

for (; i < len; i++) {
ch = text.charAt(i)
if (TERMINALS[ch]) {
if (ch === ESCAPE_CHAR) {
escape = true
continue
}
if (TERMINALS[ch] && escape !== true) {
maybePushName()
tokens.push({ tag: ch })
} else {
name += ch
}
escape = false
}
maybePushName()

Expand All @@ -62,7 +72,7 @@ function _buildTree (tokens, parent) {
var token

while ((token = tokens.shift())) {
if (token.tag === '_n') {
if (token.tag === '_n' || token.tag === '*') {
token.type = 'object'
token.properties = _buildTree(tokens, token)
if (parent.hasChild) {
Expand All @@ -87,8 +97,10 @@ function _buildTree (tokens, parent) {
}

function _addToken (token, props) {
props[token.value] = { type: token.type }
var prop = { type: token.type }
if (token.tag === '*') prop.filter = true
if (!util.isEmpty(token.properties)) {
props[token.value].properties = token.properties
prop.properties = token.properties
}
props[token.value || token.tag] = prop
}
2 changes: 1 addition & 1 deletion lib/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function _properties (obj, mask) {
value = mask[key]
ret = undefined
typeFunc = (value.type === 'object') ? _object : _array
if (key === '*') {
if (value.filter) {
ret = _forAll(obj, value.properties, typeFunc)
for (retKey in ret) {
if (!util.has(ret, retKey)) continue
Expand Down
42 changes: 42 additions & 0 deletions test/compiler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tests = {
properties: {
'*': {
type: 'object',
filter: true,
properties: {
c: { type: 'object' }
}
Expand All @@ -35,6 +36,7 @@ tests = {
properties: {
'*': {
type: 'object',
filter: true,
properties: {
g: { type: 'object' }
}
Expand Down Expand Up @@ -113,6 +115,46 @@ tests = {
}
},
e: { type: 'object' }
},
'a\\/b\\/c': {
'a/b/c': {
type: 'object'
}
},
'a\\(b\\)c': {
'a(b)c': {
type: 'object'
}
},
'a\\bc': {
abc: {
type: 'object'
}
},
'\\*': {
'*': {
type: 'object'
}
},
'*': {
'*': {
type: 'object',
filter: true
}
},
'*(a,b,\\*,\\(,\\),\\,)': {
'*': {
type: 'array',
filter: true,
properties: {
a: { type: 'object' },
b: { type: 'object' },
'*': { type: 'object' },
'(': { type: 'object' },
')': { type: 'object' },
',': { type: 'object' },
}
}
}
}

Expand Down
13 changes: 10 additions & 3 deletions test/filter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ compiledMask = {
properties: {
'*': {
type: 'object',
filter: true,
properties: {
z: { type: 'object' }
}
Expand All @@ -31,7 +32,9 @@ compiledMask = {
}
}
},
c: { type: 'object' }
c: { type: 'object' },
'd/e': { type: 'object' },
'*': { type: 'object' }
}

object = {
Expand All @@ -43,7 +46,9 @@ object = {
k: 99
}],
c: 44,
g: 99
g: 99,
'd/e': 101,
'*': 110
}

expected = {
Expand All @@ -57,7 +62,9 @@ expected = {
},
b: [{}]
}],
c: 44
c: 44,
'd/e': 101,
'*': 110
}

describe('filter', function () {
Expand Down
44 changes: 44 additions & 0 deletions test/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,50 @@ tests = [{
beta: { first: 'fv', second: { third: 'tv' } },
cappa: { first: 'fv', second: { third: 'tv' } }
}
}, {
m: 'beta(first,second\\/third),cappa(first,second\\/third)',
o: {
alpha: 3,
beta: { first: 'fv', 'second/third': 'tv', third: { fourth: 'fv' } },
cappa: { first: 'fv', 'second/third': 'tv', third: { fourth: 'fv' } }
},
e: {
beta: { first: 'fv', 'second/third': 'tv' },
cappa: { first: 'fv', 'second/third': 'tv' }
}
}, {
m: '\\*',
o: {
'*': 101,
beta: 'hidden'
},
e: {
'*': 101
}
}, {
m: 'first(\\*)',
o: {
first: {
'*': 101,
beta: 'hidden'
}
},
e: {
first: {
'*': 101
}
}
}, {
m: 'some,\\*',
o: {
'*': 101,
beta: 'hidden',
some: 'visible'
},
e: {
'*': 101,
some: 'visible'
}
}]

describe('json-mask', function () {
Expand Down

0 comments on commit 5cd8b74

Please sign in to comment.