You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Joi Schema配置问题:POST需id必填、PUT需禁用id字段

Joi Schema 适配POST/PUT请求的字段规则问题解决

问题场景

现有如下Joi Schema定义:

const commonFields = {
  id: joi.string().required(),
  name: joi.string().required().min(5).max(50),
  nif: joi.string().length(9).required(),
  diet: joi.bool().required().strict(),
  vegetarian: joi.bool().required().strict(),
};

const aluno = joi.object({
  ...commonFields,
  num: joi
    .string()
    .required()
    .regex(/^\d{1,4}\/\d{2}$/),
  regime: joi.string().required().valid("externo", "interno"),
});

需要实现两个规则:

  • POST请求创建用户时,id字段为必填项
  • PUT请求更新用户时,请求体中禁止出现id字段(id从req.params.id获取)

尝试用alter()方法配置字段规则,并在验证时传入请求方法上下文,但测试PUT请求时,请求体带id并未触发预期的"id" is not allowed错误。

问题原因

alter()方法的规则分支不是通过context参数触发的,它需要显式调用.tailor()方法指定要应用的分支名,之前的代码仅传入了context但未触发分支切换,导致规则未生效。

正确解决方案

方案1:使用alter() + .tailor()

保持alter()的规则配置,验证时通过.tailor()指定对应分支:

  1. 更新commonFields中的id字段:
const commonFields = {
  id: joi.string().alter({
    post: (schema) => schema.required(),
    put: (schema) => schema.forbidden()
  }),
  name: joi.string().required().min(5).max(50),
  nif: joi.string().length(9).required(),
  diet: joi.bool().required().strict(),
  vegetarian: joi.bool().required().strict(),
};
  1. 验证逻辑中切换分支:
async function updateUser(req, res, type, db) {
  try {
    // 根据请求方法选择对应的alter分支,注意转小写匹配分支名
    const schema = requestValidation[type].tailor(req.method.toLowerCase());
    const { error } = schema.validate(req.body);
    
    if (error) {
      return res.status(400).send({ error: error.message });
    }

    const id = req.params.id;
    const { name, nif, vegetarian, diet } = req.body;
    // ...后续业务逻辑
  } catch (err) {
    // ...错误处理
  }
}

方案2:拆分独立Schema(更直观)

如果觉得分支逻辑不够清晰,可以直接为POST和PUT分别定义独立Schema:

// 抽离基础字段,不含id的规则
const baseFields = {
  name: joi.string().required().min(5).max(50),
  nif: joi.string().length(9).required(),
  diet: joi.bool().required().strict(),
  vegetarian: joi.bool().required().strict(),
};

// POST创建用户的Schema:id必填
const alunoPostSchema = joi.object({
  ...baseFields,
  id: joi.string().required(),
  num: joi.string().required().regex(/^\d{1,4}\/\d{2}$/),
  regime: joi.string().required().valid("externo", "interno"),
});

// PUT更新用户的Schema:id禁止出现在请求体
const alunoPutSchema = joi.object({
  ...baseFields,
  id: joi.string().forbidden(),
  num: joi.string().required().regex(/^\d{1,4}\/\d{2}$/),
  regime: joi.string().required().valid("externo", "interno"),
});

验证时直接根据请求方法选择对应Schema:

async function updateUser(req, res, type, db) {
  try {
    const schema = req.method === 'POST' ? alunoPostSchema : alunoPutSchema;
    const { error } = schema.validate(req.body);
    
    if (error) {
      return res.status(400).send({ error: error.message });
    }

    const id = req.params.id;
    const { name, nif, vegetarian, diet } = req.body;
    // ...后续业务逻辑
  } catch (err) {
    // ...错误处理
  }
}

额外注意事项

  • 确保验证时未开启allowUnknown: true,否则Joi会忽略请求体中的未知字段,不会触发forbidden()的错误提示。
  • PUT请求中如果需要验证req.params.id的格式,可以单独添加参数验证逻辑:
const paramsSchema = joi.object({
  id: joi.string().required()
});
const paramsError = paramsSchema.validate(req.params).error;
if (paramsError) {
  return res.status(400).send({ error: paramsError.message });
}

内容的提问来源于stack exchange,提问作者palmutska

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 03:18:24