@shko.online/dataverse-odata 0.1.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.
Files changed (79) hide show
  1. package/.babelrc.js +161 -0
  2. package/jest.config.ts +12 -0
  3. package/lib/cjs/OData.types.d.js +1 -0
  4. package/lib/cjs/getExpandFromParser.js +115 -0
  5. package/lib/cjs/getFetchXmlFromParser.js +43 -0
  6. package/lib/cjs/getSelectFromParser.js +18 -0
  7. package/lib/cjs/getTopFromParser.js +32 -0
  8. package/lib/cjs/index.js +15 -0
  9. package/lib/cjs/parseOData.js +28 -0
  10. package/lib/esm/OData.types.d.js +0 -0
  11. package/lib/esm/getExpandFromParser.js +109 -0
  12. package/lib/esm/getFetchXmlFromParser.js +36 -0
  13. package/lib/esm/getSelectFromParser.js +11 -0
  14. package/lib/esm/getTopFromParser.js +25 -0
  15. package/lib/esm/index.js +3 -0
  16. package/lib/esm/parseOData.js +21 -0
  17. package/lib/getExpandFromParser.d.ts +7 -0
  18. package/lib/getExpandFromParser.d.ts.map +1 -0
  19. package/lib/getExpandFromParser.js +97 -0
  20. package/lib/getExpandFromParser.js.map +1 -0
  21. package/lib/getFetchXmlFromParser.d.ts +7 -0
  22. package/lib/getFetchXmlFromParser.d.ts.map +1 -0
  23. package/lib/getFetchXmlFromParser.js +41 -0
  24. package/lib/getFetchXmlFromParser.js.map +1 -0
  25. package/lib/getSelectFromParser.d.ts +7 -0
  26. package/lib/getSelectFromParser.d.ts.map +1 -0
  27. package/lib/getSelectFromParser.js +12 -0
  28. package/lib/getSelectFromParser.js.map +1 -0
  29. package/lib/getTopFromParser.d.ts +7 -0
  30. package/lib/getTopFromParser.d.ts.map +1 -0
  31. package/lib/getTopFromParser.js +27 -0
  32. package/lib/getTopFromParser.js.map +1 -0
  33. package/lib/index.d.ts +5 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +4 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/modern/OData.types.d.js +0 -0
  38. package/lib/modern/getExpandFromParser.js +109 -0
  39. package/lib/modern/getFetchXmlFromParser.js +36 -0
  40. package/lib/modern/getSelectFromParser.js +11 -0
  41. package/lib/modern/getTopFromParser.js +25 -0
  42. package/lib/modern/index.js +3 -0
  43. package/lib/modern/parseOData.js +21 -0
  44. package/lib/parseOData.d.ts +3 -0
  45. package/lib/parseOData.d.ts.map +1 -0
  46. package/lib/parseOData.js +22 -0
  47. package/lib/parseOData.js.map +1 -0
  48. package/lib/ts3.4/getExpandFromParser.d.ts +7 -0
  49. package/lib/ts3.4/getFetchXmlFromParser.d.ts +7 -0
  50. package/lib/ts3.4/getSelectFromParser.d.ts +7 -0
  51. package/lib/ts3.4/getTopFromParser.d.ts +7 -0
  52. package/lib/ts3.4/index.d.ts +5 -0
  53. package/lib/ts3.4/parseOData.d.ts +3 -0
  54. package/lib/ts3.9/getExpandFromParser.d.ts +7 -0
  55. package/lib/ts3.9/getExpandFromParser.d.ts.map +1 -0
  56. package/lib/ts3.9/getFetchXmlFromParser.d.ts +7 -0
  57. package/lib/ts3.9/getFetchXmlFromParser.d.ts.map +1 -0
  58. package/lib/ts3.9/getSelectFromParser.d.ts +7 -0
  59. package/lib/ts3.9/getSelectFromParser.d.ts.map +1 -0
  60. package/lib/ts3.9/getTopFromParser.d.ts +7 -0
  61. package/lib/ts3.9/getTopFromParser.d.ts.map +1 -0
  62. package/lib/ts3.9/index.d.ts +5 -0
  63. package/lib/ts3.9/index.d.ts.map +1 -0
  64. package/lib/ts3.9/parseOData.d.ts +3 -0
  65. package/lib/ts3.9/parseOData.d.ts.map +1 -0
  66. package/package.json +73 -0
  67. package/src/OData.types.d.ts +61 -0
  68. package/src/getExpandFromParser.ts +105 -0
  69. package/src/getFetchXmlFromParser.ts +48 -0
  70. package/src/getSelectFromParser.ts +13 -0
  71. package/src/getTopFromParser.ts +27 -0
  72. package/src/index.ts +16 -0
  73. package/src/parseOData.ts +23 -0
  74. package/tests/OData-Parser.$expand.test.ts +39 -0
  75. package/tests/OData-Parser.$top.test.ts +36 -0
  76. package/tests/OData-Parser.fetchXml.test.ts +62 -0
  77. package/tests/OData-Parser.test.ts +17 -0
  78. package/tsconfig.build.json +9 -0
  79. package/tsconfig.json +36 -0
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@shko.online/dataverse-odata",
3
+ "version": "0.1.0",
4
+ "description": "This package will help parse OData strings (only the Microsoft Dataverse subset). It can be used as a validator, or you can build some javascript library which consumes the output of this library.",
5
+ "scripts": {
6
+ "build": "npm run lint && node ../scripts/build.js",
7
+ "lint": "eslint",
8
+ "tsc": "tsc --project tsconfig.build.json",
9
+ "test": "jest --coverage --maxWorkers=4"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "require": "./lib/cjs/index.js",
14
+ "import": "./lib/esm/index.js",
15
+ "types": "./lib/ts3.9/index.d.ts"
16
+ }
17
+ },
18
+ "main": "lib/cjs/index.js",
19
+ "module": "lib/esm/index.mjs",
20
+ "types": "lib/ts3.9/index.d.ts",
21
+ "typesVersions": {
22
+ "<3.8": {
23
+ "lib/ts3.9/*": [
24
+ "lib/ts3.4/*"
25
+ ]
26
+ }
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/shko.online/dataverse-odata.git"
31
+ },
32
+ "keywords": [
33
+ "odata",
34
+ "dataverse",
35
+ "made-in-albania"
36
+ ],
37
+ "author": "Shko Online <sales@shko.online> (https://shko.online);Betim Beja",
38
+ "license": "MIT",
39
+ "bugs": {
40
+ "url": "https://github.com/shko.online/dataverse-odata/issues"
41
+ },
42
+ "homepage": "https://github.com/shko.online/dataverse-odata#readme",
43
+ "devDependencies": {
44
+ "@babel/cli": "^7.20.7",
45
+ "@babel/core": "^7.20.12",
46
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
47
+ "@babel/plugin-proposal-decorators": "^7.20.13",
48
+ "@babel/plugin-proposal-export-default-from": "^7.18.10",
49
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
50
+ "@babel/plugin-proposal-private-methods": "^7.18.6",
51
+ "@babel/plugin-proposal-private-property-in-object": "^7.20.5",
52
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
53
+ "@babel/plugin-transform-runtime": "^7.19.6",
54
+ "@babel/preset-env": "^7.20.2",
55
+ "@babel/preset-flow": "^7.18.6",
56
+ "@babel/preset-react": "^7.18.6",
57
+ "@babel/preset-typescript": "^7.18.6",
58
+ "@emotion/babel-plugin": "^11.10.5",
59
+ "@jest/globals": "^29.4.2",
60
+ "@storybook/babel-plugin-require-context-hook": "^1.0.1",
61
+ "@typescript-eslint/eslint-plugin": "^5.40.1",
62
+ "@typescript-eslint/parser": "^5.40.1",
63
+ "babel-plugin-dynamic-import-node": "^2.3.3",
64
+ "babel-plugin-macros": "^3.1.0",
65
+ "copyfiles": "^2.4.1",
66
+ "eslint": "^8.25.0",
67
+ "jest": "^29.2.1",
68
+ "jest-environment-jsdom": "^29.2.1",
69
+ "ts-jest": "^29.0.3",
70
+ "ts-node": "^10.9.1",
71
+ "typescript": "^4.8.4"
72
+ }
73
+ }
@@ -0,0 +1,61 @@
1
+ export interface ODataError {
2
+ error?: {
3
+ code: string;
4
+ message: string;
5
+ };
6
+ }
7
+
8
+ export interface ODataExpand {
9
+ $expand?: {
10
+ [relationship: string]: ODataExpandQuery;
11
+ };
12
+ }
13
+
14
+ export type ODataExpandQuery = ODataSelect & ODataExpand;
15
+
16
+ export interface ODataFilter {
17
+ $filter?: StandardOperator;
18
+ }
19
+
20
+ export interface ODataFetch {
21
+ fetchXml?: XMLDocument;
22
+ }
23
+
24
+ export interface ODataSelect {
25
+ /**
26
+ * Attributes to select.
27
+ * Empty array equals to all Attributes.
28
+ */
29
+ $select?: string[];
30
+ }
31
+
32
+ export interface ODataTop {
33
+ $top?: number;
34
+ }
35
+
36
+ export type StandardOperators = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le';
37
+
38
+ export interface StandardOperator {
39
+ operator: StandardOperators;
40
+ /**
41
+ * The left side of the 'X' operator must be a property of the entity.
42
+ */
43
+ left: string;
44
+ /**
45
+ * The right side of the 'X' operator must be a constant value.
46
+ */
47
+ right: string | number;
48
+ }
49
+
50
+ export interface UnaryOperator {
51
+ operator: 'not';
52
+ right: StandardOperator;
53
+ }
54
+
55
+ export interface BinaryOperator {
56
+ operator: 'and' | 'or';
57
+ left: StandardOperator;
58
+ right: StandardOperator;
59
+ }
60
+
61
+ export type ODataQuery = ODataError & ODataExpand & ODataFilter & ODataSelect & ODataTop & ODataFetch;
@@ -0,0 +1,105 @@
1
+ import type { ODataError, ODataExpand, ODataExpandQuery, ODataQuery } from './OData.types';
2
+ import { getSelectFromParser } from './getSelectFromParser';
3
+
4
+ /**
5
+ * Parses the $expand query
6
+ * @returns Returns true when the parse has an error
7
+ */
8
+ export const getExpandFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
9
+ const $expand = parser.get('$expand');
10
+ if ($expand !== null) {
11
+ result.$expand = {};
12
+
13
+ if (extractExpand($expand, result)) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ };
19
+
20
+ const extractExpand = (value: string, $expand: ODataExpand & ODataError) => {
21
+ const match = value.match(/^\s*(\w(\w|\d|_)*)\s*(,|\()?\s*/);
22
+ if (
23
+ match === null ||
24
+ (match[0].length < value.length && match[3] === null) ||
25
+ (match[0].length === value.length && match[3] !== undefined)
26
+ ) {
27
+ $expand.error = {
28
+ code: '0x0',
29
+ message: 'invalid expand expression',
30
+ };
31
+ return true;
32
+ }
33
+ let matchSeparator = match[3];
34
+ let matchLength = match[0].length;
35
+ if (matchSeparator !== '(') {
36
+ if ($expand.$expand !== undefined) {
37
+ $expand.$expand[match[1]] = { $select: [] };
38
+ }
39
+ } else {
40
+ const { index, error } = getClosingBracket(value.substring(matchLength));
41
+ if (error) {
42
+ $expand.error = {
43
+ code: '0x0',
44
+ message: error,
45
+ };
46
+ return true;
47
+ }
48
+
49
+ if ($expand.$expand !== undefined) {
50
+ const innerExpand = {} as ODataExpandQuery & ODataError;
51
+ const parser = new URLSearchParams('?' + value.substring(matchLength, matchLength + index));
52
+ if (getSelectFromParser(parser, innerExpand)) {
53
+ $expand.error = innerExpand.error;
54
+ return true;
55
+ }
56
+ if (getExpandFromParser(parser, innerExpand)) {
57
+ $expand.error = innerExpand.error;
58
+ return true;
59
+ }
60
+ if (innerExpand.$expand === undefined && innerExpand.$select === undefined) {
61
+ $expand.error = { code: '0x0', message: 'Empty expand' };
62
+ return true;
63
+ }
64
+ $expand.$expand[match[1]] = innerExpand;
65
+ }
66
+
67
+ matchLength = matchLength + index;
68
+ const secondMatch = value.substring(matchLength + 1).match(/\s*(,?)\s*d/);
69
+ if (secondMatch !== null) {
70
+ matchLength = matchLength + secondMatch[0].length;
71
+ if (secondMatch[1] !== null) {
72
+ matchSeparator = ',';
73
+ }
74
+ }
75
+ }
76
+
77
+ if (matchSeparator === ',') {
78
+ if (extractExpand(value.substring(matchLength), $expand)) {
79
+ return true;
80
+ }
81
+ }
82
+
83
+ return false;
84
+ };
85
+
86
+ const getClosingBracket = (value: string): { index: number; error?: string } => {
87
+ let depth = 1;
88
+ let startAt = 0;
89
+ while (depth > 0) {
90
+ const match = value.substring(startAt).match(/\(|\)/);
91
+ if (match === null) {
92
+ return { error: 'no closing bracket found', index: -1 };
93
+ }
94
+ if (match[0] === ')') {
95
+ depth -= 1;
96
+ if (depth === 0) {
97
+ return { index: match.index || 0 };
98
+ }
99
+ } else {
100
+ depth += 1;
101
+ }
102
+ startAt = (match.index || 0) + 1;
103
+ }
104
+ return { error: 'no closing bracket found', index: -1 };
105
+ };
@@ -0,0 +1,48 @@
1
+ import type { ODataQuery } from './OData.types';
2
+
3
+ /**
4
+ * Parses the $fetchXml query
5
+ * @returns Returns true when the parse has an error
6
+ */
7
+ export const getFetchXmlFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
8
+ const fetchXml = parser.get('fetchXml');
9
+ if (fetchXml !== null) {
10
+ const serializer = new DOMParser();
11
+ const fetchXmlDocument = serializer.parseFromString(fetchXml, 'text/xml');
12
+ if (fetchXmlDocument.documentElement.tagName === 'parsererror') {
13
+ result.error = {
14
+ code: '0x80040201',
15
+ message: 'Invalid XML.',
16
+ };
17
+ return true;
18
+ }
19
+ const entity = fetchXmlDocument
20
+ .evaluate('fetch/entity', fetchXmlDocument, null, XPathResult.ANY_TYPE, null)
21
+ .iterateNext() as Element;
22
+ if (fetchXmlDocument.documentElement.children.length != 1 || !entity || !entity.getAttribute('name')) {
23
+ result.error = {
24
+ code: '0x80041102',
25
+ message: 'Entity Name was not specified in FetchXml String.',
26
+ };
27
+ return true;
28
+ }
29
+ const invalidAttribute = fetchXmlDocument
30
+ .evaluate(
31
+ 'fetch/entity/*[not(self::filter or self::order or self::link-entity or self::attribute or self::all-attributes or self::no-attrs)]',
32
+ fetchXmlDocument,
33
+ null,
34
+ XPathResult.ANY_TYPE,
35
+ null,
36
+ )
37
+ .iterateNext() as Element;
38
+ if (invalidAttribute) {
39
+ result.error = {
40
+ code: '0x8004111c',
41
+ message: `Invalid Child Node, valid nodes are filter, order, link-entity, attribute, all-attributes, no-attrs. NodeName = ${invalidAttribute.tagName} NodeXml = ${invalidAttribute.outerHTML}`,
42
+ };
43
+ return true;
44
+ }
45
+ result.fetchXml = fetchXmlDocument;
46
+ }
47
+ return false;
48
+ };
@@ -0,0 +1,13 @@
1
+ import type { ODataQuery } from './OData.types';
2
+
3
+ /**
4
+ * Parses the $select query
5
+ * @returns Returns true when the parse has an error
6
+ */
7
+ export const getSelectFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
8
+ const $select = parser.get('$select');
9
+ if ($select !== null) {
10
+ result.$select = $select.split(',');
11
+ }
12
+ return false;
13
+ };
@@ -0,0 +1,27 @@
1
+ import type { ODataQuery } from './OData.types';
2
+
3
+ /**
4
+ * Parses the $top query
5
+ * @returns Returns true when the parse has an error
6
+ */
7
+ export const getTopFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
8
+ const $topValue = parser.get('$top');
9
+ if ($topValue !== null) {
10
+ let $top;
11
+ if (!$topValue.match(/^\d+$/) || ($top = parseInt($topValue)) < 0) {
12
+ result.error = {
13
+ code: '0x0',
14
+ message: `Invalid value '${$topValue}' for $top query option found. The $top query option requires a non-negative integer value.`,
15
+ };
16
+ return true;
17
+ } else if ($top === 0) {
18
+ result.error = {
19
+ code: '0x0',
20
+ message: `Invalid value for $top query option.`,
21
+ };
22
+ return true;
23
+ }
24
+ result.$top = $top;
25
+ }
26
+ return false;
27
+ };
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type {
2
+ BinaryOperator,
3
+ ODataError,
4
+ ODataExpand,
5
+ ODataExpandQuery,
6
+ ODataFetch,
7
+ ODataFilter,
8
+ ODataQuery,
9
+ ODataSelect,
10
+ ODataTop,
11
+ StandardOperator,
12
+ StandardOperators,
13
+ } from './OData.types';
14
+ export { parseOData } from './parseOData';
15
+ import { parseOData } from './parseOData';
16
+ export default parseOData;
@@ -0,0 +1,23 @@
1
+ import type { ODataQuery } from './OData.types';
2
+ import { getTopFromParser } from './getTopFromParser';
3
+ import { getSelectFromParser } from './getSelectFromParser';
4
+ import { getExpandFromParser } from './getExpandFromParser';
5
+ import { getFetchXmlFromParser } from './getFetchXmlFromParser';
6
+
7
+ export const parseOData = (query: string) => {
8
+ const parser = new URLSearchParams(query);
9
+ const result = {} as ODataQuery;
10
+ if (getExpandFromParser(parser, result)) {
11
+ return result;
12
+ }
13
+ if (getSelectFromParser(parser, result)) {
14
+ return result;
15
+ }
16
+ if (getTopFromParser(parser, result)) {
17
+ return result;
18
+ }
19
+ if (getFetchXmlFromParser(parser, result)) {
20
+ return result;
21
+ }
22
+ return result;
23
+ };
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { parseOData } from '../src';
3
+
4
+ describe('parseOData $expand', () => {
5
+ test('parse $expand Account_Leads', () => {
6
+ const result = parseOData('?$expand=Account_Leads');
7
+ expect(result.error).toBeUndefined();
8
+ expect(result.$expand).not.toBeNull();
9
+ expect(result.$expand).not.toBeUndefined();
10
+ expect(result.$expand?.Account_Leads.$select).toEqual([]);
11
+ });
12
+
13
+ test('parse $expand Account_Leads and business_unit_accounts', () => {
14
+ const result = parseOData('?$expand=Account_Leads,business_unit_accounts');
15
+ expect(result.error).toBeUndefined();
16
+ expect(result.$expand).not.toBeNull();
17
+ expect(result.$expand).not.toBeUndefined();
18
+ expect(result.$expand?.Account_Leads.$select).toEqual([]);
19
+ expect(result.$expand?.business_unit_accounts.$select).toEqual([]);
20
+ });
21
+
22
+ test('parse $expand throws on Account_Leads without details', () => {
23
+ const result = parseOData('?$expand=Account_Leads()');
24
+ expect(result.error).not.toBeUndefined();
25
+ expect(result.error?.message).toEqual('Empty expand');
26
+ });
27
+
28
+ test('parse $expand Account_Leads with $select', () => {
29
+ const result = parseOData('?$expand=Account_Leads($select=name)');
30
+ expect(result.error).toBeUndefined();
31
+ expect(result.$expand).not.toBeNull();
32
+ expect(result.$expand).not.toBeUndefined();
33
+ expect(result.$expand?.Account_Leads).not.toBeNull();
34
+ expect(result.$expand?.Account_Leads).not.toBeUndefined();
35
+ expect(result.$expand?.Account_Leads.$select).not.toBeNull();
36
+ expect(result.$expand?.Account_Leads.$select).not.toBeUndefined();
37
+ expect(result.$expand?.Account_Leads.$select).toContain('name');
38
+ });
39
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { parseOData } from '../src';
3
+
4
+ describe('parseOData $top', () => {
5
+ test('parse $top 5', () => {
6
+ const result = parseOData('?$top=5');
7
+ console.log(result);
8
+ expect(result.$top).toEqual(5);
9
+ });
10
+
11
+ test('parse $top errors for 1,0', () => {
12
+ const result = parseOData('?$select=name,numberofemployees&$top=1,0');
13
+ expect(result.error).not.toBeNull();
14
+ console.log(result.error);
15
+ expect(result.error?.code).toEqual('0x0');
16
+ expect(result.error?.message).toEqual(
17
+ `Invalid value '1,0' for $top query option found. The $top query option requires a non-negative integer value.`,
18
+ );
19
+ });
20
+
21
+ test('parse $top errors for negative value', () => {
22
+ const result = parseOData('?$select=name,numberofemployees&$top=-2');
23
+ expect(result.error).not.toBeNull();
24
+ expect(result.error?.code).toEqual('0x0');
25
+ expect(result.error?.message).toEqual(
26
+ `Invalid value '-2' for $top query option found. The $top query option requires a non-negative integer value.`,
27
+ );
28
+ });
29
+
30
+ test('parse $top errors for 0', () => {
31
+ const result = parseOData('?$select=name,numberofemployees&$top=0');
32
+ expect(result.error).not.toBeNull();
33
+ expect(result.error?.code).toEqual('0x0');
34
+ expect(result.error?.message).toEqual(`Invalid value for $top query option.`);
35
+ });
36
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { parseOData } from '../src';
3
+
4
+ describe('parseOData fetchXml', () => {
5
+ test('parse fetchXml account', () => {
6
+ const result = parseOData(
7
+ '?fetchXml=%3Cfetch%20mapping%3D%27logical%27%3E%3Centity%20name%3D%27account%27%3E%3Cattribute%20name%3D%27accountid%27%2F%3E%3Cattribute%20name%3D%27name%27%2F%3E%3Cattribute%20name%3D%27accountnumber%27%2F%3E%3C%2Fentity%3E%3C%2Ffetch%3E',
8
+ );
9
+ console.log(result);
10
+ expect(result.fetchXml?.documentElement.outerHTML).toEqual(
11
+ `<fetch mapping="logical"><entity name="account"><attribute name="accountid"/><attribute name="name"/><attribute name="accountnumber"/></entity></fetch>`,
12
+ );
13
+ });
14
+
15
+ test('parse fetchXml fails if invalid XML', () => {
16
+ const result = parseOData('?fetchXml=invalid');
17
+ expect(result.error).not.toBeNull();
18
+ console.log(result.error);
19
+ expect(result.error?.code).toEqual('0x80040201');
20
+ expect(result.error?.message).toEqual('Invalid XML.');
21
+ });
22
+
23
+ test('parse fetchXml fails if EntityName not specified', () => {
24
+ const result = parseOData('?fetchXml=<fetch><entity name=""></entity></fetch>');
25
+ expect(result.error).not.toBeNull();
26
+ console.log(result.error);
27
+ expect(result.error?.code).toEqual('0x80041102');
28
+ expect(result.error?.message).toEqual('Entity Name was not specified in FetchXml String.');
29
+ });
30
+
31
+ test('parse fetchXml fails if EntityName not specified', () => {
32
+ const result = parseOData('?fetchXml=<fetch><entity></entity></fetch>');
33
+ expect(result.error).not.toBeNull();
34
+ console.log(result.error);
35
+ expect(result.error?.code).toEqual('0x80041102');
36
+ expect(result.error?.message).toEqual('Entity Name was not specified in FetchXml String.');
37
+ });
38
+
39
+ test('parse fetchXml fails if EntityName not specified', () => {
40
+ const result = parseOData('?fetchXml=<entity></entity>');
41
+ expect(result.error).not.toBeNull();
42
+ console.log(result.error);
43
+ expect(result.error?.code).toEqual('0x80041102');
44
+ expect(result.error?.message).toEqual('Entity Name was not specified in FetchXml String.');
45
+ });
46
+
47
+ test('parse fetchXml fails if EntityName not specified', () => {
48
+ const result = parseOData('?fetchXml=<fetch></fetch>');
49
+ expect(result.error).not.toBeNull();
50
+ console.log(result.error);
51
+ expect(result.error?.code).toEqual('0x80041102');
52
+ expect(result.error?.message).toEqual('Entity Name was not specified in FetchXml String.');
53
+ });
54
+
55
+ test('parse fetchXml fails if invalid attribute specified', () => {
56
+ const result = parseOData('?fetchXml=<fetch><entity%20name="systemuser"><n%20/></entity></fetch>');
57
+ expect(result.error).not.toBeUndefined();
58
+ console.log(result.error);
59
+ expect(result.error?.code).toEqual('0x8004111c');
60
+ expect(result.error?.message).toEqual('Invalid Child Node, valid nodes are filter, order, link-entity, attribute, all-attributes, no-attrs. NodeName = n NodeXml = <n/>');
61
+ });
62
+ });
@@ -0,0 +1,17 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { parseOData } from '../src';
3
+
4
+ describe('parse odata', () => {
5
+ test('parse $select', () => {
6
+ const result = parseOData('?$select=name,numberofemployees$top=5');
7
+ expect(result.$select).not.toBeNull();
8
+ expect(result.$select).toContain('name');
9
+ });
10
+
11
+ test('$select and $expand', () => {
12
+ const result = parseOData('?$select2=name,numberofemployees&$expand=Account_Leads($select=name)');
13
+ expect(result.$select).not.toBeNull();
14
+ expect(result.$expand).not.toBeNull();
15
+ expect(result.$expand?.Account_Leads).not.toBeNull();
16
+ });
17
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": [
4
+ "tests",
5
+ "lib",
6
+ "node_modules",
7
+ "jest.config.ts"
8
+ ]
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react",
4
+ "allowSyntheticDefaultImports": true,
5
+ "esModuleInterop": true,
6
+ "target": "es5",
7
+ "lib": [
8
+ "es5",
9
+ "es6",
10
+ "dom"
11
+ ],
12
+ "module": "ES6",
13
+ "moduleResolution": "node",
14
+ "noEmit": false,
15
+ "outDir": "lib",
16
+ "resolveJsonModule": true,
17
+ "declaration": true,
18
+ "declarationMap": true,
19
+ "sourceMap": true,
20
+ "baseUrl": ".",
21
+ "typeRoots": [
22
+ "./node_modules/@types"
23
+ ]
24
+ },
25
+ "exclude": [
26
+ "node_modules",
27
+ "wwwroot",
28
+ "lib"
29
+ ],
30
+ "include": [
31
+ "src",
32
+ "tests",
33
+ "__type-tests__",
34
+ "__test-components__"
35
+ ]
36
+ }