From 734ce531a3f40dcf49703ac41cfa090f8938b2e6 Mon Sep 17 00:00:00 2001 From: Dmitry Oboukhov Date: Thu, 26 Mar 2026 18:25:15 +0300 Subject: [PATCH] fix(constraint): call constraint for map and array types In the old validator (bench/validator.lua:897) constraint was called for all types from a single shared code path. In the C implementation it was only wired up in cv_check_scalar, leaving map and array silently ignoring the constraint field. Add constraint invocation in cv_check_map (step 3, before transform) and cv_check_array (before transform), matching the original behaviour: called only on success, value in details is the pre-transform value. Add regression tests in test/compat_test.lua: test_map_constraint and test_array_constraint. --- cv/cv.c | 50 ++++++++++++++++++++- test/compat_test.lua | 102 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/cv/cv.c b/cv/cv.c index 59ce131..03eb1b2 100644 --- a/cv/cv.c +++ b/cv/cv.c @@ -2528,6 +2528,30 @@ cv_check_array(lua_State *L, struct cv_ctx *ctx, } } + /* constraint on the array itself */ + if (ok && n->constraint_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, + n->constraint_ref); + lua_pushvalue(L, data_idx); + if (lua_pcall(L, 1, 0, 0) != 0) { + int errmsg = lua_gettop(L); + int det = cv_ctx_push_error(L, ctx, + n, "CONSTRAINT_ERROR", + "Field constraint detected" + " error"); + if (det != 0) { + lua_pushvalue(L, data_idx); + lua_setfield(L, det, "value"); + lua_pushvalue(L, errmsg); + lua_setfield(L, det, + "constraint_error"); + lua_pop(L, 1); + } + lua_pop(L, 1); /* errmsg */ + return false; + } + } + /* transform on the array itself */ if (ok && !ctx->validate_only && n->transform_ref != LUA_NOREF) { @@ -2941,7 +2965,31 @@ cv_check_map(lua_State *L, struct cv_ctx *ctx, } /* return_unexpected: do nothing extra */ - /* --- step 3: transform via pcall --- */ + /* --- step 3: constraint via pcall --- */ + if (ok && n->constraint_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, + n->constraint_ref); + lua_pushvalue(L, data_idx); + if (lua_pcall(L, 1, 0, 0) != 0) { + int errmsg = lua_gettop(L); + int det = cv_ctx_push_error(L, ctx, + n, "CONSTRAINT_ERROR", + "Field constraint detected" + " error"); + if (det != 0) { + lua_pushvalue(L, data_idx); + lua_setfield(L, det, "value"); + lua_pushvalue(L, errmsg); + lua_setfield(L, det, + "constraint_error"); + lua_pop(L, 1); + } + lua_pop(L, 1); /* errmsg */ + return false; + } + } + + /* --- step 4: transform via pcall --- */ if (ok && !ctx->validate_only && n->transform_ref != LUA_NOREF) { lua_rawgeti(L, LUA_REGISTRYINDEX, diff --git a/test/compat_test.lua b/test/compat_test.lua index c157594..7eda9fc 100644 --- a/test/compat_test.lua +++ b/test/compat_test.lua @@ -680,6 +680,108 @@ function g.test_func_constraint() ) end +function g.test_map_constraint() + -- constraint ok + t.assert_equals( + { + cv.check( + {asd = 123}, + { + type = 'map', + properties = { asd = 'number' }, + constraint = function(value) + t.assert_equals( + value, {asd = 123}) + end + } + ) + }, + { {asd = 123}, {} } + ) + + -- constraint error (false) + t.assert_equals( + { + cv.check( + {asd = 123}, + { + type = 'map', + properties = { asd = 'number' }, + constraint = function(_value) + error(false, 0) + end + } + ) + }, + { + nil, + { + { + details = { + constraint_error = false, + value = {asd = 123}, + }, + message = + "Field constraint detected" + .. " error", + path = "$", + type = "CONSTRAINT_ERROR", + }, + } + } + ) +end + +function g.test_array_constraint() + -- constraint ok + t.assert_equals( + { + cv.check( + {1, 2, 3}, + { + type = 'array', + constraint = function(value) + t.assert_equals( + value, {1, 2, 3}) + end + } + ) + }, + { {1, 2, 3}, {} } + ) + + -- constraint error (string) + t.assert_equals( + { + cv.check( + {1, 2, 3}, + { + type = 'array', + constraint = function(_value) + error("bad array", 0) + end + } + ) + }, + { + nil, + { + { + details = { + constraint_error = "bad array", + value = {1, 2, 3}, + }, + message = + "Field constraint detected" + .. " error", + path = "$", + type = "CONSTRAINT_ERROR", + }, + } + } + ) +end + function g.test_oneof() t.assert_equals( {