@@ -139,10 +139,16 @@ public void CreateDocument(bool doOverrideExistingDocument = false)
139139 } ;
140140
141141 // Collect all entity tags and their descriptions for the top-level tags array
142+ // Only include entities that have REST enabled and at least one available operation
142143 List < OpenApiTag > globalTags = new ( ) ;
143144 foreach ( KeyValuePair < string , Entity > kvp in runtimeConfig . Entities )
144145 {
145146 Entity entity = kvp . Value ;
147+ if ( ! entity . Rest . Enabled || ! HasAnyAvailableOperations ( entity ) )
148+ {
149+ continue ;
150+ }
151+
146152 string restPath = entity . Rest ? . Path ?? kvp . Key ;
147153 globalTags . Add ( new OpenApiTag
148154 {
@@ -243,6 +249,12 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
243249
244250 Dictionary < OperationType , bool > configuredRestOperations = GetConfiguredRestOperations ( entity , dbObject ) ;
245251
252+ // Skip entities with no available operations
253+ if ( ! configuredRestOperations . ContainsValue ( true ) )
254+ {
255+ continue ;
256+ }
257+
246258 if ( dbObject . SourceType is EntitySourceType . StoredProcedure )
247259 {
248260 Dictionary < OperationType , OpenApiOperation > operations = CreateStoredProcedureOperations (
@@ -251,12 +263,15 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
251263 configuredRestOperations : configuredRestOperations ,
252264 tags : tags ) ;
253265
254- OpenApiPathItem openApiPathItem = new ( )
266+ if ( operations . Count > 0 )
255267 {
256- Operations = operations
257- } ;
268+ OpenApiPathItem openApiPathItem = new ( )
269+ {
270+ Operations = operations
271+ } ;
258272
259- pathsCollection . TryAdd ( entityBasePathComponent , openApiPathItem ) ;
273+ pathsCollection . TryAdd ( entityBasePathComponent , openApiPathItem ) ;
274+ }
260275 }
261276 else
262277 {
@@ -269,13 +284,12 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
269284 configuredRestOperations : configuredRestOperations ,
270285 tags : tags ) ;
271286
272- Tuple < string , List < OpenApiParameter > > pkComponents = CreatePrimaryKeyPathComponentAndParameters ( entityName , metadataProvider ) ;
273- string pkPathComponents = pkComponents . Item1 ;
274- string fullPathComponent = entityBasePathComponent + pkPathComponents ;
275-
276- // Only add path if there are operations available
277287 if ( pkOperations . Count > 0 )
278288 {
289+ Tuple < string , List < OpenApiParameter > > pkComponents = CreatePrimaryKeyPathComponentAndParameters ( entityName , metadataProvider ) ;
290+ string pkPathComponents = pkComponents . Item1 ;
291+ string fullPathComponent = entityBasePathComponent + pkPathComponents ;
292+
279293 OpenApiPathItem openApiPkPathItem = new ( )
280294 {
281295 Operations = pkOperations ,
@@ -293,7 +307,6 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
293307 configuredRestOperations : configuredRestOperations ,
294308 tags : tags ) ;
295309
296- // Only add path if there are operations available
297310 if ( operations . Count > 0 )
298311 {
299312 OpenApiPathItem openApiPathItem = new ( )
@@ -318,7 +331,7 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
318331 /// a path containing primary key parameters.
319332 /// TRUE: GET (one), PUT, PATCH, DELETE
320333 /// FALSE: GET (Many), POST</param>
321- /// <param name="configuredRestOperations">Dictionary indicating which operations are available based on permissions.</param>
334+ /// <param name="configuredRestOperations">Operations available based on permissions.</param>
322335 /// <param name="tags">Tags denoting how the operations should be categorized.
323336 /// Typically one tag value, the entity's REST path.</param>
324337 /// <returns>Collection of operation types and associated definitions.</returns>
@@ -333,10 +346,6 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
333346
334347 if ( includePrimaryKeyPathComponent )
335348 {
336- // The OpenApiResponses dictionary key represents the integer value of the HttpStatusCode,
337- // which is returned when using Enum.ToString("D").
338- // The "D" format specified "displays the enumeration entry as an integer value in the shortest representation possible."
339- // It will only contain $select query parameter to allow the user to specify which fields to return.
340349 if ( configuredRestOperations [ OperationType . Get ] )
341350 {
342351 OpenApiOperation getOperation = CreateBaseOperation ( description : GETONE_DESCRIPTION , tags : tags ) ;
@@ -345,11 +354,8 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
345354 openApiPathItemOperations . Add ( OperationType . Get , getOperation ) ;
346355 }
347356
348- // PUT and PATCH requests have the same criteria for decided whether a request body is required.
349357 bool requestBodyRequired = IsRequestBodyRequired ( sourceDefinition , considerPrimaryKeys : false ) ;
350358
351- // PUT requests must include the primary key(s) in the URI path and exclude from the request body,
352- // independent of whether the PK(s) are autogenerated.
353359 if ( configuredRestOperations [ OperationType . Put ] )
354360 {
355361 OpenApiOperation putOperation = CreateBaseOperation ( description : PUT_DESCRIPTION , tags : tags ) ;
@@ -359,8 +365,6 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
359365 openApiPathItemOperations . Add ( OperationType . Put , putOperation ) ;
360366 }
361367
362- // PATCH requests must include the primary key(s) in the URI path and exclude from the request body,
363- // independent of whether the PK(s) are autogenerated.
364368 if ( configuredRestOperations [ OperationType . Patch ] )
365369 {
366370 OpenApiOperation patchOperation = CreateBaseOperation ( description : PATCH_DESCRIPTION , tags : tags ) ;
@@ -381,7 +385,6 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
381385 }
382386 else
383387 {
384- // Primary key(s) are not included in the URI paths of the GET (all) and POST operations.
385388 if ( configuredRestOperations [ OperationType . Get ] )
386389 {
387390 OpenApiOperation getAllOperation = CreateBaseOperation ( description : GETALL_DESCRIPTION , tags : tags ) ;
@@ -394,12 +397,7 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
394397
395398 if ( configuredRestOperations [ OperationType . Post ] )
396399 {
397- // The POST body must include fields for primary key(s) which are not autogenerated because a value must be supplied
398- // for those fields. {entityName}_NoAutoPK represents the schema component which has all fields except for autogenerated primary keys.
399- // When no autogenerated primary keys exist, then all fields can be included in the POST body which is represented by the schema
400- // component: {entityName}.
401400 string postBodySchemaReferenceId = DoesSourceContainAutogeneratedPrimaryKey ( sourceDefinition ) ? $ "{ entityName } _NoAutoPK" : $ "{ entityName } ";
402-
403401 OpenApiOperation postOperation = CreateBaseOperation ( description : POST_DESCRIPTION , tags : tags ) ;
404402 postOperation . RequestBody = CreateOpenApiRequestBodyPayload ( postBodySchemaReferenceId , IsRequestBodyRequired ( sourceDefinition , considerPrimaryKeys : true ) ) ;
405403 postOperation . Responses . Add ( HttpStatusCode . Created . ToString ( "D" ) , CreateOpenApiResponse ( description : nameof ( HttpStatusCode . Created ) , responseObjectSchemaName : entityName ) ) ;
@@ -650,9 +648,8 @@ private static OpenApiParameter GetOpenApiQueryParameter(string name, string des
650648 /// <summary>
651649 /// Returns collection of OpenAPI OperationTypes and associated flag indicating whether they are enabled
652650 /// for the engine's REST endpoint.
653- /// For stored procedures, the available REST methods are determined by entity.Rest.Methods.
654- /// For tables and views, the available REST methods are determined by checking the entity's permissions
655- /// across all roles. Only operations that are available to at least one role are enabled.
651+ /// Acts as a helper for stored procedures where the runtime config can denote any combination of REST verbs
652+ /// to enable.
656653 /// </summary>
657654 /// <param name="entity">The entity.</param>
658655 /// <param name="dbObject">Database object metadata, indicating entity SourceType</param>
@@ -711,60 +708,72 @@ private static Dictionary<OperationType, bool> GetConfiguredRestOperations(Entit
711708 }
712709 else
713710 {
714- // For tables and views, determine available operations based on permissions across all roles.
715- // An operation is available if at least one role has permission for it.
716- HashSet < EntityActionOperation > availableOperations = GetAvailableOperationsFromPermissions ( entity ! ) ;
717-
718- // Map permission operations to REST operations:
719- // Read -> GET, Create -> POST, Update -> PUT/PATCH, Delete -> DELETE
720- configuredOperations [ OperationType . Get ] = availableOperations . Contains ( EntityActionOperation . Read ) ;
721- configuredOperations [ OperationType . Post ] = availableOperations . Contains ( EntityActionOperation . Create ) ;
722- configuredOperations [ OperationType . Put ] = availableOperations . Contains ( EntityActionOperation . Update ) ;
723- configuredOperations [ OperationType . Patch ] = availableOperations . Contains ( EntityActionOperation . Update ) ;
724- configuredOperations [ OperationType . Delete ] = availableOperations . Contains ( EntityActionOperation . Delete ) ;
711+ // For tables/views, determine available operations from permissions (superset of all roles)
712+ if ( entity ? . Permissions is not null )
713+ {
714+ foreach ( EntityPermission permission in entity . Permissions )
715+ {
716+ if ( permission . Actions is null )
717+ {
718+ continue ;
719+ }
720+
721+ foreach ( EntityAction action in permission . Actions )
722+ {
723+ if ( action . Action == EntityActionOperation . All )
724+ {
725+ configuredOperations [ OperationType . Get ] = true ;
726+ configuredOperations [ OperationType . Post ] = true ;
727+ configuredOperations [ OperationType . Put ] = true ;
728+ configuredOperations [ OperationType . Patch ] = true ;
729+ configuredOperations [ OperationType . Delete ] = true ;
730+ }
731+ else
732+ {
733+ switch ( action . Action )
734+ {
735+ case EntityActionOperation . Read :
736+ configuredOperations [ OperationType . Get ] = true ;
737+ break ;
738+ case EntityActionOperation . Create :
739+ configuredOperations [ OperationType . Post ] = true ;
740+ break ;
741+ case EntityActionOperation . Update :
742+ configuredOperations [ OperationType . Put ] = true ;
743+ configuredOperations [ OperationType . Patch ] = true ;
744+ break ;
745+ case EntityActionOperation . Delete :
746+ configuredOperations [ OperationType . Delete ] = true ;
747+ break ;
748+ }
749+ }
750+ }
751+ }
752+ }
725753 }
726754
727755 return configuredOperations ;
728756 }
729757
730758 /// <summary>
731- /// Returns the set of available operations for an entity by examining all permissions across all roles.
732- /// An operation is considered available if at least one role has permission for it.
733- /// The wildcard operation (*) expands to Create, Read, Update, and Delete.
759+ /// Checks if an entity has any available REST operations based on its permissions.
734760 /// </summary>
735- /// <param name="entity">The entity to examine permissions for.</param>
736- /// <returns>Set of available EntityActionOperations.</returns>
737- private static HashSet < EntityActionOperation > GetAvailableOperationsFromPermissions ( Entity entity )
761+ private static bool HasAnyAvailableOperations ( Entity entity )
738762 {
739- HashSet < EntityActionOperation > availableOperations = new ( ) ;
740-
741- if ( entity ? . Permissions is null )
763+ if ( entity ? . Permissions is null || entity . Permissions . Length == 0 )
742764 {
743- return availableOperations ;
765+ return false ;
744766 }
745767
746768 foreach ( EntityPermission permission in entity . Permissions )
747769 {
748- if ( permission . Actions is null )
749- {
750- continue ;
751- }
752-
753- foreach ( EntityAction action in permission . Actions )
770+ if ( permission . Actions ? . Length > 0 )
754771 {
755- if ( action . Action == EntityActionOperation . All )
756- {
757- // Wildcard (*) represents Create, Read, Update, Delete for tables/views
758- availableOperations . UnionWith ( EntityAction . ValidPermissionOperations ) ;
759- }
760- else if ( EntityAction . ValidPermissionOperations . Contains ( action . Action ) )
761- {
762- availableOperations . Add ( action . Action ) ;
763- }
772+ return true ;
764773 }
765774 }
766775
767- return availableOperations ;
776+ return false ;
768777 }
769778
770779 /// <summary>
@@ -1068,11 +1077,12 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas(RuntimeEntities
10681077 string entityName = entityDbMetadataMap . Key ;
10691078 DatabaseObject dbObject = entityDbMetadataMap . Value ;
10701079
1071- if ( ! entities . TryGetValue ( entityName , out Entity ? entity ) || ! entity . Rest . Enabled )
1080+ if ( ! entities . TryGetValue ( entityName , out Entity ? entity ) || ! entity . Rest . Enabled || ! HasAnyAvailableOperations ( entity ) )
10721081 {
10731082 // Don't create component schemas for:
10741083 // 1. Linking entity: The entity will be null when we are dealing with a linking entity, which is not exposed in the config.
10751084 // 2. Entity for which REST endpoint is disabled.
1085+ // 3. Entity with no available operations based on permissions.
10761086 continue ;
10771087 }
10781088
0 commit comments