Open1

slug付きのエンドポイントをTypeScriptで型制約する方法

yuyake0084yuyake0084
request.ts
export const REQUEST_PATHS = {
  GET_HOGE: '/api/hoge/[hogeId]'
} as const;

type PathKey = keyof typeof REQUEST_PATHS;
type Path = typeof REQUEST_PATHS[PathKey];

type WithoutSlash<T> = T extends `/${infer U}` ? U : never;
type Resource<T> = T extends `${infer U}/${infer S}` ? U | Resource<S> : T;
type DynamicRoute<T> = T extends `[${infer U}]` ? U : never;

type Params<T> = DynamicRoute<Resource<WithoutSlash<T>>>;
type ParamKeys<T extends Path> = Params<T>;

type PathParams<T extends Path> = {
  path: T;
  params?: { [K in ParamKeys<T>]: string | number };
};

type Args<T extends Path> = ParamKeys<T> extends never
  ? PathParams<T>
  : Required<PathParams<T>>;

export type RequestPath = typeof REQUEST_PATHS[keyof typeof REQUEST_PATHS];

export function createPath<T extends Path>({ path, params }: Args<T>) {
  if (!params) return path;

  return path
    .split('/')
    .map((str) => {
      const match = str.match(/\[(.*?)\]/);
      if (match) {
        const key = match[0];
        const trimmed = key.substring(1, key.length - 1) as ParamKeys<
          typeof path
        >;
        return params[trimmed];
      }
      return str;
    })
    .join('/');
}
index.ts
const { data } = await request.get<Hoge>(
  createPath({
    path: REQUEST_PATHS.SEEK_THUMBNAIL,
    params: {
     hogeId: '1',
    },
  })
)

console.log(data)

参考記事

https://zenn.dev/panda_program/articles/typescript-nextjs-routing