Skip to content

fix(grpc-transcode): correctly serialize empty repeated fields to [] #12391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

bytelazy
Copy link

@bytelazy bytelazy commented Jun 30, 2025

Description

In the grpc-transcode plugin, when a repeated field in the gRPC response is empty, it is incorrectly encoded as an empty JSON object ({}) instead of an empty JSON array ([]). This behavior does not comply with the gRPC-JSON transcoding specification and can cause type-related errors on the client-side, which expects an array.

Fixes #11440

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the [APISIX mailing list](https://github.com/apache/apisix/tree/master#community) first)

Changes or the new features added to this PR
The main changes are in the response.lua file to correctly handle the serialization of repeated fields.

Identify repeated fields: A new function fetch_proto_array_names has been added. It recursively traverses the loaded Protobuf schema definition to identify and collect the names of all fields marked as repeated.

Ensure correct JSON array serialization:

A new function set_default_array has been introduced. After the gRPC response is decoded into a Lua table, this function traverses the table.

For any field that was identified as repeated, it sets a specific metatable (core.json.array_mt).

This metatable forces the core.json.encode function to serialize the corresponding Lua table as a JSON array ([]), even when it is empty.

This logic is applied just before the decoded response is encoded into the final JSON output, ensuring the structure is correct.

@bytelazy bytelazy marked this pull request as ready for review June 30, 2025 15:36
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working plugin labels Jun 30, 2025
@Baoyuantop Baoyuantop moved this to 👀 In review in ⚡️ Apache APISIX Roadmap Jul 1, 2025
Copy link
Contributor

@Baoyuantop Baoyuantop left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution!

Two things need to be done:

  1. Need to add corresponding tests for this fix, please feel free to ask questions if you encounter any problems.
  2. Need to pass all CI.

@bytelazy
Copy link
Author

bytelazy commented Jul 2, 2025

Thank you for your contribution!

Two things need to be done:

  1. Need to add corresponding tests for this fix, please feel free to ask questions if you encounter any problems.
  2. Need to pass all CI.

All CI have passed.
Under which directory should I add the corresponding tests for this fix ?

@Baoyuantop
Copy link
Contributor

Thank you for your contribution!
Two things need to be done:

  1. Need to add corresponding tests for this fix, please feel free to ask questions if you encounter any problems.
  2. Need to pass all CI.

All CI have passed. Under which directory should I add the corresponding tests for this fix ?

Add test case for grpc-transcode plugin in t/plugin/grpc-transcode.t

@bytelazy
Copy link
Author

bytelazy commented Jul 2, 2025

Thank you for your contribution!
Two things need to be done:

  1. Need to add corresponding tests for this fix, please feel free to ask questions if you encounter any problems.
  2. Need to pass all CI.

All CI have passed. Under which directory should I add the corresponding tests for this fix ?

Add test case for grpc-transcode plugin in t/plugin/grpc-transcode.t

I don't know how to run the test file. Can you give me some reference documents?

@Baoyuantop
Copy link
Contributor

@bytelazy
Copy link
Author

bytelazy commented Jul 3, 2025

Thank you for your contribution!
Two things need to be done:

  1. Need to add corresponding tests for this fix, please feel free to ask questions if you encounter any problems.
  2. Need to pass all CI.

All CI have passed. Under which directory should I add the corresponding tests for this fix ?

Add test case for grpc-transcode plugin in t/plugin/grpc-transcode.t

Based on the documentation you provided, after running prove -I. -I../test-nginx/inc -I../test-nginx/lib -r t/plugin/grpc-transcode.t, I encountered connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1.

Upon observation, I noticed that the /t directory contains a /grpc_server_example directory. Does this mean that to test t/plugin/grpc-transcode.t, I need to download Golang in the dev container and run /grpc_server_example/main.go?

@Baoyuantop
Copy link
Contributor

I need to download Golang in the dev container and run /grpc_server_example/main.go?

Yes

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jul 9, 2025
@Baoyuantop
Copy link
Contributor

Hi @3kis, please fix lint error.

@bytelazy
Copy link
Author

Hi @3kis, please fix lint error.

hi~ I have fixed lint error. What should I do next?

@Baoyuantop Baoyuantop self-requested a review July 13, 2025 12:29
@Baoyuantop
Copy link
Contributor

CI has all passed, the error has nothing to do with this PR, I will review this PR as soon as possible.

Copy link
Contributor

@Baoyuantop Baoyuantop left a comment

Choose a reason for hiding this comment

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

There are many spaces used incorrectly, please check carefully.

@bytelazy
Copy link
Author

There are many spaces used incorrectly, please check carefully.

Thanks your suggestions, I have reverted to additional modifications

@Baoyuantop Baoyuantop requested a review from Copilot July 21, 2025 09:36
Copilot

This comment was marked as outdated.

adapt suggestions from copilt

Co-authored-by: Copilot <[email protected]>
@Baoyuantop Baoyuantop requested a review from Copilot July 21, 2025 10:10
Copy link

@Copilot 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 serialization issue in the grpc-transcode plugin where empty repeated fields were incorrectly encoded as empty JSON objects ({}) instead of empty JSON arrays ([]) when transcoding gRPC responses to JSON.

  • Adds functions to identify repeated fields from protobuf schema definitions
  • Implements automatic array metatable assignment for repeated fields to ensure proper JSON serialization
  • Includes comprehensive test coverage for various scenarios with empty repeated fields

Reviewed Changes

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

File Description
apisix/plugins/grpc-transcode/response.lua Implements the core fix with functions to detect repeated fields and set appropriate metatables for JSON array serialization
t/plugin/grpc-transcode4.t Adds new test file with comprehensive test cases covering various scenarios of empty repeated field serialization
Comments suppressed due to low confidence (2)

apisix/plugins/grpc-transcode/response.lua:33

  • [nitpick] The function name 'fetch_proto_array_names' could be more descriptive. Consider renaming to 'extract_repeated_field_names' or 'collect_repeated_field_names' to better reflect that it's collecting names of repeated fields, not array names.
local function fetch_proto_array_names(proto_obj)

apisix/plugins/grpc-transcode/response.lua:51

  • [nitpick] The function name 'set_default_array' is ambiguous. Consider renaming to 'apply_array_metatables' or 'set_array_metatables_for_repeated_fields' to clarify that it's setting metatables for repeated fields.
local function set_default_array(tab, array_names)

@Baoyuantop Baoyuantop requested a review from membphis July 24, 2025 09:10
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
Copy link
Member

Choose a reason for hiding this comment

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

I can't found this test backend in APISIX e2e framework, I think this new added test cased can't be pass due to #12462 , try to merge master branch and re-run CI again.

Copy link
Author

Choose a reason for hiding this comment

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

When running tests, I used a custom grpc server instead of the original grpc server in apisix. Should I merge the master branch into my local deveop branch?

Comment on lines +26 to +67
local type = type
local pairs = pairs
local setmetatable = setmetatable

pb.option "decode_default_array"
-- Protobuf repeated field label value
local PROTOBUF_REPEATED_LABEL = 3
local repeated_label = PROTOBUF_REPEATED_LABEL

local function fetch_proto_array_names(proto_obj)
local names = {}
if type(proto_obj) == "table" then
for k,v in pairs(proto_obj) do
if type(v) == "table" then
local sub_names = fetch_proto_array_names(v)
for sub_name,_ in pairs(sub_names) do
names[sub_name] = 1
end
end
end
if proto_obj["label"] == repeated_label then
if proto_obj["name"] then
names[proto_obj["name"]] = 1
end
end
end
return names
end

local function set_default_array(tab, array_names)
if type(tab) ~= "table" then
return
end
for k, v in pairs(tab) do
if type(v) == "table" then
if array_names[k] == 1 then
setmetatable(v, core.json.array_mt)
end
set_default_array(v, array_names)
end
end
end
Copy link
Member

Choose a reason for hiding this comment

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

please check starwing/lua-protobuf#240, I think we don't need to deal with this issue manually.

Copy link
Author

Choose a reason for hiding this comment

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

In starwing/lua-protobuf#240 , the author said that this change will go online in the new version, but the latest version does not directly deal with the problem of empty arrays.

Copy link
Author

Choose a reason for hiding this comment

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

please check starwing/lua-protobuf#240, I think we don't need to deal with this issue manually.

I understand, you're saying that it's better to meet the requirements with simple options rather than making extensive code changes.

@Baoyuantop Baoyuantop added the wait for update wait for the author's response in this issue/PR label Aug 1, 2025
@Baoyuantop
Copy link
Contributor

Hi @bytelazy, there are still some review comments that need to be addressed.

@bytelazy
Copy link
Author

Hi @bytelazy, there are still some review comments that need to be addressed.

thanks for reminding me, I have checked the comment and reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working plugin size:L This PR changes 100-499 lines, ignoring generated files. user responded wait for update wait for the author's response in this issue/PR
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

bug: grpc-transcode cant transcode empty array list to [] , but it did to {}
3 participants