akirarika / milkio

🌟 A TypeScript Framework - Now, break the boundaries between Frontend and Backend
https://milkio.fun
338 stars 19 forks source link

[Request]参数验证添加自定义消息功能 #20

Closed Gn3po4g closed 5 months ago

Gn3po4g commented 5 months ago
{
  "executeId": "01HXVBMDHJ5AA1NWF17E8VC3MP",
  "success": false,
  "fail": {
    "code": "TYPE_SAFE_ERROR",
    "message": "xxx不能为空"
  }
}

类似于这种吧。

akirarika commented 5 months ago

哈喽!

这个功能(大概)已经有啦,在这里:失败处理

每一个失败码都会返回不同的消息,你也可以通过为失败码添加参数,来使主动抛出失败的时候,动态返回不同的消息

Milkio 遵循约定大于配置的原则,当你想修改类型不正确时的返回的文案,编辑 /src/fail-code.ts 文件就好啦

akirarika commented 5 months ago

默认情况下返回的 message,确实容易让人感到迷惑,以为是报错,未来这方面我会优化一下下~

Gn3po4g commented 5 months ago

哈喽!

这个功能(大概)已经有啦,在这里:失败处理

每一个失败码都会返回不同的消息,你也可以通过为失败码添加参数,来使主动抛出失败的时候,动态返回不同的消息

Milkio 遵循约定大于配置的原则,当你想修改类型不正确时的返回的文案,编辑 /src/fail-code.ts 文件就好啦

是的,但是默认是系统自己处理返回的TYPE_SAFE_ERROR,希望在这部分能有更多自定义的内容。

akirarika commented 5 months ago

比如说怎么自定义哇?编辑 /src/fail-code.ts 文件有哪些需求不能满足哇?

Gn3po4g commented 5 months ago

type(string | typia.tags.MaxLength<2>).with_message("参数不满足要求") 类似这样的功能?

akirarika commented 5 months ago

这确实是个问题!Typia 还没有这样的功能,目前,我想到了这样的解决方案:

/src/fail-code.ts 其实返回的 message 是个方法,因此,你可以控制它的逻辑:

import type { MilkioFailCode } from "milkio";

export const failCode = {
  ...
  TYPE_SAFE_ERROR: (data: { path: string; expected: string; value: string }) => {
    if (path === 'password') return '密码不正确哦'
    if (path === 'phone') return '手机号不正确,注意请不要加+86'
  },
} satisfies MilkioFailCode;

这样做有一个潜在好处,也许我们许多个接口都有名为 password 的参数,对于同一个变量名,我们就不必“翻译”很多遍

但也有个问题, 万一遇到了同值显示不同的消息需要时,除了改变字段名之外,很难做出不同的处理

最好的办法其实是向 Typia 提 issue,建议对方支持此功能,这是 Typia 的仓库:https://github.com/samchon/typia

akirarika commented 5 months ago

我会继续思考,对于 Milkio 而言,是否应该在 Typia 之外,去面对这个需求去设计一些功能

但坦白讲,目前我还没有一些好的灵感,包括如何实现、语法设计成什么样子。一但我有一些想法,我会在这里回复

Gn3po4g commented 5 months ago

好的!

conglinyizhi commented 5 months ago

hmmmm....

没记错的话,很多提供 API 接口的平台,都是提供英文的错误信息和数字的错误码,除非是这个接口要提交给前端显示给用户,不然一律提示网络出现问题,然后开发者得到日志之后再去后台的表格(或者 API 文档公开的表格)中查询就是了……?

akirarika commented 5 months ago

关于这个问题我思考了很多,例如,有想过添加一个专门处理 TypeSafeError 的中间件,或者在 defineApi 中增加一个选项,专门处理这方面等

可以确认的是,这个需求很常见(是的,我自己也需要),经过我的思考后,我认为,这个需求实际上,是由两部分种需求耦合在一起的:

akirarika commented 5 months ago

以往主流的解决方案是将错误信息和类型是耦合在一起,以老牌验证器 class-validator 为例:

export class CreateUserDto {
  @IsNotEmpty({ message: '用户名不能为空' })
  @MaxLength(20, { message: '用户名不能超过20个字符' })
  username: string;

  @IsNotEmpty({ message: '密码不能为空' })
  password: string;
}

这种方案我认为还不够好,可以预见,它有这些问题:

经过我的思考,我觉得我自己(和以往的 Validator 库)都陷入了一个误区:

用户实际上是不直接与服务器沟通的,实现类似的功能是否真的是必要的?

对于前端而言,本身就会做许多的判断,例如许多流行的 UI 框架都会有校验表单数据是否符合预期的功能,用户的非合理输入,都会直接在 UI 层面被拦截。只有开发者和黑客才会看得到服务器的实际 message 消息

即使是在后端中完成了多语言的功能,前端的多语言也是必需的,有大量的文字都由前端来呈现,而且前端可以很简单地实现用户自由切换语言的功能,后端则只能依赖于 Accept-Language

所以,实际上,返回友好的自定义消息,其实是将许多前端的工作转移到了后端,这会迫使我们在前后端将逻辑重复编写两次

akirarika commented 5 months ago

等等,所以…… 你就是说,应该用前端的 UI 组件库来校验数据?这么做不对呀!直接利用后端来做校验,可以让我能够节省许多代码量!

绝大多数情况下,简单地提交后校验,并弹出 toast 就可以满足我的需求了,如果我要使用前端 UI 框架提供的校验功能,相当于我要在视图中再把类型重复定义一遍,这样太麻烦了。

是的,绝大多数情况下,简单地提交后校验,并弹出 toast 就可以满足我的需求了。但这并非是说,toast 所显示的消息,一定是要后端所返回的,由于我们可以知道错误的原因,所以,我们可以在前端控制:

const result = await client.execute(...);

if (!result.success) {
  if (foo.fail.code !== "TYPE_SAFE_ERROR") return alert(foo.fail.message); // 不想自定义类型安全错误以外的消息
  if (foo.fail.data.path === "$input.password") return alert("密码太长或者太短");
  if (foo.fail.data.path === "$input.username") return alert("用户名不符合规范,需要不包含汉字和符号");
  ...
}

前端来显示用户友好的失败消息,与后端以往的失败信息和类型耦合在一起的方案相比,可以解决以下问题:

在此前版本的 Milkio 中,fail.data 的类型永远是 any,在 milkio >= 0.3.1milkio-client >= 0.3.9(刚刚更新的版本)后,fail.data 将会有实际的类型提示了

我们甚至可以利用编辑器自动补全诸如 foo.fail.data.path === "$input.password" 这样的代码,是的,"$input.password" 这种字符串,也能够被补全和检查出错误

Gn3po4g commented 5 months ago

你说的很有道理!其实我提issue的本意是TYPE_SAFE_ERROR的错误消息太代码化了,比如"$input.by Parameter Error: The current value is 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', which does not meet 'string & MaxLength<16>' requirements"中的string & MaxLength<16>。感觉很多情况下前端很难知道后端所有的错误类型,所以一般情况下会直接将错误信息展示给用户,导致可读性大大降低。

akirarika commented 5 months ago

是这样的!我开始使用 Typia 时,也因为这一点所困扰,但也能够理解 Typia 这样设计的初衷,因为传统的 Validator 库实际上是对“Form”的升级,所处理的都是无嵌套的 Key-Value Object 数据,而 Typia 所处理的类型是不固定的,可能有既可能有多层嵌套的对象,也可能本身是个数组或者基础类型,这导致 Typia 只能通过 $input.foo.bar.baz 的方式告知我们出现错误的位置,难以对用户友好

如果未来 Typia 一旦像类似传统的 Validator 库一样,支持了自定义错误消息,我会尽快适配兼容的

感谢你的问题!这是一个很有意义的问题,能够收到问题说明有人正在用或者玩 Milkio,这会让我感到很开心❤️