This article is an in-depth study and further exploration of the previous article.

The previous article used function overloading to make typescript intelligently hint and validate, and found a number of problems in practice:

  1. For overloaded functions, typesripe will prompt multiple overloads. It is not easy to distinguish multiple overloaded parameters.
  2. Generating code is very difficult, generating a lot of formatting code, increasing the size of the generated file.

This article discusses the possibility of hints and validation through generics and uniform interface types. After observing the JSON document definition of Swagger and OpenAPI, we find that we can make typescript interface documents and Swagger definition format similar, request address can directly include the address defined by Swagger. We split the request parameters into path, Query, and body, and define the interface return type. We then define the interface request functions through typescript utility functions and generics to achieve checksum prompts.

  1. First define a unified interface configuration type, in which the request link address as the key, request method, parameter and interface return as the type to define, code as follows:
// swagger.ts
export interface SwaggerInterface1 {
    "/api/login": {
        post: {
            param: { body: { username: string; password: string; grant_type: string}}; response: {token: string; expire_in: number; refresh_token: string };
        };
    };
    "/api/user/{id}": {
        get: {
            param: { path: { id: string}}; response: {id: string; name: string };
        };
    };
    "/api/user/{id}/friends": {
        get: {
            param: {
                path: { id: string };
                query: { name: string; age: number };
            };
            response: { id: string; name: string };
        };
    };
}
Copy the code
  1. Defining utility functions
// swagger.utils.ts
import { SwaggerInterface1 } from "swagger.ts";

// Multiple interface definitions can be combined
type SwaggerInterface = SwaggerInterface1;

// For some uniform return interfaces, it is possible to break down unnecessary uniform format types in this way to get the required format
// The interface request function also needs to be split
type ReturnDataType = { code: number; data: any; message: string };
type ReturnData<T extends ReturnDataType | any> = T extends ReturnDataType ? T["data"] : T;

export type UrlKey = keyof SwaggerInterface;
export type MethodKey<U extends UrlKey> = string & keyof SwaggerInterface[U];

type SwaggerInterfaceSingle<U extends UrlKey, M extends MethodKey<U>> = SwaggerInterface[U][M];
type SwaggerField<U extends UrlKey, M extends MethodKey<U>> = keyof SwaggerInterfaceSingle<U, M>;
type SwaggerFieldType<U extends UrlKey, M extends MethodKey<U>, F extends SwaggerField<U, M>> = SwaggerInterfaceSingle<U, M>[F];

export type Param<U extends UrlKey, M extends MethodKey<U>> = SwaggerFieldType<U, M, "param" & SwaggerField<U, M>>;
export type Response<U extends UrlKey, M extends MethodKey<U>> = ReturnData<SwaggerFieldType<U, M, "response" & SwaggerField<U, M>>>;
Copy the code
  1. Define the interface request method, here is just a demonstration of the code, the specific request method also need to do some necessary processing, such as headers with token, error handling, etc
// fetch.ts
import { UrlKey, MethodKey, Param, Response } from "swagger.utils.ts";

function customFetch<U extends UrlKey.M extends MethodKey<U> > (url: U, method: M, params: Param<U, M>) :Promise<Response<U.M>> {
    let { path, query, body } = (params || {}) as{ path? :any; query? :any; body? :any };
    let iUrl = url as string;
    // Handle url path
    if (path) iUrl = mergeUrlParam(url, path);
    // Handle url query
    if (query) iUrl = mergeUrlQuery(url, query);
    return fetch(iUrl, { method, body: body })
        .then((res) = > res.json())
        .then((res) = > {
            if (res.code == 200) {
                return res.data;
            }
            return res
        });
}
interface PathParams {
    [key: string] :string | number | boolean;
}

function mergeUrlParam(url: string, paths: PathParams) :string {
    // merge code
    return "";
}

function mergeUrlQuery(url: string, query: PathParams) :string {
    // merge code
    return "";
}
Copy the code
  1. Then you can use the function method normally. Here is the result of the prompt

  1. The next step is to programmatically convert the Swagger document to the structure of the first step, which is relatively easy to convert due to the similar structure. Vite plugin-swagger2ts vite plugin-swagger2ts vite plugin-swagger2ts Vite launches to pull Swagger’s code and generate the corresponding typescript interface file.

At this point, the research of intelligent prompt and validation of front-end interfaces based on TypeScript and Swagger back-end documents is basically over.

Ps: Annoyingly, the actual project encountered some troubles, which resulted in only handwriting input interface: 1. The back-end interface is not well regulated, and you have to write the interface manually to modify the error type of the interface; 2. Generated code is too large, resulting in vscode sticking. Even so, there are significant benefits to normalization of the project.