From 5df1df87d4540ed21655f9abeef6d7346d0849cd Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 13 May 2026 20:15:50 +0000
Subject: [PATCH 1/5] perf: replace String.Format/sprintf with string
concatenation; fast-path createHttpRequest for empty query params
- combineUrl: replace sprintf with string concatenation (avoids format-string parsing on every HTTP call)
- createHttpRequest: skip UriBuilder+ParseQueryString allocation when queryParams is empty (common for
operations with no query parameters)
- formatObject: replace String.Format calls with direct string concatenation (avoids boxing string args)
- buildXmlDoc: use String.Concat to combine parts in a single allocation instead of chained +
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/SwaggerProvider.DesignTime/Utils.fs | 2 +-
src/SwaggerProvider.Runtime/RuntimeHelpers.fs | 32 +++++++++++--------
2 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/src/SwaggerProvider.DesignTime/Utils.fs b/src/SwaggerProvider.DesignTime/Utils.fs
index ac5f7d6b..783a09cf 100644
--- a/src/SwaggerProvider.DesignTime/Utils.fs
+++ b/src/SwaggerProvider.DesignTime/Utils.fs
@@ -382,7 +382,7 @@ module XmlDoc =
| Some rd when not(String.IsNullOrWhiteSpace rd) -> $"{escapeXml rd}"
| _ -> ""
- summaryPart + remarksPart + paramParts + returnsPart
+ String.Concat(summaryPart, remarksPart, paramParts, returnsPart)
type UniqueNameGenerator(?occupiedNames: string seq) =
let hash = System.Collections.Generic.HashSet<_>()
diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
index 4807e259..1fac9192 100644
--- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
+++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
@@ -343,7 +343,7 @@ module RuntimeHelpers =
let vTy = v.GetType()
if vTy = typeof then
- String.Format("\"{0}\"", v)
+ "\"" + v.ToString() + "\""
elif vTy.IsArray then
let elements =
(v :?> Array)
@@ -351,13 +351,13 @@ module RuntimeHelpers =
|> Seq.map(fun x -> if isNull x then "null" else x.ToString())
|> Array.ofSeq
- String.Format("[{0}]", String.Join("; ", elements))
+ "[" + String.Join("; ", elements) + "]"
else
v.ToString()
- String.Format("{0}={1}", p.Name, s))
+ p.Name + "=" + s)
- String.Format("{{{0}}}", String.Join("; ", strs))
+ "{" + String.Join("; ", strs) + "}"
// Cached constructor for JsonPropertyNameAttribute to avoid repeated reflection lookups
// when compiling large schemas with many properties.
@@ -501,7 +501,7 @@ module RuntimeHelpers =
new HttpClient(handler, true, BaseAddress = Uri(host))
let combineUrl (urlA: string) (urlB: string) =
- sprintf "%s/%s" (urlA.TrimEnd('/')) (urlB.TrimStart('/'))
+ urlA.TrimEnd('/') + "/" + urlB.TrimStart('/')
// Pre-built map of standard HTTP method names to their corresponding static HttpMethod
// instances. Uses an ordinal case-insensitive comparer so callers passing different
@@ -529,18 +529,22 @@ module RuntimeHelpers =
| true, m -> m
| false, _ -> HttpMethod(method.ToUpperInvariant())
- let createHttpRequest (httpMethod: string) address queryParams =
+ let createHttpRequest (httpMethod: string) address (queryParams: (string * string) list) =
let requestUrl =
- let fakeHost = "http://fake-host/"
- let builder = UriBuilder(combineUrl fakeHost address)
- let query = System.Web.HttpUtility.ParseQueryString(builder.Query)
+ // Fast path: avoid UriBuilder + ParseQueryString allocation when there are no query params.
+ if List.isEmpty queryParams then
+ address
+ else
+ let fakeHost = "http://fake-host/"
+ let builder = UriBuilder(combineUrl fakeHost address)
+ let query = System.Web.HttpUtility.ParseQueryString(builder.Query)
- for name, value in queryParams do
- if not <| isNull value then
- query.Add(name, value)
+ for name, value in queryParams do
+ if not <| isNull value then
+ query.Add(name, value)
- builder.Query <- query.ToString()
- builder.Uri.PathAndQuery.TrimStart('/')
+ builder.Query <- query.ToString()
+ builder.Uri.PathAndQuery.TrimStart('/')
let method = resolveHttpMethod httpMethod
new HttpRequestMessage(method, Uri(requestUrl, UriKind.Relative))
From bcc965b189e063dd1e7c2d4adb030b485a6e2ebb Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Wed, 13 May 2026 20:15:53 +0000
Subject: [PATCH 2/5] ci: trigger checks
From 818375befc0dff125c8f364d377425d07816271a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 21:26:30 +0000
Subject: [PATCH 3/5] fix: revert String.Concat in Utils.fs (design-time code,
minimal gain, netstandard2.0 risk)
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1f6fb00f-2880-4601-9bc2-e05206c1878e
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
---
src/SwaggerProvider.DesignTime/Utils.fs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/SwaggerProvider.DesignTime/Utils.fs b/src/SwaggerProvider.DesignTime/Utils.fs
index 783a09cf..ac5f7d6b 100644
--- a/src/SwaggerProvider.DesignTime/Utils.fs
+++ b/src/SwaggerProvider.DesignTime/Utils.fs
@@ -382,7 +382,7 @@ module XmlDoc =
| Some rd when not(String.IsNullOrWhiteSpace rd) -> $"{escapeXml rd}"
| _ -> ""
- String.Concat(summaryPart, remarksPart, paramParts, returnsPart)
+ summaryPart + remarksPart + paramParts + returnsPart
type UniqueNameGenerator(?occupiedNames: string seq) =
let hash = System.Collections.Generic.HashSet<_>()
From e92fa61e91486a6153f08abf6ecd0cb8026ad85f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 21:32:50 +0000
Subject: [PATCH 4/5] fix: strip leading slash in createHttpRequest fast path +
add tests
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1a1e0084-4082-4409-b5db-938fe506ba2a
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
---
src/SwaggerProvider.Runtime/RuntimeHelpers.fs | 8 +++++--
.../RuntimeHelpersTests.fs | 21 +++++++++++++++++++
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
index 1fac9192..194cf3dc 100644
--- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
+++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
@@ -529,11 +529,15 @@ module RuntimeHelpers =
| true, m -> m
| false, _ -> HttpMethod(method.ToUpperInvariant())
- let createHttpRequest (httpMethod: string) address (queryParams: (string * string) list) =
+ let createHttpRequest (httpMethod: string) (address: string) (queryParams: (string * string) list) =
let requestUrl =
// Fast path: avoid UriBuilder + ParseQueryString allocation when there are no query params.
+ // TrimStart('/') mirrors the UriBuilder path's PathAndQuery.TrimStart('/') normalisation,
+ // which strips the leading slash from schema paths such as "/pets" → "pets". A leading-
+ // slash relative URI resolves from the host root and silently drops any base path, so
+ // normalisation must be applied on both branches.
if List.isEmpty queryParams then
- address
+ address.TrimStart('/')
else
let fakeHost = "http://fake-host/"
let builder = UriBuilder(combineUrl fakeHost address)
diff --git a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs
index 5a1bbb85..e36ee868 100644
--- a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs
+++ b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs
@@ -553,6 +553,27 @@ module CreateHttpRequestTests =
use req = createHttpRequest "GET" "v1/pets/42" []
req.RequestUri.ToString() |> shouldContainText "v1/pets/42"
+ []
+ let ``createHttpRequest strips leading slash from path (no query params)``() =
+ // OpenAPI schema path keys start with '/'; the leading slash must be removed so that
+ // the relative URI is resolved relative to the HttpClient.BaseAddress path rather
+ // than from the host root.
+ use req = createHttpRequest "GET" "/pets" []
+ req.RequestUri.ToString() |> shouldEqual "pets"
+
+ []
+ let ``createHttpRequest strips leading slash from path (with query params)``() =
+ use req = createHttpRequest "GET" "/pets" [ ("status", "available") ]
+ let uri = req.RequestUri.ToString()
+ uri |> shouldNotContainText "/pets"
+ uri |> shouldContainText "pets"
+ uri |> shouldContainText "status=available"
+
+ []
+ let ``createHttpRequest strips leading slash from nested path (no query params)``() =
+ use req = createHttpRequest "GET" "/pets/42" []
+ req.RequestUri.ToString() |> shouldEqual "pets/42"
+
module FillHeadersTests =
From a60dc42bf90ef218572fa6c52eb77823744eeb58 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 14 May 2026 07:23:58 +0000
Subject: [PATCH 5/5] fix: use seq in createHttpRequest to
preserve original API compatibility
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/eef54ff5-7fe4-4f39-9258-82cc720dd70d
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
---
src/SwaggerProvider.Runtime/RuntimeHelpers.fs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
index 194cf3dc..504c38f3 100644
--- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
+++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs
@@ -529,14 +529,14 @@ module RuntimeHelpers =
| true, m -> m
| false, _ -> HttpMethod(method.ToUpperInvariant())
- let createHttpRequest (httpMethod: string) (address: string) (queryParams: (string * string) list) =
+ let createHttpRequest (httpMethod: string) (address: string) (queryParams: seq) =
let requestUrl =
// Fast path: avoid UriBuilder + ParseQueryString allocation when there are no query params.
// TrimStart('/') mirrors the UriBuilder path's PathAndQuery.TrimStart('/') normalisation,
// which strips the leading slash from schema paths such as "/pets" → "pets". A leading-
// slash relative URI resolves from the host root and silently drops any base path, so
// normalisation must be applied on both branches.
- if List.isEmpty queryParams then
+ if Seq.isEmpty queryParams then
address.TrimStart('/')
else
let fakeHost = "http://fake-host/"