[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add escape sequence for terminal characters #165

Merged
merged 5 commits into from
May 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 except "\" ? | EscapeSeq | Wildcard
Wildcard ::= "*"
EscapeSeq ::= "\" ? all visible characters ?
```

## 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
27 changes: 22 additions & 5 deletions lib/compiler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var util = require('./util')
var TERMINALS = { ',': 1, '/': 2, '(': 3, ')': 4 }
var ESCAPE_CHAR = '\\'
var WILDCARD_CHAR = '*'

module.exports = compile

Expand All @@ -11,14 +13,17 @@ module.exports = compile
* Prop ::= Object | Array
* Object ::= NAME | NAME "/" Prop
* Array ::= NAME "(" Props ")"
* NAME ::= ? all visible characters ?
* NAME ::= ? all visible characters except "\" ? | EscapeSeq | Wildcard
* Wildcard ::= "*"
* EscapeSeq ::= "\" ? all visible characters ?
*
* 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 +36,7 @@ function scan (text) {
var len = text.length
var tokens = []
var name = ''
var escape = false
var ch

function maybePushName () {
Expand All @@ -41,12 +47,19 @@ function scan (text) {

for (; i < len; i++) {
ch = text.charAt(i)
if (TERMINALS[ch]) {
if (ch === ESCAPE_CHAR && !escape) {
escape = true
continue
nemtsov marked this conversation as resolved.
Show resolved Hide resolved
}
if (TERMINALS[ch] && !escape) {
maybePushName()
tokens.push({ tag: ch })
} else if (ch === WILDCARD_CHAR && !name.length && !escape) {
Copy link
Owner
@nemtsov nemtsov May 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mochja q: the !name.length is for stars that follow characters (e.g. a*), what about stars that precede them (e.g. *a)? Is it intentional that you must escape them now?

(e.g. $ echo '{"*a":1}' | node ./bin/json-mask.js '*a' doesn't work and creates a compiled mask: { '*': { type: 'object', properties: { a: [Object] }, filter: true } } and yet $ echo '{"a*":1}' | node ./bin/json-mask.js 'a*' does work and has a mask: { 'a*': { type: 'object' } })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, this part would have to reworked for this to work, maybe we could use something from #167?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a few adjustments and I think it's working fully in #167
How does it look to you @mochja ?

tokens.push({ tag: ch, value: ch })
} else {
name += ch
}
escape = false
}
maybePushName()

Expand All @@ -62,7 +75,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 +100,12 @@ function _buildTree (tokens, parent) {
}

function _addToken (token, props) {
props[token.value] = { type: token.type }
var prop = { type: token.type }
if (!util.isEmpty(token.properties)) {
props[token.value].properties = token.properties
prop.properties = token.properties
}
if (token.tag === WILDCARD_CHAR) {
prop.filter = true
}
props[token.value] = 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
65 changes: 65 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,69 @@ tests = {
}
},
e: { type: 'object' }
},
'a\\/b\\/c': {
'a/b/c': {
type: 'object'
}
},
'a\\(b\\)c': {
'a(b)c': {
type: 'object'
}
},
// escaped b (`\b`) in our language resolves to `b` character.
'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' }
}
}
},
'\\\\': {
'\\': {
type: 'object'
}
},
'foo*bar': {
'foo*bar': {
type: 'object'
}
},
// mask `\n`, should not resolve in a new line,
// because we simply escape "n" character which has no meaning in our language
'\\n': {
n: {
type: 'object'
}
},
'multi\nline': {
'multi\nline': {
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
70 changes: 70 additions & 0 deletions test/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,76 @@ 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'
}
}, {
m: 'some,\\\\',
o: {
'\\': 120,
beta: 'hidden',
some: 'visible'
},
e: {
'\\': 120,
some: 'visible'
}
}, {
m: 'multi\nline(a)',
o: {
multi: 130,
line: 131,
'multi\nline': {
a: 135,
b: 134
}
},
e: {
'multi\nline': {
a: 135
}
}
}]

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