Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions src/SwaggerProvider.Runtime/RuntimeHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -343,21 +343,21 @@ module RuntimeHelpers =
let vTy = v.GetType()

if vTy = typeof<string> then
String.Format("\"{0}\"", v)
"\"" + v.ToString() + "\""
elif vTy.IsArray then
let elements =
(v :?> Array)
|> Seq.cast<obj>
|> 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.
Expand Down Expand Up @@ -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('/')
Comment thread
sergey-tihon marked this conversation as resolved.

// Pre-built map of standard HTTP method names to their corresponding static HttpMethod
// instances. Uses an ordinal case-insensitive comparer so callers passing different
Expand Down Expand Up @@ -529,18 +529,26 @@ module RuntimeHelpers =
| true, m -> m
| false, _ -> HttpMethod(method.ToUpperInvariant())

let createHttpRequest (httpMethod: string) address queryParams =
let createHttpRequest (httpMethod: string) (address: string) (queryParams: seq<string * string>) =
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.
// 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 Seq.isEmpty queryParams then
address.TrimStart('/')
else
let fakeHost = "http://fake-host/"
Comment thread
sergey-tihon marked this conversation as resolved.
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))
Expand Down
21 changes: 21 additions & 0 deletions tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,27 @@ module CreateHttpRequestTests =
use req = createHttpRequest "GET" "v1/pets/42" []
req.RequestUri.ToString() |> shouldContainText "v1/pets/42"

[<Fact>]
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"

[<Fact>]
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"

[<Fact>]
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 =

Expand Down
Loading