-
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
8 changed files
with
392 additions
and
16 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// 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, | ||
ReferenceObject, | ||
isReferenceObject, | ||
} from '@loopback/openapi-v3-types'; | ||
|
||
import * as HttpErrors from 'http-errors'; | ||
|
||
/** | ||
* Coerce the http raw data to a JavaScript type data of a parameter | ||
* according to its OpenAPI schema specification. | ||
* | ||
* @param data The raw data get from http request | ||
* @param schema The parameter's schema defined in OpenAPI specification | ||
*/ | ||
export function coerceParameter( | ||
data: string, | ||
schema?: SchemaObject | ReferenceObject, | ||
) { | ||
// ignore reference schema | ||
if (!schema || isReferenceObject(schema)) return data; | ||
|
||
let coercedResult; | ||
coercedResult = data; | ||
|
||
const OAIType = getOAIPrimitiveType(schema.type, schema.format); | ||
|
||
switch (OAIType) { | ||
case 'byte': | ||
coercedResult = Buffer.from(data, 'base64'); | ||
break; | ||
case 'date': | ||
coercedResult = new Date(data); | ||
break; | ||
case 'float': | ||
case 'double': | ||
coercedResult = parseFloat(data); | ||
break; | ||
case 'number': | ||
case 'long': | ||
coercedResult = Number(data); | ||
break; | ||
case 'integer': | ||
coercedResult = parseInt(data); | ||
break; | ||
case 'boolean': | ||
coercedResult = isTrue(data) ? true : false; | ||
case 'string': | ||
case 'password': | ||
// serizlize will be supported in next PR | ||
case 'serialize': | ||
break; | ||
case 'unknownType': | ||
default: | ||
throw new HttpErrors.NotImplemented( | ||
`Type ${schema.type} with format ${ | ||
schema.format | ||
} is not a valid OpenAPI schema`, | ||
); | ||
} | ||
return coercedResult; | ||
} | ||
|
||
/** | ||
* A set of truthy values. A data in this set will be coerced to `true`, | ||
* otherwise coerced to `false`. | ||
* | ||
* @param data The raw data get from http request | ||
* @returns The corresponding coerced boolean type | ||
*/ | ||
|
||
function isTrue(data: string): boolean { | ||
const isTrueSet = ['true', '1', true, 1]; | ||
return isTrueSet.includes(data); | ||
} | ||
|
||
/** | ||
* Return the corresponding OpenAPI data type given an OpenAPI schema | ||
* | ||
* @param type The type in an OpenAPI schema specification | ||
* @param format The format in an OpenAPI schema specification | ||
*/ | ||
|
||
function getOAIPrimitiveType(type?: string, format?: string) { | ||
let OAIType: string = 'unknownType'; | ||
// serizlize will be supported in next PR | ||
if (type === 'object' || type === 'array') OAIType = 'serialize'; | ||
if (type === 'string') { | ||
switch (format) { | ||
case 'byte': | ||
OAIType = 'byte'; | ||
break; | ||
case 'binary': | ||
OAIType = 'binary'; | ||
break; | ||
case 'date': | ||
OAIType = 'date'; | ||
break; | ||
case 'date-time': | ||
OAIType = 'date-time'; | ||
break; | ||
case 'password': | ||
OAIType = 'password'; | ||
break; | ||
default: | ||
OAIType = 'string'; | ||
break; | ||
} | ||
} | ||
if (type === 'boolean') OAIType = 'boolean'; | ||
if (type === 'number') | ||
OAIType = | ||
format === 'float' ? 'float' : format === 'double' ? 'double' : 'number'; | ||
if (type === 'integer') OAIType = format === 'int64' ? 'long' : 'integer'; | ||
return OAIType; | ||
} |
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, | ||
SinonSpy, | ||
} from '@loopback/testlab'; | ||
import {RestApplication, RestServer, get, param} from '../../..'; | ||
|
||
describe('Coercion', () => { | ||
let app: RestApplication; | ||
let server: RestServer; | ||
let client: supertest.SuperTest<supertest.Test>; | ||
|
||
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/100').expect(200); | ||
assertCoercion(spy); | ||
}); | ||
|
||
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: 100}); | ||
assertCoercion(spy); | ||
}); | ||
|
||
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: 100}) | ||
.expect(200); | ||
assertCoercion(spy); | ||
}); | ||
|
||
function assertCoercion(spy: SinonSpy) { | ||
sinon.assert.calledWithExactly(spy, 100); | ||
sinon.assert.neverCalledWith(spy, '100'); | ||
} | ||
|
||
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); | ||
} | ||
}); |
18 changes: 18 additions & 0 deletions
18
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,18 @@ | ||
// 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 {testCoercion} from './utils'; | ||
|
||
describe('coerce param from string to boolean', () => { | ||
it("value 'false' is coerced to false", async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<boolean>({type: 'boolean'}, 'false', false, caller); | ||
}); | ||
|
||
it("value 'true' is coerced to true", async () => { | ||
const caller = new Error().stack!.split(/\n/)[1]; | ||
await testCoercion<boolean>({type: 'boolean'}, 'true', true, caller); | ||
}); | ||
}); |
20 changes: 20 additions & 0 deletions
20
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,20 @@ | ||
// 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 {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, | ||
); | ||
}); | ||
}); |
18 changes: 18 additions & 0 deletions
18
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,18 @@ | ||
// 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 {testCoercion} from './utils'; | ||
|
||
describe('coerce param from string to date', () => { | ||
it('simple 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, | ||
); | ||
}); | ||
}); |
48 changes: 48 additions & 0 deletions
48
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,48 @@ | ||
// 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 {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, | ||
); | ||
}); | ||
}); |
Oops, something went wrong.