counterfact
Version:
a library for building a fake REST API for testing
290 lines (261 loc) • 6.68 kB
text/typescript
interface OpenApiHeader {
schema: unknown;
}
interface OpenApiContent {
schema: unknown;
}
interface Example {
description: string;
summary: string;
value: unknown;
}
const counterfactResponse = Symbol("Counterfact Response");
const counterfactResponseObject = {
[counterfactResponse]: counterfactResponse,
};
type COUNTERFACT_RESPONSE = typeof counterfactResponseObject;
type MediaType = `${string}/${string}`;
type OmitAll<T, K extends readonly string[]> = {
[P in keyof T as P extends `${string}${K[number]}${string}`
? never
: P]: T[P];
};
type OmitValueWhenNever<Base> = Pick<
Base,
{
[Key in keyof Base]: [Base[Key]] extends [never] ? never : Key;
}[keyof Base]
>;
interface OpenApiResponse {
content: { [key: MediaType]: OpenApiContent };
headers: { [key: string]: OpenApiHeader };
requiredHeaders: string;
}
interface OpenApiResponses {
[key: string]: OpenApiResponse;
}
type IfHasKey<
SomeObject,
Keys extends readonly string[],
Yes,
No,
> = Keys extends [
infer FirstKey extends string,
...infer RestKeys extends string[],
]
? keyof SomeObject extends `${string}${FirstKey}${string}`
? Yes
: IfHasKey<SomeObject, RestKeys, Yes, No>
: No;
type SchemasOf<T extends { [key: string]: { schema: any } }> = {
[K in keyof T]: T[K]["schema"];
}[keyof T];
type MaybeShortcut<
ContentTypes extends MediaType[],
Response extends OpenApiResponse,
> = IfHasKey<
Response["content"],
ContentTypes,
(body: SchemasOf<Response["content"]>) => GenericResponseBuilder<{
content: NeverIfEmpty<OmitAll<Response["content"], ContentTypes>>;
headers: Response["headers"];
requiredHeaders: Response["requiredHeaders"];
}>,
never
>;
type NeverIfEmpty<Record> = {} extends Record ? never : Record;
type MatchFunction<Response extends OpenApiResponse> = <
ContentType extends MediaType & keyof Response["content"],
>(
contentType: ContentType,
body: Response["content"][ContentType]["schema"],
) => GenericResponseBuilder<{
content: NeverIfEmpty<Omit<Response["content"], ContentType>>;
headers: Response["headers"];
requiredHeaders: Response["requiredHeaders"];
}>;
type HeaderFunction<Response extends OpenApiResponse> = <
Header extends string & keyof Response["headers"],
>(
header: Header,
value: Response["headers"][Header]["schema"],
) => GenericResponseBuilder<{
content: NeverIfEmpty<Response["content"]>;
headers: NeverIfEmpty<Omit<Response["headers"], Header>>;
requiredHeaders: Exclude<Response["requiredHeaders"], Header>;
}>;
type RandomFunction<Response extends OpenApiResponse> = <
Header extends string & keyof Response["headers"],
>() => COUNTERFACT_RESPONSE;
interface ResponseBuilder {
[status: number | `${number} ${string}`]: ResponseBuilder;
content?: { body: unknown; type: string }[];
header: (name: string, value: string) => ResponseBuilder;
headers: { [name: string]: string };
html: (body: unknown) => ResponseBuilder;
json: (body: unknown) => ResponseBuilder;
match: (contentType: string, body: unknown) => ResponseBuilder;
random: () => ResponseBuilder;
randomLegacy: () => ResponseBuilder;
status?: number;
text: (body: unknown) => ResponseBuilder;
xml: (body: unknown) => ResponseBuilder;
}
type GenericResponseBuilderInner<
Response extends OpenApiResponse = OpenApiResponse,
> = OmitValueWhenNever<{
header: [keyof Response["headers"]] extends [never]
? never
: HeaderFunction<Response>;
html: MaybeShortcut<["text/html"], Response>;
json: MaybeShortcut<
[
"application/json",
"text/json",
"text/x-json",
"application/xml",
"text/xml",
],
Response
>;
match: [keyof Response["content"]] extends [never]
? never
: MatchFunction<Response>;
random: [keyof Response["content"]] extends [never]
? never
: RandomFunction<Response>;
text: MaybeShortcut<["text/plain"], Response>;
xml: MaybeShortcut<["application/xml", "text/xml"], Response>;
}>;
type GenericResponseBuilder<
Response extends OpenApiResponse = OpenApiResponse,
> =
object extends OmitValueWhenNever<Response>
? COUNTERFACT_RESPONSE
: keyof OmitValueWhenNever<Response> extends "headers"
? {
ALL_REMAINING_HEADERS_ARE_OPTIONAL: COUNTERFACT_RESPONSE;
header: HeaderFunction<Response>;
}
: GenericResponseBuilderInner<Response>;
type ResponseBuilderFactory<
Responses extends OpenApiResponses = OpenApiResponses,
> = {
[StatusCode in keyof Responses]: GenericResponseBuilder<
Responses[StatusCode]
>;
} & { [key: string]: GenericResponseBuilder<Responses["default"]> };
type HttpStatusCode =
| 100
| 101
| 102
| 200
| 201
| 202
| 203
| 204
| 205
| 206
| 207
| 226
| 300
| 301
| 302
| 303
| 304
| 305
| 307
| 308
| 400
| 401
| 402
| 403
| 404
| 405
| 406
| 407
| 408
| 409
| 410
| 411
| 412
| 413
| 414
| 415
| 416
| 417
| 418
| 422
| 423
| 424
| 426
| 428
| 429
| 431
| 451
| 500
| 501
| 502
| 503
| 504
| 505
| 506
| 507
| 511;
interface OpenApiParameters {
in: "body" | "cookie" | "formData" | "header" | "path" | "query";
name: string;
schema?: {
type: string;
};
type?: "string" | "number" | "integer" | "boolean";
}
interface OpenApiOperation {
parameters?: OpenApiParameters[];
produces?: string[];
responses: {
[status: string]: {
content?: {
[type: number | string]: {
examples?: { [key: string]: Example };
schema: { [key: string]: unknown };
};
};
examples?: { [key: string]: unknown };
schema?: { [key: string]: unknown };
};
};
}
interface WideResponseBuilder {
header: (body: unknown) => WideResponseBuilder;
html: (body: unknown) => WideResponseBuilder;
json: (body: unknown) => WideResponseBuilder;
match: (contentType: string, body: unknown) => WideResponseBuilder;
random: () => WideResponseBuilder;
text: (body: unknown) => WideResponseBuilder;
xml: (body: unknown) => WideResponseBuilder;
}
interface WideOperationArgument {
body: unknown;
context: unknown;
headers: { [key: string]: string };
path: { [key: string]: string };
proxy: (url: string) => { proxyUrl: string };
query: { [key: string]: string };
response: { [key: number]: WideResponseBuilder };
}
export type { COUNTERFACT_RESPONSE };
export type {
HttpStatusCode,
MediaType,
OmitValueWhenNever,
OpenApiOperation,
OpenApiParameters,
OpenApiResponse,
ResponseBuilder,
ResponseBuilderFactory,
WideOperationArgument,
WideResponseBuilder,
OmitAll,
IfHasKey,
};