@shko.online/dataverse-odata 0.1.2 → 0.1.4

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 (88) hide show
  1. package/.eslintrc.json +59 -0
  2. package/.prettierrc.json +8 -0
  3. package/CHANGELOG.md +14 -0
  4. package/lib/cjs/getExpandFromParser.js +19 -12
  5. package/lib/cjs/getFetchXmlFromParser.js +37 -35
  6. package/lib/cjs/getOrderByFromParser.js +28 -25
  7. package/lib/cjs/getSelectFromParser.js +13 -4
  8. package/lib/cjs/getTopFromParser.js +25 -21
  9. package/lib/cjs/getXQueryFromParser.js +9 -19
  10. package/lib/cjs/parseOData.js +2 -18
  11. package/lib/cjs/validators/atMostOnce.js +24 -0
  12. package/lib/cjs/validators/differentFromEmptyString.js +23 -0
  13. package/lib/cjs/validators/hasContent.js +24 -0
  14. package/lib/cjs/validators/isGuid.js +25 -0
  15. package/lib/cjs/validators/recognizedGuid.js +23 -0
  16. package/lib/esm/getExpandFromParser.js +18 -12
  17. package/lib/esm/getFetchXmlFromParser.js +37 -35
  18. package/lib/esm/getOrderByFromParser.js +28 -25
  19. package/lib/esm/getSelectFromParser.js +13 -4
  20. package/lib/esm/getTopFromParser.js +24 -21
  21. package/lib/esm/getXQueryFromParser.js +9 -18
  22. package/lib/esm/parseOData.js +2 -18
  23. package/lib/esm/validators/atMostOnce.js +17 -0
  24. package/lib/esm/validators/differentFromEmptyString.js +16 -0
  25. package/lib/esm/validators/hasContent.js +17 -0
  26. package/lib/esm/validators/isGuid.js +18 -0
  27. package/lib/esm/validators/recognizedGuid.js +16 -0
  28. package/lib/modern/getExpandFromParser.js +18 -12
  29. package/lib/modern/getFetchXmlFromParser.js +37 -35
  30. package/lib/modern/getOrderByFromParser.js +29 -26
  31. package/lib/modern/getSelectFromParser.js +13 -4
  32. package/lib/modern/getTopFromParser.js +24 -21
  33. package/lib/modern/getXQueryFromParser.js +9 -18
  34. package/lib/modern/parseOData.js +2 -18
  35. package/lib/modern/validators/atMostOnce.js +17 -0
  36. package/lib/modern/validators/differentFromEmptyString.js +16 -0
  37. package/lib/modern/validators/hasContent.js +17 -0
  38. package/lib/modern/validators/isGuid.js +18 -0
  39. package/lib/modern/validators/recognizedGuid.js +16 -0
  40. package/lib/ts3.4/OData.types.d.ts +118 -0
  41. package/lib/ts3.4/getSelectFromParser.d.ts +1 -1
  42. package/lib/ts3.4/validators/atMostOnce.d.ts +10 -0
  43. package/lib/ts3.4/validators/differentFromEmptyString.d.ts +9 -0
  44. package/lib/ts3.4/validators/hasContent.d.ts +10 -0
  45. package/lib/ts3.4/validators/isGuid.d.ts +9 -0
  46. package/lib/ts3.4/validators/recognizedGuid.d.ts +9 -0
  47. package/lib/ts3.9/OData.types.d.ts +154 -0
  48. package/lib/ts3.9/getExpandFromParser.d.ts.map +1 -1
  49. package/lib/ts3.9/getFetchXmlFromParser.d.ts.map +1 -1
  50. package/lib/ts3.9/getOrderByFromParser.d.ts.map +1 -1
  51. package/lib/ts3.9/getSelectFromParser.d.ts +1 -1
  52. package/lib/ts3.9/getSelectFromParser.d.ts.map +1 -1
  53. package/lib/ts3.9/getTopFromParser.d.ts.map +1 -1
  54. package/lib/ts3.9/getXQueryFromParser.d.ts.map +1 -1
  55. package/lib/ts3.9/parseOData.d.ts.map +1 -1
  56. package/lib/ts3.9/validators/atMostOnce.d.ts +10 -0
  57. package/lib/ts3.9/validators/atMostOnce.d.ts.map +1 -0
  58. package/lib/ts3.9/validators/differentFromEmptyString.d.ts +9 -0
  59. package/lib/ts3.9/validators/differentFromEmptyString.d.ts.map +1 -0
  60. package/lib/ts3.9/validators/hasContent.d.ts +10 -0
  61. package/lib/ts3.9/validators/hasContent.d.ts.map +1 -0
  62. package/lib/ts3.9/validators/isGuid.d.ts +9 -0
  63. package/lib/ts3.9/validators/isGuid.d.ts.map +1 -0
  64. package/lib/ts3.9/validators/recognizedGuid.d.ts +9 -0
  65. package/lib/ts3.9/validators/recognizedGuid.d.ts.map +1 -0
  66. package/package.json +1 -1
  67. package/src/OData.types.d.ts +33 -15
  68. package/src/getExpandFromParser.ts +23 -12
  69. package/src/getFetchXmlFromParser.ts +48 -45
  70. package/src/getOrderByFromParser.ts +30 -26
  71. package/src/getSelectFromParser.ts +13 -4
  72. package/src/getTopFromParser.ts +25 -21
  73. package/src/getXQueryFromParser.ts +10 -19
  74. package/src/parseOData.ts +10 -18
  75. package/src/validators/atMostOnce.ts +19 -0
  76. package/src/validators/differentFromEmptyString.ts +18 -0
  77. package/src/validators/hasContent.ts +19 -0
  78. package/src/validators/isGuid.ts +20 -0
  79. package/src/validators/recognizedGuid.ts +18 -0
  80. package/tsconfig.build.json +3 -0
  81. package/tsconfig.json +2 -5
  82. package/lib/cjs/validateNotEmpty.js +0 -17
  83. package/lib/esm/validateNotEmpty.js +0 -10
  84. package/lib/modern/validateNotEmpty.js +0 -10
  85. package/lib/ts3.4/validateNotEmpty.d.ts +0 -3
  86. package/lib/ts3.9/validateNotEmpty.d.ts +0 -3
  87. package/lib/ts3.9/validateNotEmpty.d.ts.map +0 -1
  88. package/src/validateNotEmpty.ts +0 -12
@@ -1,7 +1,7 @@
1
1
  import type { ODataQuery } from './OData.types';
2
2
  /**
3
3
  * Parses the {@link ODataSelect.$select $select} query
4
- * @returns Returns `false` when the parse has an error
4
+ * @returns {boolean} Returns `false` when the parse has an error
5
5
  */
6
6
  export declare const getSelectFromParser: (parser: URLSearchParams, result: ODataQuery) => boolean;
7
7
  //# sourceMappingURL=getSelectFromParser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getSelectFromParser.d.ts","sourceRoot":"","sources":["../../src/getSelectFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,eAAe,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAY,eAAe,UAAU,UAAU,KAAG,OAMjF,CAAC"}
1
+ {"version":3,"file":"getSelectFromParser.d.ts","sourceRoot":"","sources":["../../src/getSelectFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,eAAe,CAAC;AAK7D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAY,eAAe,UAAU,UAAU,KAAG,OAYjF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"getTopFromParser.d.ts","sourceRoot":"","sources":["../../src/getTopFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,eAAe,CAAC;AAG1D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,WAAY,eAAe,UAAU,UAAU,KAAG,OAuB9E,CAAC"}
1
+ {"version":3,"file":"getTopFromParser.d.ts","sourceRoot":"","sources":["../../src/getTopFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,eAAe,CAAC;AAM1D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,WAAY,eAAe,UAAU,UAAU,KAAG,OAwB9E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"getXQueryFromParser.d.ts","sourceRoot":"","sources":["../../src/getXQueryFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAmC,MAAM,eAAe,CAAC;AAIjF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MACzB,YAAY,GAAG,WAAW,UACrB,eAAe,UACf,UAAU,KACnB,OAoBF,CAAC"}
1
+ {"version":3,"file":"getXQueryFromParser.d.ts","sourceRoot":"","sources":["../../src/getXQueryFromParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAmC,MAAM,eAAe,CAAC;AAIjF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MACzB,YAAY,GAAG,WAAW,UACrB,eAAe,UACf,UAAU,KACnB,OAWF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseOData.d.ts","sourceRoot":"","sources":["../../src/parseOData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAQhD;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,eAsBvC,CAAC"}
1
+ {"version":3,"file":"parseOData.d.ts","sourceRoot":"","sources":["../../src/parseOData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAShD;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,eAavC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ODataQuery } from '../OData.types';
2
+ /**
3
+ * Options of this type must be specified at most once.
4
+ * @param option The option being validated (ex. $top)
5
+ * @param value The result of {@link URLSearchParams.getAll getAll}
6
+ * @param result The {@link ODataQuery} to append the error to
7
+ * @returns {boolean} Returns `false` when the parse has an error
8
+ */
9
+ export declare const atMostOnce: (option: string, value: string[], result: ODataQuery) => boolean;
10
+ //# sourceMappingURL=atMostOnce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atMostOnce.d.ts","sourceRoot":"","sources":["../../../src/validators/atMostOnce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,WAAY,MAAM,SAAS,MAAM,EAAE,UAAU,UAAU,YAS7E,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ODataQuery } from '../OData.types';
2
+ /**
3
+ *
4
+ * @param value The result of {@link URLSearchParams.getAll getAll}
5
+ * @param result The {@link ODataQuery} to append the error to
6
+ * @returns {boolean} Returns `false` when the parse has an error
7
+ */
8
+ export declare const differentFromEmptyString: (value: string[], result: ODataQuery) => boolean;
9
+ //# sourceMappingURL=differentFromEmptyString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"differentFromEmptyString.d.ts","sourceRoot":"","sources":["../../../src/validators/differentFromEmptyString.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAW,MAAM,EAAE,UAAU,UAAU,YAS3E,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ODataQuery } from '../OData.types';
2
+ /**
3
+ * Options of this type must be specified at most once.
4
+ * @param option The option being validated (ex. $top)
5
+ * @param value The result of {@link URLSearchParams.getAll getAll}
6
+ * @param result The {@link ODataQuery} to append the error to
7
+ * @returns {boolean} Returns `false` when the parse has an error
8
+ */
9
+ export declare const hasContent: (query: string, value: string[], result: ODataQuery) => boolean;
10
+ //# sourceMappingURL=hasContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasContent.d.ts","sourceRoot":"","sources":["../../../src/validators/hasContent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,SAAS,MAAM,EAAE,UAAU,UAAU,YAS5E,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ODataQuery } from '../OData.types';
2
+ /**
3
+ *
4
+ * @param value The result of {@link URLSearchParams.getAll getAll}
5
+ * @param result The {@link ODataQuery} to append the error to
6
+ * @returns {boolean} Returns `false` when the parse has an error
7
+ */
8
+ export declare const isGuid: (value: string[], result: ODataQuery) => boolean;
9
+ //# sourceMappingURL=isGuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isGuid.d.ts","sourceRoot":"","sources":["../../../src/validators/isGuid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD;;;;;GAKG;AACH,eAAO,MAAM,MAAM,UAAW,MAAM,EAAE,UAAU,UAAU,YASzD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ODataQuery } from '../OData.types';
2
+ /**
3
+ *
4
+ * @param value The result of {@link URLSearchParams.getAll getAll}
5
+ * @param result The {@link ODataQuery} to append the error to
6
+ * @returns {boolean} Returns `false` when the parse has an error
7
+ */
8
+ export declare const recognizedGuid: (value: string[], result: ODataQuery) => boolean;
9
+ //# sourceMappingURL=recognizedGuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recognizedGuid.d.ts","sourceRoot":"","sources":["../../../src/validators/recognizedGuid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,UAAW,MAAM,EAAE,UAAU,UAAU,YASjE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shko.online/dataverse-odata",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
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
5
  "scripts": {
6
6
  "build": "npm run lint && node ../scripts/build.js",
@@ -1,11 +1,11 @@
1
- export interface ODataError {
1
+ interface ODataError {
2
2
  error?: {
3
3
  code: string;
4
4
  message: string;
5
5
  };
6
6
  }
7
7
 
8
- export interface ODataExpand {
8
+ interface ODataExpand {
9
9
  /**
10
10
  * Use the {@link ODataExpand.$expand $expand} system query option in the navigation properties
11
11
  * to control what data from related entities is returned.
@@ -26,9 +26,9 @@ export interface ODataExpand {
26
26
  };
27
27
  }
28
28
 
29
- export type ODataExpandQuery = ODataSelect & ODataExpand;
29
+ type ODataExpandQuery = ODataSelect & ODataExpand;
30
30
 
31
- export interface ODataFilter {
31
+ interface ODataFilter {
32
32
  /**
33
33
  * Use the {@link ODataFilter.$filter $filter} system query option to set criteria for which rows will be returned.
34
34
  *
@@ -37,7 +37,7 @@ export interface ODataFilter {
37
37
  $filter?: StandardOperator;
38
38
  }
39
39
 
40
- export interface ODataFetch {
40
+ interface ODataFetch {
41
41
  /**
42
42
  * You can compose a FetchXML query for a specific table.
43
43
  * Then, URL-encode the XML and pass it to the entity set
@@ -48,7 +48,7 @@ export interface ODataFetch {
48
48
  fetchXml?: XMLDocument;
49
49
  }
50
50
 
51
- export interface ODataOrderBy {
51
+ interface ODataOrderBy {
52
52
  /**
53
53
  * Specify the order in which items are returned using the {@link ODataOrderBy.$orderby $orderby}
54
54
  * system query option. Use the asc or desc suffix to specify ascending or descending order
@@ -59,7 +59,7 @@ export interface ODataOrderBy {
59
59
  $orderby?: { column: string; asc: boolean }[];
60
60
  }
61
61
 
62
- export interface ODataSavedQuery {
62
+ interface ODataSavedQuery {
63
63
  /**
64
64
  * You can use the `savedqueryid` value and pass it as the value to the {@link ODataSavedQuery.savedQuery savedQuery}
65
65
  * parameter to the entity set that matches the corresponding `returnedtypecode` of the saved query.
@@ -69,7 +69,7 @@ export interface ODataSavedQuery {
69
69
  savedQuery?: string;
70
70
  }
71
71
 
72
- export interface ODataSelect {
72
+ interface ODataSelect {
73
73
  /**
74
74
  * Use the {@link ODataSelect.$select $select} system query option to limit the properties returned.
75
75
  *
@@ -81,7 +81,7 @@ export interface ODataSelect {
81
81
  $select?: string[];
82
82
  }
83
83
 
84
- export interface ODataTop {
84
+ interface ODataTop {
85
85
  /**
86
86
  * You can limit the number of results returned by using the {@link ODataTop.$top $top} system query option.
87
87
  *
@@ -90,7 +90,7 @@ export interface ODataTop {
90
90
  $top?: number;
91
91
  }
92
92
 
93
- export interface ODataUserQuery {
93
+ interface ODataUserQuery {
94
94
  /**
95
95
  * You can use the `userqueryid` value and pass it as the value to the {@link OdataUserQuery.userQuery userQuery}
96
96
  * parameter to the entity set that matches the corresponding `returnedtypecode` of the user query.
@@ -100,9 +100,9 @@ export interface ODataUserQuery {
100
100
  userQuery?: string;
101
101
  }
102
102
 
103
- export type StandardOperators = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le';
103
+ type StandardOperators = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le';
104
104
 
105
- export interface StandardOperator {
105
+ interface StandardOperator {
106
106
  operator: StandardOperators;
107
107
  /**
108
108
  * The left side of the 'X' operator must be a property of the entity.
@@ -114,18 +114,18 @@ export interface StandardOperator {
114
114
  right: string | number;
115
115
  }
116
116
 
117
- export interface UnaryOperator {
117
+ interface UnaryOperator {
118
118
  operator: 'not';
119
119
  right: StandardOperator;
120
120
  }
121
121
 
122
- export interface BinaryOperator {
122
+ interface BinaryOperator {
123
123
  operator: 'and' | 'or';
124
124
  left: StandardOperator;
125
125
  right: StandardOperator;
126
126
  }
127
127
 
128
- export type ODataQuery = ODataError &
128
+ type ODataQuery = ODataError &
129
129
  ODataExpand &
130
130
  ODataFetch &
131
131
  ODataFilter &
@@ -134,3 +134,21 @@ export type ODataQuery = ODataError &
134
134
  ODataSelect &
135
135
  ODataTop &
136
136
  ODataUserQuery;
137
+
138
+ export type {
139
+ BinaryOperator,
140
+ ODataError,
141
+ ODataExpand,
142
+ ODataExpandQuery,
143
+ ODataFetch,
144
+ ODataFilter,
145
+ ODataOrderBy,
146
+ ODataQuery,
147
+ ODataSavedQuery,
148
+ ODataSelect,
149
+ ODataTop,
150
+ ODataUserQuery,
151
+ StandardOperator,
152
+ StandardOperators,
153
+ UnaryOperator,
154
+ };
@@ -1,25 +1,33 @@
1
1
  import type { ODataError, ODataExpand, ODataExpandQuery, ODataQuery } from './OData.types';
2
2
 
3
3
  import { getSelectFromParser } from './getSelectFromParser';
4
+ import { atMostOnce } from './validators/atMostOnce';
5
+
6
+ const option = '$expand';
4
7
 
5
8
  /**
6
9
  * Parses the {@link ODataExpand.$expand $expand} query
7
10
  * @returns Returns `false` when the parse has an error
8
11
  */
9
12
  export const getExpandFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
10
- const $expand = parser.get('$expand');
11
- if ($expand !== null) {
12
- result.$expand = {};
13
+ const value = parser.getAll(option);
14
+ if (value.length === 0) {
15
+ return true;
16
+ }
17
+ if (!atMostOnce(option, value, result)) {
18
+ return false;
19
+ }
13
20
 
14
- if (!extractExpand($expand, result)) {
15
- return false;
16
- }
21
+ result.$expand = {};
22
+ if (!extractExpand(value[0], result)) {
23
+ return false;
17
24
  }
25
+
18
26
  return true;
19
27
  };
20
28
 
21
29
  const extractExpand = (value: string, $expand: ODataExpand & ODataError) => {
22
- const match = value.match(/^\s*(\w(\w|\d|_)*)\s*(,|\()?\s*/);
30
+ const match = value.match(/^\s*(\w(\w|\d|_)*)\s*(,|\(|\))?\s*/);
23
31
  if (
24
32
  match === null ||
25
33
  (match[0].length < value.length && match[3] === null) ||
@@ -27,7 +35,7 @@ const extractExpand = (value: string, $expand: ODataExpand & ODataError) => {
27
35
  ) {
28
36
  $expand.error = {
29
37
  code: '0x0',
30
- message: 'invalid expand expression',
38
+ message: `Term '${value}' is not valid in a $select or $expand expression.`,
31
39
  };
32
40
  return false;
33
41
  }
@@ -59,7 +67,10 @@ const extractExpand = (value: string, $expand: ODataExpand & ODataError) => {
59
67
  return false;
60
68
  }
61
69
  if (innerExpand.$expand === undefined && innerExpand.$select === undefined) {
62
- $expand.error = { code: '0x0', message: 'Empty expand' };
70
+ $expand.error = {
71
+ code: '0x0',
72
+ message: `Missing expand option on navigation property '${match[1]}'. If a parenthesis expression follows an expanded navigation property, then at least one expand option must be provided.`,
73
+ };
63
74
  return false;
64
75
  }
65
76
  $expand.$expand[match[1]] = innerExpand;
@@ -90,7 +101,7 @@ const getClosingBracket = (value: string): { index: number; error?: string } =>
90
101
  while (depth > 0) {
91
102
  const match = value.substring(startAt).match(/\(|\)/);
92
103
  if (match === null) {
93
- return { error: 'no closing bracket found', index: -1 };
104
+ return { error: 'Found an unbalanced bracket expression.', index: -1 };
94
105
  }
95
106
  if (match[0] === ')') {
96
107
  depth -= 1;
@@ -100,7 +111,7 @@ const getClosingBracket = (value: string): { index: number; error?: string } =>
100
111
  } else {
101
112
  depth += 1;
102
113
  }
103
- startAt = (match.index || 0) + 1;
114
+ startAt += (match.index || 0) + 1;
104
115
  }
105
- return { error: 'no closing bracket found', index: -1 };
116
+ return { error: 'Found an unbalanced bracket expression.', index: -1 };
106
117
  };
@@ -1,56 +1,59 @@
1
1
  import type { ODataQuery, ODataFetch } from './OData.types';
2
+ import { atMostOnce } from './validators/atMostOnce';
3
+ import { differentFromEmptyString } from './validators/differentFromEmptyString';
4
+
5
+ const option = 'fetchXml';
2
6
 
3
7
  /**
4
8
  * Parses the {@link ODataFetch.fetchXml fetchXml} query
5
9
  * @returns Returns `false` when the parse has an error
6
10
  */
7
11
  export const getFetchXmlFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
8
- const fetchXml = parser.get('fetchXml');
9
- if (fetchXml !== null) {
10
- if (fetchXml === '') {
11
- result.error = {
12
- code: '0x80040203',
13
- message: 'Expected non-empty string.',
14
- };
15
- return false;
16
- }
12
+ const value = parser.getAll(option);
13
+ if (value.length === 0) {
14
+ return true;
15
+ }
16
+ if (!atMostOnce(option, value, result) || !differentFromEmptyString(value, result)) {
17
+ return false;
18
+ }
17
19
 
18
- const serializer = new DOMParser();
19
- const fetchXmlDocument = serializer.parseFromString(fetchXml, 'text/xml');
20
- if (fetchXmlDocument.documentElement.tagName === 'parsererror') {
21
- result.error = {
22
- code: '0x80040201',
23
- message: 'Invalid XML.',
24
- };
25
- return false;
26
- }
27
- const entity = fetchXmlDocument
28
- .evaluate('fetch/entity', fetchXmlDocument, null, XPathResult.ANY_TYPE, null)
29
- .iterateNext() as Element;
30
- if (fetchXmlDocument.documentElement.children.length != 1 || !entity || !entity.getAttribute('name')) {
31
- result.error = {
32
- code: '0x80041102',
33
- message: 'Entity Name was not specified in FetchXml String.',
34
- };
35
- return false;
36
- }
37
- const invalidAttribute = fetchXmlDocument
38
- .evaluate(
39
- 'fetch/entity/*[not(self::filter or self::order or self::link-entity or self::attribute or self::all-attributes or self::no-attrs)]',
40
- fetchXmlDocument,
41
- null,
42
- XPathResult.ANY_TYPE,
43
- null,
44
- )
45
- .iterateNext() as Element;
46
- if (invalidAttribute) {
47
- result.error = {
48
- code: '0x8004111c',
49
- message: `Invalid Child Node, valid nodes are filter, order, link-entity, attribute, all-attributes, no-attrs. NodeName = ${invalidAttribute.tagName} NodeXml = ${invalidAttribute.outerHTML}`,
50
- };
51
- return false;
52
- }
53
- result.fetchXml = fetchXmlDocument;
20
+ const fetchXml = value[0];
21
+ const serializer = new DOMParser();
22
+ const fetchXmlDocument = serializer.parseFromString(fetchXml, 'text/xml');
23
+ if (fetchXmlDocument.documentElement.tagName === 'parsererror') {
24
+ result.error = {
25
+ code: '0x80040201',
26
+ message: 'Invalid XML.',
27
+ };
28
+ return false;
54
29
  }
30
+ const entity = fetchXmlDocument
31
+ .evaluate('fetch/entity', fetchXmlDocument, null, XPathResult.ANY_TYPE, null)
32
+ .iterateNext() as Element;
33
+ if (fetchXmlDocument.documentElement.children.length != 1 || !entity || !entity.getAttribute('name')) {
34
+ result.error = {
35
+ code: '0x80041102',
36
+ message: 'Entity Name was not specified in FetchXml String.',
37
+ };
38
+ return false;
39
+ }
40
+ const invalidAttribute = fetchXmlDocument
41
+ .evaluate(
42
+ 'fetch/entity/*[not(self::filter or self::order or self::link-entity or self::attribute or self::all-attributes or self::no-attrs)]',
43
+ fetchXmlDocument,
44
+ null,
45
+ XPathResult.ANY_TYPE,
46
+ null,
47
+ )
48
+ .iterateNext() as Element;
49
+ if (invalidAttribute) {
50
+ result.error = {
51
+ code: '0x8004111c',
52
+ message: `Invalid Child Node, valid nodes are filter, order, link-entity, attribute, all-attributes, no-attrs. NodeName = ${invalidAttribute.tagName} NodeXml = ${invalidAttribute.outerHTML}`,
53
+ };
54
+ return false;
55
+ }
56
+ result.fetchXml = fetchXmlDocument;
57
+
55
58
  return true;
56
59
  };
@@ -1,6 +1,8 @@
1
1
  import type { ODataQuery, ODataOrderBy } from './OData.types';
2
- import { validateNotEmpty } from './validateNotEmpty';
2
+ import { atMostOnce } from './validators/atMostOnce';
3
+ import { hasContent } from './validators/hasContent';
3
4
 
5
+ const option = '$orderby';
4
6
  const edmProperty = /\w{1-255}/gi;
5
7
 
6
8
  /**
@@ -8,35 +10,37 @@ const edmProperty = /\w{1-255}/gi;
8
10
  * @returns Returns `false` when the parse has an error
9
11
  */
10
12
  export const getOrderByFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
11
- let $orderby = parser.get('$orderby');
12
- if ($orderby !== null) {
13
- if (!validateNotEmpty('$orderby', $orderby, result)) {
13
+ let value = parser.getAll(option);
14
+ if (value.length === 0) {
15
+ return true;
16
+ }
17
+ if (!atMostOnce(option, value, result) || !hasContent(option, value, result)) {
18
+ return false;
19
+ }
20
+ let $orderby = value[0].trimEnd();
21
+ const orderByArray: ODataOrderBy['$orderby'] = [];
22
+ for (let i = 0; i < $orderby.length; i++) {
23
+ if (false /* syntax error */) {
24
+ result.error = {
25
+ code: '0x0',
26
+ message: `Syntax error at position ${i} in '${$orderby}'.`,
27
+ };
28
+
14
29
  return false;
15
30
  }
16
- $orderby = $orderby.trimEnd();
17
- const orderByArray: ODataOrderBy['$orderby'] = [];
18
- for (let i = 0; i < $orderby.length; i++) {
19
- if (false /* syntax error */) {
20
- result.error = {
21
- code: '0x0',
22
- message: `Syntax error at position ${i} in '${$orderby}'.`,
23
- };
24
-
25
- return false;
26
- }
31
+ }
32
+
33
+ orderByArray.forEach((orderBy) => {
34
+ if (!orderBy.column?.match(edmProperty)) {
35
+ result.error = {
36
+ code: '0x80060888',
37
+ message: 'Order By Property must be of type EdmProperty',
38
+ };
39
+ return false;
27
40
  }
41
+ });
28
42
 
29
- orderByArray.forEach((orderBy) => {
30
- if (!orderBy.column?.match(edmProperty)) {
31
- result.error = {
32
- code: '0x80060888',
33
- message: 'Order By Property must be of type EdmProperty',
34
- };
35
- return false;
36
- }
37
- });
43
+ result.$orderby = orderByArray;
38
44
 
39
- result.$orderby = orderByArray;
40
- }
41
45
  return true;
42
46
  };
@@ -1,13 +1,22 @@
1
1
  import type { ODataQuery, ODataSelect } from './OData.types';
2
+ import { atMostOnce } from './validators/atMostOnce';
3
+
4
+ const option = '$select';
2
5
 
3
6
  /**
4
7
  * Parses the {@link ODataSelect.$select $select} query
5
- * @returns Returns `false` when the parse has an error
8
+ * @returns {boolean} Returns `false` when the parse has an error
6
9
  */
7
10
  export const getSelectFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
8
- const $select = parser.get('$select');
9
- if ($select !== null) {
10
- result.$select = $select.split(',');
11
+ const value = parser.getAll(option);
12
+ if (value.length === 0) {
13
+ return true;
14
+ }
15
+ if (!atMostOnce(option, value, result)) {
16
+ return false;
17
+ }
18
+ if (value.length > 0) {
19
+ result.$select = value[0].split(',');
11
20
  }
12
21
  return true;
13
22
  };
@@ -1,31 +1,35 @@
1
1
  import type { ODataQuery, ODataTop } from './OData.types';
2
- import { validateNotEmpty } from './validateNotEmpty';
2
+ import { atMostOnce } from './validators/atMostOnce';
3
+ import { hasContent } from './validators/hasContent';
4
+
5
+ const option = '$top';
3
6
 
4
7
  /**
5
8
  * Parses the {@link ODataTop.$top $top} query
6
9
  * @returns Returns `false` when the parse has an error
7
10
  */
8
11
  export const getTopFromParser = (parser: URLSearchParams, result: ODataQuery): boolean => {
9
- const $topValue = parser.get('$top');
10
- if ($topValue !== null) {
11
- if (!validateNotEmpty('$orderby', $topValue, result)) {
12
- return false;
13
- }
14
- let $top: number;
15
- if (!$topValue.match(/^\d+$/) || ($top = parseInt($topValue)) < 0) {
16
- result.error = {
17
- code: '0x0',
18
- message: `Invalid value '${$topValue}' for $top query option found. The $top query option requires a non-negative integer value.`,
19
- };
20
- return false;
21
- } else if ($top === 0) {
22
- result.error = {
23
- code: '0x0',
24
- message: `Invalid value for $top query option.`,
25
- };
26
- return false;
27
- }
28
- result.$top = $top;
12
+ const value = parser.getAll(option);
13
+ if (value.length === 0) {
14
+ return true;
15
+ }
16
+ if (!atMostOnce(option, value, result) || !hasContent(option, value, result)) {
17
+ return false;
18
+ }
19
+ let $top: number;
20
+ if (!value[0].match(/^\d+$/) || ($top = parseInt(value[0])) < 0) {
21
+ result.error = {
22
+ code: '0x0',
23
+ message: `Invalid value '${value}' for $top query option found. The $top query option requires a non-negative integer value.`,
24
+ };
25
+ return false;
26
+ } else if ($top === 0) {
27
+ result.error = {
28
+ code: '0x0',
29
+ message: `Invalid value for $top query option.`,
30
+ };
31
+ return false;
29
32
  }
33
+ result.$top = $top;
30
34
  return true;
31
35
  };
@@ -1,6 +1,6 @@
1
1
  import type { ODataQuery, ODataSavedQuery, ODataUserQuery } from './OData.types';
2
-
3
- const guidRegex = /[0-9A-F]{8}\-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/gi;
2
+ import { isGuid } from './validators/isGuid';
3
+ import { recognizedGuid } from './validators/recognizedGuid';
4
4
 
5
5
  /**
6
6
  * Parses the {@link ODataSavedQuery.savedQuery savedQuery} or
@@ -12,23 +12,14 @@ export const getXQueryFromParser = (
12
12
  parser: URLSearchParams,
13
13
  result: ODataQuery,
14
14
  ): boolean => {
15
- const xQuery = parser.get(X);
16
- if (xQuery !== null) {
17
- if (!xQuery.trim()) {
18
- result.error = {
19
- code: '0x0',
20
- message: 'Unrecognized Guid format.',
21
- };
22
- return false;
23
- }
24
- if (!xQuery.match(guidRegex)) {
25
- result.error = {
26
- code: '0x0',
27
- message: 'Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).',
28
- };
29
- return false;
30
- }
31
- result[X] = xQuery;
15
+ const value = parser.getAll(X);
16
+ if (value.length === 0) {
17
+ return true;
18
+ }
19
+ if (!recognizedGuid(value, result) || !isGuid(value, result)) {
20
+ return false;
32
21
  }
22
+
23
+ result[X] = value[0];
33
24
  return true;
34
25
  };