From: Vsevolod Stakhov Date: Mon, 17 Nov 2025 21:12:29 +0000 (+0000) Subject: [Minor] Migrate lua_redis.lua to lua_shape with mixin tracking X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2913c2fa7bf33dc96deaf2bda2851774eebaf195;p=thirdparty%2Frspamd.git [Minor] Migrate lua_redis.lua to lua_shape with mixin tracking Replace tableshape with lua_shape using first-class mixin system to preserve origin tracking for documentation and better error reporting. Key changes: - common_schema: Now a proper T.table() schema (not plain table) - Schema composition: Uses T.mixin() instead of lutil.table_merge() - enrich_schema: Returns T.one_of() with 6 named variants, each using mixins for redis_common and external fields - All tables use { open = true } to allow additional fields - Transform patterns: T.transform(T.number(), tostring) for type conversions - Union types: T.one_of({...}) for alternatives like string | array Benefits: - Mixin origin tracking preserved in field metadata - Documentation generation can show field sources - Better error messages with mixin context - Consistent with lua_shape design philosophy --- diff --git a/lualib/lua_redis.lua b/lualib/lua_redis.lua index 53a81423aa..0025399e06 100644 --- a/lualib/lua_redis.lua +++ b/lualib/lua_redis.lua @@ -17,71 +17,153 @@ limitations under the License. local logger = require "rspamd_logger" local lutil = require "lua_util" local rspamd_util = require "rspamd_util" -local ts = require("tableshape").types +local T = require "lua_shape.core" local exports = {} local E = {} local N = "lua_redis" -local db_schema = (ts.number / tostring + ts.string):is_optional():describe("Database number") -local common_schema = { - timeout = (ts.number + ts.string / lutil.parse_time_interval):is_optional():describe("Connection timeout (seconds)"), +local db_schema = T.one_of({ + T.transform(T.number(), tostring), + T.string() +}):optional():doc({ summary = "Database number" }) + +local common_schema = T.table({ + timeout = T.one_of({ + T.number(), + T.transform(T.string(), lutil.parse_time_interval) + }):optional():doc({ summary = "Connection timeout (seconds)" }), db = db_schema, database = db_schema, dbname = db_schema, - prefix = ts.string:is_optional():describe("Key prefix"), - username = ts.string:is_optional():describe("Username"), - password = ts.string:is_optional():describe("Password"), + prefix = T.string():optional():doc({ summary = "Key prefix" }), + username = T.string():optional():doc({ summary = "Username" }), + password = T.string():optional():doc({ summary = "Password" }), -- TLS options - ssl = ts.boolean:is_optional():describe("Enable TLS to Redis"), - no_ssl_verify = ts.boolean:is_optional():describe("Disable TLS certificate verification"), - ssl_ca = ts.string:is_optional():describe("CA certificate file"), - ssl_ca_dir = ts.string:is_optional():describe("CA certificates directory"), - ssl_cert = ts.string:is_optional():describe("Client certificate file (PEM)"), - ssl_key = ts.string:is_optional():describe("Client private key file (PEM)"), - sni = ts.string:is_optional():describe("SNI server name override"), - expand_keys = ts.boolean:is_optional():describe("Expand keys"), - sentinels = (ts.string + ts.array_of(ts.string)):is_optional():describe("Sentinel servers"), - sentinel_watch_time = (ts.number + ts.string / lutil.parse_time_interval):is_optional():describe("Sentinel watch time"), - sentinel_masters_pattern = ts.string:is_optional():describe("Sentinel masters pattern"), - sentinel_master_maxerrors = (ts.number + ts.string / tonumber):is_optional():describe("Sentinel master max errors"), - sentinel_username = ts.string:is_optional():describe("Sentinel username"), - sentinel_password = ts.string:is_optional():describe("Sentinel password"), - redis_version = (ts.number + ts.string / tonumber):is_optional():describe("Redis server version (6 or 7)"), -} - -local read_schema = lutil.table_merge({ - read_servers = ts.string + ts.array_of(ts.string), -}, common_schema) - -local write_schema = lutil.table_merge({ - write_servers = ts.string + ts.array_of(ts.string), -}, common_schema) - -local rw_schema = lutil.table_merge({ - read_servers = ts.string + ts.array_of(ts.string), - write_servers = ts.string + ts.array_of(ts.string), -}, common_schema) - -local servers_schema = lutil.table_merge({ - servers = ts.string + ts.array_of(ts.string), -}, common_schema) - -local server_schema = lutil.table_merge({ - server = ts.string + ts.array_of(ts.string), -}, common_schema) + ssl = T.boolean():optional():doc({ summary = "Enable TLS to Redis" }), + no_ssl_verify = T.boolean():optional():doc({ summary = "Disable TLS certificate verification" }), + ssl_ca = T.string():optional():doc({ summary = "CA certificate file" }), + ssl_ca_dir = T.string():optional():doc({ summary = "CA certificates directory" }), + ssl_cert = T.string():optional():doc({ summary = "Client certificate file (PEM)" }), + ssl_key = T.string():optional():doc({ summary = "Client private key file (PEM)" }), + sni = T.string():optional():doc({ summary = "SNI server name override" }), + expand_keys = T.boolean():optional():doc({ summary = "Expand keys" }), + sentinels = T.one_of({ + T.string(), + T.array(T.string()) + }):optional():doc({ summary = "Sentinel servers" }), + sentinel_watch_time = T.one_of({ + T.number(), + T.transform(T.string(), lutil.parse_time_interval) + }):optional():doc({ summary = "Sentinel watch time" }), + sentinel_masters_pattern = T.string():optional():doc({ summary = "Sentinel masters pattern" }), + sentinel_master_maxerrors = T.one_of({ + T.number(), + T.transform(T.string(), tonumber) + }):optional():doc({ summary = "Sentinel master max errors" }), + sentinel_username = T.string():optional():doc({ summary = "Sentinel username" }), + sentinel_password = T.string():optional():doc({ summary = "Sentinel password" }), + redis_version = T.one_of({ + T.number(), + T.transform(T.string(), tonumber) + }):optional():doc({ summary = "Redis server version (6 or 7)" }), +}, { open = true }) local enrich_schema = function(external) - return ts.one_of { - ts.shape(lutil.table_merge(common_schema, - external)), -- no specific redis servers (e.g when global settings are used) - ts.shape(lutil.table_merge(read_schema, external)), -- read_servers specified - ts.shape(lutil.table_merge(write_schema, external)), -- write_servers specified - ts.shape(lutil.table_merge(rw_schema, external)), -- both read and write servers defined - ts.shape(lutil.table_merge(servers_schema, external)), -- just servers for both ops - ts.shape(lutil.table_merge(server_schema, external)), -- legacy `server` attribute - } + local external_schema = T.table(external, { open = true }) + + return T.one_of({ + { + name = "common_only", + schema = T.table({}, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + }, + { + name = "read_servers", + schema = T.table({ + read_servers = T.one_of({ + T.string(), + T.array(T.string()) + }), + }, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + }, + { + name = "write_servers", + schema = T.table({ + write_servers = T.one_of({ + T.string(), + T.array(T.string()) + }), + }, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + }, + { + name = "rw_servers", + schema = T.table({ + read_servers = T.one_of({ + T.string(), + T.array(T.string()) + }), + write_servers = T.one_of({ + T.string(), + T.array(T.string()) + }), + }, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + }, + { + name = "servers", + schema = T.table({ + servers = T.one_of({ + T.string(), + T.array(T.string()) + }), + }, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + }, + { + name = "server_legacy", + schema = T.table({ + server = T.one_of({ + T.string(), + T.array(T.string()) + }), + }, { + open = true, + mixins = { + T.mixin(common_schema, { as = "redis_common" }), + T.mixin(external_schema, { as = "external" }) + } + }) + } + }) end exports.enrich_schema = enrich_schema