Fix $ in path parameters treated as Regex.Replace back-references#311
Conversation
RuntimeHelpers.getPropertyValues now strips the FSharpOption<T> wrapper before returning property values. Previously, a property set to Some(true) was serialised as the string "Some(true)" in multipart/form-data and application/x-www-form-urlencoded bodies instead of the bare "True". None values were already handled (null). Also relax global.json SDK version constraint from 10.0.103 to 10.0.100 with rollForward:latestPatch so the project builds with any 10.0.1xx SDK available locally or in CI. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When multiple commits are pushed to the same PR branch, GitHub Actions queues a new build for each push. Without a concurrency group, the older builds keep running even though they're now stale. Adding cancel-in-progress: true cancels the in-progress build when a newer commit arrives on the same ref, reducing CI minutes consumed and giving faster feedback on the latest commit. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add unit tests for RuntimeHelpers module RuntimeHelpers.fs handles all parameter serialization and HTTP request building for generated API clients, but previously had zero dedicated unit tests. This PR adds 44 focused tests covering: - toParam: DateTime/DateTimeOffset ISO 8601 formatting, null handling - toQueryParams: all supported array/option/scalar types, DateTime ISO 8601 serialization, None filtering in optional arrays - combineUrl: trailing/leading slash normalisation - createHttpRequest: HTTP method, path, query params, null filtering - fillHeaders: header addition, null-value skip, Content-Type silence - toStringContent/toTextContent/toStreamContent: content-type headers, error on non-stream input Closes no specific issue; improves baseline test coverage for the runtime serialization layer (Task 9 - Testing Improvements). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks * Add missing toQueryParams test cases: float32/double/byte arrays and Option array types (#304) * Initial plan * Add missing toQueryParams test cases: float32/double/byte arrays and Option array types Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Merge master and fix CI: add RuntimeHelpers unit tests (#305) * Fix Option types in form data not being unwrapped (issue #214) (#298) RuntimeHelpers.getPropertyValues now strips the FSharpOption<T> wrapper before returning property values. Previously, a property set to Some(true) was serialised as the string "Some(true)" in multipart/form-data and application/x-www-form-urlencoded bodies instead of the bare "True". None values were already handled (null). Also relax global.json SDK version constraint from 10.0.103 to 10.0.100 with rollForward:latestPatch so the project builds with any 10.0.1xx SDK available locally or in CI. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Initial plan * Fix Build and Test workflow to run on all pull requests, not just PRs targeting master Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: Don Syme <dsyme@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Remove unreachable null check from `toQueryParams` fallthrough case (#306) * Initial plan * Fix toQueryParams: remove unreachable null check from _ case Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Make null handling explicit in `toQueryParams` and fix misleading test comment (#307) * Initial plan * Add explicit null guard in toQueryParams and fix test comment Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Apply Fantomas formatting to RuntimeHelpers.fs Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com>
…into copilot/sub-pr-310
fc9c321
into
repo-assist/fix-issue-141-dollar-sign-path-param-0274620a2548eff5
…eference (#310) * Fix $0 in path parameter values treated as regex back-reference (#141) Regex.Replace uses $0, $1, $& etc. as back-references in the replacement string. When a path parameter value contained a literal dollar sign (e.g. "$0something"), Regex.Replace would substitute it with the matched text instead of the literal value. Fix: escape $ characters in the replacement string by replacing "$" with "$$" (the .NET replacement-string escape for a literal dollar sign). This applies to both the v2 SwaggerClientProvider and v3 OpenApiClientProvider. Also adds: - EchoPathController in the test server that returns its string path parameter - A regression test in SpecialCasesControllers.Tests.fs that passes "$0something" through a path parameter and asserts the value is echoed back unchanged - global.json rollForward updated from minor/10.0.103 to latestPatch/10.0.100 so the project builds with any 10.0.1xx SDK available in CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix $ in path parameters treated as Regex.Replace back-references (#311) * Fix Option types in form data not being unwrapped (issue #214) (#298) RuntimeHelpers.getPropertyValues now strips the FSharpOption<T> wrapper before returning property values. Previously, a property set to Some(true) was serialised as the string "Some(true)" in multipart/form-data and application/x-www-form-urlencoded bodies instead of the bare "True". None values were already handled (null). Also relax global.json SDK version constraint from 10.0.103 to 10.0.100 with rollForward:latestPatch so the project builds with any 10.0.1xx SDK available locally or in CI. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add concurrency group to CI workflow to cancel redundant builds (#309) When multiple commits are pushed to the same PR branch, GitHub Actions queues a new build for each push. Without a concurrency group, the older builds keep running even though they're now stale. Adding cancel-in-progress: true cancels the in-progress build when a newer commit arrives on the same ref, reducing CI minutes consumed and giving faster feedback on the latest commit. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * [Repo Assist] Add unit tests for RuntimeHelpers module (#295) * Add unit tests for RuntimeHelpers module RuntimeHelpers.fs handles all parameter serialization and HTTP request building for generated API clients, but previously had zero dedicated unit tests. This PR adds 44 focused tests covering: - toParam: DateTime/DateTimeOffset ISO 8601 formatting, null handling - toQueryParams: all supported array/option/scalar types, DateTime ISO 8601 serialization, None filtering in optional arrays - combineUrl: trailing/leading slash normalisation - createHttpRequest: HTTP method, path, query params, null filtering - fillHeaders: header addition, null-value skip, Content-Type silence - toStringContent/toTextContent/toStreamContent: content-type headers, error on non-stream input Closes no specific issue; improves baseline test coverage for the runtime serialization layer (Task 9 - Testing Improvements). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks * Add missing toQueryParams test cases: float32/double/byte arrays and Option array types (#304) * Initial plan * Add missing toQueryParams test cases: float32/double/byte arrays and Option array types Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Merge master and fix CI: add RuntimeHelpers unit tests (#305) * Fix Option types in form data not being unwrapped (issue #214) (#298) RuntimeHelpers.getPropertyValues now strips the FSharpOption<T> wrapper before returning property values. Previously, a property set to Some(true) was serialised as the string "Some(true)" in multipart/form-data and application/x-www-form-urlencoded bodies instead of the bare "True". None values were already handled (null). Also relax global.json SDK version constraint from 10.0.103 to 10.0.100 with rollForward:latestPatch so the project builds with any 10.0.1xx SDK available locally or in CI. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Initial plan * Fix Build and Test workflow to run on all pull requests, not just PRs targeting master Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: Don Syme <dsyme@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Remove unreachable null check from `toQueryParams` fallthrough case (#306) * Initial plan * Fix toQueryParams: remove unreachable null check from _ case Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Make null handling explicit in `toQueryParams` and fix misleading test comment (#307) * Initial plan * Add explicit null guard in toQueryParams and fix test comment Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Apply Fantomas formatting to RuntimeHelpers.fs Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com> * Initial plan --------- Co-authored-by: Don Syme <dsyme@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates runtime helper behavior and adds unit tests around runtime serialization/utilities, alongside a CI workflow concurrency tweak.
Changes:
- Add a new
RuntimeHelpersTests.fstest suite and include it in the test project. - Update
RuntimeHelpers.toQueryParamsto return an empty list fornullinputs and adjust related runtime helper behavior. - Add an option-unwrapping step in
getPropertyValuesto avoid emittingSome(value)in form data, plus workflow concurrency configuration.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj | Includes the new RuntimeHelpers unit test file in compilation. |
| tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs | Adds broad unit coverage for RuntimeHelpers (query params, content creation, headers, etc.). |
| src/SwaggerProvider.Runtime/RuntimeHelpers.fs | Alters query param serialization flow and introduces F# option unwrapping in getPropertyValues. |
| .github/workflows/dotnetcore.yml | Adds workflow-level concurrency/cancel-in-progress behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| prop.GetValue(object) | ||
| |> unwrapFSharpOption | ||
| |> Option.ofObj | ||
| |> Option.map(fun value -> (name, value))) |
There was a problem hiding this comment.
getPropertyValues now unwraps F# options before building form/urlencoded content, but there are no assertions covering this behavior in the new RuntimeHelpers unit tests. Please add a regression test that verifies Some(value) becomes the inner value and None/null is omitted when producing key/value sequences.
| [<Fact>] | ||
| let ``toQueryParams skips None items in Option array``() = | ||
| let values: Option<int>[] = [| Some 1; None; Some 3 |] | ||
| let result = toQueryParams "n" (box values) stubClient | ||
| result |> shouldEqual [ ("n", "1"); ("n", "3") ] | ||
|
|
||
| [<Fact>] | ||
| let ``toQueryParams handles Guid array``() = | ||
| let g1 = Guid("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") | ||
| let g2 = Guid("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb") | ||
| let result = toQueryParams "id" (box [| g1; g2 |]) stubClient | ||
| result |> shouldEqual [ ("id", g1.ToString()); ("id", g2.ToString()) ] |
There was a problem hiding this comment.
There’s no coverage for Option<Guid>[] query parameters (including arrays containing None/null). Given the runtime has special-cases for other Option<_>[] types, adding a regression test here would help prevent serialization bugs/crashes specific to GUID option arrays.
| name: Build and Test | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| pull_request: | ||
| branches: | ||
| - master | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
PR description/title focus on escaping $ in path parameters, but the actual diff in this PR is limited to RuntimeHelpers changes/tests and CI concurrency. If the $ path fix is already present from another merge, consider updating the PR description to reflect what’s actually changing here (or include the missing diffs) to avoid review/traceability confusion.
| | :? array<Option<string>> as xs -> xs |> toStrArrayOpt name | ||
| | :? array<Option<DateTime>> as xs -> xs |> toStrArrayDateTimeOpt name | ||
| | :? array<Option<DateTimeOffset>> as xs -> xs |> toStrArrayDateTimeOffsetOpt name | ||
| | :? array<Option<Guid>> as xs -> xs |> toStrArray name |
There was a problem hiding this comment.
array<Option<Guid>> is currently routed to toStrArray, which will serialize values as Some(...) and will throw a NullReferenceException when the array contains None (compiled as null). It should be handled the same way as the other option arrays (e.g. via toStrArrayOpt / Array.choose id) so None items are skipped and Some g becomes g.ToString().
| | :? array<Option<Guid>> as xs -> xs |> toStrArray name | |
| | :? array<Option<Guid>> as xs -> xs |> toStrArrayOpt name |
| | :? array<float32> as xs -> xs |> toStrArray name | ||
| | :? array<double> as xs -> xs |> toStrArray name | ||
| | :? array<string> as xs -> xs |> toStrArray name |
There was a problem hiding this comment.
Query param serialization for float32/double uses ToString() (via toStrArray), which is culture-sensitive. On machines with a comma decimal separator this will produce values like "1,5" in URLs; consider formatting numeric types with InvariantCulture (and aligning/adding tests accordingly) to make query strings stable across locales.
Regex.Replaceinterprets$0,$1,$&etc. as back-references in the replacement string. Path parameter values containing a literal$(e.g."$0something") were silently corrupted instead of being passed through.Changes
$→$$in the replacement string before passing toRegex.ReplaceEchoPathControllerthat returns its string path parameter verbatim"$0something"round-trips through a path parameter unchangedSome(value)in form data bodies #298)✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.