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

如何实现自动为任意层级嵌套节点的叶子节点强类型校验的createContract工具函数

如何实现自动为任意层级嵌套节点的叶子节点强类型校验的createContract工具函数

我完全理解你的痛点——手动给每个叶子节点套endpoint函数确实繁琐,尤其是接口定义嵌套较深的时候,开发体验大打折扣。咱们可以通过递归类型约束和顶层工具函数来实现你想要的效果,自动为任意层级的嵌套对象的叶子节点应用强类型校验,完全不需要手动包装每个节点。


核心思路

我们需要实现两个关键部分:

  • 一个递归类型,用来遍历输入对象的所有层级,将最终的叶子节点强制约束为Endpoint类型(保留你原来的Endpoint和ExtractParams逻辑,因为它们已经完美处理了pathParams的必填/禁止规则);
  • 一个顶层的createContract函数,用来接收这个递归约束的对象,在类型层面确保所有节点都符合要求,同时返回原结构。

完整实现代码

首先保留你原来的基础类型(ExtractParams和Endpoint),然后添加递归类型和工具函数:

import { z } from 'zod';

// --- 提取路径参数的类型(保留原逻辑)---
type ExtractParams<Path extends string> = 
  Path extends `${infer _Start}:${infer Param}/${infer Rest}` 
    ? { [K in Param]: string } & ExtractParams<`/${Rest}`> 
    : Path extends `${infer _Start}:${infer Param}` 
      ? { [K in Param]: string } 
      : {};

// --- Endpoint 核心类型(保留原逻辑)---
type Endpoint<
  TPath extends string,
  TMethod extends string,
  TQuery = unknown,
  TBody = unknown,
  TResponse = unknown
> = 
  & { 
      method: TMethod; 
      path: TPath; 
      query?: z.ZodSchema<TQuery>; 
      body?: z.ZodSchema<TBody>; 
      response: z.ZodSchema<TResponse>; 
    }
  & (keyof ExtractParams<TPath> extends never 
      ? { pathParams?: never } 
      : { pathParams: z.ZodSchema<ExtractParams<TPath>> });

// --- 递归约束:处理任意层级的嵌套对象 ---
type Contract<T> = {
  [K in keyof T]: 
    T[K] extends object 
      ? // 判断当前节点是否是叶子节点(具备Endpoint的核心字段method和path)
        T[K] extends { method: string; path: string }
          ? Endpoint<
              T[K]['path'],
              T[K]['method'],
              T[K]['query'] extends z.ZodSchema<infer Q> ? Q : unknown,
              T[K]['body'] extends z.ZodSchema<infer B> ? B : unknown,
              T[K]['response'] extends z.ZodSchema<infer R> ? R : unknown
            >
          : // 非叶子节点,继续递归处理下一层级
            Contract<T[K]>
      : // 非对象类型直接标记为错误(仅允许嵌套对象或Endpoint叶子节点)
        never;
};

// --- 顶层工具函数:自动应用递归类型约束 ---
function createContract<T>(contract: Contract<T>): Contract<T> {
  return contract;
}

使用示例

现在你可以直接用createContract包裹整个接口定义对象,不需要手动写endpoint函数,类型校验效果和原来完全一致:

const usersContract = createContract({
  getUser: {
    method: 'GET',
    path: '/users/:id',
    response: z.object({ id: z.string(), name: z.string() }),
    // ❌ 报错:path包含参数,必须提供pathParams
    // pathParams: z.object({ id: z.string() }), // ✅ 加上后错误消失
  },
  getUsers: {
    method: 'GET',
    path: '/users',
    response: z.array(z.object({ id: z.string(), name: z.string() })),
    // ❌ 报错:path无参数,禁止提供pathParams
    // pathParams: z.object({ asdkm: z.string() }),
  },
  updateUser: {
    // 支持任意层级嵌套
    location: {
      method: 'PUT',
      path: '/users/:id/location/:locationId',
      body: z.object({ name: z.string() }),
      response: z.object({ abra: z.array(z.string()), kadabra: z.number() }),
      // ❌ 报错:path包含两个参数,必须提供pathParams
      // pathParams: z.object({ id: z.string(), locationId: z.string() }), // ✅ 正确写法
    },
    // 继续嵌套也没问题
    profile: {
      method: 'PATCH',
      path: '/users/:id/profile',
      body: z.object({ avatar: z.string() }),
      response: z.object({ success: z.boolean() }),
      pathParams: z.object({ id: z.string() }), // ✅ 符合要求
    },
  },
  getUsers2: {
    method: 'GET',
    path: '/users',
    pathParams: z.object({ asdkm: z.string() }), // ❌ 报错:path无参数,禁止pathParams
    response: z.array(z.object({ id: z.string(), name: z.string() })),
  },
});

关键说明

  1. 递归类型Contract<T>:

    • 遍历输入对象的每个属性,如果是对象,先判断是否是Endpoint结构(有method和path);
    • 如果是叶子节点,直接应用原有的Endpoint类型约束,自动推导path和method的泛型,进而触发pathParams的必填/禁止规则;
    • 如果是非叶子节点(嵌套对象),则继续递归处理下一层级;
    • 非对象类型直接标记为never,确保只有合法的嵌套结构或Endpoint叶子节点能通过校验。
  2. createContract函数:

    • 不需要复杂的泛型推导,只需要接收Contract<T>类型的参数并返回相同类型;
    • 函数本身只是透传输入对象,核心作用是在编译时触发类型校验。

效果对比

  • 原来的方式:每个叶子节点都要手动写endpoint(...),嵌套越深越繁琐;
  • 新方式:只需要在顶层套一次createContract(...),所有层级的叶子节点自动应用强类型校验,开发体验大幅提升。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:54:33