Skip to content

Fix $ in path parameters treated as Regex.Replace back-references#311

Merged
sergey-tihon merged 5 commits intorepo-assist/fix-issue-141-dollar-sign-path-param-0274620a2548eff5from
copilot/sub-pr-310
Mar 10, 2026
Merged

Fix $ in path parameters treated as Regex.Replace back-references#311
sergey-tihon merged 5 commits intorepo-assist/fix-issue-141-dollar-sign-path-param-0274620a2548eff5from
copilot/sub-pr-310

Conversation

Copy link
Contributor

Copilot AI commented Mar 10, 2026

Regex.Replace interprets $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

  • v2 + v3 operation compilers: escape $$$ in the replacement string before passing to Regex.Replace
  • Test server: add EchoPathController that returns its string path parameter verbatim
  • Regression test: v3 test asserting "$0something" round-trips through a path parameter unchanged
  • master merged: brings in RuntimeHelpers unit tests, CI concurrency group, and Option form-data fix ([Repo Assist] Fix Option types sending Some(value) in form data bodies #298)
// Before — $0 consumed as back-reference, value silently mangled
Regex.Replace(url, pattern, value)

// After — literal dollar sign preserved
Regex.Replace(url, pattern, value.Replace("$", "$$"))

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

dsyme and others added 4 commits March 10, 2026 21:58
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>
Copilot AI changed the title [WIP] Fix path parameter values treated as regex back-reference Fix $ in path parameters treated as Regex.Replace back-references Mar 10, 2026
@sergey-tihon sergey-tihon marked this pull request as ready for review March 10, 2026 22:40
Copilot AI review requested due to automatic review settings March 10, 2026 22:40
@sergey-tihon sergey-tihon merged commit fc9c321 into repo-assist/fix-issue-141-dollar-sign-path-param-0274620a2548eff5 Mar 10, 2026
14 checks passed
@sergey-tihon sergey-tihon deleted the copilot/sub-pr-310 branch March 10, 2026 22:40
sergey-tihon added a commit that referenced this pull request Mar 10, 2026
…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>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.fs test suite and include it in the test project.
  • Update RuntimeHelpers.toQueryParams to return an empty list for null inputs and adjust related runtime helper behavior.
  • Add an option-unwrapping step in getPropertyValues to avoid emitting Some(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.

Comment on lines 184 to 187
prop.GetValue(object)
|> unwrapFSharpOption
|> Option.ofObj
|> Option.map(fun value -> (name, value)))
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +171
[<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()) ]
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 1 to +11
name: Build and Test

on:
push:
branches:
- master
pull_request:
branches:
- master

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
| :? 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
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Suggested change
| :? array<Option<Guid>> as xs -> xs |> toStrArray name
| :? array<Option<Guid>> as xs -> xs |> toStrArrayOpt name

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +99
| :? array<float32> as xs -> xs |> toStrArray name
| :? array<double> as xs -> xs |> toStrArray name
| :? array<string> as xs -> xs |> toStrArray name
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants