-
Notifications
You must be signed in to change notification settings - Fork 304
Fix duplicate entity groups in Swagger UI when entities have descriptions #3099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@copilot There is a related problem in #2946. Please include in this PR. Current behavior: The OpenAPI document shows all HTTP methods (GET, POST, PUT, PATCH, DELETE) for every entity, regardless of:
Example scenario: {
"entities": {
"Book": {
"source": { "object": "books", "type": "table" },
"permissions": [
{
"role": "anonymous",
"actions": [
{ "action": "read" } // ← Only READ allowed
]
}
]
}
}
}Current OpenAPI output (WRONG): paths:
/api/Book:
get: # ✅ Should show (read allowed)
post: # ❌ Shouldn't show (create not allowed)
/api/Book/{id}:
get: # ✅ Should show (read allowed)
put: # ❌ Shouldn't show (update not allowed)
patch: # ❌ Shouldn't show (update not allowed)
delete: # ❌ Shouldn't show (delete not allowed)Expected OpenAPI output (for anonymous role): paths:
/api/Book:
get: # ✅ Only show allowed methods
/api/Book/{id}:
get: # ✅ Only show allowed methodsWhy This Matters1. API Documentation AccuracyCurrent OpenAPI docs mislead developers by showing methods they can't use:
2. Security Through ObscurityWhile not a primary security measure, hiding unavailable methods:
3. Better Developer Experience
4. Role-Specific DocumentationDifferent users see different API capabilities:
Current ImplementationThe OpenAPI generator currently creates all methods regardless of permissions: private static Dictionary<OperationType, bool> GetConfiguredRestOperations(Entity entity, DatabaseObject dbObject)
{
Dictionary<OperationType, bool> configuredOperations = new()
{
[OperationType.Get] = false,
[OperationType.Post] = false,
[OperationType.Put] = false,
[OperationType.Patch] = false,
[OperationType.Delete] = false
};
if (dbObject.SourceType == EntitySourceType.StoredProcedure && entity is not null)
{
// For stored procedures, check configured HTTP methods
// ...
}
else
{
// For tables/views, ALL operations are enabled by default
configuredOperations[OperationType.Get] = true;
configuredOperations[OperationType.Post] = true;
configuredOperations[OperationType.Put] = true;
configuredOperations[OperationType.Patch] = true;
configuredOperations[OperationType.Delete] = true; // ← No permission check!
}
return configuredOperations;
}The problem: This method doesn't check entity permissions—it assumes all CRUD operations are available. Proposed SolutionThe enhancement suggests: 1. Detect Current User's RoleWhen generating OpenAPI, determine the requesting user's role from:
2. Filter Operations Based on PermissionsFor each entity, check the role's permissions and only include allowed operations: // Pseudo-code for proposed solution
private Dictionary<OperationType, bool> GetConfiguredRestOperations(
Entity entity,
DatabaseObject dbObject,
string currentRole) // ← NEW: role parameter
{
var configuredOperations = new Dictionary<OperationType, bool>();
// Get permissions for the current role
var rolePermissions = entity.Permissions
.FirstOrDefault(p => p.Role == currentRole);
if (rolePermissions == null)
{
// Role has no permissions, show nothing
return configuredOperations;
}
// Map actions to HTTP methods
foreach (var action in rolePermissions.Actions)
{
switch (action.Action)
{
case "read":
configuredOperations[OperationType.Get] = true;
break;
case "create":
configuredOperations[OperationType.Post] = true;
break;
case "update":
configuredOperations[OperationType.Put] = true;
configuredOperations[OperationType.Patch] = true;
break;
case "delete":
configuredOperations[OperationType.Delete] = true;
break;
case "*":
// All operations
configuredOperations[OperationType.Get] = true;
configuredOperations[OperationType.Post] = true;
configuredOperations[OperationType.Put] = true;
configuredOperations[OperationType.Patch] = true;
configuredOperations[OperationType.Delete] = true;
break;
}
}
return configuredOperations;
}3. Generate Role-Specific OpenAPI DocumentsOption A: Dynamic per-request
Option B: Multiple static documents
Option C: Security schemes with role filter
Example ImplementationConfiguration:{
"entities": {
"Book": {
"source": { "object": "books", "type": "table" },
"permissions": [
{
"role": "anonymous",
"actions": ["read"]
},
{
"role": "authenticated",
"actions": ["read", "create"]
},
{
"role": "admin",
"actions": ["*"]
}
]
}
}
}OpenAPI for
|
Co-authored-by: JerryNixon <[email protected]>
Co-authored-by: JerryNixon <[email protected]>
Co-authored-by: JerryNixon <[email protected]>
I need clarification on the scope for issue #2946: Question 1: Role-specific vs. All-roles documentation?
Question 2: Anonymous role handling?
Current behavior: OpenAPI shows all CRUD operations regardless of ANY role's permissions, which is misleading. Suggested approach (Option A): Filter to show only operations that at least one role can perform. This is the minimal change that fixes the core issue without requiring role-specific documentation infrastructure. Please confirm which approach you prefer so I can implement the right solution. |
Why make this change?
Swagger UI displays duplicate groups for entities with descriptions (e.g., "Actor" appears twice). OpenAPI tags were instantiated separately in
CreateDocument(global tags) andBuildPaths(operation tags), causing Swagger UI to treat identical tag names as distinct groups.What is this change?
Share tag instances between global and operation-level tags:
Before:
After:
Changes:
OpenApiDocumentor.CreateDocument: Store tags inDictionary<string, OpenApiTag>keyed by REST pathOpenApiDocumentor.BuildPaths: Accept dictionary parameter and reuse existing tag instancesHow was this tested?
OpenApiDocumentor_NoDuplicateTagstest verifying:ReferenceEquals)Sample Request(s)
N/A - This is an OpenAPI document generation fix. The Swagger UI at
/api/openapiwill now display each entity group once instead of duplicating them.Original prompt
This section details on the original issue you should resolve
<issue_title>[Bug]: Swagger UI shows duplicate entity groups for single entity definition</issue_title>
<issue_description>When using Data API builder 1.7.75, the Swagger UI generated at
/api/openapidisplays theActorentity twice, even thoughActoris defined only once in the configuration. No other entity shares its REST path or alias.Steps to Reproduce:
Use this configuration (trimmed for brevity):
{ "entities": { "Actor": { "source": { "object": "dbo.Actor", "type": "table" }, "rest": { "enabled": true, "path": "/Actor" } }, "Character": { "source": { "object": "dbo.Character", "type": "table" }, "rest": { "enabled": true, "path": "/Character" } }, "SeriesActors": { "source": { "object": "dbo.SeriesActors", "type": "view" }, "rest": { "enabled": true, "path": "/SeriesActors" } }, "GetSeriesActors": { "source": { "object": "dbo.GetSeriesActors", "type": "stored-procedure" }, "rest": { "enabled": true, "path": "/GetSeriesActors", "methods": ["post"] } } } }Start DAB (
dab start --config dab-config.json).Open the Swagger UI at
/api/openapi.Expected Behavior:
Each entity should appear once under its respective REST path group.
Actual Behavior:
Swagger shows two separate groups labeled
Actor, each listing the same endpoints (GET,PUT,PATCH,DELETE,POST).Environment:
Notes:
path: "/Actor".OpenApiDocumentBuilder.</issue_description>
<agent_instructions>Resolve this with as little new code as possible</agent_instructions>
Comments on the Issue (you are @copilot in this section)
@JerryNixon ### The Root CauseThe issue is that tags are being added to the OpenAPI document in TWO different places, and when an entity has a
descriptionproperty, it creates tags with different object identities but the same name.Where Tags Are Created
1. Global Tags (in
CreateDocumentmethod):2. Per-Path Tags (in
BuildPathsmethod):These per-path tags are then assigned to each operation, and each operation is added to the document's paths.
Why This Causes Duplication
When Swagger UI renders the OpenAPI document, it looks at:
doc.Tags) - Added inCreateDocumentTagsproperty) - Added inBuildPathsEven though both have the same
Name("Actor"), they are different object instances with the sameDescription. Swagger UI treats them as distinct tag definitions and displays them separately.Why You Noticed It with Descriptions
The user comment "I think this has to do with
descriptions" is correct! Here's why:Description = null, so Swagger might merge them💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.