]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] Fix lua_shape registry to recursively resolve nested schemas 5754/head
authorVsevolod Stakhov <vsevolod@rspamd.com>
Wed, 19 Nov 2025 16:20:57 +0000 (16:20 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Wed, 19 Nov 2025 16:20:57 +0000 (16:20 +0000)
The registry's resolve_schema function was not recursively resolving
field schemas and opts.extra in table nodes, causing mixins in nested
one_of variants to never be expanded. This broke external_relay plugin
validation where rule variants with mixins were reported as having
unknown fields.

Now recursively resolves all nested schemas including field schemas,
opts.extra, and ensures mixins are properly expanded throughout the
entire schema tree.

lualib/lua_shape/registry.lua
src/plugins/lua/external_relay.lua

index e5118a92a87ed79e79d1fe7efc4db35bd4c2d464..63b40b44887daf5ddcb4dc8dbb2f94a59862b1b9 100644 (file)
@@ -114,14 +114,40 @@ function Registry:resolve_schema(schema)
     return target.resolved
   end
 
-  -- Handle table nodes with mixins
+  -- Handle table nodes with mixins and/or extra schema
   if tag == "table" then
     local opts = schema.opts or {}
     local mixins = opts.mixins or {}
+    local has_mixins = #mixins > 0
+    local has_extra = opts.extra ~= nil
+
+    -- First, recursively resolve all field schemas
+    local fields = schema.fields or {}
+    local resolved_fields = nil
+    local fields_changed = false
+
+    for field_name, field_spec in pairs(fields) do
+      local field_schema = field_spec.schema
+      local resolved_field_schema = self:resolve_schema(field_schema)
+      if resolved_field_schema ~= field_schema then
+        if not resolved_fields then
+          resolved_fields = shallowcopy(fields)
+        end
+        local resolved_field_spec = shallowcopy(field_spec)
+        resolved_field_spec.schema = resolved_field_schema
+        resolved_fields[field_name] = resolved_field_spec
+        fields_changed = true
+      end
+    end
 
-    if #mixins > 0 then
-      -- Merge mixin fields into table
-      local merged_fields = shallowcopy(schema.fields or {})
+    local merged_fields = resolved_fields or fields
+    local resolved_extra = opts.extra
+
+    -- Merge mixin fields if present
+    if has_mixins then
+      if not resolved_fields then
+        merged_fields = shallowcopy(fields)
+      end
 
       for _, mixin_def in ipairs(mixins) do
         if mixin_def._is_mixin then
@@ -157,10 +183,22 @@ function Registry:resolve_schema(schema)
           end
         end
       end
+    end
+
+    -- Resolve extra schema if present
+    if has_extra then
+      resolved_extra = self:resolve_schema(opts.extra)
+    end
 
-      -- Create new table schema with merged fields
+    -- Create new table schema if anything changed
+    if fields_changed or has_mixins or (has_extra and resolved_extra ~= opts.extra) then
       local resolved = shallowcopy(schema)
       resolved.fields = merged_fields
+      if resolved_extra ~= opts.extra then
+        local resolved_opts = shallowcopy(opts)
+        resolved_opts.extra = resolved_extra
+        resolved.opts = resolved_opts
+      end
       self.resolved_cache[schema] = resolved
       return resolved
     end
index b40dd076b29f0def2b45cc6f6f7580e99d67490e..e4860bd944d156468c492ab3f009f0b90a425b5c 100644 (file)
@@ -92,7 +92,8 @@ local config_schema = T.table({
   }):doc({ summary = "External relay rules keyed by name" }),
 }):doc({ summary = "External relay plugin configuration" })
 
-PluginSchema.register("plugins.external_relay", config_schema)
+-- Register and get the resolved schema (with mixins expanded)
+config_schema = PluginSchema.register("plugins.external_relay", config_schema)
 
 if confighelp then
   return