@@ -7,7 +7,17 @@ import {ParameterObject, isReferenceObject} from '@loopback/openapi-v3-types';
77import { Validator } from './validator' ;
88import * as debugModule from 'debug' ;
99import { RestHttpErrors } from '../' ;
10-
10+ import {
11+ getOAIPrimitiveType ,
12+ isEmpty ,
13+ isFalse ,
14+ isTrue ,
15+ isValidDateTime ,
16+ matchDateFormat ,
17+ DateCoercionOptions ,
18+ IntegerCoercionOptions ,
19+ } from './utils' ;
20+ const isRFC3339 = require ( 'validator/lib/isRFC3339' ) ;
1121const debug = debugModule ( 'loopback:rest:coercion' ) ;
1222
1323/**
@@ -17,7 +27,10 @@ const debug = debugModule('loopback:rest:coercion');
1727 * @param data The raw data get from http request
1828 * @param schema The parameter's schema defined in OpenAPI specification
1929 */
20- export function coerceParameter ( data : string , spec : ParameterObject ) {
30+ export function coerceParameter (
31+ data : string | undefined | object ,
32+ spec : ParameterObject ,
33+ ) {
2134 const schema = spec . schema ;
2235 if ( ! schema || isReferenceObject ( schema ) ) {
2336 debug (
@@ -31,26 +44,25 @@ export function coerceParameter(data: string, spec: ParameterObject) {
3144 const validator = new Validator ( { parameterSpec : spec } ) ;
3245
3346 validator . validateParamBeforeCoercion ( data ) ;
47+ if ( data === undefined ) return data ;
3448
3549 switch ( OAIType ) {
3650 case 'byte' :
37- return Buffer . from ( data , 'base64' ) ;
51+ return coerceBuffer ( data , spec ) ;
3852 case 'date' :
39- return new Date ( data ) ;
53+ return coerceDatetime ( data , spec , { dateOnly : true } ) ;
54+ case 'date-time' :
55+ return coerceDatetime ( data , spec ) ;
4056 case 'float' :
4157 case 'double' :
42- return parseFloat ( data ) ;
4358 case 'number' :
44- const coercedData = data ? Number ( data ) : undefined ;
45- if ( coercedData === undefined ) return ;
46- if ( isNaN ( coercedData ) ) throw RestHttpErrors . invalidData ( data , spec . name ) ;
47- return coercedData ;
59+ return coerceNumber ( data , spec ) ;
4860 case 'long' :
49- return Number ( data ) ;
61+ return coerceInteger ( data , spec , { isLong : true } ) ;
5062 case 'integer' :
51- return parseInt ( data ) ;
63+ return coerceInteger ( data , spec ) ;
5264 case 'boolean' :
53- return isTrue ( data ) ? true : isFalse ( data ) ? false : undefined ;
65+ return coerceBoolean ( data , spec ) ;
5466 case 'string' :
5567 case 'password' :
5668 // serialize will be supported in next PR
@@ -60,56 +72,71 @@ export function coerceParameter(data: string, spec: ParameterObject) {
6072 }
6173}
6274
63- /**
64- * A set of truthy values. A data in this set will be coerced to `true`.
65- *
66- * @param data The raw data get from http request
67- * @returns The corresponding coerced boolean type
68- */
69- function isTrue ( data : string ) : boolean {
70- return [ 'true' , '1' ] . includes ( data ) ;
75+ function coerceBuffer ( data : string | object , spec : ParameterObject ) {
76+ if ( typeof data === 'object' )
77+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
78+ return Buffer . from ( data , 'base64' ) ;
7179}
7280
73- /**
74- * A set of falsy values. A data in this set will be coerced to `false`.
75- * @param data The raw data get from http request
76- * @returns The corresponding coerced boolean type
77- */
78- function isFalse ( data : string ) : boolean {
79- return [ 'false' , '0' ] . includes ( data ) ;
81+ function coerceDatetime (
82+ data : string | object ,
83+ spec : ParameterObject ,
84+ options ?: DateCoercionOptions ,
85+ ) {
86+ if ( typeof data === 'object' || isEmpty ( data ) )
87+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
88+
89+ if ( options && options . dateOnly ) {
90+ if ( ! matchDateFormat ( data ) )
91+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
92+ } else {
93+ if ( ! isRFC3339 ( data ) ) throw RestHttpErrors . invalidData ( data , spec . name ) ;
94+ }
95+
96+ const coercedDate = new Date ( data ) ;
97+ if ( ! isValidDateTime ( coercedDate ) )
98+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
99+ return coercedDate ;
80100}
81101
82- /**
83- * Return the corresponding OpenAPI data type given an OpenAPI schema
84- *
85- * @param type The type in an OpenAPI schema specification
86- * @param format The format in an OpenAPI schema specification
87- */
88- function getOAIPrimitiveType ( type ?: string , format ?: string ) {
89- // serizlize will be supported in next PR
90- if ( type === 'object' || type === 'array' ) return 'serialize' ;
91- if ( type === 'string' ) {
92- switch ( format ) {
93- case 'byte' :
94- return 'byte' ;
95- case 'binary' :
96- return 'binary' ;
97- case 'date' :
98- return 'date' ;
99- case 'date-time' :
100- return 'date-time' ;
101- case 'password' :
102- return 'password' ;
103- default :
104- return 'string' ;
105- }
102+ function coerceNumber ( data : string | object , spec : ParameterObject ) {
103+ if ( typeof data === 'object' || isEmpty ( data ) )
104+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
105+
106+ const coercedNum = Number ( data ) ;
107+ if ( isNaN ( coercedNum ) ) throw RestHttpErrors . invalidData ( data , spec . name ) ;
108+
109+ debug ( 'data of type number is coerced to %s' , coercedNum ) ;
110+ return coercedNum ;
111+ }
112+
113+ function coerceInteger (
114+ data : string | object ,
115+ spec : ParameterObject ,
116+ options ?: IntegerCoercionOptions ,
117+ ) {
118+ if ( typeof data === 'object' || isEmpty ( data ) )
119+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
120+
121+ const coercedInt = Number ( data ) ;
122+ if ( isNaN ( coercedInt ! ) ) throw RestHttpErrors . invalidData ( data , spec . name ) ;
123+
124+ if ( options && options . isLong ) {
125+ if ( ! Number . isInteger ( coercedInt ) )
126+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
127+ } else {
128+ if ( ! Number . isSafeInteger ( coercedInt ) )
129+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
106130 }
107- if ( type === 'boolean' ) return 'boolean' ;
108- if ( type === 'number' )
109- return format === 'float'
110- ? 'float'
111- : format === 'double'
112- ? 'double'
113- : 'number' ;
114- if ( type === 'integer' ) return format === 'int64' ? 'long' : 'integer' ;
131+
132+ debug ( 'data of type integer is coerced to %s' , coercedInt ) ;
133+ return coercedInt ;
134+ }
135+
136+ function coerceBoolean ( data : string | object , spec : ParameterObject ) {
137+ if ( typeof data === 'object' || isEmpty ( data ) )
138+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
139+ if ( isTrue ( data ) ) return true ;
140+ if ( isFalse ( data ) ) return false ;
141+ throw RestHttpErrors . invalidData ( data , spec . name ) ;
115142}
0 commit comments