-
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
6 changed files
with
221 additions
and
83 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
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,92 @@ | ||
// Copyright IBM Corp. 2017. All Rights Reserved. | ||
// Node module: @loopback/core | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {ServerRequest as Request} from 'http'; | ||
import { | ||
PathParameterValues, | ||
OperationArgs, | ||
ParsedRequest, | ||
HttpError, | ||
createHttpError, | ||
} from './router/SwaggerRouter'; | ||
import {OperationObject, ParameterObject} from '@loopback/openapi-spec'; | ||
import {promisify} from './promisify'; | ||
|
||
type jsonBodyFn = (req: Request, cb: (err?: Error, body?: {}) => void) => void; | ||
const jsonBody: jsonBodyFn = require('body/json'); | ||
|
||
// tslint:disable:no-any | ||
type MaybeBody = any | undefined; | ||
// tslint:enable:no-any | ||
|
||
const parseJsonBody: (req: Request) => Promise<MaybeBody> = promisify(jsonBody); | ||
|
||
export async function parseOperationArgs(request: ParsedRequest, operationSpec: OperationObject, pathParams: PathParameterValues): Promise<OperationArgs> { | ||
const args: OperationArgs = []; | ||
const body = await loadRequestBodyIfNeeded(operationSpec, request); | ||
return buildOperationArguments(operationSpec, request, pathParams, body); | ||
} | ||
|
||
function loadRequestBodyIfNeeded(operationSpec: OperationObject, request: Request): Promise<MaybeBody> { | ||
if (!hasArgumentsFromBody(operationSpec)) | ||
return Promise.resolve(); | ||
|
||
const contentType = request.headers['content-type']; | ||
if (contentType && !/json/.test(contentType)) { | ||
const err = createHttpError(415, `Content-type ${contentType} is not supported.`); | ||
return Promise.reject(err); | ||
} | ||
|
||
return parseJsonBody(request).catch((err: HttpError) => { | ||
err.statusCode = 400; | ||
return Promise.reject(err); | ||
}); | ||
} | ||
|
||
function hasArgumentsFromBody(operationSpec: OperationObject): boolean { | ||
if (!operationSpec.parameters || !operationSpec.parameters.length) | ||
return false; | ||
|
||
for (const paramSpec of operationSpec.parameters) { | ||
if ('$ref' in paramSpec) continue; | ||
const source = (paramSpec as ParameterObject).in; | ||
if (source === 'formData' || source === 'body') | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
function buildOperationArguments(operationSpec: OperationObject, request: ParsedRequest, | ||
pathParams: PathParameterValues, body?: MaybeBody): OperationArgs { | ||
const args: OperationArgs = []; | ||
|
||
for (const paramSpec of operationSpec.parameters || []) { | ||
if ('$ref' in paramSpec) { | ||
// TODO(bajtos) implement $ref parameters | ||
throw new Error('$ref parameters are not supported yet.'); | ||
} | ||
const spec = paramSpec as ParameterObject; | ||
switch (spec.in) { | ||
case 'query': | ||
args.push(request.query[spec.name]); | ||
break; | ||
case 'path': | ||
args.push(pathParams[spec.name]); | ||
break; | ||
case 'header': | ||
args.push(request.headers[spec.name.toLowerCase()]); | ||
break; | ||
case 'formData': | ||
args.push(body ? body[spec.name] : undefined); | ||
break; | ||
case 'body': | ||
args.push(body); | ||
break; | ||
default: | ||
throw createHttpError(501, 'Parameters with "in: ' + spec.in + '" are not supported yet.'); | ||
} | ||
} | ||
return args; | ||
} |
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,37 @@ | ||
// Copyright IBM Corp. 2017. All Rights Reserved. | ||
// Node module: @loopback/core | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
// TODO(bajtos) Move this file to a standalone module, or find an existing | ||
// npm module that we could use instead. Just make sure the existing | ||
// module is using native utils.promisify() when available. | ||
|
||
// tslint:disable:no-any | ||
|
||
import * as util from 'util'; | ||
|
||
const nativePromisify = (util as any).promisify; | ||
|
||
export function promisify<T>(func: (callback: (err: any, result: T) => void) => void): () => Promise<T>; | ||
export function promisify<T, A1>(func: (arg1: A1, callback: (err: any, result: T) => void) => void): (arg1: A1) => Promise<T>; | ||
export function promisify<T, A1, A2>(func: (arg1: A1, arg2: A2, callback: (err: any, result: T) => void) => void): (arg1: A1, arg2: A2) => Promise<T>; | ||
|
||
export function promisify<T>(func: (...args: any[]) => void): (...args: any[]) => Promise<T> { | ||
if (nativePromisify) | ||
return nativePromisify(func); | ||
|
||
// The simplest implementation of Promisify | ||
return (...args) => { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
func(...args, (err?: any, result?: any) => { | ||
if (err) reject(err); | ||
else resolve(result); | ||
}); | ||
} catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
}; | ||
} |
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,62 @@ | ||
// Copyright IBM Corp. 2013,2017. All Rights Reserved. | ||
// Node module: @loopback/core | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import { | ||
parseOperationArgs, | ||
ServerRequest, | ||
ParsedRequest, | ||
parseRequestUrl, | ||
} from '../..'; | ||
import {expect} from '@loopback/testlab'; | ||
import {OperationObject, ParameterObject} from '@loopback/openapi-spec'; | ||
|
||
import {RequestOptions as ShotRequestOptions} from 'shot'; | ||
type ShotRequestCtor = new(options: ShotRequestOptions) => ServerRequest; | ||
// tslint:disable-next-line:variable-name | ||
const ShotRequest: ShotRequestCtor = require('shot/lib/request'); | ||
|
||
describe('operationArgsParser', () => { | ||
it('parses path parameters', async () => { | ||
const spec = givenOperationWithParameters([{ | ||
name: 'id', | ||
type: 'number', | ||
in: 'path', | ||
}]); | ||
const req = givenRequest(); | ||
|
||
const args = await parseOperationArgs(req, spec, {id: 1}); | ||
|
||
expect(args).to.eql([1]); | ||
}); | ||
|
||
it('parsed body parameter', async () => { | ||
const spec = givenOperationWithParameters([{ | ||
name: 'data', | ||
type: 'object', | ||
in: 'body', | ||
}]); | ||
|
||
const req = givenRequest({ | ||
url: '/', | ||
payload: {key: 'value'}, | ||
}); | ||
|
||
const args = await parseOperationArgs(req, spec, {}); | ||
|
||
expect(args).to.eql([{key: 'value'}]); | ||
}); | ||
|
||
function givenOperationWithParameters(params?: ParameterObject[]) { | ||
return <OperationObject> { | ||
'x-operation-name': 'testOp', | ||
parameters: params, | ||
responses: {}, | ||
}; | ||
} | ||
|
||
function givenRequest(options?: ShotRequestOptions): ParsedRequest { | ||
return parseRequestUrl(new ShotRequest(options || {url: '/'})); | ||
} | ||
}); |