end
end
+-- Combinator functions registry for use with external maps and other consumers
+-- that need structured data instead of plain strings
+local combinators = {
+ -- Default: concatenate all selector results into a single string
+ string = exports.combine_selectors,
+ -- Flatten all results into a flat array
+ array = exports.flatten_selectors,
+ -- Convert pairs of selectors into key-value object (use `id` extractor for keys)
+ object = exports.kv_table_from_pairs,
+}
+
+--[[[
+-- @function lua_selectors.get_combinator(name)
+-- Returns a combinator function by name
+-- @param {string} name combinator name: 'string', 'array', or 'object'
+-- @return {function} combinator function or nil if not found
+--]]
+exports.get_combinator = function(name)
+ return combinators[name]
+end
+
+--[[[
+-- @function lua_selectors.list_combinators()
+-- Returns list of available combinator names
+-- @return {table} array of combinator names
+--]]
+exports.list_combinators = function()
+ local res = {}
+ for k, _ in pairs(combinators) do
+ table.insert(res, k)
+ end
+ return res
+end
+
+--[[[
+-- @function lua_selectors.create_selector_closure_with_combinator(cfg, selector_str, delimiter, combinator_name)
+-- Creates a closure from a string selector using named combinator
+-- @param {rspamd_config} cfg rspamd config object
+-- @param {string} selector_str selector string to parse
+-- @param {string} delimiter delimiter for combining results (used by some combinators)
+-- @param {string} combinator_name name of combinator: 'string', 'array', or 'object'
+-- @return {function} closure that processes selector on task, or nil on error
+--]]
+exports.create_selector_closure_with_combinator = function(cfg, selector_str, delimiter, combinator_name)
+ local combinator_fn = combinators[combinator_name or 'string']
+
+ if not combinator_fn then
+ logger.errx(cfg, 'unknown combinator: %s, available: %s',
+ combinator_name, table.concat(exports.list_combinators(), ', '))
+ return nil
+ end
+
+ return exports.create_selector_closure_fn(cfg, cfg, selector_str, delimiter, combinator_fn)
+end
+
-- Publish log target
exports.M = M
rspamd_logger.errx(rspamd_config, 'selector map requires selector definition')
return nil
else
- local selector = lua_selectors.create_selector_closure(
- rspamd_config, newrule['selector'], newrule['delimiter'] or "")
+ local selector
+ local combinator = newrule['combinator']
+
+ if combinator then
+ -- Use named combinator for structured output (array, object, string)
+ selector = lua_selectors.create_selector_closure_with_combinator(
+ rspamd_config, newrule['selector'], newrule['delimiter'] or "", combinator)
+
+ if not selector then
+ rspamd_logger.errx(rspamd_config, 'selector map has invalid selector or combinator: "%s", combinator: %s, symbol: %s',
+ newrule['selector'], combinator, newrule['symbol'])
+ return nil
+ end
- if not selector then
- rspamd_logger.errx(rspamd_config, 'selector map has invalid selector: "%s", symbol: %s',
- newrule['selector'], newrule['symbol'])
- return nil
+ lua_util.debugm(N, rspamd_config, 'created selector with combinator %s for rule %s',
+ combinator, newrule['symbol'])
+ else
+ -- Default behavior: use combine_selectors (string output)
+ selector = lua_selectors.create_selector_closure(
+ rspamd_config, newrule['selector'], newrule['delimiter'] or "")
+
+ if not selector then
+ rspamd_logger.errx(rspamd_config, 'selector map has invalid selector: "%s", symbol: %s',
+ newrule['selector'], newrule['symbol'])
+ return nil
+ end
end
newrule.selector = selector
end
local selector_str = string.match(newrule['map'], '^redis%+selector://(.*)$')
- local selector = lua_selectors.create_selector_closure(
- rspamd_config, selector_str, newrule['delimiter'] or "")
+ local selector
+ local combinator = newrule['combinator']
- if not selector then
- rspamd_logger.errx(rspamd_config, 'redis selector map has invalid selector: "%s", symbol: %s',
- selector_str, newrule['symbol'])
- return nil
+ if combinator then
+ selector = lua_selectors.create_selector_closure_with_combinator(
+ rspamd_config, selector_str, newrule['delimiter'] or "", combinator)
+
+ if not selector then
+ rspamd_logger.errx(rspamd_config, 'redis selector map has invalid selector or combinator: "%s", combinator: %s, symbol: %s',
+ selector_str, combinator, newrule['symbol'])
+ return nil
+ end
+
+ lua_util.debugm(N, rspamd_config, 'created redis selector with combinator %s for rule %s',
+ combinator, newrule['symbol'])
+ else
+ selector = lua_selectors.create_selector_closure(
+ rspamd_config, selector_str, newrule['delimiter'] or "")
+
+ if not selector then
+ rspamd_logger.errx(rspamd_config, 'redis selector map has invalid selector: "%s", symbol: %s',
+ selector_str, newrule['symbol'])
+ return nil
+ end
end
newrule['redis_key'] = selector