ObjectOS
构建数据

验证规则

必填字段、唯一约束与 CEL 驱动的规则,在平台层拦住坏数据 —— 错误消息由你掌控。

验证规则

**验证在每条写入路径上都会执行。**REST、Console 表单、ObjectQL —— 同一套规则处处生效,坏数据没有后门可钻。验证规则是作用于单条记录的确定性、同步、无副作用谓词:仅凭这次写入(更新时再加上更新前的记录)即可判定,不做任何 I/O。

验证是分层的 —— 能表达规则的最低层就是该用的层:

表达什么示例
字段修饰符是否必填、是否唯一required: trueunique: true
字段约束单字段形态minmaxmaxLengthformat
条件修饰符依赖上下文的必填requiredWhen: P`record.amount > 10000`
对象级验证规则跨字段业务逻辑"折扣不能超过总额"
唯一索引组合 / 限定范围的唯一性{ fields: ['code', 'organization'], unique: true }
生命周期 Hook任意验证代码beforeInsert / beforeUpdate

字段级:required、unique 与约束

通用修饰符适用于所有字段类型:

import { ObjectSchema, Field } from '@objectstack/spec/data';

fields: {
  email: Field.email({ label: 'Contact Email', required: true, unique: true }),
  quantity: Field.number({ label: 'Quantity', min: 1, max: 9999 }),
  code: Field.text({ label: 'Code', minLength: 3, maxLength: 20 }),
}
修饰符行为
required: true在运行时拒绝 null / undefined
unique: true数据库层唯一约束 —— 而不是有竞态的应用层检查
min / max数值范围(number、currency、percent、rating、slider……)
minLength / maxLength文本类型的字符长度上下限
format内置形态检查 —— emailurlphone 字段类型默认自带
readonly: true更新时由服务端强制 —— 非系统写入该字段会被静默丢弃(插入不受限)

字段类型本身也自带免费验证:email 检查 local@domain 形态,url 要求带协议,select 的值必须匹配某个选项,lookup 强制被引用记录存在,json 必须能解析。每种默认行为见字段类型参考

条件式必填 / 只读 / 可见

是否必填可以通过 CEL 谓词依赖记录的其余部分:

import { P } from '@objectstack/spec';

po_number: Field.text({
  label: 'PO Number',
  requiredWhen: P`record.amount > 10000`,
}),

visibleWhenreadonlyWhenrequiredWhen 都接受一个 CEL 谓词 —— 见公式

对象级验证规则

跨字段业务逻辑放在对象的 validations 数组里:

export const Order = ObjectSchema.create({
  name: 'order',
  fields: {
    amount: Field.currency({ label: 'Amount', required: true }),
    status: Field.select({ label: 'Status', options: [ /* ... */ ] }),
  },

  validations: [
    {
      name: 'amount_positive',
      type: 'script',
      severity: 'error',
      message: 'Amount must be greater than zero',
      // CEL 谓词 —— TRUE 表示记录无效。
      condition: 'record.amount <= 0',
      events: ['insert', 'update'],
    },
  ],
});

警告:script 规则来说,condition失败谓词 —— 它求值为 TRUE 时,验证失败。要针对状态来写判断:record.amount <= 0 拒绝非正数金额。

公共属性

每种规则类型都共享这套基础形态:

属性必填说明
name唯一的规则名(snake_case
message面向用户的错误消息
typescriptstate_machineformatcross_fieldjson_schemaconditional
severityerror(阻止保存,默认)、warning(允许保存)、info
eventsinsertupdatedelete —— 默认 ['insert', 'update']
priority0–9999,数字越小越先执行(默认 100)
active不删除即可开关(默认 true

六种规则类型

类型检查什么关键配置
script任意 CEL 谓词condition(TRUE = 无效)
state_machine允许的状态迁移fieldtransitions 映射
format单字段匹配正则或内置格式fieldregexformat: 'email' | 'url' | 'phone' | 'json'
cross_field字段之间的关系fieldscondition
json_schemaJSON 字段匹配 JSON Schemafieldschema
conditional仅在谓词成立时应用嵌套规则whenthen、可选 otherwise

两条你会反复用到的规则:

// 状态机 —— 强制状态流转
{
  name: 'order_status_transitions',
  type: 'state_machine',
  severity: 'error',
  message: 'Invalid status transition',
  field: 'status',
  transitions: {
    draft: ['submitted', 'cancelled'],
    submitted: ['approved', 'rejected', 'cancelled'],
    approved: ['completed'],
    rejected: ['draft'],
    cancelled: [],
    completed: [],
  },
  events: ['update'],
}

// 跨字段 —— 日期先后有序
{
  name: 'date_range_valid',
  type: 'cross_field',
  severity: 'error',
  message: 'End date must be after start date',
  fields: ['start_date', 'end_date'],
  condition: 'record.end_date <= record.start_date',
  events: ['insert', 'update'],
}

在更新时的条件里,previous 持有变更前的快照 —— record.stage != previous.stage 可检测到变更。

唯一性:用索引,不用规则

没有 uniqueness 验证类型 —— 这是有意为之。先 SELECT 再 INSERT 的检查天然有竞态(TOCTOU);数据库唯一约束没有。唯一性要在数据层强制:

// 字段级
email: Field.email({ label: 'Contact Email', unique: true }),

// 组合 / 限定范围的唯一性用索引
indexes: [
  { fields: ['code', 'organization'], unique: true },
  // 需要限定范围 / 条件约束时加 `partial`
]

同样的逻辑也排除了异步 / 远程验证(那是客户端表单的关注点,放在写路径上还是 SSRF 与延迟隐患)和自定义处理器 —— 任意验证代码应该放进 beforeInsert / beforeUpdate 生命周期 Hook。

自定义错误消息

message 就是规则触发时用户看到的内容 —— 出现在 Console 表单和 API 错误响应中。要写得可执行:

"Invalid input""Close date is required for closed deals"
"Validation failed""Phone must match format: +1-XXX-XXX-XXXX"
"Error""Discount cannot exceed total"

severity 来校准:error 阻止保存,warning 显示消息但放行保存,info 纯提示。

**提示:**无法求值的谓词(解析错误、未绑定变量)会被当作损坏的规则处理 —— 记录日志后跳过,而不是拦下每一次写入。

最佳实践

应该不应该
在尽可能低的层做验证(字段 → 规则 → Hook)用 Hook 做字段修饰符就能表达的事
写清晰、可执行的消息在多层重复同一个检查
priority 让廉价的格式检查先跑构造过于复杂的条件
用真实数据测试规则拦住正当的边界情况

下一步

页面原因
数据模型这些规则所保护的对象与字段
公式conditionrequiredWhen 背后的 CEL 语言
关系跨对象的引用完整性
CEL 参考条件可用的完整运算符与标准库参考
字段类型各字段类型的内置验证默认值
流程用自动化响应(合法的)记录变更

On this page