fix(admin): allow bidirectional format conversion for upstream.nodes in PATCH requests#13065
fix(admin): allow bidirectional format conversion for upstream.nodes in PATCH requests#13065janiussyafiq wants to merge 3 commits intoapache:masterfrom
Conversation
…in PATCH requests
There was a problem hiding this comment.
Pull request overview
This PR fixes asymmetric Admin API PATCH behavior when upstream.nodes changes between array and hash-table formats by adjusting the deep-merge logic to avoid creating mixed array/hash tables during patch merges.
Changes:
- Update
core.table.merge()to replace (not recursively merge) when either the existing value or incoming value is an array. - Add an Admin API test that exercises
PATCHconversions between array and hash-tableupstream.nodesformats.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
apisix/core/table.lua |
Adjusts merge behavior to prevent invalid mixed table structures when patching between array/hash formats. |
t/admin/routes-array-nodes.t |
Adds a regression test for PATCHing upstream.nodes across both supported representations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local code, body = t('/apisix/admin/routes/1', | ||
| ngx.HTTP_PUT, | ||
| [[{ | ||
| "uri": "/index.html", | ||
| "upstream": { | ||
| "nodes": [{ | ||
| "host": "127.0.0.1", | ||
| "port": 8080, | ||
| "weight": 1 | ||
| }], | ||
| "type": "roundrobin" | ||
| } | ||
| }]] | ||
| ) | ||
|
|
||
| code, body = t('/apisix/admin/routes/1', | ||
| ngx.HTTP_PATCH, | ||
| [[{ | ||
| "upstream": { | ||
| "nodes": { | ||
| "127.0.0.1:9200": 1 | ||
| } | ||
| } | ||
| }]] | ||
| ) | ||
|
|
||
| code, body = t('/apisix/admin/routes/1', | ||
| ngx.HTTP_PATCH, | ||
| [[{ | ||
| "upstream": { | ||
| "nodes": [{ | ||
| "host": "127.0.0.1", | ||
| "port": 8080, | ||
| "weight": 1 | ||
| }], | ||
| "type": "roundrobin" | ||
| } | ||
| }]] | ||
| ) |
There was a problem hiding this comment.
This test performs multiple admin calls (PUT, then PATCH array→hash, then PATCH hash→array) but only asserts the final call’s result. If the intermediate PATCH fails, the test can still pass because later calls overwrite code/body. Add checks after each call (e.g., if code >= 300 then ... return end) so the test fails as soon as any step fails, and so it actually verifies both conversions.
apisix/core/table.lua
Outdated
| merge(origin[k] or {}, extend[k] or {}) | ||
| else | ||
| local origin_is_array = _M.nkeys(origin[k]) == #origin[k] | ||
| local extend_is_array = _M.nkeys(v) == #v |
There was a problem hiding this comment.
It is recommended to use table.isarray
Description
Fixes asymmetric PATCH behavior when converting
upstream.nodesbetween array and hash table formats.Problem Statement
The Admin API exhibits inconsistent behavior when patching a route's
upstream.nodesfield:{"error_msg":"invalid configuration: property \"upstream\" validation failed: property \"nodes\" validation failed: object matches none of the required"}This happens despite both formats being officially supported by the schema.
Root Cause
The
merge()function inapisix/core/table.luaonly checks if the origin value is an array before deciding whether to replace or recursively merge:When patching from hash table → array:
Solution
Modified the merge logic to check both the origin and incoming values:
Which issue(s) this PR fixes:
Fixes #13045
Checklist