@@ -114,10 +114,10 @@ public static IEndpointConventionBuilder MapQuery(
114114 string nullRouteParameterPattern = "-" ,
115115 bool enableHead = false )
116116 {
117- var returnType = EnsureReturnTypeIsQuery ( handler ) ;
117+ var ( queryType , returnType ) = EnsureReturnTypeIsQuery ( handler ) ;
118118 if ( mapNullableRouteParameters is MapNullableRouteParameter . Disable )
119119 {
120- return MapRoutes ( route ) ;
120+ return MapRoutes ( queryType , returnType , route ) ;
121121 }
122122
123123 if ( string . IsNullOrWhiteSpace ( nullRouteParameterPattern ) )
@@ -129,7 +129,7 @@ public static IEndpointConventionBuilder MapQuery(
129129
130130 var parsedRoute = RoutePatternFactory . Parse ( route ) ;
131131 var context = new NullabilityInfoContext ( ) ;
132- var nullableRouteProperties = returnType . GetProperties ( )
132+ var nullableRouteProperties = queryType . GetProperties ( )
133133 . Where (
134134 p => p . GetMethod != null
135135 && p . SetMethod != null
@@ -150,15 +150,24 @@ public static IEndpointConventionBuilder MapQuery(
150150 var regex = new Regex ( "{" + x . Name + "[^}]*?}" , RegexOptions . IgnoreCase ) ;
151151 return regex . Replace ( r , nullRouteParameterPattern ) ;
152152 } ) ;
153- MapRoutes ( newRoute ) ;
153+ MapRoutes ( queryType , returnType , newRoute ) ;
154154 }
155155
156- return MapRoutes ( route ) ;
156+ return MapRoutes ( queryType , returnType , route ) ;
157157
158- IEndpointConventionBuilder MapRoutes ( string r )
158+ IEndpointConventionBuilder MapRoutes ( Type query , Type queryFor , string r )
159159 {
160160 var endpoint = enableHead ? app . MapMethods ( r , GetAndHeadMethods , handler ) : app . MapGet ( r , handler ) ;
161- return endpoint . AddEndpointFilter < QueryEndpointHandler > ( ) ;
161+ var builder = endpoint . AddEndpointFilter < QueryEndpointHandler > ( )
162+ . Produces ( 200 , queryFor )
163+ . WithTags ( "Queries" ) ;
164+ if ( query . GetInterfaces ( ) . Any ( i => i . IsGenericType && i . GetGenericTypeDefinition ( ) == typeof ( IQuery < > ) ) )
165+ {
166+ // may be null
167+ builder . Produces ( 404 , queryFor ) ;
168+ }
169+
170+ return builder ;
162171 }
163172 }
164173
@@ -220,7 +229,7 @@ public static IEndpointConventionBuilder MapCommand(
220229 [ StringSyntax ( "Route" ) ] string route ,
221230 Delegate handler )
222231 {
223- var commandTypeName = EnsureReturnTypeIsCommand ( handler ) . Name ;
232+ var commandTypeName = EnsureReturnTypeIsCommand ( handler ) . CommandType . Name ;
224233 if ( PostCommandPrefixes . Any ( x => commandTypeName . StartsWith ( x ) ) )
225234 {
226235 return app . MapPostCommand ( route , handler ) ;
@@ -265,8 +274,11 @@ public static IEndpointConventionBuilder MapPostCommand(
265274 [ StringSyntax ( "Route" ) ] string route ,
266275 Delegate handler )
267276 {
268- EnsureReturnTypeIsCommand ( handler ) ;
269- return app . MapPost ( route , handler ) . AddEndpointFilter < CommandEndpointHandler > ( ) ;
277+ var ( commandType , responseType , errorType ) = EnsureReturnTypeIsCommand ( handler ) ;
278+ var builder = app . MapPost ( route , handler )
279+ . AddEndpointFilter < CommandEndpointHandler > ( )
280+ . AddCommandOpenApiDescriptions ( commandType , responseType , errorType ) ;
281+ return builder ;
270282 }
271283
272284 /// <summary>
@@ -295,8 +307,9 @@ public static IEndpointConventionBuilder MapPutCommand(
295307 [ StringSyntax ( "Route" ) ] string route ,
296308 Delegate handler )
297309 {
298- EnsureReturnTypeIsCommand ( handler ) ;
299- return app . MapPut ( route , handler ) . AddEndpointFilter < CommandEndpointHandler > ( ) ;
310+ var ( commandType , responseType , errorType ) = EnsureReturnTypeIsCommand ( handler ) ;
311+ return app . MapPut ( route , handler ) . AddEndpointFilter < CommandEndpointHandler > ( )
312+ . AddCommandOpenApiDescriptions ( commandType , responseType , errorType ) ;
300313 }
301314
302315 /// <summary>
@@ -325,8 +338,9 @@ public static IEndpointConventionBuilder MapDeleteCommand(
325338 [ StringSyntax ( "Route" ) ] string route ,
326339 Delegate handler )
327340 {
328- EnsureReturnTypeIsCommand ( handler ) ;
329- return app . MapDelete ( route , handler ) . AddEndpointFilter < CommandEndpointHandler > ( ) ;
341+ var ( commandType , responseType , errorType ) = EnsureReturnTypeIsCommand ( handler ) ;
342+ return app . MapDelete ( route , handler ) . AddEndpointFilter < CommandEndpointHandler > ( )
343+ . AddCommandOpenApiDescriptions ( commandType , responseType , errorType ) ;
330344 }
331345
332346 /// <summary>
@@ -395,42 +409,48 @@ public static IEndpointRouteBuilder StopMappingPrefixToDelete(this IEndpointRout
395409 return app ;
396410 }
397411
398- private static Type EnsureReturnTypeIsCommand ( Delegate handler )
412+ private static ( Type CommandType , Type ? ResponseType , Type ErrorType ) EnsureReturnTypeIsCommand ( Delegate handler )
399413 {
400414 var returnType = handler . Method . ReturnType ;
401415 if ( returnType . IsGenericType && returnType . GetGenericTypeDefinition ( ) == typeof ( Task < > ) )
402416 {
403417 returnType = returnType . GenericTypeArguments . First ( ) ;
404418 }
405419
406- var isCommand = returnType . GetInterfaces ( ) . Where ( x => x . IsGenericType )
407- . Any ( x => CommandTypes . Contains ( x . GetGenericTypeDefinition ( ) ) ) ;
408- if ( isCommand == false )
420+ var commandType = returnType . GetInterfaces ( ) . Where ( x => x . IsGenericType )
421+ . FirstOrDefault ( x => CommandTypes . Contains ( x . GetGenericTypeDefinition ( ) ) ) ;
422+ if ( commandType == null )
409423 {
410424 throw new ArgumentException (
411425 "handler does not return command, check if delegate returns type that implements ICommand<> or ICommand<,>" ) ;
412426 }
413427
414- return returnType ;
428+ Type ? [ ] genericParams = commandType . GetGenericArguments ( ) ;
429+ if ( genericParams . Length == 1 )
430+ {
431+ genericParams = [ null , genericParams [ 0 ] ] ;
432+ }
433+
434+ return ( returnType , genericParams [ 0 ] , genericParams [ 1 ] ! ) ;
415435 }
416436
417- private static Type EnsureReturnTypeIsQuery ( Delegate handler )
437+ private static ( Type , Type ) EnsureReturnTypeIsQuery ( Delegate handler )
418438 {
419439 var returnType = handler . Method . ReturnType ;
420440 if ( returnType . IsGenericType && returnType . GetGenericTypeDefinition ( ) == typeof ( Task < > ) )
421441 {
422442 returnType = returnType . GenericTypeArguments . First ( ) ;
423443 }
424444
425- var isCommand = returnType . GetInterfaces ( ) . Where ( x => x . IsGenericType )
426- . Any ( x => QueryTypes . Contains ( x . GetGenericTypeDefinition ( ) ) ) ;
427- if ( isCommand == false )
445+ var queryInterface = returnType . GetInterfaces ( ) . Where ( x => x . IsGenericType )
446+ . FirstOrDefault ( x => QueryTypes . Contains ( x . GetGenericTypeDefinition ( ) ) ) ;
447+ if ( queryInterface == null )
428448 {
429449 throw new ArgumentException (
430450 "handler does not return query, check if delegate returns type that implements IQuery<>" ) ;
431451 }
432452
433- return returnType ;
453+ return ( returnType , queryInterface . GenericTypeArguments [ 0 ] ) ;
434454 }
435455
436456 private static List < T [ ] > GetNotEmptySubsets < T > ( ICollection < T > items )
@@ -446,4 +466,24 @@ private static List<T[]> GetNotEmptySubsets<T>(ICollection<T> items)
446466
447467 return results ;
448468 }
469+
470+ private static RouteHandlerBuilder AddCommandOpenApiDescriptions (
471+ this RouteHandlerBuilder builder ,
472+ Type commandType ,
473+ Type ? responseType ,
474+ Type errorType )
475+ {
476+ var commandResponseType = responseType is null
477+ ? typeof ( CommandResponse < > ) . MakeGenericType ( errorType )
478+ : typeof ( CommandResponse < , > ) . MakeGenericType ( responseType , errorType ) ;
479+ builder . Produces ( 200 , commandResponseType )
480+ . Produces ( 400 , commandResponseType )
481+ . WithTags ( "Commands" ) ;
482+ if ( commandType . GetInterfaces ( ) . Any ( i => i == typeof ( ILockableRequest ) ) )
483+ {
484+ builder . Produces ( 429 ) ;
485+ }
486+
487+ return builder ;
488+ }
449489}
0 commit comments