@@ -27,7 +27,7 @@ use openapiv3::{
2727 OpenAPI , Operation , Parameter , ParameterData , ParameterSchemaOrContent , PathItem , ReferenceOr ,
2828 RequestBody , Response , Schema , SchemaKind , StatusCode , Tag , Type ,
2929} ;
30- use std:: collections:: { BTreeMap , HashSet } ;
30+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
3131
3232#[ derive( Debug , Clone , PartialEq , Eq ) ]
3333enum PathElement {
@@ -186,6 +186,37 @@ struct Param {
186186 kind : ParamKind ,
187187}
188188
189+ #[ derive( Debug , Clone ) ]
190+ pub struct RequestBodyParams {
191+ params : HashMap < ContentType , Vec < Param > > ,
192+ }
193+
194+ impl RequestBodyParams {
195+ fn has_single_content_type ( & self ) -> bool {
196+ self . params . len ( ) == 1
197+ }
198+
199+ fn get_default_request_body_param ( & self ) -> Option < & Vec < Param > > {
200+ self . params
201+ . values ( )
202+ . next ( )
203+ . filter ( |_| self . has_single_content_type ( ) )
204+ }
205+ }
206+
207+ #[ derive( Debug , Clone , Eq , PartialEq , Hash ) ]
208+ pub struct ContentType ( pub String ) ;
209+
210+ impl ContentType {
211+ pub fn is_json ( & self ) -> bool {
212+ self . 0 == "application/json"
213+ }
214+
215+ pub fn is_yaml ( & self ) -> bool {
216+ self . 0 == "application/x-yaml"
217+ }
218+ }
219+
189220impl Param {
190221 fn render_declaration ( & self ) -> RustPrinter {
191222 let type_name = self . tpe . render_declaration ( true ) ;
@@ -268,7 +299,7 @@ fn match_tag(tag: &Option<Tag>, path_item: &ReferenceOr<PathItem>) -> bool {
268299fn param_data_to_type ( data : & ParameterData , ref_cache : & mut RefCache ) -> Result < DataType > {
269300 match & data. format {
270301 ParameterSchemaOrContent :: Schema ( ref_or_schema) => {
271- ref_or_schema_type ( ref_or_schema, ref_cache)
302+ ref_or_schema_type ( ref_or_schema, ref_cache, None )
272303 }
273304 ParameterSchemaOrContent :: Content ( _) => {
274305 Err ( Error :: unimplemented ( "Content parameter is not supported." ) )
@@ -295,45 +326,74 @@ fn parameter(p: &ReferenceOr<Parameter>, ref_cache: &mut RefCache) -> Result<Par
295326fn request_body_params (
296327 body : & ReferenceOr < RequestBody > ,
297328 ref_cache : & mut RefCache ,
298- ) -> Result < Vec < Param > > {
329+ ) -> Result < RequestBodyParams > {
330+ let mut content_type_params = HashMap :: new ( ) ;
331+
299332 match body {
300- ReferenceOr :: Reference { reference } => Err ( Error :: unimplemented ( format ! (
301- "Unexpected ref request body: '{reference}'."
302- ) ) ) ,
333+ ReferenceOr :: Reference { reference } => {
334+ return Err ( Error :: unimplemented ( format ! (
335+ "Unexpected ref request body: '{reference}'."
336+ ) ) )
337+ }
303338 ReferenceOr :: Item ( body) => {
304- if body. content . len ( ) != 1 {
305- Err ( Error :: unimplemented ( "Content with not exactly 1 option." ) )
306- } else {
307- let ( content_type, media_type) = body. content . first ( ) . unwrap ( ) ;
308-
309- if content_type. starts_with ( "application/json" ) {
339+ for ( content_type, media_type) in & body. content {
340+ if content_type. starts_with ( "application/json" ) || content_type == "*/*" {
310341 let schema = match & media_type. schema {
311342 None => Err ( Error :: unimplemented ( "JSON content without schema." ) ) ,
312343 Some ( schema) => Ok ( schema) ,
313344 } ;
314345
315- Ok ( vec ! [ Param {
316- original_name: "" . to_string( ) ,
317- name: "value" . to_string( ) ,
318- tpe: ref_or_schema_type( schema?, ref_cache) ?,
319- required: body. required,
320- kind: ParamKind :: Body ,
321- } ] )
346+ content_type_params. insert (
347+ ContentType ( content_type. clone ( ) ) ,
348+ vec ! [ Param {
349+ original_name: "" . to_string( ) ,
350+ name: "value" . to_string( ) ,
351+ tpe: ref_or_schema_type(
352+ schema?,
353+ ref_cache,
354+ Some ( content_type. clone( ) ) ,
355+ ) ?,
356+ required: body. required,
357+ kind: ParamKind :: Body ,
358+ } ] ,
359+ ) ;
322360 } else if content_type == "application/octet-stream" {
323- Ok ( vec ! [ Param {
361+ content_type_params. insert (
362+ ContentType ( content_type. clone ( ) ) ,
363+ vec ! [ Param {
364+ original_name: "" . to_string( ) ,
365+ name: "value" . to_string( ) ,
366+ tpe: DataType :: Binary ,
367+ required: body. required,
368+ kind: ParamKind :: Body ,
369+ } ] ,
370+ ) ;
371+ } else if content_type. contains ( "application/x-yaml" ) {
372+ let schema = match & media_type. schema {
373+ None => Err ( Error :: unimplemented ( "YAML content without schema." ) ) ,
374+ Some ( schema) => Ok ( schema) ,
375+ } ;
376+
377+ let param = Param {
324378 original_name : "" . to_string ( ) ,
325379 name : "value" . to_string ( ) ,
326- tpe: DataType :: Binary ,
380+ tpe : ref_or_schema_type ( schema? , ref_cache , Some ( content_type . clone ( ) ) ) ? ,
327381 required : body. required ,
328382 kind : ParamKind :: Body ,
329- } ] )
383+ } ;
384+
385+ content_type_params. insert ( ContentType ( content_type. clone ( ) ) , vec ! [ param] ) ;
330386 } else if content_type == "multipart/form-data" {
331387 match & media_type. schema {
332- None => Err ( Error :: unimplemented ( "Multipart content without schema." ) ) ,
388+ None => {
389+ return Err ( Error :: unimplemented ( "Multipart content without schema." ) )
390+ }
333391 Some ( schema) => match schema {
334- ReferenceOr :: Reference { reference } => Err ( Error :: unimplemented (
335- format ! ( "Unexpected ref multipart schema: '{reference}'." ) ,
336- ) ) ,
392+ ReferenceOr :: Reference { reference } => {
393+ return Err ( Error :: unimplemented ( format ! (
394+ "Unexpected ref multipart schema: '{reference}'."
395+ ) ) )
396+ }
337397 ReferenceOr :: Item ( schema) => match & schema. schema_kind {
338398 SchemaKind :: Type ( Type :: Object ( obj) ) => {
339399 fn multipart_param (
@@ -351,7 +411,8 @@ fn request_body_params(
351411 } )
352412 }
353413
354- obj. properties
414+ let params = obj
415+ . properties
355416 . iter ( )
356417 . map ( |( name, schema) | {
357418 multipart_param (
@@ -361,41 +422,39 @@ fn request_body_params(
361422 ref_cache,
362423 )
363424 } )
364- . collect ( )
425+ . collect :: < Result < Vec < _ > > > ( ) ?;
426+
427+ content_type_params
428+ . insert ( ContentType ( content_type. clone ( ) ) , params) ;
429+ }
430+ _ => {
431+ return Err ( Error :: unimplemented (
432+ "Object schema expected for multipart request body." ,
433+ ) )
365434 }
366- _ => Err ( Error :: unimplemented (
367- "Object schema expected for multipart request body." ,
368- ) ) ,
369435 } ,
370436 } ,
371437 }
372438 } else {
373- Err ( Error :: unimplemented ( format ! (
439+ return Err ( Error :: unimplemented ( format ! (
374440 "Request body content type: '{content_type}'."
375- ) ) )
441+ ) ) ) ;
376442 }
377443 }
378444 }
379445 }
446+
447+ Ok ( RequestBodyParams {
448+ params : content_type_params,
449+ } )
380450}
381451
382452fn parameters ( op : & PathOperation , ref_cache : & mut RefCache ) -> Result < Vec < Param > > {
383- let params: Result < Vec < Param > > = op
384- . op
453+ op. op
385454 . parameters
386455 . iter ( )
387456 . map ( |p| parameter ( p, ref_cache) )
388- . collect ( ) ;
389-
390- let mut params = params?;
391-
392- if let Some ( body) = & op. op . request_body {
393- for p in request_body_params ( body, ref_cache) ? {
394- params. push ( p) ;
395- }
396- }
397-
398- Ok ( params)
457+ . collect ( )
399458}
400459
401460fn as_code ( code : & StatusCode ) -> Option < u16 > {
@@ -449,7 +508,11 @@ fn response_type(response: &ReferenceOr<Response>, ref_cache: &mut RefCache) ->
449508 Some ( schema) => Ok ( schema) ,
450509 } ;
451510
452- Ok ( ref_or_schema_type ( schema?, ref_cache) ?)
511+ Ok ( ref_or_schema_type (
512+ schema?,
513+ ref_cache,
514+ Some ( content_type. clone ( ) ) ,
515+ ) ?)
453516 } else if content_type == "application/octet-stream" {
454517 Ok ( DataType :: Binary )
455518 } else {
@@ -494,40 +557,107 @@ fn method_errors(
494557 Ok ( MethodErrors { codes : codes? } )
495558}
496559
497- fn trait_method (
560+ fn trait_methods_specific_to_content_type (
498561 op : & PathOperation ,
499562 prefix_length : usize ,
500563 ref_cache : & mut RefCache ,
501- ) -> Result < Method > {
564+ ) -> Result < Vec < Method > > {
502565 let ( result_code, result_type) = method_result ( & op. op . responses . responses , ref_cache) ?;
503566
504- let name = if let Some ( op_id) = & op. op . operation_id {
505- op_id. to_case ( Case :: Snake )
567+ let name = op
568+ . op
569+ . operation_id
570+ . as_ref ( )
571+ . map ( |op_id| op_id. to_case ( Case :: Snake ) )
572+ . unwrap_or_else ( || op. path . strip_prefix ( prefix_length) . method_name ( & op. method ) ) ;
573+
574+ let mut main_params = parameters ( op, ref_cache) ?;
575+
576+ if let Some ( body) = & op. op . request_body {
577+ let content_specific = request_body_params ( body, ref_cache) ?;
578+
579+ if let Some ( request_body_params) = content_specific. get_default_request_body_param ( ) {
580+ main_params. extend ( request_body_params. iter ( ) . cloned ( ) ) ;
581+ return Ok ( vec ! [ create_method(
582+ name,
583+ op,
584+ & main_params,
585+ result_type,
586+ result_code,
587+ ref_cache,
588+ ) ?] ) ;
589+ }
590+
591+ let mut methods = Vec :: new ( ) ;
592+ for ( content_type, params) in content_specific. params {
593+ let method_name = match_content_type ( content_type, & name) ?;
594+ let new_params = [ main_params. clone ( ) , params] . concat ( ) ;
595+ methods. push ( create_method (
596+ method_name,
597+ op,
598+ & new_params,
599+ result_type. clone ( ) ,
600+ result_code. clone ( ) ,
601+ ref_cache,
602+ ) ?) ;
603+ }
604+
605+ Ok ( methods)
506606 } else {
507- op. path . strip_prefix ( prefix_length) . method_name ( & op. method )
508- } ;
607+ Ok ( vec ! [ create_method(
608+ name,
609+ op,
610+ & main_params,
611+ result_type,
612+ result_code,
613+ ref_cache,
614+ ) ?] )
615+ }
616+ }
509617
618+ fn create_method (
619+ name : String ,
620+ op : & PathOperation ,
621+ params : & [ Param ] ,
622+ result_type : DataType ,
623+ result_code : StatusCode ,
624+ ref_cache : & mut RefCache ,
625+ ) -> Result < Method > {
510626 Ok ( Method {
511627 name,
512628 path : op. path . clone ( ) ,
513629 original_path : op. original_path . clone ( ) ,
514630 http_method : op. method . to_string ( ) ,
515- params : parameters ( op , ref_cache ) ? ,
631+ params : params . to_vec ( ) ,
516632 result : result_type,
517633 result_status_code : result_code. clone ( ) ,
518634 errors : method_errors ( & op. op . responses . responses , result_code, ref_cache) ?,
519635 } )
520636}
521637
638+ fn match_content_type ( content_type : ContentType , base_name : & str ) -> Result < String > {
639+ if content_type. is_json ( ) {
640+ Ok ( format ! ( "{}_json" , base_name) )
641+ } else if content_type. is_yaml ( ) {
642+ Ok ( format ! ( "{}_yaml" , base_name) )
643+ } else {
644+ Err ( Error :: unimplemented (
645+ "Multiple content types supported only for JSON and YAML" ,
646+ ) )
647+ }
648+ }
649+
522650fn trait_methods (
523651 operations : & [ PathOperation ] ,
524652 prefix_length : usize ,
525653 ref_cache : & mut RefCache ,
526654) -> Result < Vec < Method > > {
527- operations
655+ let res = operations
528656 . iter ( )
529- . map ( |op| trait_method ( op, prefix_length, ref_cache) )
530- . collect ( )
657+ . map ( |op| trait_methods_specific_to_content_type ( op, prefix_length, ref_cache) )
658+ . collect :: < Result < Vec < Vec < _ > > > > ( ) ?;
659+
660+ Ok ( res. into_iter ( ) . flatten ( ) . collect ( ) )
531661}
532662
533663fn render_errors ( method_name : & str , error_kind : & ErrorKind , errors : & MethodErrors ) -> RustResult {
@@ -837,7 +967,18 @@ fn render_method_implementation(method: &Method, error_kind: &ErrorKind) -> Rust
837967 r#"request = request.header(reqwest::header::CONTENT_TYPE, "application/octet-stream");"# ,
838968 )
839969 + NewLine
840- } else {
970+ } else if param. tpe == DataType :: Yaml {
971+ line (
972+ unit ( )
973+ + "request = request.body(serde_yaml::to_string("
974+ + & param. name
975+ + ").unwrap_or_default().into_bytes());" ,
976+ ) + line (
977+ r#"request = request.header(reqwest::header::CONTENT_TYPE, "application/x-yaml");"# ,
978+ ) + NewLine
979+ }
980+ // Not sure why everything else is assumed to be json (previously)
981+ else {
841982 line ( unit ( ) + "request = request.json(" + & param. name + ");" ) + NewLine
842983 }
843984 }
0 commit comments