Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
README.MD
44 changes: 38 additions & 6 deletions src/types/schemeTypes.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<Schema, Method>,
> = FilterKeys<FilterKeys<Schema, Route>, Method>;

/**
* @description Checks for the existence of an internal field. If it exists, it retrieves it
*/
Expand All @@ -42,7 +51,7 @@ export type SchemeRouteField<
Method extends MethodType,
Route extends RoutesForMethod<Schema, Method>,
Field extends FieldType,
> = FilterKeys<FilterKeys<FilterKeys<Schema, Route>, Method>, Field>;
> = FilterKeys<SchemeRoute<Schema, Method, Route>, Field>;

/**
* @description Extracts all path parameters for a given route and method in the schema
Expand Down Expand Up @@ -85,10 +94,33 @@ export type RouteRequestBody<
Schema extends SchemaType,
Method extends MethodType,
Route extends RoutesForMethod<Schema, Method>,
> = FilterKeys<
FilterKeys<SchemeRouteField<Schema, Method, Route, "requestBody">, "content">,
TargetMimeTypes
>;
> =
IsFieldOptional<
SchemeRoute<Schema, Method, Route>,
"requestBody"
> extends true
?
| FilterKeys<
FilterKeys<
FilterKeys<
Required<SchemeRoute<Schema, Method, Route>>,
"requestBody"
>,
"content"
>,
TargetMimeTypes
>
| undefined
: FilterKeys<
FilterKeys<
FilterKeys<
FilterKeys<FilterKeys<Schema, Route>, Method>,
"requestBody"
>,
"content"
>,
TargetMimeTypes
>;

/**
* @example
Expand Down
13 changes: 13 additions & 0 deletions src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ export type MakeNeverEmpty<T> = {
*/
export type NotNever<T> = [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<T, K extends string> = 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`.
Expand Down
42 changes: 42 additions & 0 deletions test/example-api.test.ts
Original file line number Diff line number Diff line change
@@ -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<paths, "axios">;

const petMock: components["schemas"]["Pet"] = {
id: 1,
name: "pet",
photoUrls: ["http://test.ru"],
};

beforeAll(() => {
const axiosInstance = axios.create({});
api = new OpenApiAxios<paths, "axios">(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);
});
});
2 changes: 1 addition & 1 deletion test/fixtures/example.api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ paths:
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/Pet"
required: true
required: false
responses:
"200":
description: Successful operation
Expand Down
8 changes: 7 additions & 1 deletion test/params.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ describe("Params type tests", () => {

test("Check is method with body, body params type are may be required", () => {
expectTypeOf<
IsNotUndefinable<FetcherWithBodyParameters<paths, "post", "/pet">[1]>
IsNotUndefinable<FetcherWithBodyParameters<paths, "put", "/pet">[1]>
>().toMatchTypeOf<true>();
});

test("Check is method with body, body params type are may be optional", () => {
expectTypeOf<
IsUndefinable<FetcherWithBodyParameters<paths, "post", "/pet">[1]>
>().toMatchTypeOf<true>();
});

Expand Down
Loading