@odata-filter/marshalers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lookups.d.ts +3 -0
- package/dist/lookups.js +29 -0
- package/dist/lookups.js.map +1 -0
- package/dist/mongoJson.d.ts +3 -0
- package/dist/mongoJson.js +50 -0
- package/dist/mongoJson.js.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/jest.config.js +14 -0
- package/package.json +23 -0
- package/src/index.ts +1 -0
- package/src/lookups.ts +26 -0
- package/src/mongoJson.spec.ts +138 -0
- package/src/mongoJson.ts +56 -0
- package/src/types.ts +13 -0
- package/stryker.config.json +37 -0
- package/tsconfig.json +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# OData Filter Marshalers
|
|
2
|
+
|
|
3
|
+
* [Installation](#installation)
|
|
4
|
+
* [Usage](#usage)
|
|
5
|
+
* [Contributing](#contributing)
|
|
6
|
+
* [License](#license)
|
|
7
|
+
* [Documentation](#documentation)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
See [INSTALL.md](../../INSTALL.md) for full instructions.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Package Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm i @odata-filter/marshalers --save
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Basic Example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { tokenize, parse } from '@odata-filter/core';
|
|
25
|
+
import { toMongoJson } from '@odata-filter/marshalers';
|
|
26
|
+
|
|
27
|
+
const tokens = tokenize("country/name eq 'US' and age gte 21");
|
|
28
|
+
|
|
29
|
+
const ast = parse(tokens);
|
|
30
|
+
|
|
31
|
+
console.log(ast);
|
|
32
|
+
/*{
|
|
33
|
+
type: 'logical_operator',
|
|
34
|
+
value: 'and',
|
|
35
|
+
left: {
|
|
36
|
+
type: 'comparison_operator',
|
|
37
|
+
value: 'eq',
|
|
38
|
+
left: { type: 'field', value: 'country/name' },
|
|
39
|
+
right: { type: 'string_value', value: 'US' }
|
|
40
|
+
},
|
|
41
|
+
right: {
|
|
42
|
+
type: 'comparison_operator',
|
|
43
|
+
value: 'gte',
|
|
44
|
+
left: { type: 'field', value: 'age' },
|
|
45
|
+
right: { type: 'number_value', value: 21 }
|
|
46
|
+
}
|
|
47
|
+
}*/
|
|
48
|
+
|
|
49
|
+
toMongoJson(ast);
|
|
50
|
+
/*{
|
|
51
|
+
$and: [
|
|
52
|
+
{ 'country/name': { $eq: 'US' } },
|
|
53
|
+
{ age: { $gte: 21 } }
|
|
54
|
+
]
|
|
55
|
+
}*/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Contributing
|
|
59
|
+
|
|
60
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for full instructions.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
See [LICENSE](../../LICENSE) for licensing information.
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
* [OData Standard v4](https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html)
|
|
69
|
+
* [JSONSchema](https://json-schema.org/)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toMongoJson } from './mongoJson';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toMongoJson = void 0;
|
|
4
|
+
var mongoJson_1 = require("./mongoJson");
|
|
5
|
+
Object.defineProperty(exports, "toMongoJson", { enumerable: true, get: function () { return mongoJson_1.toMongoJson; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAA0C;AAAjC,wGAAA,WAAW,OAAA"}
|
package/dist/lookups.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.mongoOperatorLookup = void 0;
|
|
5
|
+
var core_1 = require("@odata-filter/core");
|
|
6
|
+
var types_1 = require("./types");
|
|
7
|
+
exports.mongoOperatorLookup = (_a = {},
|
|
8
|
+
_a[core_1.TokenType.AND] = types_1.MongoOperators.AND,
|
|
9
|
+
_a[core_1.TokenType.OR] = types_1.MongoOperators.OR,
|
|
10
|
+
_a[core_1.TokenType.IN] = types_1.MongoOperators.IN,
|
|
11
|
+
_a[core_1.TokenType.EQ] = types_1.MongoOperators.EQ,
|
|
12
|
+
_a[core_1.TokenType.NE] = types_1.MongoOperators.NE,
|
|
13
|
+
_a[core_1.TokenType.LT] = types_1.MongoOperators.LT,
|
|
14
|
+
_a[core_1.TokenType.LTE] = types_1.MongoOperators.LTE,
|
|
15
|
+
_a[core_1.TokenType.GT] = types_1.MongoOperators.GT,
|
|
16
|
+
_a[core_1.TokenType.GTE] = types_1.MongoOperators.GTE,
|
|
17
|
+
_a[core_1.TokenType.NOT] = types_1.MongoOperators.NOT,
|
|
18
|
+
_a[core_1.TokenType.WHITESPACE] = types_1.MongoOperators.UNKNOWN,
|
|
19
|
+
_a[core_1.TokenType.STRING] = types_1.MongoOperators.UNKNOWN,
|
|
20
|
+
_a[core_1.TokenType.SYMBOL] = types_1.MongoOperators.UNKNOWN,
|
|
21
|
+
_a[core_1.TokenType.NUMBER] = types_1.MongoOperators.UNKNOWN,
|
|
22
|
+
_a[core_1.TokenType.TUPLE] = types_1.MongoOperators.UNKNOWN,
|
|
23
|
+
_a[core_1.TokenType.OPEN_PAREN] = types_1.MongoOperators.UNKNOWN,
|
|
24
|
+
_a[core_1.TokenType.CLOSE_PAREN] = types_1.MongoOperators.UNKNOWN,
|
|
25
|
+
_a[core_1.TokenType.TRUE] = types_1.MongoOperators.UNKNOWN,
|
|
26
|
+
_a[core_1.TokenType.FALSE] = types_1.MongoOperators.UNKNOWN,
|
|
27
|
+
_a[core_1.TokenType.NULL] = types_1.MongoOperators.UNKNOWN,
|
|
28
|
+
_a);
|
|
29
|
+
//# sourceMappingURL=lookups.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookups.js","sourceRoot":"","sources":["../src/lookups.ts"],"names":[],"mappings":";;;;AAAA,2CAA+C;AAC/C,iCAAyC;AAE5B,QAAA,mBAAmB;IAC9B,GAAC,gBAAS,CAAC,GAAG,IAAG,sBAAc,CAAC,GAAG;IACnC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,GAAG,IAAG,sBAAc,CAAC,GAAG;IACnC,GAAC,gBAAS,CAAC,EAAE,IAAG,sBAAc,CAAC,EAAE;IACjC,GAAC,gBAAS,CAAC,GAAG,IAAG,sBAAc,CAAC,GAAG;IACnC,GAAC,gBAAS,CAAC,GAAG,IAAG,sBAAc,CAAC,GAAG;IAEnC,GAAC,gBAAS,CAAC,UAAU,IAAG,sBAAc,CAAC,OAAO;IAC9C,GAAC,gBAAS,CAAC,MAAM,IAAG,sBAAc,CAAC,OAAO;IAC1C,GAAC,gBAAS,CAAC,MAAM,IAAG,sBAAc,CAAC,OAAO;IAC1C,GAAC,gBAAS,CAAC,MAAM,IAAG,sBAAc,CAAC,OAAO;IAC1C,GAAC,gBAAS,CAAC,KAAK,IAAG,sBAAc,CAAC,OAAO;IACzC,GAAC,gBAAS,CAAC,UAAU,IAAG,sBAAc,CAAC,OAAO;IAC9C,GAAC,gBAAS,CAAC,WAAW,IAAG,sBAAc,CAAC,OAAO;IAC/C,GAAC,gBAAS,CAAC,IAAI,IAAG,sBAAc,CAAC,OAAO;IACxC,GAAC,gBAAS,CAAC,KAAK,IAAG,sBAAc,CAAC,OAAO;IACzC,GAAC,gBAAS,CAAC,IAAI,IAAG,sBAAc,CAAC,OAAO;QACxC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toMongoJson = void 0;
|
|
4
|
+
var core_1 = require("@odata-filter/core");
|
|
5
|
+
var lookups_1 = require("./lookups");
|
|
6
|
+
var toMongoJson = function (ast, json) {
|
|
7
|
+
var _a;
|
|
8
|
+
var _b, _c;
|
|
9
|
+
if (json === void 0) { json = {}; }
|
|
10
|
+
if (!ast)
|
|
11
|
+
return json;
|
|
12
|
+
var tokenType = ast.value && ast.value.toString().toLowerCase() + '_operator';
|
|
13
|
+
if (ast.type === core_1.NodeType.LOGICAL_OPERATOR) {
|
|
14
|
+
if (tokenType === core_1.TokenType.AND) {
|
|
15
|
+
var leftJson_1 = (0, exports.toMongoJson)(ast.left, json);
|
|
16
|
+
return (0, exports.toMongoJson)(ast.right, leftJson_1);
|
|
17
|
+
}
|
|
18
|
+
var leftJson = (0, exports.toMongoJson)(ast.left);
|
|
19
|
+
var rightJson = (0, exports.toMongoJson)(ast.right);
|
|
20
|
+
var operator = lookups_1.mongoOperatorLookup[tokenType];
|
|
21
|
+
json[operator] = [leftJson, rightJson];
|
|
22
|
+
return json;
|
|
23
|
+
}
|
|
24
|
+
if (ast.type === core_1.NodeType.COMPARISON_OPERATOR) {
|
|
25
|
+
var field = (_b = ast.left) === null || _b === void 0 ? void 0 : _b.value;
|
|
26
|
+
var value = (_c = ast.right) === null || _c === void 0 ? void 0 : _c.value;
|
|
27
|
+
if (!field)
|
|
28
|
+
return json;
|
|
29
|
+
// Take only the first value in the filter per parameter.
|
|
30
|
+
if (json[field.toString()] !== undefined)
|
|
31
|
+
return json;
|
|
32
|
+
if ([core_1.TokenType.EQ, core_1.TokenType.IN].includes(tokenType)) {
|
|
33
|
+
json[field.toString()] = value;
|
|
34
|
+
return json;
|
|
35
|
+
}
|
|
36
|
+
var operator = lookups_1.mongoOperatorLookup[tokenType];
|
|
37
|
+
json[field.toString()] = (_a = {}, _a[operator] = value, _a);
|
|
38
|
+
return json;
|
|
39
|
+
}
|
|
40
|
+
if (ast.type === core_1.NodeType.UNARY_OPERATOR) {
|
|
41
|
+
// by definition, Unary Operators only have a single condition ("left" for our purposes)
|
|
42
|
+
var leftJson = (0, exports.toMongoJson)(ast.left);
|
|
43
|
+
var operator = lookups_1.mongoOperatorLookup[tokenType];
|
|
44
|
+
json[operator] = leftJson;
|
|
45
|
+
return json;
|
|
46
|
+
}
|
|
47
|
+
return json;
|
|
48
|
+
};
|
|
49
|
+
exports.toMongoJson = toMongoJson;
|
|
50
|
+
//# sourceMappingURL=mongoJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongoJson.js","sourceRoot":"","sources":["../src/mongoJson.ts"],"names":[],"mappings":";;;AAAA,2CAAyD;AAEzD,qCAAgD;AAGzC,IAAM,WAAW,GAAG,UACzB,GAAU,EACV,IAA0B;;;IAA1B,qBAAA,EAAA,SAA0B;IAE1B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,IAAM,SAAS,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,GAAG,WAAW,CAAC;IAEhF,IAAI,GAAG,CAAC,IAAI,KAAK,eAAQ,CAAC,gBAAgB,EAAE,CAAC;QAC3C,IAAI,SAAS,KAAK,gBAAS,CAAC,GAAG,EAAE,CAAC;YAChC,IAAM,UAAQ,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC7C,OAAO,IAAA,mBAAW,EAAC,GAAG,CAAC,KAAK,EAAE,UAAQ,CAAC,CAAC;QAC1C,CAAC;QAED,IAAM,QAAQ,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAM,SAAS,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEzC,IAAM,QAAQ,GAAG,6BAAmB,CAAC,SAAsB,CAAC,CAAC;QAC7D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,eAAQ,CAAC,mBAAmB,EAAE,CAAC;QAC9C,IAAM,KAAK,GAAG,MAAA,GAAG,CAAC,IAAI,0CAAE,KAAK,CAAC;QAC9B,IAAM,KAAK,GAAG,MAAA,GAAG,CAAC,KAAK,0CAAE,KAAK,CAAC;QAE/B,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,yDAAyD;QACzD,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAEtD,IAAI,CAAC,gBAAS,CAAC,EAAE,EAAE,gBAAS,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAsB,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,GAAG,KAAK,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAM,QAAQ,GAAG,6BAAmB,CAAC,SAAsB,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,aAAK,GAAC,QAAQ,IAAG,KAAK,KAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,eAAQ,CAAC,cAAc,EAAE,CAAC;QACzC,wFAAwF;QACxF,IAAM,QAAQ,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAM,QAAQ,GAAG,6BAAmB,CAAC,SAAsB,CAAC,CAAC;QAC7D,IAAI,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAA;AAlDY,QAAA,WAAW,eAkDvB"}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoOperators = void 0;
|
|
4
|
+
var MongoOperators;
|
|
5
|
+
(function (MongoOperators) {
|
|
6
|
+
MongoOperators["AND"] = "$and";
|
|
7
|
+
MongoOperators["OR"] = "$or";
|
|
8
|
+
MongoOperators["IN"] = "$in";
|
|
9
|
+
MongoOperators["EQ"] = "$eq";
|
|
10
|
+
MongoOperators["NE"] = "$ne";
|
|
11
|
+
MongoOperators["LT"] = "$lt";
|
|
12
|
+
MongoOperators["LTE"] = "$lte";
|
|
13
|
+
MongoOperators["GT"] = "$gt";
|
|
14
|
+
MongoOperators["GTE"] = "$gte";
|
|
15
|
+
MongoOperators["NOT"] = "$not";
|
|
16
|
+
MongoOperators["UNKNOWN"] = "unknown";
|
|
17
|
+
})(MongoOperators || (exports.MongoOperators = MongoOperators = {}));
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAAA,IAAY,cAYX;AAZD,WAAY,cAAc;IACxB,8BAAY,CAAA;IACZ,4BAAU,CAAA;IACV,4BAAU,CAAA;IACV,4BAAU,CAAA;IACV,4BAAU,CAAA;IACV,4BAAU,CAAA;IACV,8BAAY,CAAA;IACZ,4BAAU,CAAA;IACV,8BAAY,CAAA;IACZ,8BAAY,CAAA;IACZ,qCAAmB,CAAA;AACrB,CAAC,EAZW,cAAc,8BAAd,cAAc,QAYzB"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
coverageReporters: ['text', 'lcov'],
|
|
3
|
+
collectCoverage: true,
|
|
4
|
+
collectCoverageFrom: ['src/**/*.ts'],
|
|
5
|
+
roots: ['<rootDir>/src'],
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.[tj]sx?$': 'ts-jest',
|
|
8
|
+
},
|
|
9
|
+
coverageThreshold: {
|
|
10
|
+
global: {
|
|
11
|
+
branches: 100
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@odata-filter/marshalers",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A query filter parser implementing the OData standard",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"clean": "rimraf dist node_modules reports coverage",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:mutation": "stryker run"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@odata-filter/core": "^1.0.0",
|
|
18
|
+
"@types/mongodb": "^4.0.7"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@odata-filter/core": "^1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toMongoJson } from './mongoJson';
|
package/src/lookups.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TokenType } from '@odata-filter/core';
|
|
2
|
+
import { MongoOperators } from './types';
|
|
3
|
+
|
|
4
|
+
export const mongoOperatorLookup: Record<TokenType, MongoOperators> = {
|
|
5
|
+
[TokenType.AND]: MongoOperators.AND,
|
|
6
|
+
[TokenType.OR]: MongoOperators.OR,
|
|
7
|
+
[TokenType.IN]: MongoOperators.IN,
|
|
8
|
+
[TokenType.EQ]: MongoOperators.EQ,
|
|
9
|
+
[TokenType.NE]: MongoOperators.NE,
|
|
10
|
+
[TokenType.LT]: MongoOperators.LT,
|
|
11
|
+
[TokenType.LTE]: MongoOperators.LTE,
|
|
12
|
+
[TokenType.GT]: MongoOperators.GT,
|
|
13
|
+
[TokenType.GTE]: MongoOperators.GTE,
|
|
14
|
+
[TokenType.NOT]: MongoOperators.NOT,
|
|
15
|
+
|
|
16
|
+
[TokenType.WHITESPACE]: MongoOperators.UNKNOWN,
|
|
17
|
+
[TokenType.STRING]: MongoOperators.UNKNOWN,
|
|
18
|
+
[TokenType.SYMBOL]: MongoOperators.UNKNOWN,
|
|
19
|
+
[TokenType.NUMBER]: MongoOperators.UNKNOWN,
|
|
20
|
+
[TokenType.TUPLE]: MongoOperators.UNKNOWN,
|
|
21
|
+
[TokenType.OPEN_PAREN]: MongoOperators.UNKNOWN,
|
|
22
|
+
[TokenType.CLOSE_PAREN]: MongoOperators.UNKNOWN,
|
|
23
|
+
[TokenType.TRUE]: MongoOperators.UNKNOWN,
|
|
24
|
+
[TokenType.FALSE]: MongoOperators.UNKNOWN,
|
|
25
|
+
[TokenType.NULL]: MongoOperators.UNKNOWN,
|
|
26
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { toMongoJson } from './index';
|
|
2
|
+
import type { Node } from '@odata-filter/core';
|
|
3
|
+
|
|
4
|
+
describe('mongoJson', () => {
|
|
5
|
+
test('it transforms an AST node into a JSON object', () => {
|
|
6
|
+
// odata string: "country eq 'US' and user/age gte 21 or not (name in ('Hello', 'World') and grade lt 9 or state ne 'GA')"
|
|
7
|
+
const astStub: Node = {
|
|
8
|
+
type: 'logical_operator',
|
|
9
|
+
value: 'and',
|
|
10
|
+
left: {
|
|
11
|
+
type: 'comparison_operator',
|
|
12
|
+
value: 'eq',
|
|
13
|
+
left: { type: 'field', value: 'country' },
|
|
14
|
+
right: { type: 'string_value', value: 'US' },
|
|
15
|
+
},
|
|
16
|
+
right: {
|
|
17
|
+
type: 'logical_operator',
|
|
18
|
+
value: 'or',
|
|
19
|
+
left: {
|
|
20
|
+
type: 'comparison_operator',
|
|
21
|
+
value: 'gte',
|
|
22
|
+
left: { type: 'field', value: 'user/age' },
|
|
23
|
+
right: { type: 'number_value', value: 21 },
|
|
24
|
+
},
|
|
25
|
+
right: {
|
|
26
|
+
type: 'unary_operator',
|
|
27
|
+
value: 'not',
|
|
28
|
+
left: {
|
|
29
|
+
type: 'logical_operator',
|
|
30
|
+
value: 'and',
|
|
31
|
+
left: {
|
|
32
|
+
type: 'comparison_operator',
|
|
33
|
+
value: 'in',
|
|
34
|
+
left: { type: 'field', value: 'name' },
|
|
35
|
+
right: { type: 'array', value: ['Hello', 'World'] },
|
|
36
|
+
},
|
|
37
|
+
right: {
|
|
38
|
+
type: 'logical_operator',
|
|
39
|
+
value: 'or',
|
|
40
|
+
left: {
|
|
41
|
+
type: 'comparison_operator',
|
|
42
|
+
value: 'lt',
|
|
43
|
+
left: { type: 'field', value: 'grade' },
|
|
44
|
+
right: { type: 'number_value', value: 9 },
|
|
45
|
+
},
|
|
46
|
+
right: {
|
|
47
|
+
type: 'comparison_operator',
|
|
48
|
+
value: 'ne',
|
|
49
|
+
left: { type: 'field', value: 'state' },
|
|
50
|
+
right: { type: 'string_value', value: 'GA' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} as Node;
|
|
57
|
+
|
|
58
|
+
const result = toMongoJson(astStub);
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
country: 'US',
|
|
61
|
+
$or: [
|
|
62
|
+
{ 'user/age': { $gte: 21 } },
|
|
63
|
+
{
|
|
64
|
+
$not: {
|
|
65
|
+
name: ['Hello', 'World'],
|
|
66
|
+
$or: [{ grade: { $lt: 9 } }, { state: { $ne: 'GA' } }],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('it returns the given query parameters if no AST node is given', () => {
|
|
74
|
+
const result = toMongoJson(undefined, { query: 'parameters' });
|
|
75
|
+
expect(result).toEqual({ query: 'parameters' });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('it returns the given query parameters if AST node is not a logical, comparison, or unary operator type', () => {
|
|
79
|
+
const astStub: Node = {
|
|
80
|
+
type: 'field',
|
|
81
|
+
value: 'and',
|
|
82
|
+
} as Node;
|
|
83
|
+
const result = toMongoJson(astStub, { query: 'parameters' });
|
|
84
|
+
expect(result).toEqual({ query: 'parameters' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('it only accepts the first occurrence of a field and ignores duplicate occurrences of a field', () => {
|
|
88
|
+
const astStub: Node = {
|
|
89
|
+
type: 'logical_operator',
|
|
90
|
+
value: 'and',
|
|
91
|
+
left: {
|
|
92
|
+
type: 'comparison_operator',
|
|
93
|
+
value: 'eq',
|
|
94
|
+
left: { type: 'field', value: 'country' },
|
|
95
|
+
right: { type: 'string_value', value: 'US' },
|
|
96
|
+
},
|
|
97
|
+
right: {
|
|
98
|
+
type: 'comparison_operator',
|
|
99
|
+
value: 'eq',
|
|
100
|
+
left: { type: 'field', value: 'country' },
|
|
101
|
+
right: { type: 'string_value', value: 'UK' },
|
|
102
|
+
},
|
|
103
|
+
} as Node;
|
|
104
|
+
|
|
105
|
+
const result = toMongoJson(astStub);
|
|
106
|
+
expect(result).toEqual({ country: 'US' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('it does not assess a comparison operator if the "field" value is empty', () => {
|
|
110
|
+
const astStub: Node = {
|
|
111
|
+
type: 'comparison_operator',
|
|
112
|
+
value: 'eq',
|
|
113
|
+
left: { type: 'field', value: null },
|
|
114
|
+
right: { type: 'string_value', value: 'US' },
|
|
115
|
+
} as Node;
|
|
116
|
+
|
|
117
|
+
const result = toMongoJson(astStub);
|
|
118
|
+
expect(result).toEqual({});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('it does not assess a comparison operator if the "left" or "right" field is missing', () => {
|
|
122
|
+
let astStub: Node = {
|
|
123
|
+
type: 'comparison_operator',
|
|
124
|
+
value: 'eq',
|
|
125
|
+
right: { type: 'string_value', value: 'US' },
|
|
126
|
+
} as Node;
|
|
127
|
+
let result = toMongoJson(astStub);
|
|
128
|
+
expect(result).toEqual({});
|
|
129
|
+
|
|
130
|
+
astStub = {
|
|
131
|
+
type: 'comparison_operator',
|
|
132
|
+
value: 'eq',
|
|
133
|
+
left: { type: 'string_value', value: 'US' },
|
|
134
|
+
} as Node;
|
|
135
|
+
result = toMongoJson(astStub);
|
|
136
|
+
expect(result).toEqual({});
|
|
137
|
+
});
|
|
138
|
+
});
|
package/src/mongoJson.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NodeType, TokenType } from '@odata-filter/core';
|
|
2
|
+
import type { Node } from '@odata-filter/core';
|
|
3
|
+
import { mongoOperatorLookup } from './lookups';
|
|
4
|
+
import { Filter } from 'mongodb';
|
|
5
|
+
|
|
6
|
+
export const toMongoJson = (
|
|
7
|
+
ast?: Node,
|
|
8
|
+
json: Filter<unknown> = {},
|
|
9
|
+
): Filter<unknown> => {
|
|
10
|
+
if (!ast) return json;
|
|
11
|
+
|
|
12
|
+
const tokenType = ast.value && ast.value.toString().toLowerCase() + '_operator';
|
|
13
|
+
|
|
14
|
+
if (ast.type === NodeType.LOGICAL_OPERATOR) {
|
|
15
|
+
if (tokenType === TokenType.AND) {
|
|
16
|
+
const leftJson = toMongoJson(ast.left, json);
|
|
17
|
+
return toMongoJson(ast.right, leftJson);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const leftJson = toMongoJson(ast.left);
|
|
21
|
+
const rightJson = toMongoJson(ast.right);
|
|
22
|
+
|
|
23
|
+
const operator = mongoOperatorLookup[tokenType as TokenType];
|
|
24
|
+
json[operator] = [leftJson, rightJson];
|
|
25
|
+
return json;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (ast.type === NodeType.COMPARISON_OPERATOR) {
|
|
29
|
+
const field = ast.left?.value;
|
|
30
|
+
const value = ast.right?.value;
|
|
31
|
+
|
|
32
|
+
if (!field) return json;
|
|
33
|
+
|
|
34
|
+
// Take only the first value in the filter per parameter.
|
|
35
|
+
if (json[field.toString()] !== undefined) return json;
|
|
36
|
+
|
|
37
|
+
if ([TokenType.EQ, TokenType.IN].includes(tokenType as TokenType)) {
|
|
38
|
+
json[field.toString()] = value;
|
|
39
|
+
return json;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const operator = mongoOperatorLookup[tokenType as TokenType];
|
|
43
|
+
json[field.toString()] = { [operator]: value };
|
|
44
|
+
return json;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (ast.type === NodeType.UNARY_OPERATOR) {
|
|
48
|
+
// by definition, Unary Operators only have a single condition ("left" for our purposes)
|
|
49
|
+
const leftJson = toMongoJson(ast.left);
|
|
50
|
+
const operator = mongoOperatorLookup[tokenType as TokenType];
|
|
51
|
+
json[operator] = leftJson;
|
|
52
|
+
return json;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return json;
|
|
56
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
|
|
3
|
+
"_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.",
|
|
4
|
+
"packageManager": "npm",
|
|
5
|
+
"reporters": [
|
|
6
|
+
"html",
|
|
7
|
+
"clear-text",
|
|
8
|
+
"progress"
|
|
9
|
+
],
|
|
10
|
+
"testRunner": "jest",
|
|
11
|
+
"testRunner_comment": "Take a look at https://stryker-mutator.io/docs/stryker-js/jest-runner for information about the jest plugin.",
|
|
12
|
+
"coverageAnalysis": "perTest",
|
|
13
|
+
"jest": {
|
|
14
|
+
"projectType": "custom",
|
|
15
|
+
"configFile": "jest.config.js"
|
|
16
|
+
},
|
|
17
|
+
"checkers": ["typescript"],
|
|
18
|
+
"tsconfigFile": "tsconfig.json",
|
|
19
|
+
"typescriptChecker": {
|
|
20
|
+
"prioritizePerformanceOverAccuracy": true
|
|
21
|
+
},
|
|
22
|
+
"incremental": true,
|
|
23
|
+
"mutator": {
|
|
24
|
+
"plugins": []
|
|
25
|
+
},
|
|
26
|
+
"mutate": [
|
|
27
|
+
"src/**/*.ts",
|
|
28
|
+
"!src/**/*.spec.ts"
|
|
29
|
+
],
|
|
30
|
+
"thresholds": {
|
|
31
|
+
"high": 100,
|
|
32
|
+
"low": 100,
|
|
33
|
+
"break": 100
|
|
34
|
+
},
|
|
35
|
+
"ignorePatterns": ["coverage","dist","node_modules"],
|
|
36
|
+
"warnings": false
|
|
37
|
+
}
|
package/tsconfig.json
ADDED