Skip to content

根据openapi生成代码 ts request type zod MSW Faker nestjs

License

Notifications You must be signed in to change notification settings

Vc-great/openapi-to

Repository files navigation

codecov

The current version is not compatible with V1.V1 document

openapi-to

Generate SDKs,OpenAPI to:

  • ts request
  • ts type
  • zod
  • Faker.js
  • MSW
  • nestjs
  • request object
  • vue-Query

OpenAPI Specifications are supported:

  • swagger 2.0
  • openapi 3.0

Install

npm i openapi-to -g

Usage

  openapi init  // Generate openapi.config.js file
  openapi g     // Generate code from the openapi.config.js file

openapi.config

import {
  defineConfig,
  createTSRequest,
  createTSType,
  createZod,
  createFaker,
  createMSW,
  createNestjs,
} from "openapi-to";

export default defineConfig({
  servers: [
    {
      input: {
        name: "swagger", // output file folder name
        path:'https://petstore.swagger.io/v2/swagger.json'  //api documentation url
      },
    },
  ],
  plugins: [
    createTSRequest({
      createZodDecorator: true,
    }),
    createTSType(),
    createZod(),
    createFaker(),
    createMSW(),
    createNestjs(),
  ],
});

Zod

增加 zod 主要用于端到端的校验.zodDecorator 会在请求方法上增加三个方法,需要自己去实现具体逻辑,给出示例供大家参考

  • paramsZodSchema

    收集请求参数 zodSchema

@paramsZodSchema(ZOD.uploadImagePostBodyRequest)
  • responseZodSchema 响应Zod模式

    收集响应数据 zodSchema

@responseZodSchema(ZOD.uploadImagePostResponse)
  • zodValidate zod验证

    根据 zodSchema 进行校验

@zodValidate example 例子

import _ from "lodash";
export function zodValidate(target: object, propertyKey: string, descriptor) {
const fn = descriptor.value;
descriptor.value = async (...args) => {
args.forEach((item, index) => {
const zodSchema = _.get(target, `_zodSchema.${propertyKey}.${index}`);
if (!zodSchema) {
return "";
}
const safeParse = zodSchema.safeParse(item);
!safeParse.success &&
console.error(
`[${propertyKey}]request params error`,
safeParse.error
);
});
//
const result = await fn(...args);
//response 校验
const responseZodSchema = _.get(
target,
`_zodSchema.${propertyKey}.responseZodSchema`
);
const safeParse = responseZodSchema.safeParse(result[1]);
result[1] &&
!safeParse.success &&
console.error(`[${propertyKey}]response error`, safeParse.error);

    return result;
};
}

export const responseZodSchema =
(zodSchema) => (target: object, propertyKey: string, descriptor) => {
_.set(target, `_zodSchema.${propertyKey}.responseZodSchema`, zodSchema);
};

export const paramsZodSchema =
(zodSchema) => (target: object, propertyKey: string, index) => {
_.set(target, `_zodSchema.${propertyKey}.${index}`, zodSchema);
};
ts request
import type { Pet } from "./Pet";
import { request } from "@/api/request";

/**
 *
 * @tag pet
 * @description Everything about your Pets
 * @UUID API-pet
 */
class PetAPI {
  /**
   *
   * @summary summary
   * @description
   * @UUID operationId
   */
  testPost(bodyParams: Pet.TestPostBodyParams): Promise<[Pet.TestPostErrorResponse, Pet.TestPostResponse]> {
    return request({
      method: 'post',
      url: `/pet/test`,
      data: bodyParams
    })
  }

  /**
   *
   * @summary Add a new pet to the store
   * @description
   * @UUID addPet
   */
  create(bodyParams: Pet.CreateBodyParams): Promise<[Pet.CreateErrorResponse, Pet.CreateResponse]> {
    return request({
      method: 'post',
      url: `/pet`,
      data: bodyParams
    })
  }

  /**
   *
   * @summary Update an existing pet
   * @description
   * @UUID updatePet
   */
  update(bodyParams: Pet.UpdateBodyParams): Promise<[Pet.UpdateErrorResponse, Pet.UpdateResponse]> {
    return request({
      method: 'put',
      url: `/pet`,
      data: bodyParams
    })
  }
}

export const petAPI = new PetAPI;
ts type
import type { TestDto, TestDto2, Test32145, ApiResponse, Pet } from "./typeModels";

/**
 *
 * @tag pet
 * @description Everything about your Pets
 * @UUID type-pet
 */
export namespace Pet {
    /** */
    export type TestPostBodyParams = TestDto;
    /** OK */
    export type TestPostResponse = TestDto2;
    /** */
    export type TestPostResponse401 = unknown;
    /** */
    export type TestPostResponse403 = unknown;
    /** */
    export type TestPostResponse404 = unknown;
    /** */
    export type TestPostErrorResponse = TestPostResponse401 | TestPostResponse403 | TestPostResponse404;
    /** */
    export type TestPutBodyParams = TestDto;
    /** OK */
    export type TestPutResponse = TestDto2;
    /** */
    export type TestPutResponse401 = unknown;
    /** */
    export type TestPutResponse403 = unknown;
    /** */
    export type TestPutResponse404 = unknown;
    /** */
    export type TestPutErrorResponse = TestPutResponse401 | TestPutResponse403 | TestPutResponse404;
    /** */
    export type DelByTestBodyParams = Array<number>;
    /** OK */
    export type DelByTestResponse = Test32145;
    /** */
    export type DelByTestResponse401 = unknown;
    /** */
    export type DelByTestResponse403 = unknown;
    /** */
    export type DelByTestErrorResponse = DelByTestResponse401 | DelByTestResponse403;

    /** queryParams */
    export interface TestIdGetQueryParams {
        /**
         *
         * @description
         */
        fields?: Array<string>;
        /**
         *
         * @description
         */
        page: number;
        /**
         *
         * @description
         */
        size: number;
    }

    /** pathParams */
    export interface TestIdGetPathParams {
        /**
         *
         * @description
         */
        testId?: number;
        /**
         *
         * @description
         */
        testId2?: string;
    }

    /** OK */
    export type TestIdGetResponse = TestDto2;
    /** */
    export type TestIdGetResponse401 = unknown;
    /** */
    export type TestIdGetResponse403 = unknown;
    /** */
    export type TestIdGetResponse404 = unknown;
    /** */
    export type TestIdGetErrorResponse = TestIdGetResponse401 | TestIdGetResponse403 | TestIdGetResponse404;

    /** pathParams */
    export interface UploadImagePostPathParams {
        /**
         *
         * @description
         */
        petId: number;
    }

    /** bodyParams */
    export interface UploadImagePostBodyParams {
        /**
         *
         * @description Additional data to pass to server
         */
        additionalMetadata?: string;
        /**
         *
         * @description file to upload
         */
        file?: string;
    }

    /** successful operation */
    export type UploadImagePostResponse = ApiResponse;
    /** */
    export type UploadImagePostErrorResponse = unknown;
    /** */
    export type CreateBodyParams = Pet;
    /** */
    export type CreateResponse405 = unknown;
    /** */
    export type CreateErrorResponse = CreateResponse405;
    /** */
    export type CreateResponse = unknown;
    /** */
    export type UpdateBodyParams = Pet;
    /** */
    export type UpdateResponse400 = unknown;
    /** */
    export type UpdateResponse404 = unknown;
    /** */
    export type UpdateResponse405 = unknown;
    /** */
    export type UpdateErrorResponse = UpdateResponse400 | UpdateResponse404 | UpdateResponse405;
    /** */
    export type UpdateResponse = unknown;

    /** queryParams */
    export interface FindByStatusGetQueryParams {
        /**
         *
         * @description
         */
        status: Array<string>;
    }

    /** successful operation */
    export type FindByStatusGetResponse = Pet[];
    /** */
    export type FindByStatusGetResponse400 = unknown;
    /** */
    export type FindByStatusGetErrorResponse = FindByStatusGetResponse400;

    /** queryParams */
    export interface FindByTagsGetQueryParams {
        /**
         *
         * @description
         */
        tags: Array<string>;
    }

    /** successful operation */
    export type FindByTagsGetResponse = Pet[];
    /** */
    export type FindByTagsGetResponse400 = unknown;
    /** */
    export type FindByTagsGetErrorResponse = FindByTagsGetResponse400;

    /** pathParams */
    export interface FindByPetIdPathParams {
        /**
         *
         * @description
         */
        petId: number;
    }

    /** successful operation */
    export type FindByPetIdResponse = Pet;
    /** */
    export type FindByPetIdResponse400 = unknown;
    /** */
    export type FindByPetIdResponse404 = unknown;
    /** */
    export type FindByPetIdErrorResponse = FindByPetIdResponse400 | FindByPetIdResponse404;

    /** pathParams */
    export interface PetIdPostPathParams {
        /**
         *
         * @description
         */
        petId: number;
    }

    /** bodyParams */
    export interface PetIdPostBodyParams {
        /**
         *
         * @description Updated name of the pet
         */
        name?: string;
        /**
         *
         * @description Updated status of the pet
         */
        status?: string;
    }

    /** */
    export type PetIdPostResponse405 = unknown;
    /** */
    export type PetIdPostErrorResponse = PetIdPostResponse405;
    /** */
    export type PetIdPostResponse = unknown;

    /** pathParams */
    export interface DelByPetIdPathParams {
        /**
         *
         * @description
         */
        petId: number;
    }

    /** */
    export type DelByPetIdResponse400 = unknown;
    /** */
    export type DelByPetIdResponse404 = unknown;
    /** */
    export type DelByPetIdErrorResponse = DelByPetIdResponse400 | DelByPetIdResponse404;
    /** */
    export type DelByPetIdResponse = unknown;
}
zod
import { z } from "zod";
import { testDto, testDto2, test32145, apiResponse, pet } from "./zodModels";
/** bodyParams */
const testPostBodyParams = z.lazy(() => testDto);
/** OK */
const testPostResponse = z.lazy(() => testDto2);
/** */
const testPostResponse401 = z.unknown();
/** */
const testPostResponse403 = z.unknown();
/** */
const testPostResponse404 = z.unknown();
/** */
const testPostErrorResponse = z.union([testPostResponse401, testPostResponse403, testPostResponse404]);
/** bodyParams */
const testPutBodyParams = z.lazy(() => testDto);
/** OK */
const testPutResponse = z.lazy(() => testDto2);
/** */
const testPutResponse401 = z.unknown();
/** */
const testPutResponse403 = z.unknown();
/** */
const testPutResponse404 = z.unknown();
/** */
const testPutErrorResponse = z.union([testPutResponse401, testPutResponse403, testPutResponse404]);
/** bodyParams */
const delByTestBodyParams = z.number().array();
/** OK */
const delByTestResponse = z.lazy(() => test32145);
/** */
const delByTestResponse401 = z.unknown();
/** */
const delByTestResponse403 = z.unknown();
/** */
const delByTestErrorResponse = z.union([delByTestResponse401, delByTestResponse403]);
/** queryParams */
const testIdGetQueryParams = z.object({
    /***/
    fields: z.string().array().optional(),
    /***/
    page: z.number(),
    /***/
    size: z.number()
});
/** pathParams */
export const testIdGetPathParams = z.object({
    /***/
    testId: z.number().optional(),
    /***/
    testId2: z.string().optional()
});
/** OK */
const testIdGetResponse = z.lazy(() => testDto2);
/** */
const testIdGetResponse401 = z.unknown();
/** */
const testIdGetResponse403 = z.unknown();
/** */
const testIdGetResponse404 = z.unknown();
/** */
const testIdGetErrorResponse = z.union([testIdGetResponse401, testIdGetResponse403, testIdGetResponse404]);
/** pathParams */
export const uploadImagePostPathParams = z.object({
    /***/
    petId: z.number()
});
/** bodyParams */
const uploadImagePostBodyParams = z.object({
    /**Additional data to pass to server*/
    additionalMetadata: z.string().optional(),
    /**file to upload*/
    file: z.string().optional()
});
/** successful operation */
const uploadImagePostResponse = z.lazy(() => apiResponse);
/** */
const uploadImagePostErrorResponse = z.unknown();
/** bodyParams */
const createBodyParams = z.lazy(() => pet);
/** */
const createResponse405 = z.unknown();
/** */
const createErrorResponse = createResponse405;
/** */
const createResponse = z.unknown();
/** bodyParams */
const updateBodyParams = z.lazy(() => pet);
/** */
const updateResponse400 = z.unknown();
/** */
const updateResponse404 = z.unknown();
/** */
const updateResponse405 = z.unknown();
/** */
const updateErrorResponse = z.union([updateResponse400, updateResponse404, updateResponse405]);
/** */
const updateResponse = z.unknown();
/** queryParams */
const findByStatusGetQueryParams = z.object({
    /***/
    status: z.string().array()
});
/** successful operation */
const findByStatusGetResponse = z.lazy(() => pet.array());
/** */
const findByStatusGetResponse400 = z.unknown();
/** */
const findByStatusGetErrorResponse = findByStatusGetResponse400;
/** queryParams */
const findByTagsGetQueryParams = z.object({
    /***/
    tags: z.string().array()
});
/** successful operation */
const findByTagsGetResponse = z.lazy(() => pet.array());
/** */
const findByTagsGetResponse400 = z.unknown();
/** */
const findByTagsGetErrorResponse = findByTagsGetResponse400;
/** pathParams */
export const findByPetIdPathParams = z.object({
    /***/
    petId: z.number()
});
/** successful operation */
const findByPetIdResponse = z.lazy(() => pet);
/** */
const findByPetIdResponse400 = z.unknown();
/** */
const findByPetIdResponse404 = z.unknown();
/** */
const findByPetIdErrorResponse = z.union([findByPetIdResponse400, findByPetIdResponse404]);
/** pathParams */
export const petIdPostPathParams = z.object({
    /***/
    petId: z.number()
});
/** bodyParams */
const petIdPostBodyParams = z.object({
    /**Updated name of the pet*/
    name: z.string().optional(),
    /**Updated status of the pet*/
    status: z.string().optional()
});
/** */
const petIdPostResponse405 = z.unknown();
/** */
const petIdPostErrorResponse = petIdPostResponse405;
/** */
const petIdPostResponse = z.unknown();
/** pathParams */
export const delByPetIdPathParams = z.object({
    /***/
    petId: z.number()
});
/** */
const delByPetIdResponse400 = z.unknown();
/** */
const delByPetIdResponse404 = z.unknown();
/** */
const delByPetIdErrorResponse = z.union([delByPetIdResponse400, delByPetIdResponse404]);
/** */
const delByPetIdResponse = z.unknown();
/**
 *
 * @tag pet
 * @description Everything about your Pets
 * @UUID zod-pet
 */
export const petZod = {
    /**bodyParams*/
    testPostBodyParams,
    /**OK*/
    testPostResponse,
    /***/
    testPostResponse401,
    /***/
    testPostResponse403,
    /***/
    testPostResponse404,
    /***/
    testPostErrorResponse,
    /**bodyParams*/
    testPutBodyParams,
    /**OK*/
    testPutResponse,
    /***/
    testPutResponse401,
    /***/
    testPutResponse403,
    /***/
    testPutResponse404,
    /***/
    testPutErrorResponse,
    /**bodyParams*/
    delByTestBodyParams,
    /**OK*/
    delByTestResponse,
    /***/
    delByTestResponse401,
    /***/
    delByTestResponse403,
    /***/
    delByTestErrorResponse,
    /**queryParams*/
    testIdGetQueryParams,
    /**pathParams*/
    testIdGetPathParams,
    /**OK*/
    testIdGetResponse,
    /***/
    testIdGetResponse401,
    /***/
    testIdGetResponse403,
    /***/
    testIdGetResponse404,
    /***/
    testIdGetErrorResponse,
    /**pathParams*/
    uploadImagePostPathParams,
    /**bodyParams*/
    uploadImagePostBodyParams,
    /**successful operation*/
    uploadImagePostResponse,
    /***/
    uploadImagePostErrorResponse,
    /**bodyParams*/
    createBodyParams,
    /***/
    createResponse405,
    /***/
    createErrorResponse,
    /***/
    createResponse,
    /**bodyParams*/
    updateBodyParams,
    /***/
    updateResponse400,
    /***/
    updateResponse404,
    /***/
    updateResponse405,
    /***/
    updateErrorResponse,
    /***/
    updateResponse,
    /**queryParams*/
    findByStatusGetQueryParams,
    /**successful operation*/
    findByStatusGetResponse,
    /***/
    findByStatusGetResponse400,
    /***/
    findByStatusGetErrorResponse,
    /**queryParams*/
    findByTagsGetQueryParams,
    /**successful operation*/
    findByTagsGetResponse,
    /***/
    findByTagsGetResponse400,
    /***/
    findByTagsGetErrorResponse,
    /**pathParams*/
    findByPetIdPathParams,
    /**successful operation*/
    findByPetIdResponse,
    /***/
    findByPetIdResponse400,
    /***/
    findByPetIdResponse404,
    /***/
    findByPetIdErrorResponse,
    /**pathParams*/
    petIdPostPathParams,
    /**bodyParams*/
    petIdPostBodyParams,
    /***/
    petIdPostResponse405,
    /***/
    petIdPostErrorResponse,
    /***/
    petIdPostResponse,
    /**pathParams*/
    delByPetIdPathParams,
    /***/
    delByPetIdResponse400,
    /***/
    delByPetIdResponse404,
    /***/
    delByPetIdErrorResponse,
    /***/
    delByPetIdResponse
};

/**
 *
 * @tag pet
 * @description Everything about your Pets
 * @UUID zod-pet
 */
export namespace Pet {
    /** bodyParams */
    export type TestPostBodyParams = z.infer<typeof testPostBodyParams>;
    /** OK */
    export type TestPostResponse = z.infer<typeof testPostResponse>;
    /** */
    export type TestPostResponse401 = z.infer<typeof testPostResponse401>;
    /** */
    export type TestPostResponse403 = z.infer<typeof testPostResponse403>;
    /** */
    export type TestPostResponse404 = z.infer<typeof testPostResponse404>;
    /** */
    export type TestPostErrorResponse = z.infer<typeof testPostErrorResponse>;
    /** bodyParams */
    export type TestPutBodyParams = z.infer<typeof testPutBodyParams>;
    /** OK */
    export type TestPutResponse = z.infer<typeof testPutResponse>;
    /** */
    export type TestPutResponse401 = z.infer<typeof testPutResponse401>;
    /** */
    export type TestPutResponse403 = z.infer<typeof testPutResponse403>;
    /** */
    export type TestPutResponse404 = z.infer<typeof testPutResponse404>;
    /** */
    export type TestPutErrorResponse = z.infer<typeof testPutErrorResponse>;
    /** bodyParams */
    export type DelByTestBodyParams = z.infer<typeof delByTestBodyParams>;
    /** OK */
    export type DelByTestResponse = z.infer<typeof delByTestResponse>;
    /** */
    export type DelByTestResponse401 = z.infer<typeof delByTestResponse401>;
    /** */
    export type DelByTestResponse403 = z.infer<typeof delByTestResponse403>;
    /** */
    export type DelByTestErrorResponse = z.infer<typeof delByTestErrorResponse>;
    /** queryParams */
    export type TestIdGetQueryParams = z.infer<typeof testIdGetQueryParams>;
    /** pathParams */
    export type TestIdGetPathParams = z.infer<typeof testIdGetPathParams>;
    /** OK */
    export type TestIdGetResponse = z.infer<typeof testIdGetResponse>;
    /** */
    export type TestIdGetResponse401 = z.infer<typeof testIdGetResponse401>;
    /** */
    export type TestIdGetResponse403 = z.infer<typeof testIdGetResponse403>;
    /** */
    export type TestIdGetResponse404 = z.infer<typeof testIdGetResponse404>;
    /** */
    export type TestIdGetErrorResponse = z.infer<typeof testIdGetErrorResponse>;
    /** pathParams */
    export type UploadImagePostPathParams = z.infer<typeof uploadImagePostPathParams>;
    /** bodyParams */
    export type UploadImagePostBodyParams = z.infer<typeof uploadImagePostBodyParams>;
    /** successful operation */
    export type UploadImagePostResponse = z.infer<typeof uploadImagePostResponse>;
    /** */
    export type UploadImagePostErrorResponse = z.infer<typeof uploadImagePostErrorResponse>;
    /** bodyParams */
    export type CreateBodyParams = z.infer<typeof createBodyParams>;
    /** */
    export type CreateResponse405 = z.infer<typeof createResponse405>;
    /** */
    export type CreateErrorResponse = z.infer<typeof createErrorResponse>;
    /** */
    export type CreateResponse = z.infer<typeof createResponse>;
    /** bodyParams */
    export type UpdateBodyParams = z.infer<typeof updateBodyParams>;
    /** */
    export type UpdateResponse400 = z.infer<typeof updateResponse400>;
    /** */
    export type UpdateResponse404 = z.infer<typeof updateResponse404>;
    /** */
    export type UpdateResponse405 = z.infer<typeof updateResponse405>;
    /** */
    export type UpdateErrorResponse = z.infer<typeof updateErrorResponse>;
    /** */
    export type UpdateResponse = z.infer<typeof updateResponse>;
    /** queryParams */
    export type FindByStatusGetQueryParams = z.infer<typeof findByStatusGetQueryParams>;
    /** successful operation */
    export type FindByStatusGetResponse = z.infer<typeof findByStatusGetResponse>;
    /** */
    export type FindByStatusGetResponse400 = z.infer<typeof findByStatusGetResponse400>;
    /** */
    export type FindByStatusGetErrorResponse = z.infer<typeof findByStatusGetErrorResponse>;
    /** queryParams */
    export type FindByTagsGetQueryParams = z.infer<typeof findByTagsGetQueryParams>;
    /** successful operation */
    export type FindByTagsGetResponse = z.infer<typeof findByTagsGetResponse>;
    /** */
    export type FindByTagsGetResponse400 = z.infer<typeof findByTagsGetResponse400>;
    /** */
    export type FindByTagsGetErrorResponse = z.infer<typeof findByTagsGetErrorResponse>;
    /** pathParams */
    export type FindByPetIdPathParams = z.infer<typeof findByPetIdPathParams>;
    /** successful operation */
    export type FindByPetIdResponse = z.infer<typeof findByPetIdResponse>;
    /** */
    export type FindByPetIdResponse400 = z.infer<typeof findByPetIdResponse400>;
    /** */
    export type FindByPetIdResponse404 = z.infer<typeof findByPetIdResponse404>;
    /** */
    export type FindByPetIdErrorResponse = z.infer<typeof findByPetIdErrorResponse>;
    /** pathParams */
    export type PetIdPostPathParams = z.infer<typeof petIdPostPathParams>;
    /** bodyParams */
    export type PetIdPostBodyParams = z.infer<typeof petIdPostBodyParams>;
    /** */
    export type PetIdPostResponse405 = z.infer<typeof petIdPostResponse405>;
    /** */
    export type PetIdPostErrorResponse = z.infer<typeof petIdPostErrorResponse>;
    /** */
    export type PetIdPostResponse = z.infer<typeof petIdPostResponse>;
    /** pathParams */
    export type DelByPetIdPathParams = z.infer<typeof delByPetIdPathParams>;
    /** */
    export type DelByPetIdResponse400 = z.infer<typeof delByPetIdResponse400>;
    /** */
    export type DelByPetIdResponse404 = z.infer<typeof delByPetIdResponse404>;
    /** */
    export type DelByPetIdErrorResponse = z.infer<typeof delByPetIdErrorResponse>;
    /** */
    export type DelByPetIdResponse = z.infer<typeof delByPetIdResponse>;
}
MSW
import { HttpResponse, http, HttpHandler } from "msw";
import { petFaker } from "./petFaker";
/** */
const handlers = [{
    name: 'testPost',
    start: false,
    msw: http.post('/pet/test', (req) => {
        return HttpResponse.json(petFaker.testPost())
    })
}, {
    name: 'testPut',
    start: false,
    msw: http.put('/pet/test', (req) => {
        return HttpResponse.json(petFaker.testPut())
    })
}, {
    name: 'delByTest',
    start: false,
    msw: http.delete('/pet/test', (req) => {
        return HttpResponse.json(petFaker.delByTest())
    })
}, {
    name: 'testIdGet',
    start: false,
    msw: http.get('/pet/test/:testId', (req) => {
        return HttpResponse.json(petFaker.testIdGet())
    })
}, {
    name: 'uploadImagePost',
    start: false,
    msw: http.post('/pet/:petId/uploadImage', (req) => {
        return HttpResponse.json(petFaker.uploadImagePost())
    })
}, {
    name: 'create',
    start: false,
    msw: http.post('/pet', (req) => {
        return HttpResponse.json(petFaker.create())
    })
}, {
    name: 'update',
    start: false,
    msw: http.put('/pet', (req) => {
        return HttpResponse.json(petFaker.update())
    })
}, {
    name: 'findByStatusGet',
    start: false,
    msw: http.get('/pet/findByStatus', (req) => {
        return HttpResponse.json(petFaker.findByStatusGet())
    })
}, {
    name: 'findByTagsGet',
    start: false,
    msw: http.get('/pet/findByTags', (req) => {
        return HttpResponse.json(petFaker.findByTagsGet())
    })
}, {
    name: 'findByPetId',
    start: false,
    msw: http.get('/pet/:petId', (req) => {
        return HttpResponse.json(petFaker.findByPetId())
    })
}, {
    name: 'petIdPost',
    start: false,
    msw: http.post('/pet/:petId', (req) => {
        return HttpResponse.json(petFaker.petIdPost())
    })
}, {
    name: 'delByPetId',
    start: false,
    msw: http.delete('/pet/:petId', (req) => {
        return HttpResponse.json(petFaker.delByPetId())
    })
}];
export const petHandler: Array<HttpHandler> = handlers
    .filter(x => x.start)
    .map(x => x.msw);
faker
import { faker } from "@faker-js/faker";
import { testDto2, test32145, apiResponse, pet } from "./fakerModels";
import type { Pet } from "./Pet";

/**
 *
 * @tag pet
 * @description Everything about your Pets
 * @UUID Faker-pet
 */
class PetFaker {
    /**
     *
     * @summary summary
     * @description
     * @UUID operationId
     */
    testPost(): NonNullable<Pet.TestPostResponse> {
        return testDto2()
    }

    /**
     *
     * @summary summary
     * @description
     * @UUID operationId
     */
    testPut(): NonNullable<Pet.TestPutResponse> {
        return testDto2()
    }

    /**
     *
     * @summary summary
     * @description
     * @UUID operationId
     */
    delByTest(): NonNullable<Pet.DelByTestResponse> {
        return test32145()
    }

    /**
     *
     * @summary summary
     * @description
     * @UUID operationId
     */
    testIdGet(): NonNullable<Pet.TestIdGetResponse> {
        return testDto2()
    }

    /**
     *
     * @summary uploads an image
     * @description pet
     * @UUID uploadFile
     */
    uploadImagePost(): NonNullable<Pet.UploadImagePostResponse> {
        return apiResponse()
    }

    /**
     *
     * @summary Add a new pet to the store
     * @description
     * @UUID addPet
     */
    create(): NonNullable<Pet.CreateResponse> {
        return {}
    }

    /**
     *
     * @summary Update an existing pet
     * @description
     * @UUID updatePet
     */
    update(): NonNullable<Pet.UpdateResponse> {
        return {}
    }

    /**
     *
     * @summary Finds Pets by status
     * @description Multiple status values can be provided with comma separated strings
     * @UUID findPetsByStatus
     */
    findByStatusGet(): NonNullable<Pet.FindByStatusGetResponse> {
        return faker.helpers.multiple(() => pet(), {
            count: 10,
        })
    }

    /**
     *
     * @summary Finds Pets by tags
     * @description Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
     * @UUID findPetsByTags
     */
    findByTagsGet(): NonNullable<Pet.FindByTagsGetResponse> {
        return faker.helpers.multiple(() => pet(), {
            count: 10,
        })
    }

    /**
     *
     * @summary Find pet by ID
     * @description Returns a single pet
     * @UUID getPetById
     */
    findByPetId(): NonNullable<Pet.FindByPetIdResponse> {
        return pet()
    }

    /**
     *
     * @summary Updates a pet in the store with form data
     * @description
     * @UUID updatePetWithForm
     */
    petIdPost(): NonNullable<Pet.PetIdPostResponse> {
        return {}
    }

    /**
     *
     * @summary Deletes a pet
     * @description
     * @UUID deletePet
     */
    delByPetId(): NonNullable<Pet.DelByPetIdResponse> {
        return {}
    }
}

export const petFaker = new PetFaker;

nestjs

ApiTag

Since the ApiTags in @/common/swagger don't support the description attribute, we create an ApiTag method that supports it

// users.controller.ts
@ApiTag({
  name: 'users',
  description: 'Get all users',
})
@Controller('users')
export class UsersController {}


// ApiTag.decorator.ts
import { swaggerConfig } from '@/common/swagger';
import { TagObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
import { ApiTags } from '@nestjs/swagger';
import { applyDecorators } from '@nestjs/common';

export const ApiTag = (tagObject: TagObject) => {
  swaggerConfig.tags.unshift(tagObject);
  return applyDecorators(ApiTags(tagObject.name));
};

//  common/swagger.ts
import { DocumentBuilder } from '@nestjs/swagger';

const swaggerConfig = new DocumentBuilder()
  .setTitle('title')
  .setDescription('api docs')
  .setVersion('1.0')
  .addBearerAuth()
  .build();

export { swaggerConfig };

//main.ts
  const options: SwaggerDocumentOptions = {
    operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
  };
  const document = SwaggerModule.createDocument(app, swaggerConfig, options);
  SwaggerModule.setup('docs', app, document);

tree

.
├── pet
│   ├── domain
│   │   ├── ApiResponse.vo.ts
│   │   ├── Pet.dto.ts
│   │   ├── Pet.vo.ts
│   │   ├── findPetsByStatus-query.dto.ts
│   │   └── findPetsByTags-query.dto.ts
│   ├── pet.controller.ts
│   ├── pet.service.ts
│   └── repository
│       ├── pet-repository.module.ts
│       ├── pet.mapper.ts
│       └── pet.repository.ts
├── store
│   ├── domain
│   │   ├── Order.dto.ts
│   │   └── Order.vo.ts
│   ├── repository
│   │   ├── store-repository.module.ts
│   │   ├── store.mapper.ts
│   │   └── store.repository.ts
│   ├── store.controller.ts
│   └── store.service.ts
└── user
    ├── domain
    │   ├── User.dto.ts
    │   ├── User.vo.ts
    │   └── loginUser-query.dto.ts
    ├── repository
    │   ├── user-repository.module.ts
    │   ├── user.mapper.ts
    │   └── user.repository.ts
    ├── user.controller.ts
    └── user.service.ts

controller import { Controller, HttpStatus, HttpCode, Post, Param, Body, ParseIntPipe, Put, Get, Query, Delete } from "@nestjs/common"; import { ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger"; import { Permissions } from "@/common/decorators/auth.decorator"; import { ApiTag } from "@/common/swagger"; import { PetService } from "./pet.service"; import { ApiResponse } from "./domain/ApiResponse.vo"; import { Pet } from "./domain/Pet.dto"; import { FindPetsByStatusQueryDto } from "./domain/findPetsByStatus-query.dto"; import { FindPetsByTagsQueryDto } from "./domain/findPetsByTags-query.dto";

@ApiTag({ name: 'pet', description: 'Everything about your Pets', }) @Controller('pet') export class PetController { constructor(private readonly petService: PetService) { }

@ApiOperation({ summary: 'uploads an image' })
@ApiResponse({ status: HttpStatus.OK, description: "successful operation", ApiResponse })
@ApiParam({
    name: "petId",
    description: "ID of pet to update"
})
@Post('/:petId/uploadImage')
@HttpCode(HttpStatus.OK)
async uploadFile(@Param("petId", ParseIntPipe) petId: number, @Body() data: any): Promise<ApiResponse> {
    return await this.petService.uploadFile(petId, data)
}

@ApiOperation({ summary: 'Add a new pet to the store' })
@ApiResponse({ status: HttpStatus.OK })
@Post()
@HttpCode(HttpStatus.OK)
async addPet(@Body() data: Pet): Promise<void> {
    return await this.petService.addPet(data)
}

@ApiOperation({ summary: 'Update an existing pet' })
@ApiResponse({ status: HttpStatus.OK })
@Put()
@HttpCode(HttpStatus.OK)
async updatePet(@Body() data: Pet): Promise<void> {
    return await this.petService.updatePet(data)
}

@ApiOperation({ summary: 'Finds Pets by status', description: 'Multiple status values can be provided with comma separated strings' })
@ApiResponse({ status: HttpStatus.OK, description: "successful operation", isArray: true })
@Get('/findByStatus')
@HttpCode(HttpStatus.OK)
async findPetsByStatus(@Query() query: FindPetsByStatusQueryDto): Promise<Pet[]> {
    return await this.petService.findPetsByStatus(query)
}

@ApiOperation({ summary: 'Finds Pets by tags', description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.' })
@ApiResponse({ status: HttpStatus.OK, description: "successful operation", isArray: true })
@Get('/findByTags')
@HttpCode(HttpStatus.OK)
async findPetsByTags(@Query() query: FindPetsByTagsQueryDto): Promise<Pet[]> {
    return await this.petService.findPetsByTags(query)
}

@ApiOperation({ summary: 'Find pet by ID', description: 'Returns a single pet' })
@ApiResponse({ status: HttpStatus.OK, description: "successful operation", Pet })
@ApiParam({
    name: "petId",
    description: "ID of pet to return"
})
@Get('/:petId')
@HttpCode(HttpStatus.OK)
async getPetById(@Param("petId", ParseIntPipe) petId: number): Promise<Pet> {
    return await this.petService.getPetById(petId)
}

@ApiOperation({ summary: 'Updates a pet in the store with form data' })
@ApiResponse({ status: HttpStatus.OK })
@ApiParam({
    name: "petId",
    description: "ID of pet that needs to be updated"
})
@Post('/:petId')
@HttpCode(HttpStatus.OK)
async updatePetWithForm(@Param("petId", ParseIntPipe) petId: number, @Body() data: any): Promise<void> {
    return await this.petService.updatePetWithForm(petId, data)
}

@ApiOperation({ summary: 'Deletes a pet' })
@ApiResponse({ status: HttpStatus.OK })
@ApiParam({
    name: "petId",
    description: "Pet id to delete"
})
@Delete('/:petId')
@HttpCode(HttpStatus.OK)
async deletePet(@Param("petId", ParseIntPipe) petId: number): Promise<void> {
    return await this.petService.deletePet(petId)
}

}

service import { Injectable } from "@nestjs/common"; import { PetRepository } from "./repository/pet.repository"; import { ApiResponse } from "./domain/ApiResponse.vo"; import { Pet } from "./domain/Pet.dto"; import { FindPetsByStatusQueryDto } from "./domain/findPetsByStatus-query.dto"; import { FindPetsByTagsQueryDto } from "./domain/findPetsByTags-query.dto";

@Injectable export class PetService { constructor(private readonly petRepository: PetRepository) { }

async uploadFile(petId: number, data: any): Promise<ApiResponse> {
    return await this.petRepository.uploadFile(petId, data)
}

async addPet(data: Pet): Promise<void> {
    return await this.petRepository.addPet(data)
}

async updatePet(data: Pet): Promise<void> {
    return await this.petRepository.updatePet(data)
}

async findPetsByStatus(query: FindPetsByStatusQueryDto): Promise<Pet[]> {
    return await this.petRepository.findPetsByStatus(query)
}

async findPetsByTags(query: FindPetsByTagsQueryDto): Promise<Pet[]> {
    return await this.petRepository.findPetsByTags(query)
}

async getPetById(petId: number): Promise<Pet> {
    return await this.petRepository.getPetById(petId)
}

async updatePetWithForm(petId: number, data: any): Promise<void> {
    return await this.petRepository.updatePetWithForm(petId, data)
}

async deletePet(petId: number): Promise<void> {
    return await this.petRepository.deletePet(petId)
}

}

repository import { Injectable, NotFoundException } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { petEntity } from "../entities/pet.entity.ts"; import { PetMappers } from "./PetMappers"; import { Repository } from "typeorm"; import { plainToInstance } from "class-transformer"; import { ApiResponse } from "./domain/ApiResponse.vo"; import { Pet } from "./domain/Pet.dto"; import { FindPetsByStatusQueryDto } from "./domain/findPetsByStatus-query.dto"; import { FindPetsByTagsQueryDto } from "./domain/findPetsByTags-query.dto";

@Injectable export class PetRepository { constructor(@InjectRepository(petEntity) private readonly petRepository: Repository) { }

async uploadFile(petId: number, data: any): Promise<ApiResponse> {
    const newEntity = PetMappers.toPersistence(data);

    const savedEntity = await this.petRepository.save(newEntity);

    return plainToInstance(ApiResponse, savedEntity, {
        excludeExtraneousValues: true,
    });
}

async addPet(data: Pet): Promise<void> {
    const newEntity = PetMappers.toPersistence(data);

    const savedEntity = await this.petRepository.save(newEntity);

    return plainToInstance(undefined, savedEntity, {
        excludeExtraneousValues: true,
    });
}

async updatePet(data: Pet): Promise<void> {
    const detail = await this.petRepository.findOneBy({});
    if (!detail) {
        throw new NotFoundException(`id ${id} not found`);
    }

    const savedEntity = await this.petRepository.save(
        PetMappers.toPersistence(_.assign(detail, data)),
    );
    return plainToInstance(FindOneUserVo, savedEntity, {
        excludeExtraneousValues: true,
    });
}

async findPetsByStatus(query: FindPetsByStatusQueryDto): Promise<Pet[]> {

    const [data, total] = await this.petRepository.find({
        where: { status: query.status },



    });

    return plainToInstance(
        undefined,
        { data, total },
        {
            exposeDefaultValues: true,
        },
    );
}

async findPetsByTags(query: FindPetsByTagsQueryDto): Promise<Pet[]> {

    const [data, total] = await this.petRepository.find({
        where: { tags: query.tags },



    });

    return plainToInstance(
        undefined,
        { data, total },
        {
            exposeDefaultValues: true,
        },
    );
}

async getPetById(petId: number): Promise<Pet> {
    const newEntity = await this.petRepository.findOne({ where: { petId: query.petId });
    return plainToInstance(FindOneUserVo, newEntity, {
        exposeDefaultValues: true,
    });
}

async updatePetWithForm(petId: number, data: any): Promise<void> {
    const newEntity = PetMappers.toPersistence(data);

    const savedEntity = await this.petRepository.save(newEntity);

    return plainToInstance(undefined, savedEntity, {
        excludeExtraneousValues: true,
    });
}

async deletePet(petId: number): Promise<void> {
    return this.petRepository.softRemove(_.assign(new petEntity(), { petId }));
}

}

domain import { Type, IsNumber, IsOptional, IsString } from "class-validator"; import { ApiProperty } from "@nestjs/swagger"; import { Category } from "Category"; import { Tag } from "Tag";

/** *

  • @description Pet object that needs to be added to the store */ export class Pet {

    @IsNumber() @IsOptional() @ApiProperty({ format: 'int64', required: false, name: 'id' }) id?: number;

    @IsOptional() @ApiProperty({ $ref: '#/components/schemas/Category', required: false, name: 'category' }) category?: Category;

    @IsString() @ApiProperty({ example: 'doggie', required: true, name: 'name' }) name: string;

    @Type(() => String) @IsString({ "each": true }) @ApiProperty({ xml: { wrapped: true }, isArray: true, required: true, name: 'photoUrls' }) photoUrls: string[];

    @Type(() => Tag) @IsOptional() @ApiProperty({ xml: { wrapped: true }, isArray: true, required: false, name: 'tags' }) tags?: Tag;

    @IsString() @IsOptional() @ApiProperty({ description: 'pet status in the store', enum: [ 'available', 'pending', 'sold' ], required: false, name: 'status' }) status?: string; }