Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/resty/openapi_validator/params.lua
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,12 @@ function _M.validate(route, path_params, query_args, headers, skip)
end

if not skip.header and route.params.header then
validate_param_group(route.params.header, "header", headers)
-- Normalize header keys to lowercase for case-insensitive matching
local lower_headers = {}
for k, v in pairs(headers) do
lower_headers[str_lower(k)] = v
end
validate_param_group(route.params.header, "header", lower_headers)
end

if #errs > 0 then
Expand Down
28 changes: 27 additions & 1 deletion t/conformance/test_validate_header.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,33 @@ T.describe("header: missing required headers", function()
T.like(err, "[Cc]ontent%-[Tt]ype", "error mentions Content-Type")
end)

-- TEST 3: skip header validation
-- TEST 3: case-insensitive header matching (spec has capitalized names, request uses exact match)
T.describe("header: case-insensitive match with exact case keys", function()
local ok, err = validator:validate_request({
method = "GET",
path = "/validateHeaders",
headers = {
["Authorization"] = "Bearer dGVzdA==.dGVzdA==.dGVzdA==",
["Content-Type"] = "application/json",
},
})
T.ok(ok, "exact case headers pass: " .. tostring(err))
end)

-- TEST 4: case-insensitive header matching (spec has capitalized names, request all uppercase)
T.describe("header: case-insensitive match with uppercase keys", function()
local ok, err = validator:validate_request({
method = "GET",
path = "/validateHeaders",
headers = {
["AUTHORIZATION"] = "Bearer dGVzdA==.dGVzdA==.dGVzdA==",
["CONTENT-TYPE"] = "application/json",
},
})
T.ok(ok, "uppercase headers pass: " .. tostring(err))
Comment thread
jarvis9443 marked this conversation as resolved.
end)

-- TEST 5: skip header validation
T.describe("header: skip header validation", function()
local ok, err = validator:validate_request({
method = "GET",
Expand Down
38 changes: 38 additions & 0 deletions t/unit/test_params.lua
Original file line number Diff line number Diff line change
Expand Up @@ -315,4 +315,42 @@ T.describe("params: deepObject anyOf composed (allOf) object branch", function()
T.ok(not errs or #errs == 0, "no errors")
end)

-- Header case-insensitive matching: spec has lowercase name, request has
-- canonical case key (e.g. HTTP/1.1 clients sending X-Client-Id).
T.describe("params: header case-insensitive match (canonical case)", function()
local route = make_route({
{ name = "x-client-id", ["in"] = "header", required = true,
schema = { type = "string" } },
}, "header")

local ok, errs = params_mod.validate(route, {}, {},
{ ["X-Client-Id"] = "test123" })
T.ok(ok, "canonical case header found")
T.ok(not errs or #errs == 0, "no errors")
end)

T.describe("params: header case-insensitive match (uppercase)", function()
local route = make_route({
{ name = "x-client-id", ["in"] = "header", required = true,
schema = { type = "string" } },
}, "header")

local ok, errs = params_mod.validate(route, {}, {},
{ ["X-CLIENT-ID"] = "test123" })
T.ok(ok, "uppercase header found")
T.ok(not errs or #errs == 0, "no errors")
end)

T.describe("params: header case-insensitive match (spec has mixed case)", function()
local route = make_route({
{ name = "Authorization", ["in"] = "header", required = true,
schema = { type = "string" } },
}, "header")

local ok, errs = params_mod.validate(route, {}, {},
{ ["authorization"] = "Bearer token" })
T.ok(ok, "lowercase header matches mixed-case spec name")
T.ok(not errs or #errs == 0, "no errors")
end)

T.done()
Loading