Skip to content

Fix URL encoding of query parameter delimiter in relative paths#170

Merged
linyows merged 3 commits intomainfrom
fix-urlencode
Mar 5, 2026
Merged

Fix URL encoding of query parameter delimiter in relative paths#170
linyows merged 3 commits intomainfrom
fix-urlencode

Conversation

@linyows
Copy link
Owner

@linyows linyows commented Mar 5, 2026

Summary

  • Fix ResolveMethodAndURL incorrectly encoding ? as %3F when relative paths contain query parameters (e.g., /search?q=hello)
  • The root cause is that path.Join merges the query string into u.Path, causing u.String() to percent-encode the ? delimiter
  • Parse the route with url.Parse to separate path and query components before setting u.Path and u.RawQuery independently

Test plan

  • Add test cases for relative paths with query parameters (currently failing)
  • Implement fix in ResolveMethodAndURL
  • Verify all tests pass

🤖 Generated with Claude Code

Add test cases that verify query parameters in relative paths are
preserved correctly. Currently fails because `?` is being URL-encoded
as `%3F` when route contains query parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Parse route with url.Parse to separate path and query components
before joining with base URL. This prevents `?` from being included
in u.Path and subsequently encoded as `%3F` by u.String().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@linyows linyows self-assigned this Mar 5, 2026
@linyows linyows requested a review from Copilot March 5, 2026 01:45
@linyows linyows added the bug Something isn't working label Mar 5, 2026
@linyows
Copy link
Owner Author

linyows commented Mar 5, 2026

/run-e2e

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 fixes a bug in ResolveMethodAndURL where relative paths containing query parameters (e.g., /search?q=hello) were incorrectly percent-encoded by path.Join, turning ? into %3F. The fix parses the route with url.Parse to properly separate the path and query components, then assigns them independently on the base URL struct.

Changes:

  • http/client.go: Replaces the single path.Join(u.Path, route) call with a url.Parse(route) step that separates the route's path and raw query, setting them on u independently.
  • http/client_test.go: Adds three new test cases covering relative paths with query parameters and an absolute URL with query parameters.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
http/client.go Core fix: parse the route URL before joining, and propagate RawQuery separately
http/client_test.go New test cases for query-parameter URL handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

},
wantErr: false,
},
{
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

There is no test case covering the scenario where the base URL already contains query parameters (e.g., "url": "https://api.example.com?token=abc") and the route also carries query parameters. Given the current implementation silently overwrites the base URL's RawQuery, this edge case should have a test to document and verify the intended behavior.

Suggested change
{
{
name: "GET method with base URL and route both containing query parameters",
input: map[string]any{
"get": "/search?q=hello&page=1",
"url": "https://api.example.com?token=abc",
},
expected: map[string]any{
"method": "GET",
// The route's query parameters overwrite the base URL's RawQuery.
"url": "https://api.example.com/search?q=hello&page=1",
},
wantErr: false,
},
{

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +237
if routeURL.RawQuery != "" {
u.RawQuery = routeURL.RawQuery
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

The condition if routeURL.RawQuery != "" only sets the query when the route has one, but does not handle the case where the base URL already contains query parameters AND the route also has query parameters. In that scenario, the base URL's existing query string is silently overwritten by the route's query string, which may discard important base-URL parameters (e.g., API keys or tokens in the base URL like https://api.example.com?token=abc).

If merging base and route query parameters is the intended behavior, the two query strings should be combined (e.g., using url.Values to merge them). If overwriting is intentional, a comment explaining this design decision would help future readers. There is also no test covering the case where both the base URL and the route carry query parameters.

Suggested change
if routeURL.RawQuery != "" {
u.RawQuery = routeURL.RawQuery
}
// Merge query parameters from base URL and route instead of overwriting.
baseQuery := u.Query()
if routeURL.RawQuery != "" {
routeQuery := routeURL.Query()
for key, values := range routeQuery {
for _, v := range values {
baseQuery.Add(key, v)
}
}
}
u.RawQuery = baseQuery.Encode()

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +234
routeURL, err := url.Parse(route)
if err != nil {
return fmt.Errorf("invalid route: %w", err)
}
u.Path = path.Join(u.Path, routeURL.Path)
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

The PR description's test plan still shows two items as unchecked: "Implement fix in ResolveMethodAndURL" and "Verify all tests pass". The fix has clearly been implemented in the diff, so these checkboxes appear to have been left in their pre-implementation state by mistake.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

✅ E2E test passed successfully!

Document the current behavior where route query parameters overwrite
the base URL's query string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

Code Metrics Report

main (147e47a) #170 (ff438aa) +/-
Coverage 55.9% 55.9% +0.0%
Code to Test Ratio 1:1.0 1:1.0 0.0
Test Execution Time 24s 11s -13s
Details
  |                     | main (147e47a) | #170 (ff438aa) |  +/-  |
  |---------------------|----------------|----------------|-------|
+ | Coverage            |          55.9% |          55.9% | +0.0% |
  |   Files             |             65 |             65 |     0 |
  |   Lines             |           6548 |           6553 |    +5 |
+ |   Covered           |           3663 |           3666 |    +3 |
  | Code to Test Ratio  |          1:1.0 |          1:1.0 |   0.0 |
  |   Code              |          12848 |          12848 |     0 |
  |   Test              |          13267 |          13267 |     0 |
+ | Test Execution Time |            24s |            11s |  -13s |

Code coverage of files in pull request scope (78.4% → 78.0%)

Files Coverage +/- Status
http/client.go 81.6% -0.1% modified
mail/mock_server.go 72.0% -1.1% affected

Reported by octocov

@linyows linyows merged commit fd5141b into main Mar 5, 2026
6 checks passed
@linyows linyows deleted the fix-urlencode branch March 5, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants