Skip to content

Commit

Permalink
Ref: using node:assert/strict instead of if + throw (#1371)
Browse files Browse the repository at this point in the history
Reducing branch complexity that way.
⚠️ Requires to get rid of Jest: #1369
  • Loading branch information
RobinTail authored Dec 11, 2023
1 parent db622e3 commit 1aaa26a
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 114 deletions.
5 changes: 2 additions & 3 deletions example/endpoints/retrieve-user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import createHttpError from "http-errors";
import assert from "node:assert/strict";
import { z } from "zod";
import { taggedEndpointsFactory } from "../factories";
import { methodProviderMiddleware } from "../middlewares";
Expand Down Expand Up @@ -37,9 +38,7 @@ export const retrieveUserEndpoint = taggedEndpointsFactory
handler: async ({ input: { id }, options: { method }, logger }) => {
logger.debug(`Requested id: ${id}, method ${method}`);
const name = "John Doe";
if (id > 100) {
throw createHttpError(404, "User not found");
}
assert(id <= 100, createHttpError(404, "User not found"));
return {
id,
name,
Expand Down
5 changes: 2 additions & 3 deletions example/endpoints/update-user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import createHttpError from "http-errors";
import assert from "node:assert/strict";
import { z } from "zod";
import { ez, withMeta } from "../../src";
import { keyAndTokenAuthenticatedEndpointsFactory } from "../factories";
Expand Down Expand Up @@ -41,9 +42,7 @@ export const updateUserEndpoint =
logger,
}) => {
logger.debug(`id, key and token: ${id}, ${key}, ${token}`);
if (id > 100) {
throw createHttpError(404, "User not found");
}
assert(id <= 100, createHttpError(404, "User not found"));
return {
createdAt: new Date("2022-01-22"),
name,
Expand Down
13 changes: 7 additions & 6 deletions example/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import createHttpError from "http-errors";
import assert from "node:assert/strict";
import { z } from "zod";
import { Method, createMiddleware, withMeta } from "../src";

Expand All @@ -18,12 +19,12 @@ export const authMiddleware = createMiddleware({
}),
middleware: async ({ input: { key }, request, logger }) => {
logger.debug("Checking the key and token...");
if (key !== "123") {
throw createHttpError(401, "Invalid key");
}
if (request.headers.token !== "456") {
throw createHttpError(401, "Invalid token");
}
assert.equal(key, "123", createHttpError(401, "Invalid key"));
assert.equal(
request.headers.token,
"456",
createHttpError(401, "Invalid token"),
);
return { token: request.headers.token };
},
});
Expand Down
74 changes: 39 additions & 35 deletions src/documentation-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from "node:assert/strict";
import {
ContentObject,
ExampleObject,
Expand Down Expand Up @@ -132,12 +133,13 @@ export const depictAny: Depicter<z.ZodAny> = () => ({
});

export const depictUpload: Depicter<ZodUpload> = (ctx) => {
if (ctx.isResponse) {
throw new DocumentationError({
assert(
!ctx.isResponse,
new DocumentationError({
message: "Please use z.upload() only for input.",
...ctx,
});
}
}),
);
return {
type: "string",
format: "binary",
Expand Down Expand Up @@ -243,12 +245,13 @@ export const depictNull: Depicter<z.ZodNull> = () => ({
});

export const depictDateIn: Depicter<ZodDateIn> = (ctx) => {
if (ctx.isResponse) {
throw new DocumentationError({
assert(
!ctx.isResponse,
new DocumentationError({
message: "Please use z.dateOut() for output.",
...ctx,
});
}
}),
);
return {
description: "YYYY-MM-DDTHH:mm:ss.sssZ",
type: "string",
Expand All @@ -261,12 +264,13 @@ export const depictDateIn: Depicter<ZodDateIn> = (ctx) => {
};

export const depictDateOut: Depicter<ZodDateOut> = (ctx) => {
if (!ctx.isResponse) {
throw new DocumentationError({
assert(
ctx.isResponse,
new DocumentationError({
message: "Please use z.dateIn() for input.",
...ctx,
});
}
}),
);
return {
description: "YYYY-MM-DDTHH:mm:ss.sssZ",
type: "string",
Expand All @@ -278,16 +282,17 @@ export const depictDateOut: Depicter<ZodDateOut> = (ctx) => {
};

/** @throws DocumentationError */
export const depictDate: Depicter<z.ZodDate> = (ctx) => {
throw new DocumentationError({
message: `Using z.date() within ${
ctx.isResponse ? "output" : "input"
} schema is forbidden. Please use z.date${
ctx.isResponse ? "Out" : "In"
}() instead. Check out the documentation for details.`,
...ctx,
});
};
export const depictDate: Depicter<z.ZodDate> = (ctx) =>
assert.fail(
new DocumentationError({
message: `Using z.date() within ${
ctx.isResponse ? "output" : "input"
} schema is forbidden. Please use z.date${
ctx.isResponse ? "Out" : "In"
}() instead. Check out the documentation for details.`,
...ctx,
}),
);

export const depictBoolean: Depicter<z.ZodBoolean> = () => ({
type: "boolean",
Expand Down Expand Up @@ -625,14 +630,15 @@ export const extractObjectSchema = (
.map((option) => extractObjectSchema(option, ctx))
.reduce((acc, option) => acc.merge(option.partial()), z.object({}));
} else if (subject instanceof z.ZodEffects) {
if (hasTopLevelTransformingEffect(subject)) {
throw new DocumentationError({
assert(
!hasTopLevelTransformingEffect(subject),
new DocumentationError({
message: `Using transformations on the top level of ${
ctx.isResponse ? "response" : "input"
} schema is not allowed.`,
...ctx,
});
}
}),
);
objectSchema = extractObjectSchema(subject._def.schema, ctx); // object refinement
} else {
// intersection
Expand Down Expand Up @@ -773,15 +779,13 @@ export const onEach: Depicter<z.ZodTypeAny, "each"> = ({
};
};

export const onMissing: Depicter<z.ZodTypeAny, "last"> = ({
schema,
...ctx
}) => {
throw new DocumentationError({
message: `Zod type ${schema.constructor.name} is unsupported.`,
...ctx,
});
};
export const onMissing: Depicter<z.ZodTypeAny, "last"> = ({ schema, ...ctx }) =>
assert.fail(
new DocumentationError({
message: `Zod type ${schema.constructor.name} is unsupported.`,
...ctx,
}),
);

export const excludeParamsFromDepiction = (
depicted: SchemaObject | ReferenceObject,
Expand Down
10 changes: 6 additions & 4 deletions src/documentation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from "node:assert/strict";
import {
OpenApiBuilder,
OperationObject,
Expand Down Expand Up @@ -75,14 +76,15 @@ export class Documentation extends OpenApiBuilder {
userDefinedOperationId?: string,
) {
if (userDefinedOperationId) {
if (userDefinedOperationId in this.lastOperationIdSuffixes) {
throw new DocumentationError({
assert(
!(userDefinedOperationId in this.lastOperationIdSuffixes),
new DocumentationError({
message: `Duplicated operationId: "${userDefinedOperationId}"`,
method,
isResponse: false,
path,
});
}
}),
);
this.lastOperationIdSuffixes[userDefinedOperationId] = 1;
return userDefinedOperationId;
}
Expand Down
10 changes: 6 additions & 4 deletions src/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Request, Response } from "express";
import assert from "node:assert/strict";
import { z } from "zod";
import { ApiResponse } from "./api-response";
import { CommonConfig } from "./config-type";
Expand Down Expand Up @@ -131,11 +132,12 @@ export class Endpoint<
{ name: "input schema", schema: inputSchema },
{ name: "output schema", schema: outputSchema },
].forEach(({ name, schema }) => {
if (hasTopLevelTransformingEffect(schema)) {
throw new IOSchemaError(
assert(
!hasTopLevelTransformingEffect(schema),
new IOSchemaError(
`Using transformations on the top level of endpoint ${name} is not allowed.`,
);
}
),
);
});
this.#handler = handler;
this.#resultHandler = resultHandler;
Expand Down
10 changes: 6 additions & 4 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IOSchema } from "./io-schema";
import { LogicalContainer } from "./logical-container";
import { Security } from "./security";
import { AbstractLogger } from "./logger";
import assert from "node:assert/strict";

interface MiddlewareParams<IN, OPT> {
input: IN;
Expand Down Expand Up @@ -50,11 +51,12 @@ export const createMiddleware = <
>(
props: MiddlewareCreationProps<IN, OPT, OUT, SCO>,
): MiddlewareDefinition<IN, OPT, OUT, SCO> => {
if (hasTopLevelTransformingEffect(props.input)) {
throw new IOSchemaError(
assert(
!hasTopLevelTransformingEffect(props.input),
new IOSchemaError(
"Using transformations on the top level of middleware input schema is not allowed.",
);
}
),
);
return {
...props,
type: "proprietary",
Expand Down
20 changes: 12 additions & 8 deletions src/routing-walker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from "node:assert/strict";
import { DependsOnMethod } from "./depends-on-method";
import { AbstractEndpoint } from "./endpoint";
import { RoutingError } from "./errors";
Expand Down Expand Up @@ -26,11 +27,13 @@ export const walkRouting = ({
}: RoutingWalkerParams) => {
Object.entries(routing).forEach(([segment, element]) => {
segment = segment.trim();
if (segment.match(/\//)) {
throw new RoutingError(
assert.doesNotMatch(
segment,
/\//,
new RoutingError(
`The entry '${segment}' must avoid having slashes — use nesting instead.`,
);
}
),
);
const path = `${parentPath || ""}${segment ? `/${segment}` : ""}`;
if (element instanceof AbstractEndpoint) {
const methods: (Method | AuxMethod)[] = element.getMethods().slice();
Expand All @@ -46,11 +49,12 @@ export const walkRouting = ({
}
} else if (element instanceof DependsOnMethod) {
Object.entries(element.endpoints).forEach(([method, endpoint]) => {
if (!endpoint.getMethods().includes(method as Method)) {
throw new RoutingError(
assert(
endpoint.getMethods().includes(method as Method),
new RoutingError(
`Endpoint assigned to ${method} method of ${path} must support ${method} method.`,
);
}
),
);
onEndpoint(endpoint, path, method as Method);
});
if (hasCors && Object.keys(element.endpoints).length > 0) {
Expand Down
28 changes: 11 additions & 17 deletions tests/system/system.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cors from "cors";
import assert from "node:assert/strict";
import { z } from "zod";
import {
EndpointsFactory,
Expand Down Expand Up @@ -38,9 +39,7 @@ describe("App", async () => {
createResultHandler({
getPositiveResponse: () => z.object({}),
getNegativeResponse: () => z.object({}),
handler: () => {
throw new Error("I am faulty");
},
handler: () => assert.fail("I am faulty"),
}),
)
.addMiddleware(
Expand All @@ -49,13 +48,12 @@ describe("App", async () => {
mwError: z
.any()
.optional()
.transform((value) => {
if (value) {
throw new Error(
"Custom error in the Middleware input validation",
);
}
}),
.transform((value) =>
assert(
!value,
"Custom error in the Middleware input validation",
),
),
}),
middleware: async () => ({}),
}),
Expand All @@ -66,13 +64,9 @@ describe("App", async () => {
epError: z
.any()
.optional()
.transform((value) => {
if (value) {
throw new Error(
"Custom error in the Endpoint input validation",
);
}
}),
.transform((value) =>
assert(!value, "Custom error in the Endpoint input validation"),
),
}),
output: z.object({
test: z.string(),
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/documentation-helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from "node:assert/strict";
import { ReferenceObject, SchemaObject } from "openapi3-ts/oas30";
import { z } from "zod";
import { defaultSerializer } from "../../src/common-helpers";
Expand Down Expand Up @@ -665,9 +666,7 @@ describe("Documentation helpers", () => {

test.each([
z.number().transform((num) => () => num),
z.number().transform(() => {
throw new Error("this should be handled");
}),
z.number().transform(() => assert.fail("this should be handled")),
])("should handle edge cases", (schema) => {
expect(
depictEffect({
Expand Down
Loading

0 comments on commit 1aaa26a

Please sign in to comment.