diff --git a/changelog/unreleased/kong/fix-missing-flattened-erros.yml b/changelog/unreleased/kong/fix-missing-flattened-erros.yml new file mode 100644 index 000000000000..03a34540cde7 --- /dev/null +++ b/changelog/unreleased/kong/fix-missing-flattened-erros.yml @@ -0,0 +1,4 @@ +message: | + Fixed an issue where flattened errors had missing info when fields were lost or conflicted in a nested entity. +type: bugfix +scope: Core \ No newline at end of file diff --git a/kong/db/errors.lua b/kong/db/errors.lua index a1f96cccd1bf..777d9237c07c 100644 --- a/kong/db/errors.lua +++ b/kong/db/errors.lua @@ -3,6 +3,7 @@ local pl_keys = require("pl.tablex").keys local nkeys = require("table.nkeys") local table_isarray = require("table.isarray") local uuid = require("kong.tools.uuid") +local split = require("kong.tools.string").split local type = type @@ -862,6 +863,9 @@ do -- } -- } -- + if type(err_t[ref.field]) == "string" then + err_t[ref.field] = entity_type..":"..err_t[ref.field] ..":"..ref.field + end if ref.entity == entity_type then local field_name = ref.field local field_value = entity[field_name] @@ -1029,7 +1033,14 @@ do goto next_section end - local entities = input[entity_type] + local entity_type_list = split(entity_type, "|") + local entitys_level = input + for i, entity_type_elem in ipairs(entity_type_list) do + local next_elem = tonumber(entity_type_elem) or entity_type_elem + entitys_level = entitys_level[next_elem] + end + + local entities = entitys_level if type(entities) ~= "table" then -- put it back into the error table diff --git a/kong/db/schema/others/declarative_config.lua b/kong/db/schema/others/declarative_config.lua index 7cae8ffe3c64..ab4dbeceeadb 100644 --- a/kong/db/schema/others/declarative_config.lua +++ b/kong/db/schema/others/declarative_config.lua @@ -400,8 +400,12 @@ local function populate_references(input, known_entities, by_id, by_key, expecte if key and key ~= ngx.null then local ok = add_to_by_key(by_key, entity_schema, item, entity, key) if not ok then - errs[entity] = errs[entity] or {} - errs[entity][i] = uniqueness_error_msg(entity, endpoint_key, key) + local errs_item_name = entity + if parent_entity and tostring(parent_idx) then + errs_item_name = parent_entity.."|"..tostring(parent_idx).."|"..entity + end + errs[errs_item_name] = errs[errs_item_name] or {} + errs[errs_item_name][i] = uniqueness_error_msg(entity, endpoint_key, key) failed = true end end diff --git a/spec/02-integration/04-admin_api/15-off_spec.lua b/spec/02-integration/04-admin_api/15-off_spec.lua index 554b445fcedf..fee89c2911d9 100644 --- a/spec/02-integration/04-admin_api/15-off_spec.lua +++ b/spec/02-integration/04-admin_api/15-off_spec.lua @@ -536,6 +536,45 @@ describe("Admin API #off", function() assert.equals(snis.certificate.id, certificates.id) end) + it("snis in certificates without certificate", function() + local res = assert(client:send { + method = "POST", + path = "/config?flatten_errors=1", + body = { + _format_version = "1.1", + consumers = { + { + username = "x", + basicauth_credentials = { + username = "x", + password = "x", + } + } + }, + certificates = { + { + cert = ssl_fixtures.cert, + id = "d83994d2-c24c-4315-b431-ee76b6611dcb", + key = ssl_fixtures.key, + snis = { + { + name = "foo.example", + id = "1c6e83b7-c9ad-40ac-94e8-52f5ee7bde44", + }, + } + } + }, + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + + local body = assert.response(res).has.status(400) + local entities = cjson.decode(body) + assert.equals("snis:required field missing:certificate", entities.flattened_errors[1].errors[1].message) + end) + it("can reload upstreams (regression test)", function() local config = [[ _format_version: "1.1" @@ -2323,6 +2362,76 @@ R6InCcH2Wh8wSeY5AuDXvu2tv9g/PW9wIJmPuKSHMA== }, post_config(input)) end) + it("flatten_errors when conflicting inputs", function() + local conflicting_input = { + _format_version = "3.0", + consumers = { + { + username = "username1", + tags = { + "k8s-group:configuration.konghq.com", + "k8s-version:v1", + "k8s-kind:KongConsumer", + "k8s-namespace:default", + "k8s-name:consumer1" + }, + basicauth_credentials = { + { + username = "username1", + password = "password", + tags = { + "k8s-group:", + "k8s-version:v1", + "k8s-kind:Secret", + "k8s-namespace:default", + "k8s-name:basic-1" + } + }, + { + username = "username1", + password = "password", + tags = { + "k8s-group:", + "k8s-version:v1", + "k8s-kind:Secret", + "k8s-namespace:default", + "k8s-name:basic-2" + } + }, + } + } + } + } + validate({ + { + entity = { + username = "username1", + password = "password", + tags = { + "k8s-group:", + "k8s-version:v1", + "k8s-kind:Secret", + "k8s-namespace:default", + "k8s-name:basic-2" + } + }, + entity_tags = { + "k8s-group:", + "k8s-version:v1", + "k8s-kind:Secret", + "k8s-namespace:default", + "k8s-name:basic-2" + }, + entity_type = "consumers|1|basicauth_credential", + errors = { { + message = "uniqueness violation: 'basicauth_credentials' entity with username set to 'username1' already declared", + type = "entity", + } + } + }, + }, post_config(conflicting_input)) + end) + it("preserves IDs from the input", function() local id = "0175e0e8-3de9-56b4-96f1-b12dcb4b6691" local service = {