@@ -100,6 +100,8 @@ pub struct RequestContext<Context: ServerContext> {
100
100
pub request : Arc < Mutex < Request < Body > > > ,
101
101
/** HTTP request routing variables */
102
102
pub path_variables : VariableSet ,
103
+ /** expected request body mime type */
104
+ pub body_content_type : ApiEndpointBodyContentType ,
103
105
/** unique id assigned to this request */
104
106
pub request_id : String ,
105
107
/** logger for this specific request */
@@ -190,7 +192,9 @@ pub trait Extractor: Send + Sync + Sized {
190
192
rqctx : Arc < RequestContext < Context > > ,
191
193
) -> Result < Self , HttpError > ;
192
194
193
- fn metadata ( ) -> ExtractorMetadata ;
195
+ fn metadata (
196
+ body_content_type : ApiEndpointBodyContentType ,
197
+ ) -> ExtractorMetadata ;
194
198
}
195
199
196
200
/**
@@ -217,13 +221,13 @@ macro_rules! impl_extractor_for_tuple {
217
221
futures:: try_join!( $( $T:: from_request( Arc :: clone( & _rqctx) ) , ) * )
218
222
}
219
223
220
- fn metadata( ) -> ExtractorMetadata {
224
+ fn metadata( _body_content_type : ApiEndpointBodyContentType ) -> ExtractorMetadata {
221
225
#[ allow( unused_mut) ]
222
226
let mut paginated = false ;
223
227
#[ allow( unused_mut) ]
224
228
let mut parameters = vec![ ] ;
225
229
$(
226
- let mut metadata = $T:: metadata( ) ;
230
+ let mut metadata = $T:: metadata( _body_content_type . clone ( ) ) ;
227
231
paginated = paginated | metadata. paginated;
228
232
parameters. append( & mut metadata. parameters) ;
229
233
) *
@@ -607,7 +611,9 @@ where
607
611
http_request_load_query ( & request)
608
612
}
609
613
610
- fn metadata ( ) -> ExtractorMetadata {
614
+ fn metadata (
615
+ _body_content_type : ApiEndpointBodyContentType ,
616
+ ) -> ExtractorMetadata {
611
617
get_metadata :: < QueryType > ( & ApiEndpointParameterLocation :: Query )
612
618
}
613
619
}
@@ -653,7 +659,9 @@ where
653
659
Ok ( Path { inner : params } )
654
660
}
655
661
656
- fn metadata ( ) -> ExtractorMetadata {
662
+ fn metadata (
663
+ _body_content_type : ApiEndpointBodyContentType ,
664
+ ) -> ExtractorMetadata {
657
665
get_metadata :: < PathType > ( & ApiEndpointParameterLocation :: Path )
658
666
}
659
667
}
@@ -934,31 +942,72 @@ impl<BodyType: JsonSchema + DeserializeOwned + Send + Sync>
934
942
}
935
943
936
944
/**
937
- * Given an HTTP request, attempt to read the body, parse it as JSON, and
938
- * deserialize an instance of `BodyType` from it .
945
+ * Given an HTTP request, attempt to read the body, parse it according
946
+ * to the content type, and deserialize it to an instance of `BodyType`.
939
947
*/
940
- async fn http_request_load_json_body < Context : ServerContext , BodyType > (
948
+ async fn http_request_load_body < Context : ServerContext , BodyType > (
941
949
rqctx : Arc < RequestContext < Context > > ,
942
950
) -> Result < TypedBody < BodyType > , HttpError >
943
951
where
944
952
BodyType : JsonSchema + DeserializeOwned + Send + Sync ,
945
953
{
946
954
let server = & rqctx. server ;
947
955
let mut request = rqctx. request . lock ( ) . await ;
948
- let body_bytes = http_read_body (
956
+ let body = http_read_body (
949
957
request. body_mut ( ) ,
950
958
server. config . request_body_max_bytes ,
951
959
)
952
960
. await ?;
953
- let value: Result < BodyType , serde_json:: Error > =
954
- serde_json:: from_slice ( & body_bytes) ;
955
- match value {
956
- Ok ( j) => Ok ( TypedBody { inner : j } ) ,
957
- Err ( e) => Err ( HttpError :: for_bad_request (
958
- None ,
959
- format ! ( "unable to parse body: {}" , e) ,
960
- ) ) ,
961
- }
961
+
962
+ // RFC 7231 §3.1.1.1: media types are case insensitive and may
963
+ // be followed by whitespace and/or a parameter (e.g., charset),
964
+ // which we currently ignore.
965
+ let content_type = request
966
+ . headers ( )
967
+ . get ( http:: header:: CONTENT_TYPE )
968
+ . map ( |hv| {
969
+ hv. to_str ( ) . map_err ( |e| {
970
+ HttpError :: for_bad_request (
971
+ None ,
972
+ format ! ( "invalid content type: {}" , e) ,
973
+ )
974
+ } )
975
+ } )
976
+ . unwrap_or ( Ok ( CONTENT_TYPE_JSON ) ) ?;
977
+ let end = content_type. find ( ';' ) . unwrap_or_else ( || content_type. len ( ) ) ;
978
+ let mime_type = content_type[ ..end] . trim_end ( ) . to_lowercase ( ) ;
979
+ let body_content_type =
980
+ ApiEndpointBodyContentType :: from_mime_type ( & mime_type)
981
+ . map_err ( |e| HttpError :: for_bad_request ( None , e) ) ?;
982
+ let expected_content_type = rqctx. body_content_type . clone ( ) ;
983
+
984
+ use ApiEndpointBodyContentType :: * ;
985
+ let content: BodyType = match ( expected_content_type, body_content_type) {
986
+ ( Json , Json ) => serde_json:: from_slice ( & body) . map_err ( |e| {
987
+ HttpError :: for_bad_request (
988
+ None ,
989
+ format ! ( "unable to parse JSON body: {}" , e) ,
990
+ )
991
+ } ) ?,
992
+ ( UrlEncoded , UrlEncoded ) => serde_urlencoded:: from_bytes ( & body)
993
+ . map_err ( |e| {
994
+ HttpError :: for_bad_request (
995
+ None ,
996
+ format ! ( "unable to parse URL-encoded body: {}" , e) ,
997
+ )
998
+ } ) ?,
999
+ ( expected, requested) => {
1000
+ return Err ( HttpError :: for_bad_request (
1001
+ None ,
1002
+ format ! (
1003
+ "expected content type \" {}\" , got \" {}\" " ,
1004
+ expected. mime_type( ) ,
1005
+ requested. mime_type( )
1006
+ ) ,
1007
+ ) )
1008
+ }
1009
+ } ;
1010
+ Ok ( TypedBody { inner : content } )
962
1011
}
963
1012
964
1013
/*
@@ -977,12 +1026,12 @@ where
977
1026
async fn from_request < Context : ServerContext > (
978
1027
rqctx : Arc < RequestContext < Context > > ,
979
1028
) -> Result < TypedBody < BodyType > , HttpError > {
980
- http_request_load_json_body ( rqctx) . await
1029
+ http_request_load_body ( rqctx) . await
981
1030
}
982
1031
983
- fn metadata ( ) -> ExtractorMetadata {
1032
+ fn metadata ( content_type : ApiEndpointBodyContentType ) -> ExtractorMetadata {
984
1033
let body = ApiEndpointParameter :: new_body (
985
- ApiEndpointBodyContentType :: Json ,
1034
+ content_type ,
986
1035
true ,
987
1036
ApiSchemaGenerator :: Gen {
988
1037
name : BodyType :: schema_name,
@@ -1047,7 +1096,9 @@ impl Extractor for UntypedBody {
1047
1096
Ok ( UntypedBody { content : body_bytes } )
1048
1097
}
1049
1098
1050
- fn metadata ( ) -> ExtractorMetadata {
1099
+ fn metadata (
1100
+ _content_type : ApiEndpointBodyContentType ,
1101
+ ) -> ExtractorMetadata {
1051
1102
ExtractorMetadata {
1052
1103
parameters : vec ! [ ApiEndpointParameter :: new_body(
1053
1104
ApiEndpointBodyContentType :: Bytes ,
0 commit comments