@@ -299,6 +299,11 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
299299
300300 services . AddSwaggerGen ( options =>
301301 {
302+ // Several models declare nested types that share a name with a top-level model
303+ // (e.g. DelegationBatchInputDto.Permission vs AccessPackage.Permission). Swashbuckle's
304+ // default schema id is the plain type name, which makes those collide and fails generation.
305+ options . CustomSchemaIds ( SwaggerSchemaId ) ;
306+
302307 options . AddSecurityDefinition ( "oauth2" , new OpenApiSecurityScheme
303308 {
304309 Description = "Standard Authorization header using the Bearer scheme. Example: \" bearer {token}\" " ,
@@ -326,6 +331,24 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
326331 }
327332}
328333
334+ // Unique swagger schema id for a model type. Non-generic top-level types keep their plain name,
335+ // and generics keep Swashbuckle's "XOfY" shape, so existing schema names are unchanged. Nested
336+ // types are prefixed with their declaring type to keep them distinct from same-named models.
337+ static string SwaggerSchemaId ( Type type )
338+ {
339+ string name = type . Name ;
340+
341+ if ( type . IsGenericType )
342+ {
343+ name = string . Concat (
344+ name . AsSpan ( 0 , name . IndexOf ( '`' ) ) ,
345+ "Of" ,
346+ string . Join ( "And" , type . GetGenericArguments ( ) . Select ( SwaggerSchemaId ) ) ) ;
347+ }
348+
349+ return type . DeclaringType is null ? name : SwaggerSchemaId ( type . DeclaringType ) + name ;
350+ }
351+
329352void ConfigureMockableClients ( IServiceCollection services , IConfiguration config )
330353{
331354 MockSettings mockSettings = config . GetSection ( "MockSettings" ) . Get < MockSettings > ( ) ?? new MockSettings ( false ) ;
0 commit comments