@shko.online/dataverse-odata 0.1.5 → 0.2.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/.github/workflows/publish.yml +3 -3
- package/CHANGELOG.md +20 -0
- package/azure-pipelines.yml +1 -1
- package/lib/cjs/OData.types.d.js +1 -0
- package/lib/cjs/getAliasedProperty.js +36 -0
- package/lib/cjs/getExpandFromParser.js +122 -0
- package/lib/cjs/getFetchXmlFromParser.js +52 -0
- package/lib/cjs/getFilterFromParser.js +257 -0
- package/lib/cjs/getOrderByFromParser.js +78 -0
- package/lib/cjs/getSelectFromParser.js +27 -0
- package/lib/cjs/getTopFromParser.js +40 -0
- package/lib/cjs/getXQueryFromParser.js +25 -0
- package/lib/cjs/index.js +56 -0
- package/lib/cjs/parseOData.js +25 -0
- package/lib/cjs/validators/atMostOnce.js +24 -0
- package/lib/cjs/validators/differentFromEmptyString.js +23 -0
- package/lib/cjs/validators/hasContent.js +24 -0
- package/lib/cjs/validators/isGuid.js +25 -0
- package/lib/cjs/validators/recognizedGuid.js +23 -0
- package/lib/esm/OData.types.d.js +0 -0
- package/lib/esm/getAliasedProperty.js +29 -0
- package/lib/esm/getExpandFromParser.js +115 -0
- package/lib/esm/getFetchXmlFromParser.js +45 -0
- package/lib/esm/getFilterFromParser.js +250 -0
- package/lib/esm/getOrderByFromParser.js +71 -0
- package/lib/esm/getSelectFromParser.js +20 -0
- package/lib/esm/getTopFromParser.js +33 -0
- package/lib/esm/getXQueryFromParser.js +19 -0
- package/lib/esm/index.js +9 -0
- package/lib/esm/parseOData.js +19 -0
- package/lib/esm/validators/atMostOnce.js +17 -0
- package/lib/esm/validators/differentFromEmptyString.js +16 -0
- package/lib/esm/validators/hasContent.js +17 -0
- package/lib/esm/validators/isGuid.js +18 -0
- package/lib/esm/validators/recognizedGuid.js +16 -0
- package/lib/modern/OData.types.d.js +0 -0
- package/lib/modern/getAliasedProperty.js +29 -0
- package/lib/modern/getExpandFromParser.js +115 -0
- package/lib/modern/getFetchXmlFromParser.js +45 -0
- package/lib/modern/getFilterFromParser.js +255 -0
- package/lib/modern/getOrderByFromParser.js +72 -0
- package/lib/modern/getSelectFromParser.js +20 -0
- package/lib/modern/getTopFromParser.js +33 -0
- package/lib/modern/getXQueryFromParser.js +19 -0
- package/lib/modern/index.js +9 -0
- package/lib/modern/parseOData.js +19 -0
- package/lib/modern/validators/atMostOnce.js +17 -0
- package/lib/modern/validators/differentFromEmptyString.js +16 -0
- package/lib/modern/validators/hasContent.js +17 -0
- package/lib/modern/validators/isGuid.js +18 -0
- package/lib/modern/validators/recognizedGuid.js +16 -0
- package/lib/ts3.4/OData.types.d.ts +172 -0
- package/lib/ts3.4/getAliasedProperty.d.ts +10 -0
- package/lib/ts3.4/getExpandFromParser.d.ts +7 -0
- package/lib/ts3.4/getFetchXmlFromParser.d.ts +7 -0
- package/lib/ts3.4/getFilterFromParser.d.ts +7 -0
- package/lib/ts3.4/getOrderByFromParser.d.ts +7 -0
- package/lib/ts3.4/getSelectFromParser.d.ts +7 -0
- package/lib/ts3.4/getTopFromParser.d.ts +7 -0
- package/lib/ts3.4/getXQueryFromParser.d.ts +8 -0
- package/lib/ts3.4/index.d.ts +11 -0
- package/lib/ts3.4/parseOData.d.ts +8 -0
- package/lib/ts3.4/validators/atMostOnce.d.ts +10 -0
- package/lib/ts3.4/validators/differentFromEmptyString.d.ts +9 -0
- package/lib/ts3.4/validators/hasContent.d.ts +10 -0
- package/lib/ts3.4/validators/isGuid.d.ts +9 -0
- package/lib/ts3.4/validators/recognizedGuid.d.ts +9 -0
- package/lib/ts3.9/OData.types.d.ts +172 -0
- package/lib/ts3.9/getAliasedProperty.d.ts +10 -0
- package/lib/ts3.9/getExpandFromParser.d.ts +7 -0
- package/lib/ts3.9/getFetchXmlFromParser.d.ts +7 -0
- package/lib/ts3.9/getFilterFromParser.d.ts +7 -0
- package/lib/ts3.9/getOrderByFromParser.d.ts +7 -0
- package/lib/ts3.9/getSelectFromParser.d.ts +7 -0
- package/lib/ts3.9/getTopFromParser.d.ts +7 -0
- package/lib/ts3.9/getXQueryFromParser.d.ts +8 -0
- package/lib/ts3.9/index.d.ts +11 -0
- package/lib/ts3.9/parseOData.d.ts +8 -0
- package/lib/ts3.9/validators/atMostOnce.d.ts +10 -0
- package/lib/ts3.9/validators/differentFromEmptyString.d.ts +9 -0
- package/lib/ts3.9/validators/hasContent.d.ts +10 -0
- package/lib/ts3.9/validators/isGuid.d.ts +9 -0
- package/lib/ts3.9/validators/recognizedGuid.d.ts +9 -0
- package/lib/ts4.2/OData.types.d.ts +226 -0
- package/lib/ts4.2/getAliasedProperty.d.ts +10 -0
- package/lib/ts4.2/getAliasedProperty.d.ts.map +1 -0
- package/lib/ts4.2/getExpandFromParser.d.ts +7 -0
- package/lib/ts4.2/getExpandFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getFetchXmlFromParser.d.ts +7 -0
- package/lib/ts4.2/getFetchXmlFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getFilterFromParser.d.ts +7 -0
- package/lib/ts4.2/getFilterFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getOrderByFromParser.d.ts +7 -0
- package/lib/ts4.2/getOrderByFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getSelectFromParser.d.ts +7 -0
- package/lib/ts4.2/getSelectFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getTopFromParser.d.ts +7 -0
- package/lib/ts4.2/getTopFromParser.d.ts.map +1 -0
- package/lib/ts4.2/getXQueryFromParser.d.ts +8 -0
- package/lib/ts4.2/getXQueryFromParser.d.ts.map +1 -0
- package/lib/ts4.2/index.d.ts +11 -0
- package/lib/ts4.2/index.d.ts.map +1 -0
- package/lib/ts4.2/parseOData.d.ts +8 -0
- package/lib/ts4.2/parseOData.d.ts.map +1 -0
- package/lib/ts4.2/validators/atMostOnce.d.ts +10 -0
- package/lib/ts4.2/validators/atMostOnce.d.ts.map +1 -0
- package/lib/ts4.2/validators/differentFromEmptyString.d.ts +9 -0
- package/lib/ts4.2/validators/differentFromEmptyString.d.ts.map +1 -0
- package/lib/ts4.2/validators/hasContent.d.ts +10 -0
- package/lib/ts4.2/validators/hasContent.d.ts.map +1 -0
- package/lib/ts4.2/validators/isGuid.d.ts +9 -0
- package/lib/ts4.2/validators/isGuid.d.ts.map +1 -0
- package/lib/ts4.2/validators/recognizedGuid.d.ts +9 -0
- package/lib/ts4.2/validators/recognizedGuid.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/OData.types.d.ts +48 -5
- package/src/getFilterFromParser.ts +206 -5
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { atMostOnce } from './validators/atMostOnce';
|
|
2
|
+
import { hasContent } from './validators/hasContent';
|
|
3
|
+
const option = '$filter';
|
|
4
|
+
const STANDARD_OPERATORS = ['eq', 'ne', 'gt', 'ge', 'lt', 'le'];
|
|
5
|
+
const QUERY_FUNCTION_OPERATORS = ['contains', 'endswith', 'startswith'];
|
|
6
|
+
function isStandardOperator(s) {
|
|
7
|
+
return STANDARD_OPERATORS.includes(s);
|
|
8
|
+
}
|
|
9
|
+
function isQueryFunctionOperator(s) {
|
|
10
|
+
return QUERY_FUNCTION_OPERATORS.includes(s);
|
|
11
|
+
}
|
|
12
|
+
const GUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
|
|
13
|
+
function tokenize(input) {
|
|
14
|
+
const tokens = [];
|
|
15
|
+
let i = 0;
|
|
16
|
+
while (i < input.length) {
|
|
17
|
+
var _input;
|
|
18
|
+
if (/\s/.test(input[i])) {
|
|
19
|
+
i++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (input[i] === '(') {
|
|
23
|
+
tokens.push({
|
|
24
|
+
type: 'lparen',
|
|
25
|
+
value: '('
|
|
26
|
+
});
|
|
27
|
+
i++;
|
|
28
|
+
} else if (input[i] === ')') {
|
|
29
|
+
tokens.push({
|
|
30
|
+
type: 'rparen',
|
|
31
|
+
value: ')'
|
|
32
|
+
});
|
|
33
|
+
i++;
|
|
34
|
+
} else if (input[i] === ',') {
|
|
35
|
+
tokens.push({
|
|
36
|
+
type: 'comma',
|
|
37
|
+
value: ','
|
|
38
|
+
});
|
|
39
|
+
i++;
|
|
40
|
+
} else if (input[i] === "'") {
|
|
41
|
+
let j = i + 1;
|
|
42
|
+
let str = '';
|
|
43
|
+
while (j < input.length) {
|
|
44
|
+
if (input[j] === "'" && j + 1 < input.length && input[j + 1] === "'") {
|
|
45
|
+
str += "'";
|
|
46
|
+
j += 2;
|
|
47
|
+
} else if (input[j] === "'") {
|
|
48
|
+
break;
|
|
49
|
+
} else {
|
|
50
|
+
str += input[j];
|
|
51
|
+
j++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
tokens.push({
|
|
55
|
+
type: 'string',
|
|
56
|
+
value: str
|
|
57
|
+
});
|
|
58
|
+
i = j + 1;
|
|
59
|
+
} else if (/[0-9a-fA-F]/.test(input[i]) && GUID_REGEX.test(input.slice(i))) {
|
|
60
|
+
tokens.push({
|
|
61
|
+
type: 'string',
|
|
62
|
+
value: input.slice(i, i + 36)
|
|
63
|
+
});
|
|
64
|
+
i += 36;
|
|
65
|
+
} else if (/[0-9]/.test(input[i]) || input[i] === '-' && /[0-9]/.test((_input = input[i + 1]) !== null && _input !== void 0 ? _input : '')) {
|
|
66
|
+
let j = i;
|
|
67
|
+
if (input[j] === '-') j++;
|
|
68
|
+
while (j < input.length && /[0-9.]/.test(input[j])) j++;
|
|
69
|
+
tokens.push({
|
|
70
|
+
type: 'number',
|
|
71
|
+
value: input.slice(i, j)
|
|
72
|
+
});
|
|
73
|
+
i = j;
|
|
74
|
+
} else if (/[a-zA-Z_]/.test(input[i])) {
|
|
75
|
+
let j = i;
|
|
76
|
+
while (j < input.length && /[a-zA-Z0-9_]/.test(input[j])) j++;
|
|
77
|
+
tokens.push({
|
|
78
|
+
type: 'word',
|
|
79
|
+
value: input.slice(i, j)
|
|
80
|
+
});
|
|
81
|
+
i = j;
|
|
82
|
+
} else {
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return tokens;
|
|
87
|
+
}
|
|
88
|
+
class FilterParser {
|
|
89
|
+
constructor(tokens) {
|
|
90
|
+
this.tokens = void 0;
|
|
91
|
+
this.pos = 0;
|
|
92
|
+
this.tokens = tokens;
|
|
93
|
+
}
|
|
94
|
+
peek() {
|
|
95
|
+
var _this$tokens$this$pos;
|
|
96
|
+
return (_this$tokens$this$pos = this.tokens[this.pos]) !== null && _this$tokens$this$pos !== void 0 ? _this$tokens$this$pos : null;
|
|
97
|
+
}
|
|
98
|
+
consume(expected) {
|
|
99
|
+
const token = this.tokens[this.pos++];
|
|
100
|
+
if (!token) throw new Error(`Unexpected end of filter expression${expected ? `, expected ${expected}` : ''}`);
|
|
101
|
+
return token;
|
|
102
|
+
}
|
|
103
|
+
expect(type, value) {
|
|
104
|
+
const token = this.consume(`${type}${value ? ` '${value}'` : ''}`);
|
|
105
|
+
if (token.type !== type || value !== undefined && token.value !== value) {
|
|
106
|
+
throw new Error(`Expected ${type}${value ? ` '${value}'` : ''} but got ${token.type} '${token.value}'`);
|
|
107
|
+
}
|
|
108
|
+
return token;
|
|
109
|
+
}
|
|
110
|
+
parse() {
|
|
111
|
+
const expr = this.parseOr();
|
|
112
|
+
if (this.pos < this.tokens.length) {
|
|
113
|
+
throw new Error(`Unexpected token '${this.tokens[this.pos].value}'`);
|
|
114
|
+
}
|
|
115
|
+
return expr;
|
|
116
|
+
}
|
|
117
|
+
parseOr() {
|
|
118
|
+
let left = this.parseAnd();
|
|
119
|
+
while (((_this$peek = this.peek()) === null || _this$peek === void 0 ? void 0 : _this$peek.value) === 'or') {
|
|
120
|
+
var _this$peek;
|
|
121
|
+
this.consume();
|
|
122
|
+
const right = this.parseAnd();
|
|
123
|
+
left = {
|
|
124
|
+
operator: 'or',
|
|
125
|
+
left,
|
|
126
|
+
right
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return left;
|
|
130
|
+
}
|
|
131
|
+
parseAnd() {
|
|
132
|
+
let left = this.parseNot();
|
|
133
|
+
while (((_this$peek2 = this.peek()) === null || _this$peek2 === void 0 ? void 0 : _this$peek2.value) === 'and') {
|
|
134
|
+
var _this$peek2;
|
|
135
|
+
this.consume();
|
|
136
|
+
const right = this.parseNot();
|
|
137
|
+
left = {
|
|
138
|
+
operator: 'and',
|
|
139
|
+
left,
|
|
140
|
+
right
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return left;
|
|
144
|
+
}
|
|
145
|
+
parseNot() {
|
|
146
|
+
var _this$peek3;
|
|
147
|
+
if (((_this$peek3 = this.peek()) === null || _this$peek3 === void 0 ? void 0 : _this$peek3.value) === 'not') {
|
|
148
|
+
this.consume();
|
|
149
|
+
const right = this.parseNot();
|
|
150
|
+
return {
|
|
151
|
+
operator: 'not',
|
|
152
|
+
right
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return this.parsePrimary();
|
|
156
|
+
}
|
|
157
|
+
parsePrimary() {
|
|
158
|
+
const token = this.peek();
|
|
159
|
+
if (!token) throw new Error('Unexpected end of filter expression');
|
|
160
|
+
if (token.type === 'lparen') {
|
|
161
|
+
this.consume();
|
|
162
|
+
const expr = this.parseOr();
|
|
163
|
+
this.expect('rparen');
|
|
164
|
+
return expr;
|
|
165
|
+
}
|
|
166
|
+
if (token.type === 'word' && isQueryFunctionOperator(token.value.toLowerCase())) {
|
|
167
|
+
const func = this.consume().value.toLowerCase();
|
|
168
|
+
this.expect('lparen');
|
|
169
|
+
const left = this.expect('word').value;
|
|
170
|
+
this.expect('comma');
|
|
171
|
+
const right = this.expect('string').value;
|
|
172
|
+
this.expect('rparen');
|
|
173
|
+
return {
|
|
174
|
+
operator: func,
|
|
175
|
+
left,
|
|
176
|
+
right
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (token.type === 'word') {
|
|
180
|
+
const left = this.consume().value;
|
|
181
|
+
const opToken = this.consume(`a comparison operator`);
|
|
182
|
+
if (!isStandardOperator(opToken.value.toLowerCase())) {
|
|
183
|
+
throw new Error(`Invalid operator '${opToken.value}'`);
|
|
184
|
+
}
|
|
185
|
+
const operator = opToken.value.toLowerCase();
|
|
186
|
+
const right = this.consume(`a value or column name`);
|
|
187
|
+
if (right.type === 'string') {
|
|
188
|
+
return {
|
|
189
|
+
operator,
|
|
190
|
+
left,
|
|
191
|
+
right: right.value
|
|
192
|
+
};
|
|
193
|
+
} else if (right.type === 'number') {
|
|
194
|
+
return {
|
|
195
|
+
operator,
|
|
196
|
+
left,
|
|
197
|
+
right: Number(right.value)
|
|
198
|
+
};
|
|
199
|
+
} else if (right.type === 'word') {
|
|
200
|
+
// Constant keywords stay as StandardOperator; bare identifiers are column comparisons
|
|
201
|
+
const BOOL_CONSTANTS = ['true', 'false'];
|
|
202
|
+
if (BOOL_CONSTANTS.includes(right.value.toLowerCase())) {
|
|
203
|
+
return {
|
|
204
|
+
operator,
|
|
205
|
+
left,
|
|
206
|
+
isBooleanOperation: true,
|
|
207
|
+
right: right.value === 'true'
|
|
208
|
+
};
|
|
209
|
+
} else if (right.value.toLowerCase() === 'null') {
|
|
210
|
+
return {
|
|
211
|
+
operator,
|
|
212
|
+
left,
|
|
213
|
+
isNullOperation: true,
|
|
214
|
+
right: null
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
left,
|
|
219
|
+
operator,
|
|
220
|
+
isColumnOperation: true,
|
|
221
|
+
right: right.value
|
|
222
|
+
};
|
|
223
|
+
} else {
|
|
224
|
+
throw new Error(`Invalid right-hand side value of type '${right.type}' in filter expression`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw new Error(`Unexpected token '${token.value}'`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Parses the {@link ODataFilter.$filter $filter} query
|
|
233
|
+
* @returns {boolean} Returns `false` when the parse has an error
|
|
234
|
+
*/
|
|
235
|
+
export const getFilterFromParser = (parser, result) => {
|
|
236
|
+
const value = parser.getAll(option);
|
|
237
|
+
if (value.length === 0) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (!atMostOnce(option, value, result) || !hasContent(option, value, result)) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const tokens = tokenize(value[0]);
|
|
245
|
+
const p = new FilterParser(tokens);
|
|
246
|
+
result.$filter = p.parse();
|
|
247
|
+
} catch (e) {
|
|
248
|
+
result.error = {
|
|
249
|
+
code: '0x80060888',
|
|
250
|
+
message: `Syntax error in '$filter': ${e.message}`
|
|
251
|
+
};
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "core-js/modules/esnext.string.match-all.js";
|
|
2
|
+
import { getAliasedProperty } from './getAliasedProperty';
|
|
3
|
+
import { atMostOnce } from './validators/atMostOnce';
|
|
4
|
+
import { hasContent } from './validators/hasContent';
|
|
5
|
+
const option = '$orderby';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parses the {@link ODataOrderBy.$orderby $orderby} query
|
|
9
|
+
* @returns Returns `false` when the parse has an error
|
|
10
|
+
*/
|
|
11
|
+
export const getOrderByFromParser = (parser, result) => {
|
|
12
|
+
let 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 $orderby = value[0].trimEnd();
|
|
20
|
+
let $orderbyParts = $orderby.split(',');
|
|
21
|
+
const orderByArray = [];
|
|
22
|
+
let position = 0;
|
|
23
|
+
for (const element of $orderbyParts) {
|
|
24
|
+
const parts = Array.from(element.matchAll(/\s*(\S+)/gi));
|
|
25
|
+
if (parts.length > 2) {
|
|
26
|
+
position = position + parts[0][0].length + parts[1][0].length + parts[2][0].length;
|
|
27
|
+
result.error = {
|
|
28
|
+
code: '0x80060888',
|
|
29
|
+
message: `Syntax error at position ${position} in '${$orderby}'.`
|
|
30
|
+
};
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (!/^[@a-zA-Z]\w+/gi.test(parts[0][1])) {
|
|
34
|
+
position = position + parts[0][0].length;
|
|
35
|
+
result.error = {
|
|
36
|
+
code: '0x80060888',
|
|
37
|
+
message: `Syntax error at position ${position} in '${$orderby}'.`
|
|
38
|
+
};
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const orderBy = {
|
|
42
|
+
column: parts[0][1],
|
|
43
|
+
asc: true // default is ascending
|
|
44
|
+
};
|
|
45
|
+
if (parts[0][1].startsWith('@')) {
|
|
46
|
+
orderBy.column = getAliasedProperty(parser, result, parts[0][1]);
|
|
47
|
+
if (!orderBy.column) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (parts.length === 1) {
|
|
52
|
+
orderByArray.push(orderBy);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (parts[1][1].toLowerCase() === 'asc') {
|
|
56
|
+
orderBy.asc = true;
|
|
57
|
+
orderByArray.push(orderBy);
|
|
58
|
+
} else if (parts[1][1].toLowerCase() === 'desc') {
|
|
59
|
+
orderBy.asc = false;
|
|
60
|
+
orderByArray.push(orderBy);
|
|
61
|
+
} else {
|
|
62
|
+
position = position + parts[0][0].length + parts[1][0].length;
|
|
63
|
+
result.error = {
|
|
64
|
+
code: '0x80060888',
|
|
65
|
+
message: `Syntax error at position ${position} in '${$orderby}'.`
|
|
66
|
+
};
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
result.$orderby = orderByArray;
|
|
71
|
+
return true;
|
|
72
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { atMostOnce } from './validators/atMostOnce';
|
|
2
|
+
const option = '$select';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parses the {@link ODataSelect.$select $select} query
|
|
6
|
+
* @returns {boolean} Returns `false` when the parse has an error
|
|
7
|
+
*/
|
|
8
|
+
export const getSelectFromParser = (parser, result) => {
|
|
9
|
+
const value = parser.getAll(option);
|
|
10
|
+
if (value.length === 0) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (!atMostOnce(option, value, result)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (value.length > 0) {
|
|
17
|
+
result.$select = value[0].split(',');
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { atMostOnce } from './validators/atMostOnce';
|
|
2
|
+
import { hasContent } from './validators/hasContent';
|
|
3
|
+
const option = '$top';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parses the {@link ODataTop.$top $top} query
|
|
7
|
+
* @returns Returns `false` when the parse has an error
|
|
8
|
+
*/
|
|
9
|
+
export const getTopFromParser = (parser, result) => {
|
|
10
|
+
const value = parser.getAll(option);
|
|
11
|
+
if (value.length === 0) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (!atMostOnce(option, value, result) || !hasContent(option, value, result)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
let $top;
|
|
18
|
+
if (!value[0].match(/^\d+$/) || ($top = parseInt(value[0])) < 0) {
|
|
19
|
+
result.error = {
|
|
20
|
+
code: '0x0',
|
|
21
|
+
message: `Invalid value '${value}' for $top query option found. The $top query option requires a non-negative integer value.`
|
|
22
|
+
};
|
|
23
|
+
return false;
|
|
24
|
+
} else if ($top === 0) {
|
|
25
|
+
result.error = {
|
|
26
|
+
code: '0x0',
|
|
27
|
+
message: `Invalid value for $top query option.`
|
|
28
|
+
};
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
result.$top = $top;
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isGuid } from './validators/isGuid';
|
|
2
|
+
import { recognizedGuid } from './validators/recognizedGuid';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parses the {@link ODataSavedQuery.savedQuery savedQuery} or
|
|
6
|
+
* {@link ODataUserQuery.userQuery userQuery} query
|
|
7
|
+
* @returns Returns `false` when the parse has an error
|
|
8
|
+
*/
|
|
9
|
+
export const getXQueryFromParser = (X, parser, result) => {
|
|
10
|
+
const value = parser.getAll(X);
|
|
11
|
+
if (value.length === 0) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (!recognizedGuid(value, result) || !isGuid(value, result)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
result[X] = value[0];
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { getExpandFromParser } from './getExpandFromParser';
|
|
2
|
+
export { getFetchXmlFromParser } from './getFetchXmlFromParser';
|
|
3
|
+
export { getOrderByFromParser } from './getOrderByFromParser';
|
|
4
|
+
export { getSelectFromParser } from './getSelectFromParser';
|
|
5
|
+
export { getTopFromParser } from './getTopFromParser';
|
|
6
|
+
export { getXQueryFromParser } from './getXQueryFromParser';
|
|
7
|
+
export { parseOData } from './parseOData';
|
|
8
|
+
import { parseOData } from './parseOData';
|
|
9
|
+
export default parseOData;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getTopFromParser } from './getTopFromParser';
|
|
2
|
+
import { getSelectFromParser } from './getSelectFromParser';
|
|
3
|
+
import { getExpandFromParser } from './getExpandFromParser';
|
|
4
|
+
import { getFetchXmlFromParser } from './getFetchXmlFromParser';
|
|
5
|
+
import { getXQueryFromParser } from './getXQueryFromParser';
|
|
6
|
+
import { getOrderByFromParser } from './getOrderByFromParser';
|
|
7
|
+
import { getFilterFromParser } from './getFilterFromParser';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* parses the OData query and applies some Dataverse validations
|
|
11
|
+
* @param query The OData query
|
|
12
|
+
* @returns The parsed OData query
|
|
13
|
+
*/
|
|
14
|
+
export const parseOData = query => {
|
|
15
|
+
const parser = new URLSearchParams(query);
|
|
16
|
+
const result = {};
|
|
17
|
+
getExpandFromParser(parser, result) && getFetchXmlFromParser(parser, result) && getFilterFromParser(parser, result) && getSelectFromParser(parser, result) && getTopFromParser(parser, result) && getXQueryFromParser('savedQuery', parser, result) && getXQueryFromParser('userQuery', parser, result) && getOrderByFromParser(parser, result);
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options of this type must be specified at most once.
|
|
3
|
+
* @param option The option being validated (ex. $top)
|
|
4
|
+
* @param value The result of {@link URLSearchParams.prototype.getAll URLSearchParams.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 const atMostOnce = (option, value, result) => {
|
|
9
|
+
if (value.length > 1) {
|
|
10
|
+
result.error = {
|
|
11
|
+
code: '0x0',
|
|
12
|
+
message: `Query option '${option}' was specified more than once, but it must be specified at most once.`
|
|
13
|
+
};
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param value The result of {@link URLSearchParams.getAll getAll}
|
|
4
|
+
* @param result The {@link ODataQuery} to append the error to
|
|
5
|
+
* @returns {boolean} Returns `false` when the parse has an error
|
|
6
|
+
*/
|
|
7
|
+
export const differentFromEmptyString = (value, result) => {
|
|
8
|
+
if (value[0] === '') {
|
|
9
|
+
result.error = {
|
|
10
|
+
code: '0x80040203',
|
|
11
|
+
message: 'Expected non-empty string.'
|
|
12
|
+
};
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options of this type must be specified at most once.
|
|
3
|
+
* @param option The option being validated (ex. $top)
|
|
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 const hasContent = (query, value, result) => {
|
|
9
|
+
if (!value[0].trim()) {
|
|
10
|
+
result.error = {
|
|
11
|
+
code: '0x0',
|
|
12
|
+
message: `The value for OData query '${query}' cannot be empty.`
|
|
13
|
+
};
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const guidRegex = /[0-9A-F]{8}\-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/gi;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
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 const isGuid = (value, result) => {
|
|
10
|
+
if (!value[0].match(guidRegex)) {
|
|
11
|
+
result.error = {
|
|
12
|
+
code: '0x0',
|
|
13
|
+
message: 'Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).'
|
|
14
|
+
};
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param value The result of {@link URLSearchParams.getAll getAll}
|
|
4
|
+
* @param result The {@link ODataQuery} to append the error to
|
|
5
|
+
* @returns {boolean} Returns `false` when the parse has an error
|
|
6
|
+
*/
|
|
7
|
+
export const recognizedGuid = (value, result) => {
|
|
8
|
+
if (!value[0].trim()) {
|
|
9
|
+
result.error = {
|
|
10
|
+
code: '0x0',
|
|
11
|
+
message: 'Unrecognized Guid format.'
|
|
12
|
+
};
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
};
|