8
8
isReferenceObject ,
9
9
OperationObject ,
10
10
ParameterObject ,
11
+ ReferenceObject ,
11
12
SchemasObject ,
12
13
} from '@loopback/openapi-v3-types' ;
13
14
import * as debugModule from 'debug' ;
@@ -26,6 +27,7 @@ import {
26
27
RequestBodyParserOptions ,
27
28
} from './types' ;
28
29
import { validateRequestBody } from './validation/request-body.validator' ;
30
+ import { is } from 'type-is' ;
29
31
30
32
type HttpError = HttpErrors . HttpError ;
31
33
@@ -38,6 +40,8 @@ Object.freeze(QUERY_NOT_PARSED);
38
40
type RequestBody = {
39
41
value : any | undefined ;
40
42
coercionRequired ?: boolean ;
43
+ mediaType ?: string ;
44
+ schema ?: SchemasObject | ReferenceObject ;
41
45
} ;
42
46
43
47
const parseJsonBody : (
@@ -55,14 +59,7 @@ const parseFormBody: (
55
59
* @param req Http request
56
60
*/
57
61
function getContentType ( req : Request ) : string | undefined {
58
- const val = req . headers [ 'content-type' ] ;
59
- if ( typeof val === 'string' ) {
60
- return val ;
61
- } else if ( Array . isArray ( val ) ) {
62
- // Assume only one value is present
63
- return val [ 0 ] ;
64
- }
65
- return undefined ;
62
+ return req . get ( 'content-type' ) ;
66
63
}
67
64
68
65
/**
@@ -90,54 +87,92 @@ export async function parseOperationArgs(
90
87
) ;
91
88
}
92
89
93
- async function loadRequestBodyIfNeeded (
90
+ function normalizeParsingError ( err : HttpError ) {
91
+ debug ( 'Cannot parse request body %j' , err ) ;
92
+ if ( ! err . statusCode || err . statusCode >= 500 ) {
93
+ err . statusCode = 400 ;
94
+ }
95
+ return err ;
96
+ }
97
+
98
+ export async function loadRequestBodyIfNeeded (
94
99
operationSpec : OperationObject ,
95
100
request : Request ,
96
101
options : RequestBodyParserOptions = { } ,
97
102
) : Promise < RequestBody > {
98
- if ( ! operationSpec . requestBody ) return Promise . resolve ( { value : undefined } ) ;
103
+ const requestBody : RequestBody = {
104
+ value : undefined ,
105
+ } ;
106
+ if ( ! operationSpec . requestBody ) return Promise . resolve ( requestBody ) ;
99
107
100
108
debug ( 'Request body parser options: %j' , options ) ;
101
109
102
- const contentType = getContentType ( request ) ;
110
+ const contentType = getContentType ( request ) || 'application/json' ;
103
111
debug ( 'Loading request body with content type %j' , contentType ) ;
104
112
105
- if (
106
- contentType &&
107
- contentType . startsWith ( 'application/x-www-form-urlencoded' )
108
- ) {
109
- const body = await parseFormBody ( request , options ) . catch (
110
- ( err : HttpError ) => {
111
- debug ( 'Cannot parse request body %j' , err ) ;
112
- if ( ! err . statusCode || err . statusCode >= 500 ) {
113
- err . statusCode = 400 ;
114
- }
115
- throw err ;
116
- } ,
117
- ) ;
118
- // form parser returns an object with prototype
119
- return {
120
- value : Object . assign ( { } , body ) ,
121
- coercionRequired : true ,
113
+ // the type of `operationSpec.requestBody` could be `RequestBodyObject`
114
+ // or `ReferenceObject`, resolving a `$ref` value is not supported yet.
115
+ if ( isReferenceObject ( operationSpec . requestBody ) ) {
116
+ throw new Error ( '$ref requestBody is not supported yet.' ) ;
117
+ }
118
+
119
+ let content = operationSpec . requestBody . content || { } ;
120
+ if ( ! Object . keys ( content ) . length ) {
121
+ content = {
122
+ // default to allow json and urlencoded
123
+ 'application/json' : { schema : { type : 'object' } } ,
124
+ 'application/x-www-form-urlencoded' : { schema : { type : 'object' } } ,
122
125
} ;
123
126
}
124
127
125
- if ( contentType && ! / j s o n / . test ( contentType ) ) {
128
+ // Check of the request content type matches one of the expected media
129
+ // types in the request body spec
130
+ let matchedMediaType : string | false = false ;
131
+ for ( const type in content ) {
132
+ matchedMediaType = is ( contentType , type ) ;
133
+ if ( matchedMediaType ) {
134
+ requestBody . mediaType = type ;
135
+ requestBody . schema = content [ type ] . schema ;
136
+ break ;
137
+ }
138
+ }
139
+
140
+ if ( ! matchedMediaType ) {
141
+ // No matching media type found, fail fast
126
142
throw new HttpErrors . UnsupportedMediaType (
127
- `Content-type ${ contentType } is not supported .` ,
143
+ `Content-type ${ contentType } does not match [ ${ Object . keys ( content ) } ] .` ,
128
144
) ;
129
145
}
130
146
131
- const jsonBody = await parseJsonBody ( request , options ) . catch (
132
- ( err : HttpError ) => {
133
- debug ( 'Cannot parse request body %j' , err ) ;
134
- if ( ! err . statusCode || err . statusCode >= 500 ) {
135
- err . statusCode = 400 ;
136
- }
137
- throw err ;
138
- } ,
147
+ if ( is ( matchedMediaType , 'urlencoded' ) ) {
148
+ try {
149
+ const body = await parseFormBody ( request , options ) ;
150
+ return Object . assign ( requestBody , {
151
+ // form parser returns an object without prototype
152
+ // create a new copy to simplify shouldjs assertions
153
+ value : Object . assign ( { } , body ) ,
154
+ // urlencoded body only provide string values
155
+ // set the flag so that AJV can coerce them based on the schema
156
+ coercionRequired : true ,
157
+ } ) ;
158
+ } catch ( err ) {
159
+ throw normalizeParsingError ( err ) ;
160
+ }
161
+ }
162
+
163
+ if ( is ( matchedMediaType , 'json' ) ) {
164
+ try {
165
+ const jsonBody = await parseJsonBody ( request , options ) ;
166
+ requestBody . value = jsonBody ;
167
+ return requestBody ;
168
+ } catch ( err ) {
169
+ throw normalizeParsingError ( err ) ;
170
+ }
171
+ }
172
+
173
+ throw new HttpErrors . UnsupportedMediaType (
174
+ `Content-type ${ matchedMediaType } is not supported.` ,
139
175
) ;
140
- return { value : jsonBody } ;
141
176
}
142
177
143
178
function buildOperationArguments (
@@ -175,6 +210,7 @@ function buildOperationArguments(
175
210
debug ( 'Validating request body - value %j' , body ) ;
176
211
validateRequestBody ( body . value , operationSpec . requestBody , globalSchemas , {
177
212
coerceTypes : body . coercionRequired ,
213
+ schema : body . schema ,
178
214
} ) ;
179
215
180
216
if ( requestBodyIndex > - 1 ) paramArgs . splice ( requestBodyIndex , 0 , body . value ) ;
0 commit comments