Skip to content

Commit 2b0ae19

Browse files
committed
Alias Refactoring, filter key option
Aliases are collected from query options. Key, Filters and Functions allow aliases Undefined and null are values for key filtering
1 parent 87cb04b commit 2b0ae19

File tree

2 files changed

+68
-80
lines changed

2 files changed

+68
-80
lines changed

src/index.ts

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,20 @@ export type GroupBy<T> = {
5151
transform?: Transform<T>;
5252
}
5353

54-
export type Value = {
55-
type: 'raw' | 'guid' | 'duration' | 'binary' | 'json' | 'alias';
56-
value: any;
57-
}
58-
59-
export type Alias = Value & {
60-
name: string;
61-
handleName(): string;
62-
handleValue(): string;
63-
}
64-
65-
export const raw = (value: string): Value => ({type: 'raw', value});
66-
export const guid = (value: string): Value => ({type: 'guid', value});
67-
export const duration = (value: string): Value => ({type: 'duration', value});
68-
export const binary = (value: string): Value => ({type: 'binary', value});
69-
export const json = (value: PlainObject): Value => ({type: 'json', value});
70-
export const alias = (name: string, value: PlainObject): Alias => ({
71-
type: 'alias', name, value,
72-
handleName() {
73-
return `@${this.name}`;
74-
},
75-
handleValue() {
76-
return handleValue(this.value);
77-
}
78-
});
54+
export type Raw = { type: 'raw'; value: any; }
55+
export type Guid = { type: 'guid'; value: any; }
56+
export type Duration = { type: 'duration'; value: any; }
57+
export type Binary = { type: 'binary'; value: any; }
58+
export type Json = { type: 'json'; value: any; }
59+
export type Alias = { type: 'alias'; name: string; value: any; }
60+
export type Value = string | Date | number | boolean | Raw | Guid | Duration | Binary | Json | Alias;
61+
62+
export const raw = (value: string): Raw => ({ type: 'raw', value });
63+
export const guid = (value: string): Guid => ({ type: 'guid', value });
64+
export const duration = (value: string): Duration => ({ type: 'duration', value });
65+
export const binary = (value: string): Binary => ({ type: 'binary', value });
66+
export const json = (value: PlainObject): Json => ({ type: 'json', value });
67+
export const alias = (name: string, value: PlainObject): Alias => ({ type: 'alias', name, value });
7968

8069
export type QueryOptions<T> = ExpandOptions<T> & {
8170
search: string;
@@ -106,26 +95,20 @@ export default function <T>({
10695
count,
10796
expand,
10897
action,
109-
func,
110-
aliases
98+
func
11199
}: Partial<QueryOptions<T>> = {}) {
112-
let path = '';
100+
let path: string = '';
101+
let aliases: Alias[] = [];
113102

114103
const params: any = {};
115104

116-
if (key) {
117-
if (typeof key === 'object') {
118-
const keys = Object.keys(key)
119-
.map(k => `${k}=${key[k]}`)
120-
.join(',');
121-
path += `(${keys})`;
122-
} else {
123-
path += `(${key})`;
105+
// key is not (null, undefined)
106+
if (key != undefined) {
107+
path += `(${handleValue(key as Value, aliases)})`;
124108
}
125-
}
126109

127110
if (filter || typeof count === 'object')
128-
params.$filter = buildFilter(typeof count === 'object' ? count : filter);
111+
params.$filter = buildFilter(typeof count === 'object' ? count : filter, aliases);
129112

130113
if (transform)
131114
params.$apply = buildTransforms(transform);
@@ -162,7 +145,7 @@ export default function <T>({
162145
} else if (typeof func === 'object') {
163146
const [funcName] = Object.keys(func);
164147
const funcArgs = Object.keys(func[funcName]).reduce(
165-
(acc: string[], item) => [...acc, `${item}=${handleValue(func[funcName][item])}`],
148+
(acc: string[], item) => [...acc, `${item}=${handleValue(func[funcName][item], aliases)}`],
166149
[]
167150
);
168151

@@ -173,31 +156,32 @@ export default function <T>({
173156
}
174157
}
175158

176-
if (aliases) {
177-
aliases
178-
.reduce((acc, alias) => Object.assign(acc, {[alias.handleName()]: alias.handleValue()}), params);
159+
if (aliases.length > 0) {
160+
Object.assign(params, aliases.reduce((acc, alias) =>
161+
Object.assign(acc, { [`@${alias.name}`]: handleValue(alias.value) })
162+
, {}));
179163
}
180164

181165
return buildUrl(path, { $select, $search, $skiptoken, $format, ...params });
182166
}
183167

184-
function renderPrimitiveValue(key: string, val: any) {
185-
return `${key} eq ${handleValue(val)}`
168+
function renderPrimitiveValue(key: string, val: any, aliases: Alias[] = []) {
169+
return `${key} eq ${handleValue(val, aliases)}`
186170
}
187171

188-
function buildFilter(filters: Filter = {}, propPrefix = ''): string {
172+
function buildFilter(filters: Filter = {}, aliases: Alias[] = [], propPrefix = ''): string {
189173
return ((Array.isArray(filters) ? filters : [filters])
190174
.reduce((acc: string[], filter) => {
191175
if (filter) {
192-
const builtFilter = buildFilterCore(filter, propPrefix);
176+
const builtFilter = buildFilterCore(filter, aliases, propPrefix);
193177
if (builtFilter) {
194178
acc.push(builtFilter);
195179
}
196180
}
197181
return acc;
198182
}, []) as string[]).join(' and ');
199183

200-
function buildFilterCore(filter: Filter = {}, propPrefix = '') {
184+
function buildFilterCore(filter: Filter = {}, aliases: Alias[] = [], propPrefix = '') {
201185
let filterExpr = "";
202186
if (typeof filter === 'string') {
203187
// Use raw filter string
@@ -233,11 +217,11 @@ function buildFilter(filters: Filter = {}, propPrefix = ''): string {
233217
value === null
234218
) {
235219
// Simple key/value handled as equals operator
236-
result.push(renderPrimitiveValue(propName, value));
220+
result.push(renderPrimitiveValue(propName, value, aliases));
237221
} else if (Array.isArray(value)) {
238222
const op = filterKey;
239223
const builtFilters = value
240-
.map(v => buildFilter(v, propPrefix))
224+
.map(v => buildFilter(v, aliases, propPrefix))
241225
.filter(f => f)
242226
.map(f => (LOGICAL_OPERATORS.indexOf(op) !== -1 ? `(${f})` : f));
243227
if (builtFilters.length) {
@@ -265,29 +249,29 @@ function buildFilter(filters: Filter = {}, propPrefix = ''): string {
265249
result.push(`${builtFilters.join(` ${op} `)}`);
266250
}
267251
}
268-
} else if (value instanceof Object) {
252+
} else if (typeof value === 'object') {
269253
if ('type' in value) {
270-
result.push(renderPrimitiveValue(propName, value));
254+
result.push(renderPrimitiveValue(propName, value, aliases));
271255
} else {
272256
const operators = Object.keys(value);
273257
operators.forEach(op => {
274258
if (COMPARISON_OPERATORS.indexOf(op) !== -1) {
275-
result.push(`${propName} ${op} ${handleValue(value[op])}`);
259+
result.push(`${propName} ${op} ${handleValue(value[op], aliases)}`);
276260
} else if (LOGICAL_OPERATORS.indexOf(op) !== -1) {
277261
if (Array.isArray(value[op])) {
278262
result.push(
279263
value[op]
280-
.map((v: any) => '(' + buildFilterCore(v, propName) + ')')
264+
.map((v: any) => '(' + buildFilterCore(v, aliases, propName) + ')')
281265
.join(` ${op} `)
282266
);
283267
} else {
284-
result.push('(' + buildFilterCore(value[op], propName) + ')');
268+
result.push('(' + buildFilterCore(value[op], aliases, propName) + ')');
285269
}
286270
} else if (COLLECTION_OPERATORS.indexOf(op) !== -1) {
287271
const collectionClause = buildCollectionClause(filterKey.toLowerCase(), value[op], op, propName);
288272
if (collectionClause) { result.push(collectionClause); }
289273
} else if (op === 'has') {
290-
result.push(`${propName} ${op} ${handleValue(value[op])}`);
274+
result.push(`${propName} ${op} ${handleValue(value[op], aliases)}`);
291275
} else if (op === 'in') {
292276
const resultingValues = Array.isArray(value[op])
293277
? value[op]
@@ -297,14 +281,14 @@ function buildFilter(filters: Filter = {}, propPrefix = ''): string {
297281
}));
298282

299283
result.push(
300-
propName + ' in (' + resultingValues.map((v: any) => handleValue(v)).join(',') + ')'
284+
propName + ' in (' + resultingValues.map((v: any) => handleValue(v, aliases)).join(',') + ')'
301285
);
302286
} else if (BOOLEAN_FUNCTIONS.indexOf(op) !== -1) {
303287
// Simple boolean functions (startswith, endswith, contains)
304-
result.push(`${op}(${propName},${handleValue(value[op])})`);
288+
result.push(`${op}(${propName},${handleValue(value[op], aliases)})`);
305289
} else {
306290
// Nested property
307-
const filter = buildFilterCore(value, propName);
291+
const filter = buildFilterCore(value, aliases, propName);
308292
if (filter) {
309293
result.push(filter);
310294
}
@@ -349,7 +333,7 @@ function buildFilter(filters: Filter = {}, propPrefix = ''): string {
349333
return {...acc, ...item}
350334
}, {}) : value;
351335

352-
const filter = buildFilterCore(filterValue, lambdaParameter);
336+
const filter = buildFilterCore(filterValue, aliases, lambdaParameter);
353337
clause = `${propName}/${op}(${filter ? `${lambdaParameter}:${filter}` : ''})`;
354338
}
355339
return clause;
@@ -375,36 +359,40 @@ function escapeIllegalChars(string: string) {
375359
return string;
376360
}
377361

378-
function handleValue(value: any) {
362+
function handleValue(value: Value, aliases?: Alias[]): any {
379363
if (typeof value === 'string') {
380364
return `'${escapeIllegalChars(value)}'`;
381365
} else if (value instanceof Date) {
382366
return value.toISOString();
383-
} else if (value instanceof Number) {
367+
} else if (typeof value === 'number') {
384368
return value;
385369
} else if (Array.isArray(value)) {
386-
// Double quote strings to keep them after `.join`
387-
const arr = value.map(d => (typeof d === 'string' ? `'${d}'` : d));
388-
return `[${arr.join(',')}]`;
389-
} else {
390-
// TODO: Figure out how best to specify types. See: https://github.com/devnixs/ODataAngularResources/blob/master/src/odatavalue.js
391-
switch (value && value.type) {
392-
case 'guid':
370+
return `[${value.map(d => handleValue(d)).join(',')}]`;
371+
} else if (value === null) {
372+
return value;
373+
} else if (typeof value === 'object') {
374+
if (value.type === 'raw') {
375+
return value.value;
376+
} else if (value.type === 'guid') {
393377
return value.value;
394-
case 'duration':
378+
} else if (value.type === 'duration') {
395379
return `duration'${value.value}'`;
396-
case 'raw':
397-
return value.value;
398-
case 'binary':
380+
} else if (value.type === 'binary') {
399381
return `binary'${value.value}'`;
400-
case 'alias':
401-
return (value as Alias).handleName();
402-
case 'json':
382+
} else if (value.type === 'alias') {
383+
// Store
384+
if (Array.isArray(aliases))
385+
aliases.push(value as Alias);
386+
return `@${(value as Alias).name}`;
387+
} else if (value.type === 'json') {
403388
return escape(JSON.stringify(value.value));
389+
} else {
390+
return Object.keys(value)
391+
.map(k => `${k}=${handleValue(value[k], aliases)}`).join(',');
392+
}
404393
}
405394
return value;
406395
}
407-
}
408396

409397
function buildExpand<T>(expands: Expand<T>): string {
410398
if (typeof expands === 'number') {

test/index.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,12 @@ describe('filter', () => {
302302
const filter = { DateProp: { ge: start, le: end } };
303303
let expected =
304304
'?$filter=DateProp ge @start and DateProp le @end&@start=2017-01-01T00:00:00.000Z&@end=2017-03-01T00:00:00.000Z';
305-
let actual = buildQuery({ filter, aliases: [start, end] });
305+
let actual = buildQuery({ filter });
306306
expect(actual).toEqual(expected);
307307
end.value = new Date(Date.UTC(2017, 5, 1));
308308
expected =
309309
'?$filter=DateProp ge @start and DateProp le @end&@start=2017-01-01T00:00:00.000Z&@end=2017-06-01T00:00:00.000Z';
310-
actual = buildQuery({ filter, aliases: [start, end] });
310+
actual = buildQuery({ filter });
311311
expect(actual).toEqual(expected);
312312
});
313313

@@ -1616,7 +1616,7 @@ describe('function', () => {
16161616
};
16171617
const expected =
16181618
"/Test(SomeCollection=@SomeCollection)?@SomeCollection=['Sean','Jason']";
1619-
const actual = buildQuery({ func, aliases: [someCollection] });
1619+
const actual = buildQuery({ func });
16201620
expect(actual).toEqual(expected);
16211621
});
16221622

@@ -1627,7 +1627,7 @@ describe('function', () => {
16271627
};
16281628
const expected =
16291629
'/Test(SomeCollection=@SomeCollection)?@SomeCollection=%5B%7B%22Name%22%3A%22Sean%22%7D%2C%7B%22Name%22%3A%22Jason%22%7D%5D';
1630-
const actual = buildQuery({ func, aliases: [someCollection] });
1630+
const actual = buildQuery({ func });
16311631
expect(actual).toEqual(expected);
16321632
});
16331633
});

0 commit comments

Comments
 (0)