objectui#3470(PR 见下方 Refs)实施过程中的范围外发现,只记录不修、不指派。实测证据如下。
实测
后端:已发布 @objectstack/*@17.0.0-rc.2 + examples/app-showcase,objectstack dev --seed-admin --fresh;对象 showcase_task,status 为 select。
GET /api/v1/data/showcase_task?$filter=[["status","not_in",["done"]]] -> 200,8 行(与 ["status","!=","done"] 基线同 8 行)
GET /api/v1/data/showcase_task?$filter=[["status","not_in","done"]] -> 500 {"code":"DATABASE_ERROR"}
GET /api/v1/data/showcase_task?$filter=[["status","nin","done"]] -> 500 {"code":"DATABASE_ERROR"}
GET /api/v1/data/showcase_task?$filter=[["status","notin","done"]] -> 500 {"code":"DATABASE_ERROR"}
即:集合算子 + 标量比较值 = 500。not_in/nin/notin 三种拼法一致,说明是规范化之后的同一条路径。
作为对照,同一批探针里形状错误的算子拼法是响亮且正确的 400:[["status","not in",["done"]]](带空格,不在 VALID_AST_OPERATORS)→ 400 INVALID_FILTER。所以问题不在拒收机制本身,而在这一类比较值形状根本没被拒收就下到了 DB 层。
为什么这是个缺陷而不是脏输入
ViewFilterRuleSchema.value 声明为 string | number | boolean | null | (string|number)[],没有按算子约束形状。因此
{ "field": "status", "operator": "not_in", "value": "done" }
是一条 spec 合法的 ViewFilterRule,可以正常写进 ListViewSchema.filter / ViewTab.filter 并通过发布校验。它下降成 AST 三元组 ["status","not_in","done"] 后又能通过 isFilterAST(该谓词只看算子与结构,不看值形状),于是一路走到 DB 层才炸。
可达性:objectui 侧 plugin-list 的 normalizeFilterCondition 只对数组值做 in/not-in 展开,标量值原样上线,所以作者写一个标量 not_in 的 tab preset,点开就是 500。
期望
与 #5348 / #5346 同一个口径:在收口点按形状拒收,给 ADR-0112 信封的 400 INVALID_FILTER,点名是哪个算子、哪个成员、期望什么形状 —— 而不是 500 DATABASE_ERROR(把服务端内部失败暴露成客户端错误的错误类别,也无法告诉作者该改哪个字段)。
一并建议评估:in 的标量值、以及 between 的非二元组值,是否落在同一条路径上(本次只测了 not_in 族,未展开)。
与既有账的关系
同属 #3948 / #4209 点名的「过滤器形状错误未被响亮拒收」大类,与 #5234($in/$nin 列表里的非 $field 对象成员 → 静默零行)是同一轴的不同格:
修法很可能能共用 PR #5223 装在比较发射器上的那道形状守卫(同一收口点、同一信封),但因为症状与入口面都不同,先按标准立独立条目,是否并入 #5234 的完成范围请 PM 裁决。
Refs:#5234、#5346、#5348、#5369、#3948、#4209;发现于 objectui#3470(objectui PR:claude/issue-3470-userfilters-operator-table)。
objectui#3470(PR 见下方 Refs)实施过程中的范围外发现,只记录不修、不指派。实测证据如下。
实测
后端:已发布
@objectstack/*@17.0.0-rc.2+examples/app-showcase,objectstack dev --seed-admin --fresh;对象showcase_task,status为 select。即:集合算子 + 标量比较值 = 500。
not_in/nin/notin三种拼法一致,说明是规范化之后的同一条路径。作为对照,同一批探针里形状错误的算子拼法是响亮且正确的 400:
[["status","not in",["done"]]](带空格,不在VALID_AST_OPERATORS)→400 INVALID_FILTER。所以问题不在拒收机制本身,而在这一类比较值形状根本没被拒收就下到了 DB 层。为什么这是个缺陷而不是脏输入
ViewFilterRuleSchema.value声明为string | number | boolean | null | (string|number)[],没有按算子约束形状。因此{ "field": "status", "operator": "not_in", "value": "done" }是一条 spec 合法的
ViewFilterRule,可以正常写进ListViewSchema.filter/ViewTab.filter并通过发布校验。它下降成 AST 三元组["status","not_in","done"]后又能通过isFilterAST(该谓词只看算子与结构,不看值形状),于是一路走到 DB 层才炸。可达性:objectui 侧
plugin-list的normalizeFilterCondition只对数组值做 in/not-in 展开,标量值原样上线,所以作者写一个标量not_in的 tab preset,点开就是 500。期望
与 #5348 / #5346 同一个口径:在收口点按形状拒收,给 ADR-0112 信封的
400 INVALID_FILTER,点名是哪个算子、哪个成员、期望什么形状 —— 而不是 500 DATABASE_ERROR(把服务端内部失败暴露成客户端错误的错误类别,也无法告诉作者该改哪个字段)。一并建议评估:
in的标量值、以及between的非二元组值,是否落在同一条路径上(本次只测了not_in族,未展开)。与既有账的关系
同属 #3948 / #4209 点名的「过滤器形状错误未被响亮拒收」大类,与 #5234(
$in/$nin列表里的非$field对象成员 → 静默零行)是同一轴的不同格:$in/$nin的非$field对象成员,与 LIKE 族的对象比较值(String 成[object Object]) #5234 的症状是静默空谓词(fail-closed,不抛错),入口是 ObjectQL 对象语法的列表成员形状;修法很可能能共用 PR #5223 装在比较发射器上的那道形状守卫(同一收口点、同一信封),但因为症状与入口面都不同,先按标准立独立条目,是否并入 #5234 的完成范围请 PM 裁决。
Refs:#5234、#5346、#5348、#5369、#3948、#4209;发现于 objectui#3470(objectui PR:
claude/issue-3470-userfilters-operator-table)。