From 3038475a93055ea83e6569efa1102a54a2bfae6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D0=BE=D0=BC=D0=B8=D0=BD=20=D0=9D=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=D1=82=D0=B0?= Date: Thu, 17 Jul 2025 14:26:04 +0300 Subject: [PATCH 1/3] fix: Fix no required body in types --- src/types/schemeTypes.ts | 44 ++++++++++++++++++++++++++++++----- src/types/utils.ts | 13 +++++++++++ test/fixtures/example.api.yml | 2 +- test/params.test-d.ts | 8 ++++++- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/types/schemeTypes.ts b/src/types/schemeTypes.ts index be3a115..1fe07ba 100644 --- a/src/types/schemeTypes.ts +++ b/src/types/schemeTypes.ts @@ -1,6 +1,6 @@ import type { FilterKeys, PathsWithMethod } from "openapi-typescript-helpers"; import type { MethodType } from "../const/methods.js"; -import type { FilterKeyOrNever } from "./utils.js"; +import type { FilterKeyOrNever, IsFieldOptional } from "./utils.js"; /** * @description Define possible field types in OpenAPI schema @@ -34,6 +34,15 @@ export type SchemaType = { }; }; +/** + * @description Checks for the existence of an internal route. If it exists, it retrieves it + */ +export type SchemeRoute< + Schema extends SchemaType, + Method extends MethodType, + Route extends RoutesForMethod, +> = FilterKeys, Method>; + /** * @description Checks for the existence of an internal field. If it exists, it retrieves it */ @@ -42,7 +51,7 @@ export type SchemeRouteField< Method extends MethodType, Route extends RoutesForMethod, Field extends FieldType, -> = FilterKeys, Method>, Field>; +> = FilterKeys, Field>; /** * @description Extracts all path parameters for a given route and method in the schema @@ -85,10 +94,33 @@ export type RouteRequestBody< Schema extends SchemaType, Method extends MethodType, Route extends RoutesForMethod, -> = FilterKeys< - FilterKeys, "content">, - TargetMimeTypes ->; +> = + IsFieldOptional< + SchemeRoute, + "requestBody" + > extends true + ? + | FilterKeys< + FilterKeys< + FilterKeys< + Required>, + "requestBody" + >, + "content" + >, + TargetMimeTypes + > + | undefined + : FilterKeys< + FilterKeys< + FilterKeys< + FilterKeys, Method>, + "requestBody" + >, + "content" + >, + TargetMimeTypes + >; /** * @example diff --git a/src/types/utils.ts b/src/types/utils.ts index 75e9929..3e0f6c1 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -28,6 +28,19 @@ export type MakeNeverEmpty = { */ export type NotNever = [T] extends [never] ? false : true; +/** + * @description Type utility to determine if a field in an object type `T` can be `undefined`. + * Returns `true` if the field `K` of type `T` can be `undefined`, otherwise `false`. + * + * @template T - The object type to check. + * @template K - The key of the field in the object type `T` to check for `undefined`. + */ +export type IsFieldOptional = K extends keyof T + ? undefined extends T[K] + ? true + : false + : false; + /** * @description Type utility to filter the key `K` from type `T`. * Returns the type of `T[K]` if `K` is a key of `T` and `T[K]` is not `undefined`. diff --git a/test/fixtures/example.api.yml b/test/fixtures/example.api.yml index e85b7e9..1f79bd1 100644 --- a/test/fixtures/example.api.yml +++ b/test/fixtures/example.api.yml @@ -105,7 +105,7 @@ paths: application/x-www-form-urlencoded: schema: $ref: "#/components/schemas/Pet" - required: true + required: false responses: "200": description: Successful operation diff --git a/test/params.test-d.ts b/test/params.test-d.ts index 0938f00..21fe419 100644 --- a/test/params.test-d.ts +++ b/test/params.test-d.ts @@ -40,7 +40,13 @@ describe("Params type tests", () => { test("Check is method with body, body params type are may be required", () => { expectTypeOf< - IsNotUndefinable[1]> + IsNotUndefinable[1]> + >().toMatchTypeOf(); + }); + + test("Check is method with body, body params type are may be optional", () => { + expectTypeOf< + IsUndefinable[1]> >().toMatchTypeOf(); }); From e33bef6d9a59e13ed58335aca3400054dcc83765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D0=BE=D0=BC=D0=B8=D0=BD=20=D0=9D=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=D1=82=D0=B0?= Date: Thu, 17 Jul 2025 14:43:17 +0300 Subject: [PATCH 2/3] chore: add prettier ignore file --- .prettierignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a93194e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +README.MD \ No newline at end of file From 5cdf537c19247c67df7ad07001b960b725caab60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D0=BE=D0=BC=D0=B8=D0=BD=20=D0=9D=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=D1=82=D0=B0?= Date: Fri, 18 Jul 2025 12:54:10 +0300 Subject: [PATCH 3/3] feat: Add tests to check body types --- test/example-api.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/example-api.test.ts diff --git a/test/example-api.test.ts b/test/example-api.test.ts new file mode 100644 index 0000000..b01efc0 --- /dev/null +++ b/test/example-api.test.ts @@ -0,0 +1,42 @@ +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { beforeAll, describe, expect, test } from "vitest"; +import { OpenApiAxios } from "../src/index.js"; +import { type components, type paths } from "./fixtures/example.api.js"; + +let mockAdapter: MockAdapter; +let api: OpenApiAxios; + +const petMock: components["schemas"]["Pet"] = { + id: 1, + name: "pet", + photoUrls: ["http://test.ru"], +}; + +beforeAll(() => { + const axiosInstance = axios.create({}); + api = new OpenApiAxios(axiosInstance, { + validStatus: "axios", + }); + + // @ts-expect-error @TODO See issue #4 - https://github.com/web-bee-ru/openapi-axios/issues/4 + mockAdapter = new MockAdapter(axiosInstance); + + mockAdapter.onPost("/pet").reply(200); + mockAdapter.onPut("/pet").reply(200); +}); + +describe("Body serializer", () => { + test("Check required body", async () => { + const { status } = await api.put("/pet", petMock); + expect(status).toBe(200); + }); + + test("Check no required body", async () => { + const { status } = await api.post("/pet", petMock); + const { status: status2 } = await api.post("/pet", undefined); + + expect(status).toBe(200); + expect(status2).toBe(200); + }); +});