@@ -441,44 +441,228 @@ pub fn parse_request_body(
441441 }
442442}
443443
444- /// Analyze return type and convert to Response
444+ /// Unwrap Json<T> to get T
445+ fn unwrap_json ( ty : & Type ) -> & Type {
446+ if let Type :: Path ( type_path) = ty {
447+ let path = & type_path. path ;
448+ if !path. segments . is_empty ( ) {
449+ let segment = & path. segments [ 0 ] ;
450+ if segment. ident == "Json" {
451+ if let syn:: PathArguments :: AngleBracketed ( args) = & segment. arguments {
452+ if let Some ( syn:: GenericArgument :: Type ( inner_ty) ) = args. args . first ( ) {
453+ return inner_ty;
454+ }
455+ }
456+ }
457+ }
458+ }
459+ ty
460+ }
461+
462+ /// Extract Ok and Err types from Result<T, E> or Result<Json<T>, E>
463+ /// Handles both Result and std::result::Result, and unwraps references
464+ fn extract_result_types ( ty : & Type ) -> Option < ( Type , Type ) > {
465+ // First unwrap Json if present
466+ let unwrapped = unwrap_json ( ty) ;
467+
468+ // Handle both Type::Path and Type::Reference (for &Result<...>)
469+ let result_type = match unwrapped {
470+ Type :: Path ( type_path) => type_path,
471+ Type :: Reference ( type_ref) => {
472+ // Unwrap reference and check if it's a Result
473+ if let Type :: Path ( type_path) = type_ref. elem . as_ref ( ) {
474+ type_path
475+ } else {
476+ return None ;
477+ }
478+ }
479+ _ => return None ,
480+ } ;
481+
482+ let path = & result_type. path ;
483+ if path. segments . is_empty ( ) {
484+ return None ;
485+ }
486+
487+ // Check if any segment is "Result" (handles both Result and std::result::Result)
488+ let is_result = path. segments . iter ( ) . any ( |seg| seg. ident == "Result" ) ;
489+
490+ if is_result {
491+ // Get the last segment (Result) to check for generics
492+ if let Some ( segment) = path. segments . last ( ) {
493+ if let syn:: PathArguments :: AngleBracketed ( args) = & segment. arguments {
494+ if args. args . len ( ) >= 2 {
495+ if let (
496+ Some ( syn:: GenericArgument :: Type ( ok_ty) ) ,
497+ Some ( syn:: GenericArgument :: Type ( err_ty) ) ,
498+ ) = ( args. args . first ( ) , args. args . get ( 1 ) )
499+ {
500+ // Unwrap Json from Ok type if present
501+ let ok_ty_unwrapped = unwrap_json ( ok_ty) ;
502+ return Some ( ( ok_ty_unwrapped. clone ( ) , err_ty. clone ( ) ) ) ;
503+ }
504+ }
505+ }
506+ }
507+ }
508+ None
509+ }
510+
511+ /// Check if error type is a tuple (StatusCode, E) or (StatusCode, Json<E>)
512+ /// Returns the error type E and a default status code (400)
513+ fn extract_status_code_tuple ( err_ty : & Type ) -> Option < ( u16 , Type ) > {
514+ if let Type :: Tuple ( tuple) = err_ty {
515+ if tuple. elems . len ( ) == 2 {
516+ // Check if first element is StatusCode
517+ if let Type :: Path ( type_path) = & tuple. elems [ 0 ] {
518+ let path = & type_path. path ;
519+ if !path. segments . is_empty ( ) {
520+ let segment = & path. segments [ 0 ] ;
521+ // Check if it's StatusCode (could be qualified like axum::http::StatusCode)
522+ let is_status_code = segment. ident == "StatusCode"
523+ || ( path. segments . len ( ) > 1
524+ && path. segments . iter ( ) . any ( |s| s. ident == "StatusCode" ) ) ;
525+
526+ if is_status_code {
527+ // Use 400 as default status code
528+ // The actual status code value is determined at runtime
529+ if let Some ( error_type) = tuple. elems . get ( 1 ) {
530+ // Unwrap Json if present
531+ let error_type_unwrapped = unwrap_json ( error_type) ;
532+ return Some ( ( 400 , error_type_unwrapped. clone ( ) ) ) ;
533+ }
534+ }
535+ }
536+ }
537+ }
538+ }
539+ None
540+ }
541+
542+ /// Analyze return type and convert to Responses map
445543pub fn parse_return_type (
446544 return_type : & ReturnType ,
447545 known_schemas : & HashMap < String , String > ,
448- ) -> Response {
449- let schema = match return_type {
450- ReturnType :: Default => None ,
451- ReturnType :: Type ( _, ty) => Some ( parse_type_to_schema_ref ( ty, known_schemas) ) ,
452- } ;
546+ ) -> BTreeMap < String , Response > {
547+ let mut responses = BTreeMap :: new ( ) ;
453548
454- let mut content = BTreeMap :: new ( ) ;
455- if let Some ( schema) = schema {
456- content. insert (
457- "application/json" . to_string ( ) ,
458- MediaType {
459- schema : Some ( schema) ,
460- example : None ,
461- examples : None ,
462- } ,
463- ) ;
464- }
549+ match return_type {
550+ ReturnType :: Default => {
551+ // No return type - just 200 with no content
552+ responses. insert (
553+ "200" . to_string ( ) ,
554+ Response {
555+ description : "Successful response" . to_string ( ) ,
556+ headers : None ,
557+ content : None ,
558+ } ,
559+ ) ;
560+ }
561+ ReturnType :: Type ( _, ty) => {
562+ // Check if it's a Result<T, E>
563+ if let Some ( ( ok_ty, err_ty) ) = extract_result_types ( ty) {
564+ // Handle success response (200)
565+ let ok_schema = parse_type_to_schema_ref ( & ok_ty, known_schemas) ;
566+ let mut ok_content = BTreeMap :: new ( ) ;
567+ ok_content. insert (
568+ "application/json" . to_string ( ) ,
569+ MediaType {
570+ schema : Some ( ok_schema) ,
571+ example : None ,
572+ examples : None ,
573+ } ,
574+ ) ;
465575
466- Response {
467- description : "Successful response" . to_string ( ) ,
468- headers : None ,
469- content : if content. is_empty ( ) {
470- None
471- } else {
472- Some ( content)
473- } ,
576+ responses. insert (
577+ "200" . to_string ( ) ,
578+ Response {
579+ description : "Successful response" . to_string ( ) ,
580+ headers : None ,
581+ content : Some ( ok_content) ,
582+ } ,
583+ ) ;
584+
585+ // Handle error response
586+ // Check if error is (StatusCode, E) tuple
587+ if let Some ( ( status_code, error_type) ) = extract_status_code_tuple ( & err_ty) {
588+ // Use the status code from the tuple
589+ let err_schema = parse_type_to_schema_ref ( & error_type, known_schemas) ;
590+ let mut err_content = BTreeMap :: new ( ) ;
591+ err_content. insert (
592+ "application/json" . to_string ( ) ,
593+ MediaType {
594+ schema : Some ( err_schema) ,
595+ example : None ,
596+ examples : None ,
597+ } ,
598+ ) ;
599+
600+ responses. insert (
601+ status_code. to_string ( ) ,
602+ Response {
603+ description : "Error response" . to_string ( ) ,
604+ headers : None ,
605+ content : Some ( err_content) ,
606+ } ,
607+ ) ;
608+ } else {
609+ // Regular error type - use default 400
610+ // Unwrap Json if present
611+ let err_ty_unwrapped = unwrap_json ( & err_ty) ;
612+ let err_schema = parse_type_to_schema_ref ( err_ty_unwrapped, known_schemas) ;
613+ let mut err_content = BTreeMap :: new ( ) ;
614+ err_content. insert (
615+ "application/json" . to_string ( ) ,
616+ MediaType {
617+ schema : Some ( err_schema) ,
618+ example : None ,
619+ examples : None ,
620+ } ,
621+ ) ;
622+
623+ responses. insert (
624+ "400" . to_string ( ) ,
625+ Response {
626+ description : "Error response" . to_string ( ) ,
627+ headers : None ,
628+ content : Some ( err_content) ,
629+ } ,
630+ ) ;
631+ }
632+ } else {
633+ // Not a Result type - regular response
634+ let schema = parse_type_to_schema_ref ( ty, known_schemas) ;
635+ let mut content = BTreeMap :: new ( ) ;
636+ content. insert (
637+ "application/json" . to_string ( ) ,
638+ MediaType {
639+ schema : Some ( schema) ,
640+ example : None ,
641+ examples : None ,
642+ } ,
643+ ) ;
644+
645+ responses. insert (
646+ "200" . to_string ( ) ,
647+ Response {
648+ description : "Successful response" . to_string ( ) ,
649+ headers : None ,
650+ content : Some ( content) ,
651+ } ,
652+ ) ;
653+ }
654+ }
474655 }
656+
657+ responses
475658}
476659
477660/// Build Operation from function signature
478661pub fn build_operation_from_function (
479662 sig : & syn:: Signature ,
480663 path : & str ,
481664 known_schemas : & HashMap < String , String > ,
665+ error_status : Option < & [ u16 ] > ,
482666) -> Operation {
483667 let path_params = extract_path_parameters ( path) ;
484668 let mut parameters = Vec :: new ( ) ;
@@ -494,10 +678,50 @@ pub fn build_operation_from_function(
494678 }
495679 }
496680
497- // Parse return type
498- let response = parse_return_type ( & sig. output , known_schemas) ;
499- let mut responses = BTreeMap :: new ( ) ;
500- responses. insert ( "200" . to_string ( ) , response) ;
681+ // Parse return type - may return multiple responses (for Result types)
682+ let mut responses = parse_return_type ( & sig. output , known_schemas) ;
683+
684+ // Add additional error status codes from error_status attribute
685+ if let Some ( status_codes) = error_status {
686+ // Find the error response schema (usually 400 or the first error response)
687+ let error_schema = responses
688+ . iter ( )
689+ . find ( |( code, _) | code != & & "200" . to_string ( ) )
690+ . and_then ( |( _, resp) | {
691+ resp. content
692+ . as_ref ( ) ?
693+ . get ( "application/json" ) ?
694+ . schema
695+ . clone ( )
696+ } ) ;
697+
698+ if let Some ( schema) = error_schema {
699+ for & status_code in status_codes {
700+ let status_str = status_code. to_string ( ) ;
701+ // Only add if not already present
702+ if !responses. contains_key ( & status_str) {
703+ let mut err_content = BTreeMap :: new ( ) ;
704+ err_content. insert (
705+ "application/json" . to_string ( ) ,
706+ MediaType {
707+ schema : Some ( schema. clone ( ) ) ,
708+ example : None ,
709+ examples : None ,
710+ } ,
711+ ) ;
712+
713+ responses. insert (
714+ status_str,
715+ Response {
716+ description : "Error response" . to_string ( ) ,
717+ headers : None ,
718+ content : Some ( err_content) ,
719+ } ,
720+ ) ;
721+ }
722+ }
723+ }
724+ }
501725
502726 Operation {
503727 operation_id : Some ( sig. ident . to_string ( ) ) ,
0 commit comments