@@ -176,6 +176,20 @@ public bool TryGetDocumentForRole(string role, [NotNullWhen(true)] out string? d
176176 /// Generates an OpenAPI document filtered for a specific role.
177177 /// </summary>
178178 private OpenApiDocument ? GenerateDocumentForRole ( RuntimeConfig runtimeConfig , string role )
179+ {
180+ string title = $ "{ DOCUMENTOR_UI_TITLE } - { role } ";
181+ return BuildOpenApiDocument ( runtimeConfig , role , title ) ;
182+ }
183+
184+ /// <summary>
185+ /// Builds an OpenAPI document with optional role-based filtering.
186+ /// Shared logic for both superset and role-specific document generation.
187+ /// </summary>
188+ /// <param name="runtimeConfig">Runtime configuration.</param>
189+ /// <param name="role">Optional role to filter permissions. If null, returns superset of all roles.</param>
190+ /// <param name="title">Document title.</param>
191+ /// <returns>OpenAPI document.</returns>
192+ private OpenApiDocument BuildOpenApiDocument ( RuntimeConfig runtimeConfig , string ? role , string title )
179193 {
180194 string restEndpointPath = runtimeConfig . RestPath ;
181195 string ? runtimeBaseRoute = runtimeConfig . Runtime ? . BaseRoute ;
@@ -208,8 +222,7 @@ public bool TryGetDocumentForRole(string role, [NotNullWhen(true)] out string? d
208222 Info = new OpenApiInfo
209223 {
210224 Version = ProductInfo . GetProductVersion ( ) ,
211- // Use the role name directly since it was already validated to exist in permissions
212- Title = $ "{ DOCUMENTOR_UI_TITLE } - { role } "
225+ Title = title
213226 } ,
214227 Servers = new List < OpenApiServer >
215228 {
@@ -250,48 +263,7 @@ public void CreateDocument(bool doOverrideExistingDocument = false)
250263
251264 try
252265 {
253- string restEndpointPath = runtimeConfig . RestPath ;
254- string ? runtimeBaseRoute = runtimeConfig . Runtime ? . BaseRoute ;
255- string url = string . IsNullOrEmpty ( runtimeBaseRoute ) ? restEndpointPath : runtimeBaseRoute + "/" + restEndpointPath ;
256- OpenApiComponents components = new ( )
257- {
258- Schemas = CreateComponentSchemas ( runtimeConfig . Entities , runtimeConfig . DefaultDataSourceName , role : null , isRequestBodyStrict : runtimeConfig . IsRequestBodyStrict )
259- } ;
260-
261- // Collect all entity tags and their descriptions for the top-level tags array
262- // Only include entities that have REST enabled and at least one available operation
263- List < OpenApiTag > globalTags = new ( ) ;
264- foreach ( KeyValuePair < string , Entity > kvp in runtimeConfig . Entities )
265- {
266- Entity entity = kvp . Value ;
267- if ( ! entity . Rest . Enabled || ! HasAnyAvailableOperations ( entity ) )
268- {
269- continue ;
270- }
271-
272- string restPath = entity . Rest ? . Path ?? kvp . Key ;
273- globalTags . Add ( new OpenApiTag
274- {
275- Name = restPath ,
276- Description = string . IsNullOrWhiteSpace ( entity . Description ) ? null : entity . Description
277- } ) ;
278- }
279-
280- OpenApiDocument doc = new ( )
281- {
282- Info = new OpenApiInfo
283- {
284- Version = ProductInfo . GetProductVersion ( ) ,
285- Title = DOCUMENTOR_UI_TITLE
286- } ,
287- Servers = new List < OpenApiServer >
288- {
289- new ( ) { Url = url }
290- } ,
291- Paths = BuildPaths ( runtimeConfig . Entities , runtimeConfig . DefaultDataSourceName ) ,
292- Components = components ,
293- Tags = globalTags
294- } ;
266+ OpenApiDocument doc = BuildOpenApiDocument ( runtimeConfig , role : null , title : DOCUMENTOR_UI_TITLE ) ;
295267 _openApiDocument = doc ;
296268 }
297269 catch ( Exception ex )
@@ -1314,13 +1286,21 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas(RuntimeEntities
13141286 // Filter fields based on the superset of permissions across all roles (or specific role)
13151287 exposedColumnNames = FilterFieldsByPermissions ( entity , exposedColumnNames , role ) ;
13161288
1289+ // Get configured operations to determine which schemas to generate
1290+ Dictionary < OperationType , bool > configuredOps = GetConfiguredRestOperations ( entity , dbObject , role ) ;
1291+ bool hasPostOperation = configuredOps . GetValueOrDefault ( OperationType . Post ) ;
1292+ bool hasPutPatchOperation = configuredOps . GetValueOrDefault ( OperationType . Put ) || configuredOps . GetValueOrDefault ( OperationType . Patch ) ;
1293+
13171294 HashSet < string > nonAutoGeneratedPKColumnNames = new ( ) ;
13181295
13191296 if ( dbObject . SourceType is EntitySourceType . StoredProcedure )
13201297 {
1321- // Request body schema whose properties map to stored procedure parameters
1322- DatabaseStoredProcedure spObject = ( DatabaseStoredProcedure ) dbObject ;
1323- schemas . Add ( entityName + SP_REQUEST_SUFFIX , CreateSpRequestComponentSchema ( fields : spObject . StoredProcedureDefinition . Parameters , isRequestBodyStrict : isRequestBodyStrict ) ) ;
1298+ // Only generate request body schema if SP has operations that use it
1299+ if ( hasPostOperation || hasPutPatchOperation )
1300+ {
1301+ DatabaseStoredProcedure spObject = ( DatabaseStoredProcedure ) dbObject ;
1302+ schemas . Add ( entityName + SP_REQUEST_SUFFIX , CreateSpRequestComponentSchema ( fields : spObject . StoredProcedureDefinition . Parameters , isRequestBodyStrict : isRequestBodyStrict ) ) ;
1303+ }
13241304
13251305 // Response body schema whose properties map to the stored procedure's first result set columns
13261306 // as described by sys.dm_exec_describe_first_result_set.
@@ -1334,43 +1314,47 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas(RuntimeEntities
13341314 // Response schemas don't need additionalProperties restriction
13351315 schemas . Add ( entityName , CreateComponentSchema ( entityName , fields : exposedColumnNames , metadataProvider , entities , isRequestBodySchema : false ) ) ;
13361316
1337- // Create an entity's request body component schema excluding autogenerated primary keys.
1338- // A POST request requires any non-autogenerated primary key references to be in the request body.
1339- foreach ( string primaryKeyColumn in sourceDefinition . PrimaryKey )
1317+ // Only generate request body schemas if mutation operations are available
1318+ if ( hasPostOperation || hasPutPatchOperation )
13401319 {
1341- // Non-Autogenerated primary key(s) should appear in the request body.
1342- if ( ! sourceDefinition . Columns [ primaryKeyColumn ] . IsAutoGenerated )
1320+ // Create an entity's request body component schema excluding autogenerated primary keys.
1321+ // A POST request requires any non-autogenerated primary key references to be in the request body.
1322+ foreach ( string primaryKeyColumn in sourceDefinition . PrimaryKey )
13431323 {
1344- nonAutoGeneratedPKColumnNames . Add ( primaryKeyColumn ) ;
1345- continue ;
1346- }
1324+ // Non-Autogenerated primary key(s) should appear in the request body.
1325+ if ( ! sourceDefinition . Columns [ primaryKeyColumn ] . IsAutoGenerated )
1326+ {
1327+ nonAutoGeneratedPKColumnNames . Add ( primaryKeyColumn ) ;
1328+ continue ;
1329+ }
13471330
1348- if ( metadataProvider . TryGetExposedColumnName ( entityName , backingFieldName : primaryKeyColumn , out string ? exposedColumnName )
1349- && exposedColumnName is not null )
1350- {
1351- exposedColumnNames . Remove ( exposedColumnName ) ;
1331+ if ( metadataProvider . TryGetExposedColumnName ( entityName , backingFieldName : primaryKeyColumn , out string ? exposedColumnName )
1332+ && exposedColumnName is not null )
1333+ {
1334+ exposedColumnNames . Remove ( exposedColumnName ) ;
1335+ }
13521336 }
1353- }
13541337
1355- // Request body schema for POST - apply additionalProperties based on strict mode
1356- schemas . Add ( $ "{ entityName } _NoAutoPK", CreateComponentSchema ( entityName , fields : exposedColumnNames , metadataProvider , entities , isRequestBodySchema : true , isRequestBodyStrict : isRequestBodyStrict ) ) ;
1338+ // Request body schema for POST - apply additionalProperties based on strict mode
1339+ schemas . Add ( $ "{ entityName } _NoAutoPK", CreateComponentSchema ( entityName , fields : exposedColumnNames , metadataProvider , entities , isRequestBodySchema : true , isRequestBodyStrict : isRequestBodyStrict ) ) ;
13571340
1358- // Create an entity's request body component schema excluding all primary keys
1359- // by removing the tracked non-autogenerated primary key column names and removing them from
1360- // the exposedColumnNames collection.
1361- // The schema component without primary keys is used for PUT and PATCH operation request bodies because
1362- // those operations require all primary key references to be in the URI path, not the request body.
1363- foreach ( string primaryKeyColumn in nonAutoGeneratedPKColumnNames )
1364- {
1365- if ( metadataProvider . TryGetExposedColumnName ( entityName , backingFieldName : primaryKeyColumn , out string ? exposedColumnName )
1366- && exposedColumnName is not null )
1341+ // Create an entity's request body component schema excluding all primary keys
1342+ // by removing the tracked non-autogenerated primary key column names and removing them from
1343+ // the exposedColumnNames collection.
1344+ // The schema component without primary keys is used for PUT and PATCH operation request bodies because
1345+ // those operations require all primary key references to be in the URI path, not the request body.
1346+ foreach ( string primaryKeyColumn in nonAutoGeneratedPKColumnNames )
13671347 {
1368- exposedColumnNames . Remove ( exposedColumnName ) ;
1348+ if ( metadataProvider . TryGetExposedColumnName ( entityName , backingFieldName : primaryKeyColumn , out string ? exposedColumnName )
1349+ && exposedColumnName is not null )
1350+ {
1351+ exposedColumnNames . Remove ( exposedColumnName ) ;
1352+ }
13691353 }
1370- }
13711354
1372- // Request body schema for PUT/PATCH - apply additionalProperties based on strict mode
1373- schemas . Add ( $ "{ entityName } _NoPK", CreateComponentSchema ( entityName , fields : exposedColumnNames , metadataProvider , entities , isRequestBodySchema : true , isRequestBodyStrict : isRequestBodyStrict ) ) ;
1355+ // Request body schema for PUT/PATCH - apply additionalProperties based on strict mode
1356+ schemas . Add ( $ "{ entityName } _NoPK", CreateComponentSchema ( entityName , fields : exposedColumnNames , metadataProvider , entities , isRequestBodySchema : true , isRequestBodyStrict : isRequestBodyStrict ) ) ;
1357+ }
13741358 }
13751359 }
13761360
0 commit comments