feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
-- (the "License"); you may not use this file except in compliance with
|
||||
-- the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
local core = require("apisix.core")
|
||||
local config_util = require("apisix.core.config_util")
|
||||
local pb = require("pb")
|
||||
local protoc = require("protoc")
|
||||
local pcall = pcall
|
||||
local ipairs = ipairs
|
||||
local decode_base64 = ngx.decode_base64
|
||||
|
||||
|
||||
local protos
|
||||
local lrucache_proto = core.lrucache.new({
|
||||
ttl = 300, count = 100
|
||||
})
|
||||
|
||||
local proto_fake_file = "filename for loaded"
|
||||
|
||||
local function compile_proto_text(content)
|
||||
protoc.reload()
|
||||
local _p = protoc.new()
|
||||
-- the loaded proto won't appears in _p.loaded without a file name after lua-protobuf=0.3.2,
|
||||
-- which means _p.loaded after _p:load(content) is always empty, so we can pass a fake file
|
||||
-- name to keep the code below unchanged, or we can create our own load function with returning
|
||||
-- the loaded DescriptorProto table additionally, see more details in
|
||||
-- https://github.com/apache/apisix/pull/4368
|
||||
local ok, res = pcall(_p.load, _p, content, proto_fake_file)
|
||||
if not ok then
|
||||
return nil, res
|
||||
end
|
||||
|
||||
if not res or not _p.loaded then
|
||||
return nil, "failed to load proto content"
|
||||
end
|
||||
|
||||
local compiled = _p.loaded
|
||||
|
||||
local index = {}
|
||||
for _, s in ipairs(compiled[proto_fake_file].service or {}) do
|
||||
local method_index = {}
|
||||
for _, m in ipairs(s.method) do
|
||||
method_index[m.name] = m
|
||||
end
|
||||
|
||||
index[compiled[proto_fake_file].package .. '.' .. s.name] = method_index
|
||||
end
|
||||
|
||||
compiled[proto_fake_file].index = index
|
||||
|
||||
return compiled
|
||||
end
|
||||
|
||||
|
||||
local function compile_proto_bin(content)
|
||||
content = decode_base64(content)
|
||||
if not content then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- pb.load doesn't return err
|
||||
local ok = pb.load(content)
|
||||
if not ok then
|
||||
return nil
|
||||
end
|
||||
|
||||
local files = pb.decode("google.protobuf.FileDescriptorSet", content).file
|
||||
local index = {}
|
||||
for _, f in ipairs(files) do
|
||||
for _, s in ipairs(f.service or {}) do
|
||||
local method_index = {}
|
||||
for _, m in ipairs(s.method) do
|
||||
method_index[m.name] = m
|
||||
end
|
||||
|
||||
index[f.package .. '.' .. s.name] = method_index
|
||||
end
|
||||
end
|
||||
|
||||
local compiled = {}
|
||||
compiled[proto_fake_file] = {}
|
||||
compiled[proto_fake_file].index = index
|
||||
return compiled
|
||||
end
|
||||
|
||||
|
||||
local function compile_proto(content)
|
||||
-- clear pb state
|
||||
local old_pb_state = pb.state(nil)
|
||||
|
||||
local compiled, err = compile_proto_text(content)
|
||||
if not compiled then
|
||||
compiled = compile_proto_bin(content)
|
||||
if not compiled then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
-- fetch pb state
|
||||
compiled.pb_state = pb.state(old_pb_state)
|
||||
return compiled
|
||||
end
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
compile_proto = compile_proto,
|
||||
proto_fake_file = proto_fake_file
|
||||
}
|
||||
|
||||
local function create_proto_obj(proto_id)
|
||||
if protos.values == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local content
|
||||
for _, proto in config_util.iterate_values(protos.values) do
|
||||
if proto_id == proto.value.id then
|
||||
content = proto.value.content
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not content then
|
||||
return nil, "failed to find proto by id: " .. proto_id
|
||||
end
|
||||
|
||||
return compile_proto(content)
|
||||
end
|
||||
|
||||
|
||||
function _M.fetch(proto_id)
|
||||
return lrucache_proto(proto_id, protos.conf_version,
|
||||
create_proto_obj, proto_id)
|
||||
end
|
||||
|
||||
|
||||
function _M.protos()
|
||||
if not protos then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return protos.values, protos.conf_version
|
||||
end
|
||||
|
||||
|
||||
local grpc_status_proto = [[
|
||||
syntax = "proto3";
|
||||
|
||||
package grpc_status;
|
||||
|
||||
message Any {
|
||||
// A URL/resource name that uniquely identifies the type of the serialized
|
||||
// protocol buffer message. This string must contain at least
|
||||
// one "/" character. The last segment of the URL's path must represent
|
||||
// the fully qualified name of the type (as in
|
||||
// `path/google.protobuf.Duration`). The name should be in a canonical form
|
||||
// (e.g., leading "." is not accepted).
|
||||
//
|
||||
// In practice, teams usually precompile into the binary all types that they
|
||||
// expect it to use in the context of Any. However, for URLs which use the
|
||||
// scheme `http`, `https`, or no scheme, one can optionally set up a type
|
||||
// server that maps type URLs to message definitions as follows:
|
||||
//
|
||||
// * If no scheme is provided, `https` is assumed.
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Note: this functionality is not currently available in the official
|
||||
// protobuf release, and it is not used for type URLs beginning with
|
||||
// type.googleapis.com.
|
||||
//
|
||||
// Schemes other than `http`, `https` (or the empty scheme) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
string type_url = 1;
|
||||
|
||||
// Must be a valid serialized protocol buffer of the above specified type.
|
||||
bytes value = 2;
|
||||
}
|
||||
|
||||
// The `Status` type defines a logical error model that is suitable for
|
||||
// different programming environments, including REST APIs and RPC APIs. It is
|
||||
// used by [gRPC](https://github.com/grpc). Each `Status` message contains
|
||||
// three pieces of data: error code, error message, and error details.
|
||||
//
|
||||
// You can find out more about this error model and how to work with it in the
|
||||
// [API Design Guide](https://cloud.google.com/apis/design/errors).
|
||||
message ErrorStatus {
|
||||
// The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
|
||||
int32 code = 1;
|
||||
|
||||
// A developer-facing error message, which should be in English. Any
|
||||
// user-facing error message should be localized and sent in the
|
||||
// [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
|
||||
string message = 2;
|
||||
|
||||
// A list of messages that carry the error details. There is a common set of
|
||||
// message types for APIs to use.
|
||||
repeated Any details = 3;
|
||||
}
|
||||
]]
|
||||
|
||||
|
||||
local status_pb_state
|
||||
local function init_status_pb_state()
|
||||
if not status_pb_state then
|
||||
-- clear current pb state
|
||||
local old_pb_state = pb.state(nil)
|
||||
|
||||
-- initialize protoc compiler
|
||||
protoc.reload()
|
||||
local status_protoc = protoc.new()
|
||||
-- do not use loadfile here, it can not load the proto file when using a relative address
|
||||
-- after luarocks install apisix
|
||||
local ok, err = status_protoc:load(grpc_status_proto, "grpc_status.proto")
|
||||
if not ok then
|
||||
status_protoc:reset()
|
||||
pb.state(old_pb_state)
|
||||
return "failed to load grpc status protocol: " .. err
|
||||
end
|
||||
|
||||
status_pb_state = pb.state(old_pb_state)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.fetch_status_pb_state()
|
||||
return status_pb_state
|
||||
end
|
||||
|
||||
|
||||
function _M.init()
|
||||
local err
|
||||
protos, err = core.config.new("/protos", {
|
||||
automatic = true,
|
||||
item_schema = core.schema.proto
|
||||
})
|
||||
if not protos then
|
||||
core.log.error("failed to create etcd instance for fetching protos: ",
|
||||
err)
|
||||
return
|
||||
end
|
||||
|
||||
if not status_pb_state then
|
||||
err = init_status_pb_state()
|
||||
if err then
|
||||
core.log.error("failed to init grpc status proto: ",
|
||||
err)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _M.destroy()
|
||||
if protos then
|
||||
protos:close()
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
@@ -0,0 +1,72 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
-- (the "License"); you may not use this file except in compliance with
|
||||
-- the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
|
||||
local util = require("apisix.plugins.grpc-transcode.util")
|
||||
local core = require("apisix.core")
|
||||
local pb = require("pb")
|
||||
local bit = require("bit")
|
||||
local ngx = ngx
|
||||
local string = string
|
||||
local table = table
|
||||
local pcall = pcall
|
||||
local tonumber = tonumber
|
||||
local req_read_body = ngx.req.read_body
|
||||
|
||||
return function (proto, service, method, pb_option, deadline, default_values)
|
||||
core.log.info("proto: ", core.json.delay_encode(proto, true))
|
||||
local m = util.find_method(proto, service, method)
|
||||
if not m then
|
||||
return false, "Undefined service method: " .. service .. "/" .. method
|
||||
.. " end", 503
|
||||
end
|
||||
|
||||
req_read_body()
|
||||
|
||||
local pb_old_state = pb.state(proto.pb_state)
|
||||
util.set_options(proto, pb_option)
|
||||
|
||||
local map_message = util.map_message(m.input_type, default_values or {})
|
||||
local ok, encoded = pcall(pb.encode, m.input_type, map_message)
|
||||
pb.state(pb_old_state)
|
||||
|
||||
if not ok or not encoded then
|
||||
return false, "failed to encode request data to protobuf", 400
|
||||
end
|
||||
|
||||
local size = #encoded
|
||||
local prefix = {
|
||||
string.char(0),
|
||||
string.char(bit.band(bit.rshift(size, 24), 0xFF)),
|
||||
string.char(bit.band(bit.rshift(size, 16), 0xFF)),
|
||||
string.char(bit.band(bit.rshift(size, 8), 0xFF)),
|
||||
string.char(bit.band(size, 0xFF))
|
||||
}
|
||||
|
||||
local message = table.concat(prefix, "") .. encoded
|
||||
|
||||
ngx.req.set_method(ngx.HTTP_POST)
|
||||
ngx.req.set_uri("/" .. service .. "/" .. method, false)
|
||||
ngx.req.set_uri_args({})
|
||||
ngx.req.set_body_data(message)
|
||||
|
||||
local dl = tonumber(deadline)
|
||||
if dl~= nil and dl > 0 then
|
||||
ngx.req.set_header("grpc-timeout", dl .. "m")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
@@ -0,0 +1,144 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
-- (the "License"); you may not use this file except in compliance with
|
||||
-- the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
local util = require("apisix.plugins.grpc-transcode.util")
|
||||
local grpc_proto = require("apisix.plugins.grpc-transcode.proto")
|
||||
local core = require("apisix.core")
|
||||
local pb = require("pb")
|
||||
local ngx = ngx
|
||||
local string = string
|
||||
local ngx_decode_base64 = ngx.decode_base64
|
||||
local ipairs = ipairs
|
||||
local pcall = pcall
|
||||
|
||||
|
||||
local function handle_error_response(status_detail_type, proto)
|
||||
local err_msg
|
||||
|
||||
local grpc_status = ngx.header["grpc-status-details-bin"]
|
||||
if grpc_status then
|
||||
grpc_status = ngx_decode_base64(grpc_status)
|
||||
if grpc_status == nil then
|
||||
err_msg = "grpc-status-details-bin is not base64 format"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg
|
||||
end
|
||||
|
||||
local status_pb_state = grpc_proto.fetch_status_pb_state()
|
||||
local old_pb_state = pb.state(status_pb_state)
|
||||
|
||||
local ok, decoded_grpc_status = pcall(pb.decode, "grpc_status.ErrorStatus", grpc_status)
|
||||
pb.state(old_pb_state)
|
||||
if not ok then
|
||||
err_msg = "failed to call pb.decode to decode grpc-status-details-bin"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg .. ", err: " .. decoded_grpc_status
|
||||
end
|
||||
|
||||
if not decoded_grpc_status then
|
||||
err_msg = "failed to decode grpc-status-details-bin"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg
|
||||
end
|
||||
|
||||
local details = decoded_grpc_status.details
|
||||
if status_detail_type and details then
|
||||
local decoded_details = {}
|
||||
for _, detail in ipairs(details) do
|
||||
local pb_old_state = pb.state(proto.pb_state)
|
||||
local ok, err_or_value = pcall(pb.decode, status_detail_type, detail.value)
|
||||
pb.state(pb_old_state)
|
||||
if not ok then
|
||||
err_msg = "failed to call pb.decode to decode details in "
|
||||
.. "grpc-status-details-bin"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg .. ", err: " .. err_or_value
|
||||
end
|
||||
|
||||
if not err_or_value then
|
||||
err_msg = "failed to decode details in grpc-status-details-bin"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg
|
||||
end
|
||||
|
||||
core.table.insert(decoded_details, err_or_value)
|
||||
end
|
||||
|
||||
decoded_grpc_status.details = decoded_details
|
||||
end
|
||||
|
||||
local resp_body = {error = decoded_grpc_status}
|
||||
local response, err = core.json.encode(resp_body)
|
||||
if not response then
|
||||
err_msg = "failed to json_encode response body"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg .. ", error: " .. err
|
||||
end
|
||||
|
||||
ngx.arg[1] = response
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return function(ctx, proto, service, method, pb_option, show_status_in_body, status_detail_type)
|
||||
local buffer = core.response.hold_body_chunk(ctx)
|
||||
if not buffer then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- handle error response after the last response chunk
|
||||
if ngx.status >= 300 and show_status_in_body then
|
||||
return handle_error_response(status_detail_type, proto)
|
||||
end
|
||||
|
||||
-- when body has already been read by other plugin
|
||||
-- the buffer is an empty string
|
||||
if buffer == "" and ctx.resp_body then
|
||||
buffer = ctx.resp_body
|
||||
end
|
||||
|
||||
local m = util.find_method(proto, service, method)
|
||||
if not m then
|
||||
return false, "2.Undefined service method: " .. service .. "/" .. method
|
||||
.. " end."
|
||||
end
|
||||
|
||||
if not ngx.req.get_headers()["X-Grpc-Web"] then
|
||||
buffer = string.sub(buffer, 6)
|
||||
end
|
||||
|
||||
local pb_old_state = pb.state(proto.pb_state)
|
||||
util.set_options(proto, pb_option)
|
||||
|
||||
local err_msg
|
||||
local decoded = pb.decode(m.output_type, buffer)
|
||||
pb.state(pb_old_state)
|
||||
if not decoded then
|
||||
err_msg = "failed to decode response data by protobuf"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg
|
||||
end
|
||||
|
||||
local response, err = core.json.encode(decoded)
|
||||
if not response then
|
||||
err_msg = "failed to json_encode response body"
|
||||
ngx.arg[1] = err_msg
|
||||
return err_msg .. ", err: " .. err
|
||||
end
|
||||
|
||||
ngx.arg[1] = response
|
||||
return nil
|
||||
end
|
@@ -0,0 +1,202 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
-- (the "License"); you may not use this file except in compliance with
|
||||
-- the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
local core = require("apisix.core")
|
||||
local proto_fake_file = require("apisix.plugins.grpc-transcode.proto").proto_fake_file
|
||||
local json = core.json
|
||||
local pb = require("pb")
|
||||
local ngx = ngx
|
||||
local string = string
|
||||
local table = table
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local tonumber = tonumber
|
||||
local type = type
|
||||
|
||||
|
||||
local _M = {version = 0.1}
|
||||
|
||||
|
||||
function _M.find_method(proto, service, method)
|
||||
local loaded = proto[proto_fake_file]
|
||||
if type(loaded) ~= "table" then
|
||||
core.log.error("compiled proto not found")
|
||||
return nil
|
||||
end
|
||||
|
||||
if type(loaded.index[service]) ~= "table" then
|
||||
core.log.error("compiled proto service not found")
|
||||
return nil
|
||||
end
|
||||
|
||||
local res = loaded.index[service][method]
|
||||
if not res then
|
||||
core.log.error("compiled proto method not found")
|
||||
return nil
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
function _M.set_options(proto, options)
|
||||
local cur_opts = proto.options
|
||||
if cur_opts then
|
||||
if cur_opts == options then
|
||||
-- same route
|
||||
return
|
||||
end
|
||||
|
||||
local same = true
|
||||
table.sort(options)
|
||||
for i, v in ipairs(options) do
|
||||
if cur_opts[i] ~= v then
|
||||
same = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if same then
|
||||
-- Routes have the same configuration, usually the default one.
|
||||
-- As this is a small optimization, we don't care about routes have different
|
||||
-- configuration but have the same effect eventually.
|
||||
return
|
||||
end
|
||||
else
|
||||
table.sort(options)
|
||||
end
|
||||
|
||||
for _, opt in ipairs(options) do
|
||||
pb.option(opt)
|
||||
end
|
||||
|
||||
proto.options = options
|
||||
end
|
||||
|
||||
|
||||
local function get_request_table()
|
||||
local method = ngx.req.get_method()
|
||||
local content_type = ngx.req.get_headers()["Content-Type"] or ""
|
||||
if string.find(content_type, "application/json", 1, true) and
|
||||
(method == "POST" or method == "PUT" or method == "PATCH")
|
||||
then
|
||||
local req_body, _ = core.request.get_body()
|
||||
if req_body then
|
||||
local data, _ = json.decode(req_body)
|
||||
if data then
|
||||
return data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if method == "POST" then
|
||||
return ngx.req.get_post_args()
|
||||
end
|
||||
|
||||
return ngx.req.get_uri_args()
|
||||
end
|
||||
|
||||
|
||||
local function get_from_request(request_table, name, kind)
|
||||
if not request_table then
|
||||
return nil
|
||||
end
|
||||
|
||||
local prefix = kind:sub(1, 3)
|
||||
if prefix == "int" then
|
||||
if request_table[name] then
|
||||
if kind == "int64" then
|
||||
return request_table[name]
|
||||
else
|
||||
return tonumber(request_table[name])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return request_table[name]
|
||||
end
|
||||
|
||||
|
||||
function _M.map_message(field, default_values, request_table, real_key)
|
||||
if not pb.type(field) then
|
||||
return nil, "Field " .. field .. " is not defined"
|
||||
end
|
||||
|
||||
local request = {}
|
||||
local sub, err
|
||||
if not request_table then
|
||||
request_table = get_request_table()
|
||||
end
|
||||
|
||||
for name, _, field_type in pb.fields(field) do
|
||||
local _, _, ty = pb.type(field_type)
|
||||
if ty ~= "enum" and field_type:sub(1, 1) == "." then
|
||||
if request_table[name] == nil then
|
||||
sub = default_values and default_values[name]
|
||||
elseif core.table.isarray(request_table[name]) then
|
||||
local sub_array = core.table.new(#request_table[name], 0)
|
||||
for i, value in ipairs(request_table[name]) do
|
||||
local sub_array_obj
|
||||
if type(value) == "table" then
|
||||
sub_array_obj, err = _M.map_message(field_type,
|
||||
default_values and default_values[name], value)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
else
|
||||
sub_array_obj = value
|
||||
end
|
||||
sub_array[i] = sub_array_obj
|
||||
end
|
||||
sub = sub_array
|
||||
else
|
||||
if ty == "map" then
|
||||
for k, v in pairs(request_table[name]) do
|
||||
local tbl, err = _M.map_message(field_type,
|
||||
default_values and default_values[name],
|
||||
request_table[name], k)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
if not sub then
|
||||
sub = {}
|
||||
end
|
||||
sub[k] = tbl[k]
|
||||
end
|
||||
else
|
||||
sub, err = _M.map_message(field_type,
|
||||
default_values and default_values[name],
|
||||
request_table[name])
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
request[name] = sub
|
||||
else
|
||||
if real_key then
|
||||
name = real_key
|
||||
end
|
||||
request[name] = get_from_request(request_table, name, field_type)
|
||||
or (default_values and default_values[name])
|
||||
end
|
||||
end
|
||||
return request
|
||||
end
|
||||
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user