Skip to content

Commit

Permalink
feat: support odata v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Soontao committed Jul 7, 2021
1 parent 0748a56 commit 91c694d
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 64 deletions.
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "vscode-jest-tests",
"request": "launch",
"args": [
"--runInBand",
"--watchAll=false"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"program": "${workspaceFolder}/node_modules/.bin/jest"
}
]
}
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
"jest.autoEnable": false,
"cSpell.words": [
"Theo",
"allpages",
"datetime",
"datetimeoffset",
"inlinecount",
"odata"
]
}
38 changes: 19 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"typescript": "^4.3.5"
},
"dependencies": {
"@odata/metadata": "^0.2.5",
"@odata/metadata": "^0.2.6",
"@newdash/newdash": "^5.19.0"
},
"keywords": [
Expand Down
88 changes: 57 additions & 31 deletions src/builder/filter.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import join from '@newdash/newdash/join';
import { Edm } from '@odata/metadata';
import { convertPrimitiveValueToString } from './types';
import { convertPrimitiveValueToString, ODataVersion } from './types';

export enum ExprOperator {
eq = 'eq',
ne = 'ne',
gt = 'gt',
lt = 'lt',
ge = 'ge',
le = 'le',
le = 'le'
}

type FieldExpr = {
op: ExprOperator;
value: string;
value: any;
};

type FieldExprMappings = {
Expand Down Expand Up @@ -52,33 +52,15 @@ class ODataFieldExpr {
private _addExpr(op: ExprOperator, value: any) {
if (value === null) {
this._getFieldExprs().push({ op, value: 'null' });
return;
}

switch (typeof value) {
case 'number':
case 'boolean':
this._getFieldExprs().push({ op, value: `${value}` });
break;
case 'string':
if (value.startsWith("'") || value.startsWith('datetime')) {
this._getFieldExprs().push({ op, value });
} else {
this._getFieldExprs().push({ op, value: `'${value}'` });
}
break;
case 'object':
if (value instanceof Edm.PrimitiveTypeValue) {
this._getFieldExprs().push({
op,
value: convertPrimitiveValueToString(value)
});
} else {
throw new Error(
`Not support object ${
value?.constructor?.name || typeof value
} in odata filter eq/ne/gt/ge/ne/nt ...`
);
}
this._getFieldExprs().push({ op, value });
break;
case 'undefined':
throw new Error(
Expand Down Expand Up @@ -228,28 +210,72 @@ export class ODataFilter<T = any> {
return new ODataFieldExpr(this, name as string, this.getExprMapping());
}

toString(): string {
return this.build();
public toString(version: ODataVersion = 'v4'): string {
return this.build(version);
}

private _buildExprLit(value: any, version: ODataVersion = 'v4') {
if (value === null) {
return 'null';
}

switch (typeof value) {
case 'number':
case 'boolean':
return `${value}`;
case 'string':
if (value.startsWith("'") || value.startsWith('datetime')) {
return value;
}
return `'${value}'`;
case 'object':
if (value instanceof Edm.PrimitiveTypeValue) {
return convertPrimitiveValueToString(value, version);
}
throw new Error(
`Not support object ${
value?.constructor?.name || typeof value
} in odata filter eq/ne/gt/ge/ne/nt ...`
);

case 'undefined':
throw new Error(
`You must set value in odata filter eq/ne/gt/ge/ne/nt ...`
);
default:
throw new Error(
`Not support typeof ${typeof value}: ${value} in odata filter eq/ne/gt/ge/ne/nt ...`
);
}
}

protected _buildFieldExprString(field: string): string {
protected _buildFieldExprString(
field: string,
version: ODataVersion = 'v4'
): string {
const exprs = this.getExprMapping()[field];
if (exprs.length > 0) {
if (exprs.filter((expr) => expr.op == ExprOperator.eq).length == 0) {
return `(${join(
exprs.map(({ op, value }) => `${field} ${op} ${value}`),
exprs.map(
({ op, value }) =>
`${field} ${op} ${this._buildExprLit(value, version)}`
),
' and '
)})`;
}
return `(${join(
exprs.map(({ op, value }) => `${field} ${op} ${value}`),
exprs.map(
({ op, value }) =>
`${field} ${op} ${this._buildExprLit(value, version)}`
),
' or '
)})`;
}
return '';
}

build(): string {
public build(version: ODataVersion = 'v4'): string {
let _rt = '';
_rt = join(
// join all fields exprs string
Expand All @@ -261,10 +287,10 @@ export class ODataFilter<T = any> {
// only have one expr
case 1:
const { op, value } = exprs[0];
return `${fieldName} ${op} ${value}`;
return `${fieldName} ${op} ${this._buildExprLit(value, version)}`;
default:
// multi exprs
return this._buildFieldExprString(fieldName);
return this._buildFieldExprString(fieldName, version);
}
}),
' and '
Expand Down
18 changes: 15 additions & 3 deletions src/builder/param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import isArray from '@newdash/newdash/isArray';
import join from '@newdash/newdash/join';
import uniq from '@newdash/newdash/uniq';
import { ODataFilter } from './filter';
import { ODataVersion } from './types';

class SearchParams {
_store = new Map();
Expand Down Expand Up @@ -197,7 +198,7 @@ export class ODataQueryParam {
return this;
}

toString(): string {
toString(version: ODataVersion = 'v4'): string {
const rt = new SearchParams();
if (this.$format) {
rt.append('$format', this.$format);
Expand All @@ -223,8 +224,19 @@ export class ODataQueryParam {
if (this.$expand && this.$expand.length > 0) {
rt.append('$expand', this.$expand.join(','));
}
if (this.$count) {
rt.append('$count', 'true');
switch (version) {
case 'v2':
if (this.$count) {
rt.append('$inlinecount', 'allpages');
}
break;
case 'v4':
if (this.$count) {
rt.append('$count', 'true');
}
break;
default:
break;
}
return rt.toString();
}
Expand Down
19 changes: 18 additions & 1 deletion src/builder/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Edm } from '@odata/metadata';

export type ODataVersion = 'v2' | 'v4';

/**
*
* @param value primitive literal value
* @returns the string representation
*/
export function convertPrimitiveValueToString(value: Edm.PrimitiveTypeValue) {
export function convertPrimitiveValueToString(
value: Edm.PrimitiveTypeValue,
version: ODataVersion = 'v4'
) {
if (value?.getValue?.() === null) {
return 'null';
}
Expand Down Expand Up @@ -35,11 +40,23 @@ export function convertPrimitiveValueToString(value: Edm.PrimitiveTypeValue) {
case Edm.Duration:
// TODO integrate with some other duration lib
return value.getValue();
case Edm.DateTime:
let vd = value.getValue();
if (typeof vd === 'string') {
vd = new Date(vd);
}
if (version === 'v2') {
return `datetime'${vd.toISOString()}'`;
}
throw new Error("OData V4 is not support 'Edm.DateTime' values");
case Edm.DateTimeOffset:
let v1 = value.getValue();
if (typeof v1 === 'string') {
v1 = new Date(v1);
}
if (version === 'v2') {
return `datetimeoffset'${v1.toISOString()}'`;
}
return v1.toISOString();
case Edm.Date:
const v2 = value.getValue();
Expand Down
18 changes: 9 additions & 9 deletions test/builder/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('OData Query Builder - Filter Test Suite', () => {
it('should support filter by value/name', () => {

expect(ODataFilter.New().field('A').eq('a').toString()).toBe("A eq 'a'");
expect(ODataFilter.New().field('A').eq(literalValues.String("a")).toString()).toBe("A eq 'a'");
expect(ODataFilter.New().field('A').eq(literalValues.String('a')).toString()).toBe("A eq 'a'");
expect(ODataFilter.New().field('A').eq(1).toString()).toBe('A eq 1');

});
Expand All @@ -30,21 +30,21 @@ describe('OData Query Builder - Filter Test Suite', () => {
it('should support filter guid', () => {
expect(
filter()
.field("A")
.eq(literalValues.Guid("253f842d-d739-41b8-ac8c-139ac7a9dd14"))
.field('A')
.eq(literalValues.Guid('253f842d-d739-41b8-ac8c-139ac7a9dd14'))
.build()
).toBe("A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14")
).toBe('A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14');

expect(
filter({ A: literalValues.Guid("253f842d-d739-41b8-ac8c-139ac7a9dd14") })
filter({ A: literalValues.Guid('253f842d-d739-41b8-ac8c-139ac7a9dd14') })
.build()
).toBe("A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14")
).toBe('A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14');
});

it('should support filter with type', () => {
expect(filter({ A: 1 }).build()).toBe("A eq 1")
expect(filter({ A: literalValues.String('1') }).build()).toBe("A eq '1'")
expect(filter({ A: literalValues.Guid("253f842d-d739-41b8-ac8c-139ac7a9dd14") }).build()).toBe("A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14")
expect(filter({ A: 1 }).build()).toBe('A eq 1');
expect(filter({ A: literalValues.String('1') }).build()).toBe("A eq '1'");
expect(filter({ A: literalValues.Guid('253f842d-d739-41b8-ac8c-139ac7a9dd14') }).build()).toBe('A eq 253f842d-d739-41b8-ac8c-139ac7a9dd14');
});

});
Loading

0 comments on commit 91c694d

Please sign in to comment.