Skip to content

Commit c102ddb

Browse files
Nayte91dunglas
authored andcommitted
fix: skip URL rewriting for non-relation field values
A "?fields=" query selecting scalar fields returned corrupted values: plain strings were percent-encoded ("Book 1" -> "Book%201") and numbers were coerced to strings (2019 -> "2019"). The header form was unaffected because it never rewrites the response body. The relation handler ran for every scalar leaf, including pure field projections that are not relations. Those values were parsed with url.Parse and re-serialized via url.URL.String(), which percent-encodes the path and stringifies numbers. Guard the handler so a node with no relation to push and no child selector to propagate is left untouched. Besides fixing the corruption, this skips url.Parse, the query re-encoding and the json.Marshal for every projected scalar, cutting allocations on the fields path by ~30%. Fixes #96
1 parent 6518cee commit c102ddb

3 files changed

Lines changed: 31 additions & 0 deletions

File tree

fixtures/api/jsonld.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func (h *JSONLDHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
7373
"@id": `+string(encodedURI)+`,
7474
"title": "Book 1",
7575
"description": "A good book",
76+
"year": 2019,
7677
"author": "/authors/1.jsonld",
7778
"related": "/books/99.jsonld"
7879
}`); err != nil {

server_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,28 @@ func TestFieldsQuery(t *testing.T) {
202202
assert.Equal(t, `{"@id":"/books.jsonld"}`, string(b))
203203
}
204204

205+
func TestFieldsQueryDoesNotEncodePlainValues(t *testing.T) {
206+
upstream, gateway := createServers(-1, false)
207+
defer upstream.Close()
208+
defer gateway.Close()
209+
210+
resp, _ := http.Get(gateway.URL + `/books/1.jsonld?fields="/title","/description"`)
211+
b, _ := io.ReadAll(resp.Body)
212+
213+
assert.Equal(t, `{"title":"Book 1","description":"A good book"}`, string(b))
214+
}
215+
216+
func TestFieldsQueryPreservesNumberType(t *testing.T) {
217+
upstream, gateway := createServers(-1, false)
218+
defer upstream.Close()
219+
defer gateway.Close()
220+
221+
resp, _ := http.Get(gateway.URL + `/books/1.jsonld?fields="/year"`)
222+
b, _ := io.ReadAll(resp.Body)
223+
224+
assert.Equal(t, `{"year":2019}`, string(b))
225+
}
226+
205227
func TestFieldsHeader(t *testing.T) {
206228
upstream, gateway := createServers(-1, false)
207229
defer upstream.Close()

vulcain.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ func (v *Vulcain) Apply(req *http.Request, rw http.ResponseWriter, responseBody
212212
oaRouteTested, usePreloadLinks bool
213213
)
214214
newBody := v.traverseJSON(currentBody, tree, len(f) > 0, func(n *node, val string) string {
215+
// A pure field projection (a "fields" leaf with no relation to push and no child
216+
// selector to propagate) is not a relation: leave its value untouched. Routing it
217+
// through the URL machinery below would percent-encode plain strings and coerce
218+
// numbers to strings (https://github.com/dunglas/vulcain/issues/96).
219+
if !n.preload && !n.hasChildren(preload) && !n.hasChildren(fields) {
220+
return ""
221+
}
222+
215223
var (
216224
u *url.URL
217225
useOA bool

0 commit comments

Comments
 (0)