@@ -7,7 +7,17 @@ import {ParameterObject, isReferenceObject} from '@loopback/openapi-v3-types';
7
7
import { Validator } from './validator' ;
8
8
import * as debugModule from 'debug' ;
9
9
import { 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' ) ;
11
21
const debug = debugModule ( 'loopback:rest:coercion' ) ;
12
22
13
23
/**
@@ -17,7 +27,10 @@ const debug = debugModule('loopback:rest:coercion');
17
27
* @param data The raw data get from http request
18
28
* @param schema The parameter's schema defined in OpenAPI specification
19
29
*/
20
- export function coerceParameter ( data : string , spec : ParameterObject ) {
30
+ export function coerceParameter (
31
+ data : string | undefined | object ,
32
+ spec : ParameterObject ,
33
+ ) {
21
34
const schema = spec . schema ;
22
35
if ( ! schema || isReferenceObject ( schema ) ) {
23
36
debug (
@@ -31,26 +44,25 @@ export function coerceParameter(data: string, spec: ParameterObject) {
31
44
const validator = new Validator ( { parameterSpec : spec } ) ;
32
45
33
46
validator . validateParamBeforeCoercion ( data ) ;
47
+ if ( data === undefined ) return data ;
34
48
35
49
switch ( OAIType ) {
36
50
case 'byte' :
37
- return Buffer . from ( data , 'base64' ) ;
51
+ return coerceBuffer ( data , spec ) ;
38
52
case 'date' :
39
- return new Date ( data ) ;
53
+ return coerceDatetime ( data , spec , { dateOnly : true } ) ;
54
+ case 'date-time' :
55
+ return coerceDatetime ( data , spec ) ;
40
56
case 'float' :
41
57
case 'double' :
42
- return parseFloat ( data ) ;
43
58
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 ) ;
48
60
case 'long' :
49
- return Number ( data ) ;
61
+ return coerceInteger ( data , spec , { isLong : true } ) ;
50
62
case 'integer' :
51
- return parseInt ( data ) ;
63
+ return coerceInteger ( data , spec ) ;
52
64
case 'boolean' :
53
- return isTrue ( data ) ? true : isFalse ( data ) ? false : undefined ;
65
+ return coerceBoolean ( data , spec ) ;
54
66
case 'string' :
55
67
case 'password' :
56
68
// serialize will be supported in next PR
@@ -60,56 +72,71 @@ export function coerceParameter(data: string, spec: ParameterObject) {
60
72
}
61
73
}
62
74
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' ) ;
71
79
}
72
80
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 ;
80
100
}
81
101
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 ) ;
106
130
}
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 ) ;
115
142
}
0 commit comments