diff --git a/assets/policy-extras/listener_domains.lua b/assets/policy-extras/listener_domains.lua index deb2658a..1b7b95fd 100644 --- a/assets/policy-extras/listener_domains.lua +++ b/assets/policy-extras/listener_domains.lua @@ -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 diff --git a/assets/policy-extras/queue.lua b/assets/policy-extras/queue.lua index 4f822810..88387a40 100644 --- a/assets/policy-extras/queue.lua +++ b/assets/policy-extras/queue.lua @@ -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 diff --git a/assets/policy-extras/typing.lua b/assets/policy-extras/typing.lua index 101ece35..e6414a0c 100644 --- a/assets/policy-extras/typing.lua +++ b/assets/policy-extras/typing.lua @@ -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'