Skip to content

Commit 2b34e0b

Browse files
Remove trailing slash from OpenAPI server URLs when pathBase is empty (#64716)
* Initial plan * Fix: Remove trailing slash from OpenAPI server URLs when pathBase is empty Co-authored-by: captainsafia <[email protected]> * Update snapshot test for trailing slash fix Co-authored-by: captainsafia <[email protected]> * Improve code comment for trailing slash removal logic Co-authored-by: captainsafia <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: captainsafia <[email protected]>
1 parent 8d32470 commit 2b34e0b

File tree

3 files changed

+43
-2
lines changed

3 files changed

+43
-2
lines changed

src/OpenApi/src/Services/OpenApiDocumentService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ internal List<OpenApiServer> GetOpenApiServers(HttpRequest? httpRequest = null)
209209
if (httpRequest is not null)
210210
{
211211
var serverUrl = UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase);
212+
// Remove trailing slash when pathBase is empty to align with OpenAPI specification.
213+
// Keep the trailing slash if pathBase explicitly contains "/" to preserve intentional path structure.
214+
if (serverUrl.EndsWith('/') && !httpRequest.PathBase.HasValue)
215+
{
216+
serverUrl = serverUrl.TrimEnd('/');
217+
}
212218
return [new OpenApiServer { Url = serverUrl }];
213219
}
214220
else

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApiDocumentLocalizationTests.VerifyOpenApiDocumentIsInvariant.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"servers": [
99
{
10-
"url": "http://localhost/"
10+
"url": "http://localhost"
1111
}
1212
],
1313
"paths": {

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Servers.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
public partial class OpenApiDocumentServiceTests
1212
{
1313
[Theory]
14-
[InlineData("Development", "localhost:5001", "", "http", "http://localhost:5001/")]
14+
[InlineData("Development", "localhost:5001", "", "http", "http://localhost:5001")]
1515
[InlineData("Development", "example.com", "/api", "https", "https://example.com/api")]
1616
[InlineData("Staging", "localhost:5002", "/v1", "http", "http://localhost:5002/v1")]
1717
[InlineData("Staging", "api.example.com", "/base/path", "https", "https://api.example.com/base/path")]
@@ -145,4 +145,39 @@ public void GetOpenApiServers_HandlesServerAddressFeatureWithNoValues()
145145
// Assert
146146
Assert.Empty(servers);
147147
}
148+
149+
[Fact]
150+
public void GetOpenApiServers_RemovesTrailingSlashWhenPathBaseIsEmpty()
151+
{
152+
// Arrange
153+
var hostEnvironment = new HostingEnvironment
154+
{
155+
ApplicationName = "TestApplication",
156+
EnvironmentName = "Development"
157+
};
158+
var docService = new OpenApiDocumentService(
159+
"v1",
160+
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
161+
hostEnvironment,
162+
GetMockOptionsMonitor(),
163+
new Mock<IKeyedServiceProvider>().Object,
164+
new OpenApiTestServer(["http://localhost:5000"]));
165+
var httpContext = new DefaultHttpContext()
166+
{
167+
Request =
168+
{
169+
Host = new HostString("example.com"),
170+
PathBase = "",
171+
Scheme = "https"
172+
}
173+
};
174+
175+
// Act
176+
var servers = docService.GetOpenApiServers(httpContext.Request);
177+
178+
// Assert
179+
Assert.Single(servers);
180+
Assert.Equal("https://example.com", servers[0].Url);
181+
Assert.DoesNotContain("https://example.com/", servers.Select(s => s.Url));
182+
}
148183
}

0 commit comments

Comments
 (0)