typing: refine error handling for _dynamic fields

Better distinguish between unknown and mismatched types for dynamic
fields, and show a better error message for them.

refs: #211
This commit is contained in:
Wez Furlong
2024-06-28 10:06:27 -07:00
parent 11bbd07f3f
commit e96dfcfe38
3 changed files with 48 additions and 6 deletions
+17 -1
View File
@@ -14,9 +14,11 @@ local function is_listener_domain_option(name, value)
local p = { [name] = value }
local status, err = pcall(kumo.make_listener_domain, p)
if not status then
local err = typing.extract_deserialize_error(err)
if tostring(err):find 'invalid type' then
error(err, 3)
return false, err
end
return false, nil
end
return status
end
@@ -332,6 +334,20 @@ log_oob = false
),
{ relay_from = { '10.0.0.0/24' }, relay_to = false }
)
local status, err = pcall(
parse_toml_data,
[[
# Define a per-listener configuration
[listener."127.0.0.1:25"."*.example.com"]
log_arf = "yes"
]]
)
assert(not status)
utils.assert_matches(
err,
'ListenerConfig: invalid value for field \'log_arf\'\n.*invalid type: string "yes", expected a boolean'
)
end
return mod
+3 -1
View File
@@ -75,9 +75,11 @@ local function is_queue_config_option(name, value)
local p = { [name] = value }
local status, err = pcall(kumo.make_queue_config, p)
if not status then
local err = typing.extract_deserialize_error(err)
if tostring(err):find 'invalid type' then
error(err, 3)
return false, err
end
return false, nil
end
return status
end
+28 -4
View File
@@ -176,7 +176,10 @@ end
-- rust code in the embedding application. The value in that
-- case must be a function that accepts the key and value
-- and returns a boolean to indicate whether that combination
-- is acceptable.
-- is acceptable. If the function returns `false, nil` then
-- that signals that the field is unknown. Otherwise it should
-- return `false, err` with the second element of the tuple
-- containing the error about the mismatched type.
--
-- { _dynamic = function(key, value) return true end }
function mod.record(name, fields)
@@ -204,9 +207,20 @@ function mod.record(name, fields)
local field_def = ty.fields[k]
if not field_def then
local dyn_validate = ty.fields._dynamic
if dyn_validate and dyn_validate(k, v) then
rawset(t, k, v)
return
if dyn_validate then
local status, err = dyn_validate(k, v)
if status then
rawset(t, k, v)
return
end
if err then
TypeError
:new(
string.format("%s: invalid value for field '%s'", ty.name, k),
err
)
:raise()
end
end
TypeError:new(string.format("%s: unknown field '%s'", ty.name, k))
@@ -499,6 +513,16 @@ function mod.option(target_type)
return make_simple_ctor(ty)
end
function mod.extract_deserialize_error(err)
local err = tostring(err)
local re = kumo.regex.compile 'deserialize error: (.*), while processing'
local cap = re:captures(err)
if cap then
return cap[1]
end
return err
end
function mod:test()
local utils = require 'policy-extras.policy_utils'