-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
319 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { | ||
SchemaObject, | ||
ReferenceObject, | ||
isReferenceObject, | ||
} from '@loopback/openapi-v3-types'; | ||
|
||
import * as HttpErrors from 'http-errors'; | ||
type HttpError = HttpErrors.HttpError; | ||
|
||
export function paramCoerce(data: string, schema?: SchemaObject | ReferenceObject): any { | ||
// ignore reference schema | ||
if (!schema || isReferenceObject(schema)) return data; | ||
|
||
let coercedResult; | ||
coercedResult = data; | ||
const type = schema.type; | ||
const format = schema.format; | ||
|
||
switch(type) { | ||
case 'string': | ||
if (format === 'byte') { | ||
coercedResult = Buffer.from(data, 'base64'); | ||
} else if (format === 'date') { | ||
coercedResult = new Date(data); | ||
} else { | ||
coercedResult = data | ||
} | ||
break; | ||
case 'number': | ||
if (format === 'float' || 'double') { | ||
coercedResult = parseFloat(data); | ||
} else { | ||
throw new HttpErrors.NotImplemented('Type number with format ' + format + ' is not valid') | ||
} | ||
break; | ||
case 'integer': | ||
if (format === 'int32') { | ||
coercedResult = parseInt(data) | ||
} else if(format === 'int64') { | ||
coercedResult = Number(data) | ||
} else { | ||
throw new HttpErrors.NotImplemented('Type integer with format ' + format + ' is not valid'); | ||
} | ||
break; | ||
case 'boolean': | ||
coercedResult = isTrue(data)? true: false; | ||
default: break; | ||
} | ||
return coercedResult; | ||
} | ||
|
||
function isTrue(data: string): boolean { | ||
const isTrueSet = ['true', '1', true, 1]; | ||
return isTrueSet.includes(data); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
packages/rest/test/acceptance/coercion/coercion.acceptance.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import {supertest, createClientForHandler, sinon} from '@loopback/testlab'; | ||
import { | ||
RestApplication, | ||
RestServer, | ||
get, | ||
param, | ||
post, | ||
requestBody, | ||
RestBindings, | ||
InvokeMethod, | ||
} from '../../..'; | ||
|
||
describe('Coercion', () => { | ||
let app: RestApplication; | ||
let server: RestServer; | ||
let client: supertest.SuperTest<supertest.Test>; | ||
let invokeMethod: InvokeMethod; | ||
|
||
before(givenAnApplication); | ||
before(givenAServer); | ||
before(givenAClient); | ||
|
||
after(async () => { | ||
await app.stop(); | ||
}); | ||
|
||
class MyController { | ||
@get('/create-number-from-path/{num}') | ||
createNumberFromPath(@param.path.number('num') num: number) { | ||
return num; | ||
} | ||
|
||
@get('/create-number-from-query') | ||
createNumberFromQuery(@param.query.number('num') num: number) { | ||
return num; | ||
} | ||
|
||
@get('/create-number-from-header') | ||
createNumberFromHeader(@param.header.number('num') num: number) { | ||
return num; | ||
} | ||
} | ||
|
||
it('coerces parameter in path from string to number', async () => { | ||
const spy = sinon.spy(MyController.prototype, 'createNumberFromPath'); | ||
await client.get('/create-number-from-path/13').expect(200); | ||
sinon.assert.calledWithExactly(spy, 13); | ||
sinon.assert.neverCalledWith(spy, '13'); | ||
}); | ||
|
||
it('coerces parameter in header from string to number', async () => { | ||
const spy = sinon.spy(MyController.prototype, 'createNumberFromHeader'); | ||
await client.get('/create-number-from-header').set({num: 13}); | ||
sinon.assert.calledWithExactly(spy, 13); | ||
sinon.assert.neverCalledWith(spy, '13'); | ||
}); | ||
|
||
it('coerces parameter in query from string to number', async () => { | ||
const spy = sinon.spy(MyController.prototype, 'createNumberFromQuery'); | ||
await client.get('/create-number-from-query').query({num: 13}).expect(200); | ||
sinon.assert.calledWithExactly(spy, 13); | ||
sinon.assert.neverCalledWith(spy, '13'); | ||
}); | ||
|
||
async function givenAnApplication() { | ||
app = new RestApplication(); | ||
app.controller(MyController); | ||
await app.start(); | ||
} | ||
|
||
async function givenAServer() { | ||
server = await app.getServer(RestServer); | ||
} | ||
|
||
async function givenAClient() { | ||
client = await createClientForHandler(server.requestHandler); | ||
} | ||
}); |
28 changes: 28 additions & 0 deletions
28
packages/rest/test/unit/coercion/paramStringToBoolean.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright IBM Corp. 2018. All Rights Reserved. | ||
// Node module: @loopback/rest | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {SchemaObject} from '@loopback/openapi-v3-types'; | ||
|
||
import { | ||
expect | ||
} from '@loopback/testlab'; | ||
|
||
import { | ||
parseOperationArgs, | ||
} from '../../..'; | ||
|
||
import {givenOperationWithParameters, givenRequest, givenResolvedRoute, testCoercion} from './utils' | ||
|
||
describe('coerce param from string to boolean', () => { | ||
it('false - \'false\'', async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<boolean>({type: 'boolean'}, 'false', false, caller); | ||
}) | ||
|
||
it('true - \'true\'', async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<boolean>({type: 'boolean'}, 'true', true, caller); | ||
}) | ||
}); |
25 changes: 25 additions & 0 deletions
25
packages/rest/test/unit/coercion/paramStringToBuffer.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright IBM Corp. 2018. All Rights Reserved. | ||
// Node module: @loopback/rest | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {SchemaObject} from '@loopback/openapi-v3-types'; | ||
|
||
import { | ||
expect | ||
} from '@loopback/testlab'; | ||
|
||
import { | ||
parseOperationArgs, | ||
} from '../../..'; | ||
|
||
import {givenOperationWithParameters, givenRequest, givenResolvedRoute, testCoercion} from './utils' | ||
|
||
describe('coerce param from string to buffer', () => { | ||
it('base64', async () => { | ||
const base64 = Buffer.from("Hello World").toString('base64'); | ||
const buffer = Buffer.from(base64, 'base64'); | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<Buffer>({type: 'string', format: 'byte'}, base64, buffer, caller); | ||
}) | ||
}); |
23 changes: 23 additions & 0 deletions
23
packages/rest/test/unit/coercion/paramStringToDate.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright IBM Corp. 2018. All Rights Reserved. | ||
// Node module: @loopback/rest | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {SchemaObject} from '@loopback/openapi-v3-types'; | ||
|
||
import { | ||
expect | ||
} from '@loopback/testlab'; | ||
|
||
import { | ||
parseOperationArgs, | ||
} from '../../..'; | ||
|
||
import {givenOperationWithParameters, givenRequest, givenResolvedRoute, testCoercion} from './utils' | ||
|
||
describe('coerce param from string to date', () => { | ||
it('string to date', async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<Date>({ type: 'string', format: 'date'}, '2015-03-01', new Date('2015-03-01'), caller); | ||
}) | ||
}); |
38 changes: 38 additions & 0 deletions
38
packages/rest/test/unit/coercion/paramStringToNumber.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright IBM Corp. 2018. All Rights Reserved. | ||
// Node module: @loopback/rest | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {SchemaObject} from '@loopback/openapi-v3-types'; | ||
|
||
import { | ||
expect | ||
} from '@loopback/testlab'; | ||
|
||
import { | ||
parseOperationArgs, | ||
} from '../../..'; | ||
|
||
import {givenOperationWithParameters, givenRequest, givenResolvedRoute, testCoercion} from './utils' | ||
|
||
describe('coerce param from string to number', () => { | ||
it('string to float', async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<number>({type: 'number', format: 'float'}, '3.333333', 3.333333, caller); | ||
}) | ||
|
||
it('string to double', async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<number>({type: 'number', format: 'double'}, '3.333333333', 3.333333333, caller); | ||
}) | ||
|
||
it('string to integer', async() => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<number>({type: 'integer', format: 'int32'}, '100', 100, caller); | ||
}) | ||
|
||
it('string to long', async()=> { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<number>({type: 'integer', format: 'int64'}, '9223372036854775807', 9223372036854775807, caller); | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
|
||
import { | ||
OperationObject, | ||
ParameterObject, | ||
RequestBodyObject, | ||
SchemaObject, | ||
} from '@loopback/openapi-v3-types'; | ||
|
||
import { | ||
ShotRequestOptions, | ||
expect, | ||
stubExpressContext, | ||
} from '@loopback/testlab'; | ||
|
||
import { | ||
PathParameterValues, | ||
Request, | ||
Route, | ||
createResolvedRoute, | ||
parseOperationArgs, | ||
ResolvedRoute, | ||
} from '../../..'; | ||
|
||
export function givenOperationWithParameters(params?: ParameterObject[]) { | ||
return <OperationObject>{ | ||
'x-operation-name': 'testOp', | ||
parameters: params, | ||
responses: {}, | ||
}; | ||
} | ||
|
||
export function givenRequest(options?: ShotRequestOptions): Request { | ||
return stubExpressContext(options).request; | ||
} | ||
|
||
export function givenResolvedRoute( | ||
spec: OperationObject, | ||
pathParams: PathParameterValues = {}, | ||
): ResolvedRoute { | ||
const route = new Route('get', '/', spec, () => {}); | ||
return createResolvedRoute(route, pathParams); | ||
} | ||
|
||
export async function testCoercion<T>( | ||
schemaSpec: SchemaObject, | ||
valueFromReq: string, | ||
expectedResult: T, | ||
caller: string | ||
) { | ||
try { | ||
const req = givenRequest(); | ||
const spec = givenOperationWithParameters([{ | ||
name: 'aparameter', | ||
in: 'path', | ||
schema: schemaSpec | ||
}]); | ||
const route = givenResolvedRoute(spec, {aparameter: valueFromReq}); | ||
const args = await parseOperationArgs(req, route); | ||
expect(args).to.eql([expectedResult]); | ||
} catch (err) { | ||
throw new Error(`${err} \n Failed ${caller}`); | ||
} | ||
} |