Repository: RobinTail/express-zod-api
Pull request #751 (2022-12-18 21:01:05)

Abstract walkers
I realized that I wanna have an abstract walkers for `Routing` and `IOSchema` for different purposes:

- attaching routes to handlers
- depicting endpoints 
  - for docs (OpenAPI)
  - for client (Typescript)
- for testing 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73fa1128..ae2c9dab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 ## Version 8
 
+### v8.7.0
+
+- No new features, no any fixes.
+- However, the routing initialization and the schema documenting processes have been refactored.
+  - Some properties in the documentation may change their order, but the overall depiction should remain.
+
 ### v8.6.0
 
 - `zod` version is 3.20.2.
diff --git a/README.md b/README.md
index 4ca64035..f0a9eadc 100644
--- a/README.md
+++ b/README.md
@@ -642,7 +642,7 @@ import express from "express";
 import { createConfig, attachRouting } from "express-zod-api";
 
 const app = express();
-const config = createConfig({ app /* ..., */ });
+const config = createConfig({ app /* cors, logger, ... */ });
 const routing = {
   /* ... */
 };
diff --git a/example/example.swagger.yaml b/example/example.swagger.yaml
index f298c505..0fb5b1f7 100644
--- a/example/example.swagger.yaml
+++ b/example/example.swagger.yaml
@@ -89,9 +89,6 @@ paths:
                     enum:
                       - success
                   data:
-                    example:
-                      name: John Doe
-                      createdAt: 2021-12-31T00:00:00.000Z
                     type: object
                     properties:
                       name:
@@ -105,6 +102,9 @@ paths:
                     required:
                       - name
                       - createdAt
+                    example:
+                      name: John Doe
+                      createdAt: 2021-12-31T00:00:00.000Z
                 required:
                   - status
                   - data
@@ -162,19 +162,16 @@ paths:
             schema:
               description: POST /v1/user/:id request body
               allOf:
-                - example:
-                    key: 1234-5678-90
-                  type: object
+                - type: object
                   properties:
                     key:
                       type: string
                       minLength: 1
                   required:
                     - key
-                - example:
-                    name: John Doe
-                    birthday: 1963-04-21
-                  type: object
+                  example:
+                    key: 1234-5678-90
+                - type: object
                   properties:
                     name:
                       type: string
@@ -189,6 +186,9 @@ paths:
                   required:
                     - name
                     - birthday
+                  example:
+                    name: John Doe
+                    birthday: 1963-04-21
             examples:
               example1:
                 value:
@@ -286,8 +286,8 @@ paths:
                       otherInputs:
                         type: object
                         additionalProperties:
-                          nullable: true
                           format: any
+                          nullable: true
                     required:
                       - name
                       - size
diff --git a/src/client.ts b/src/client.ts
index 5449f088..f7fe7192 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -30,7 +30,8 @@ import {
 } from "./client-helpers";
 import { methods } from "./method";
 import { mimeJson } from "./mime";
-import { Routing, routingCycle } from "./routing";
+import { Routing } from "./routing";
+import { walkRouting } from "./routing-walker";
 
 interface Registry {
   [METHOD_PATH: string]: Record<"in" | "out", string> & { isJson: boolean };
@@ -42,9 +43,9 @@ export class Client {
   protected paths: string[] = [];
 
   constructor(routing: Routing) {
-    routingCycle({
+    walkRouting({
       routing,
-      endpointCb: (endpoint, path, method) => {
+      onEndpoint: (endpoint, path, method) => {
         const inputId = cleanId(path, method, "input");
         const responseId = cleanId(path, method, "response");
         const input = zodToTs(endpoint.getInputSchema(), inputId, {
diff --git a/src/open-api-helpers.ts b/src/open-api-helpers.ts
index b6155d5b..5b297e9d 100644
--- a/src/open-api-helpers.ts
+++ b/src/open-api-helpers.ts
@@ -22,11 +22,11 @@ import {
   routePathParamsRegex,
 } from "./common-helpers";
 import { InputSources, TagsConfig } from "./config-type";
-import { ZodDateIn, ZodDateInDef, isoDateRegex } from "./date-in-schema";
-import { ZodDateOut, ZodDateOutDef } from "./date-out-schema";
+import { ZodDateIn, isoDateRegex } from "./date-in-schema";
+import { ZodDateOut } from "./date-out-schema";
 import { AbstractEndpoint } from "./endpoint";
 import { OpenAPIError } from "./errors";
-import { ZodFile, ZodFileDef } from "./file-schema";
+import { ZodFile } from "./file-schema";
 import { IOSchema } from "./io-schema";
 import {
   LogicalContainer,
@@ -35,27 +35,25 @@ import {
 } from "./logical-container";
 import { copyMeta } from "./metadata";
 import { Method } from "./method";
+import {
+  HandlingRules,
+  HandlingVariant,
+  SchemaHandler,
+  walkSchema,
+} from "./schema-walker";
 import { Security } from "./security";
-import { ZodUpload, ZodUploadDef } from "./upload-schema";
+import { ZodUpload } from "./upload-schema";
 
 type MediaExamples = Pick<MediaTypeObject, "examples">;
 
-type DepictHelper<T extends z.ZodType<any>> = (params: {
-  schema: T;
-  initial?: SchemaObject;
+export interface OpenAPIContext {
   isResponse: boolean;
-}) => SchemaObject;
-
-type DepictingRules = Partial<
-  Record<
-    | z.ZodFirstPartyTypeKind
-    | ZodFileDef["typeName"]
-    | ZodUploadDef["typeName"]
-    | ZodDateInDef["typeName"]
-    | ZodDateOutDef["typeName"],
-    DepictHelper<any>
-  >
->;
+}
+
+type Depicter<
+  T extends z.ZodTypeAny,
+  Variant extends HandlingVariant = "regular"
+> = SchemaHandler<T, SchemaObject, OpenAPIContext, Variant>;
 
 interface ReqResDepictHelperCommonProps {
   method: Method;
@@ -72,151 +70,112 @@ const isoDateDocumentationUrl =
 export const reformatParamsInPath = (path: string) =>
   path.replace(routePathParamsRegex, (param) => `{${param.slice(1)}}`);
 
-export const depictDefault: DepictHelper<z.ZodDefault<z.ZodTypeAny>> = ({
+export const depictDefault: Depicter<z.ZodDefault<z.ZodTypeAny>> = ({
   schema: {
     _def: { innerType, defaultValue },
   },
-  initial,
-  isResponse,
+  next,
 }) => ({
-  ...initial,
-  ...depictSchema({ schema: innerType, initial, isResponse }),
+  ...next({ schema: innerType }),
   default: defaultValue(),
 });
 
-export const depictCatch: DepictHelper<z.ZodCatch<z.ZodTypeAny>> = ({
+export const depictCatch: Depicter<z.ZodCatch<z.ZodTypeAny>> = ({
   schema: {
     _def: { innerType },
   },
-  initial,
-  isResponse,
-}) => ({
-  ...initial,
-  ...depictSchema({ schema: innerType, initial, isResponse }),
-});
+  next,
+}) => next({ schema: innerType });
 
-export const depictAny: DepictHelper<z.ZodAny> = ({ initial }) => ({
-  ...initial,
+export const depictAny: Depicter<z.ZodAny> = () => ({
   format: "any",
 });
 
-export const depictUpload: DepictHelper<ZodUpload> = ({
-  initial,
-  isResponse,
-}) => {
+export const depictUpload: Depicter<ZodUpload> = ({ isResponse }) => {
   if (isResponse) {
     throw new OpenAPIError("Please use z.upload() only for input.");
   }
   return {
-    ...initial,
     type: "string",
     format: "binary",
   };
 };
 
-export const depictFile: DepictHelper<ZodFile> = ({
+export const depictFile: Depicter<ZodFile> = ({
   schema: { isBinary, isBase64 },
-  initial,
   isResponse,
 }) => {
   if (!isResponse) {
     throw new OpenAPIError("Please use z.file() only within ResultHandler.");
   }
   return {
-    ...initial,
     type: "string",
     format: isBinary ? "binary" : isBase64 ? "byte" : "file",
   };
 };
 
-export const depictUnion: DepictHelper<
+export const depictUnion: Depicter<
   z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>
-> = ({ schema: { options }, initial, isResponse }) => ({
-  ...initial,
-  oneOf: options.map((option) => depictSchema({ schema: option, isResponse })),
+> = ({ schema: { options }, next }) => ({
+  oneOf: options.map((option) => next({ schema: option })),
 });
 
-export const depictDiscriminatedUnion: DepictHelper<
+export const depictDiscriminatedUnion: Depicter<
   z.ZodDiscriminatedUnion<string, z.ZodObject<any>[]>
-> = ({ schema: { options, discriminator }, initial, isResponse }) => {
+> = ({ schema: { options, discriminator }, next }) => {
   return {
-    ...initial,
-    discriminator: {
-      propertyName: discriminator,
-    },
+    discriminator: { propertyName: discriminator },
     oneOf: Array.from(options.values()).map((option) =>
-      depictSchema({ schema: option, isResponse })
+      next({ schema: option })
     ),
   };
 };
 
-export const depictIntersection: DepictHelper<
+export const depictIntersection: Depicter<
   z.ZodIntersection<z.ZodTypeAny, z.ZodTypeAny>
 > = ({
   schema: {
     _def: { left, right },
   },
-  initial,
-  isResponse,
+  next,
 }) => ({
-  ...initial,
-  allOf: [
-    depictSchema({ schema: left, isResponse }),
-    depictSchema({ schema: right, isResponse }),
-  ],
+  allOf: [left, right].map((entry) => next({ schema: entry })),
 });
 
-export const depictOptional: DepictHelper<z.ZodOptional<any>> = ({
+export const depictOptional: Depicter<z.ZodOptional<any>> = ({
   schema,
-  initial,
-  isResponse,
-}) => ({
-  ...initial,
-  ...depictSchema({ schema: schema.unwrap(), isResponse }),
-});
+  next,
+}) => next({ schema: schema.unwrap() });
 
-export const depictNullable: DepictHelper<z.ZodNullable<any>> = ({
+export const depictNullable: Depicter<z.ZodNullable<any>> = ({
   schema,
-  initial,
-  isResponse,
+  next,
 }) => ({
-  ...initial,
   nullable: true,
-  ...depictSchema({ schema: schema.unwrap(), isResponse }),
+  ...next({ schema: schema.unwrap() }),
 });
 
-export const depictEnum: DepictHelper<
-  z.ZodEnum<any> | z.ZodNativeEnum<any>
-> = ({
-  schema: {
-    _def: { values },
-  },
-  initial,
+export const depictEnum: Depicter<z.ZodEnum<any> | z.ZodNativeEnum<any>> = ({
+  schema,
 }) => ({
-  ...initial,
-  type: typeof Object.values(values)[0] as "string" | "number",
-  enum: Object.values(values),
+  type: typeof Object.values(schema.enum)[0] as "string" | "number",
+  enum: Object.values(schema.enum),
 });
 
-export const depictLiteral: DepictHelper<z.ZodLiteral<any>> = ({
-  schema: {
-    _def: { value },
-  },
-  initial,
+export const depictLiteral: Depicter<z.ZodLiteral<any>> = ({
+  schema: { value },
 }) => ({
-  ...initial,
   type: typeof value as "string" | "number" | "boolean",
   enum: [value],
 });
 
-export const depictObject: DepictHelper<z.AnyZodObject> = ({
+export const depictObject: Depicter<z.AnyZodObject> = ({
   schema,
-  initial,
   isResponse,
+  next,
 }) => ({
-  ...initial,
   type: "object",
-  properties: depictObjectProperties({ schema, isResponse }),
+  properties: depictObjectProperties({ schema, isResponse, next }),
   required: Object.keys(schema.shape).filter((key) => {
     const prop = schema.shape[key];
     return isResponse && hasCoercion(prop)
@@ -229,23 +188,18 @@ export const depictObject: DepictHelper<z.AnyZodObject> = ({
  * @see https://swagger.io/docs/specification/data-models/data-types/
  * @todo use type:"null" for OpenAPI 3.1
  * */
-export const depictNull: DepictHelper<z.ZodNull> = ({ initial }) => ({
-  ...initial,
+export const depictNull: Depicter<z.ZodNull> = () => ({
   type: "string",
   nullable: true,
   format: "null",
 });
 
-export const depictDateIn: DepictHelper<ZodDateIn> = ({
-  initial,
-  isResponse,
-}) => {
+export const depictDateIn: Depicter<ZodDateIn> = ({ isResponse }) => {
   if (isResponse) {
     throw new OpenAPIError("Please use z.dateOut() for output.");
   }
   return {
     description: "YYYY-MM-DDTHH:mm:ss.sssZ",
-    ...initial,
     type: "string",
     format: "date-time",
     pattern: isoDateRegex.source,
@@ -255,16 +209,12 @@ export const depictDateIn: DepictHelper<ZodDateIn> = ({
   };
 };
 
-export const depictDateOut: DepictHelper<ZodDateOut> = ({
-  initial,
-  isResponse,
-}) => {
+export const depictDateOut: Depicter<ZodDateOut> = ({ isResponse }) => {
   if (!isResponse) {
     throw new OpenAPIError("Please use z.dateIn() for input.");
   }
   return {
     description: "YYYY-MM-DDTHH:mm:ss.sssZ",
-    ...initial,
     type: "string",
     format: "date-time",
     externalDocs: {
@@ -274,7 +224,7 @@ export const depictDateOut: DepictHelper<ZodDateOut> = ({
 };
 
 /** @throws OpenAPIError */
-export const depictDate: DepictHelper<z.ZodDate> = ({ isResponse }) => {
+export const depictDate: Depicter<z.ZodDate> = ({ isResponse }) => {
   throw new OpenAPIError(
     `Using z.date() within ${
       isResponse ? "output" : "input"
@@ -284,130 +234,118 @@ export const depictDate: DepictHelper<z.ZodDate> = ({ isResponse }) => {
   );
 };
 
-export const depictBoolean: DepictHelper<z.ZodBoolean> = ({ initial }) => ({
-  ...initial,
+export const depictBoolean: Depicter<z.ZodBoolean> = () => ({
   type: "boolean",
 });
 
-export const depictBigInt: DepictHelper<z.ZodBigInt> = ({ initial }) => ({
-  ...initial,
+export const depictBigInt: Depicter<z.ZodBigInt> = () => ({
   type: "integer",
   format: "bigint",
 });
 
-export const depictRecord: DepictHelper<z.ZodRecord<z.ZodTypeAny>> = ({
-  schema: { _def: def },
-  initial,
+export const depictRecord: Depicter<z.ZodRecord<z.ZodTypeAny>> = ({
+  schema: { keySchema, valueSchema },
   isResponse,
+  next,
 }) => {
-  if (
-    def.keyType instanceof z.ZodEnum ||
-    def.keyType instanceof z.ZodNativeEnum
-  ) {
-    const keys = Object.values(def.keyType._def.values) as string[];
+  if (keySchema instanceof z.ZodEnum || keySchema instanceof z.ZodNativeEnum) {
+    const keys = Object.values(keySchema.enum) as string[];
     const shape = keys.reduce(
       (carry, key) => ({
         ...carry,
-        [key]: def.valueType,
+        [key]: valueSchema,
       }),
       {} as z.ZodRawShape
     );
     return {
-      ...initial,
       type: "object",
       properties: depictObjectProperties({
         schema: z.object(shape),
         isResponse,
+        next,
       }),
       required: keys,
     };
   }
-  if (def.keyType instanceof z.ZodLiteral) {
+  if (keySchema instanceof z.ZodLiteral) {
     return {
-      ...initial,
       type: "object",
       properties: depictObjectProperties({
         schema: z.object({
-          [def.keyType._def.value]: def.valueType,
+          [keySchema.value]: valueSchema,
         }),
         isResponse,
+        next,
       }),
-      required: [def.keyType._def.value],
+      required: [keySchema.value],
     };
   }
-  if (def.keyType instanceof z.ZodUnion) {
-    const areOptionsLiteral = def.keyType.options.reduce(
+  if (keySchema instanceof z.ZodUnion) {
+    const areOptionsLiteral = keySchema.options.reduce(
       (carry: boolean, option: z.ZodTypeAny) =>
         carry && option instanceof z.ZodLiteral,
       true
     );
     if (areOptionsLiteral) {
-      const shape = def.keyType.options.reduce(
+      const shape = keySchema.options.reduce(
         (carry: z.ZodRawShape, option: z.ZodLiteral<any>) => ({
           ...carry,
-          [option.value]: def.valueType,
+          [option.value]: valueSchema,
         }),
         {} as z.ZodRawShape
       );
       return {
-        ...initial,
         type: "object",
         properties: depictObjectProperties({
           schema: z.object(shape),
           isResponse,
+          next,
         }),
-        required: def.keyType.options.map(
+        required: keySchema.options.map(
           (option: z.ZodLiteral<any>) => option.value
         ),
       };
     }
   }
   return {
-    ...initial,
     type: "object",
-    additionalProperties: depictSchema({ schema: def.valueType, isResponse }),
+    additionalProperties: next({ schema: valueSchema }),
   };
 };
 
-export const depictArray: DepictHelper<z.ZodArray<z.ZodTypeAny>> = ({
+export const depictArray: Depicter<z.ZodArray<z.ZodTypeAny>> = ({
   schema: { _def: def, element },
-  initial,
-  isResponse,
+  next,
 }) => ({
-  ...initial,
   type: "array",
-  items: depictSchema({ schema: element, isResponse }),
-  ...(def.minLength ? { minItems: def.minLength.value } : {}),
-  ...(def.maxLength ? { maxItems: def.maxLength.value } : {}),
+  items: next({ schema: element }),
+  ...(def.minLength !== null && { minItems: def.minLength.value }),
+  ...(def.maxLength !== null && { maxItems: def.maxLength.value }),
 });
 
 /** @todo improve it when OpenAPI 3.1.0 will be released */
-export const depictTuple: DepictHelper<z.ZodTuple> = ({
+export const depictTuple: Depicter<z.ZodTuple> = ({
   schema: { items },
-  initial,
-  isResponse,
+  next,
 }) => {
-  const types = items.map((item) => depictSchema({ schema: item, isResponse }));
+  const types = items.map((item) => next({ schema: item }));
   return {
-    ...initial,
     type: "array",
     minItems: types.length,
     maxItems: types.length,
     items: {
       oneOf: types,
       format: "tuple",
-      ...(types.length === 0
-        ? {}
-        : {
-            description: types
-              .map((item, index) => `${index}: ${item.type}`)
-              .join(", "),
-          }),
+      ...(types.length > 0 && {
+        description: types
+          .map((item, index) => `${index}: ${item.type}`)
+          .join(", "),
+      }),
     },
   };
 };
 
-export const depictString: DepictHelper<z.ZodString> = ({
+export const depictString: Depicter<z.ZodString> = ({
   schema: {
     isEmail,
     isURL,
@@ -418,7 +356,6 @@ export const depictString: DepictHelper<z.ZodString> = ({
     isDatetime,
     _def: { checks },
   },
-  initial,
 }) => {
   const regexCheck = checks.find(
     (check): check is z.ZodStringCheck & { kind: "regex" } =>
@@ -438,24 +375,20 @@ export const depictString: DepictHelper<z.ZodString> = ({
       : new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$`)
     : undefined;
   return {
-    ...initial,
     type: "string" as const,
-    ...(isDatetime ? { format: "date-time" } : {}),
-    ...(isEmail ? { format: "email" } : {}),
-    ...(isURL ? { format: "url" } : {}),
-    ...(isUUID ? { format: "uuid" } : {}),
-    ...(isCUID ? { format: "cuid" } : {}),
-    ...(minLength ? { minLength } : {}),
-    ...(maxLength ? { maxLength } : {}),
-    ...(regex ? { pattern: `/${regex.source}/${regex.flags}` } : {}),
+    ...(isDatetime && { format: "date-time" }),
+    ...(isEmail && { format: "email" }),
+    ...(isURL && { format: "url" }),
+    ...(isUUID && { format: "uuid" }),
+    ...(isCUID && { format: "cuid" }),
+    ...(minLength !== null && { minLength }),
+    ...(maxLength !== null && { maxLength }),
+    ...(regex && { pattern: `/${regex.source}/${regex.flags}` }),
   };
 };
 
 /** @todo support exclusive min/max as numbers in case of OpenAPI v3.1.x */
-export const depictNumber: DepictHelper<z.ZodNumber> = ({
-  schema,
-  initial,
-}) => {
+export const depictNumber: Depicter<z.ZodNumber> = ({ schema }) => {
   const minCheck = schema._def.checks.find(({ kind }) => kind === "min") as
     | Extract<ArrayElement<z.ZodNumberDef["checks"]>, { kind: "min" }>
     | undefined;
@@ -465,7 +398,6 @@ export const depictNumber: DepictHelper<z.ZodNumber> = ({
     | undefined;
   const isMaxInclusive = maxCheck ? maxCheck.inclusive : true;
   return {
-    ...initial,
     type: schema.isInt ? ("integer" as const) : ("number" as const),
     format: schema.isInt ? ("int64" as const) : ("double" as const),
     minimum:
@@ -487,23 +419,23 @@ export const depictNumber: DepictHelper<z.ZodNumber> = ({
 
 export const depictObjectProperties = ({
   schema: { shape },
-  isResponse,
-}: Parameters<DepictHelper<z.AnyZodObject>>[0]) => {
+  next,
+}: Parameters<Depicter<z.AnyZodObject>>[0]) => {
   return Object.keys(shape).reduce(
     (carry, key) => ({
       ...carry,
-      [key]: depictSchema({ schema: shape[key], isResponse }),
+      [key]: next({ schema: shape[key] }),
     }),
     {} as Record<string, SchemaObject>
   );
 };
 
-export const depictEffect: DepictHelper<z.ZodEffects<z.ZodTypeAny>> = ({
+export const depictEffect: Depicter<z.ZodEffects<z.ZodTypeAny>> = ({
   schema,
-  initial,
   isResponse,
+  next,
 }) => {
-  const input = depictSchema({ schema: schema._def.schema, isResponse });
+  const input = next({ schema: schema._def.schema });
   const effect = schema._def.effect;
   if (isResponse && effect && effect.type === "transform") {
     let output = "undefined";
@@ -528,42 +460,32 @@ export const depictEffect: DepictHelper<z.ZodEffects<z.ZodTypeAny>> = ({
       /**/
     }
     return {
-      ...initial,
       ...input,
-      ...(["number", "string", "boolean"].includes(output)
-        ? {
-            type: output as "number" | "string" | "boolean",
-          }
-        : {}),
+      ...(["number", "string", "boolean"].includes(output) && {
+        type: output as "number" | "string" | "boolean",
+      }),
     };
   }
   if (!isResponse && effect && effect.type === "preprocess") {
     const { type: inputType, ...rest } = input;
     return {
-      ...initial,
       ...rest,
       format: `${rest.format || inputType} (preprocessed)`,
     };
   }
-  return { ...initial, ...input };
+  return input;
 };
 
-export const depictPipeline: DepictHelper<z.ZodPipeline<any, any>> = ({
+export const depictPipeline: Depicter<z.ZodPipeline<any, any>> = ({
   schema,
-  initial,
   isResponse,
-}) =>
-  depictSchema({
-    schema: schema._def[isResponse ? "out" : "in"],
-    isResponse,
-    initial,
-  });
+  next,
+}) => next({ schema: schema._def[isResponse ? "out" : "in"] });
 
-export const depictBranded: DepictHelper<z.ZodBranded<z.ZodTypeAny, any>> = ({
+export const depictBranded: Depicter<z.ZodBranded<z.ZodTypeAny, any>> = ({
   schema,
-  initial,
-  isResponse,
-}) => depictSchema({ schema: schema.unwrap(), isResponse, initial });
+  next,
+}) => next({ schema: schema.unwrap() });
 
 export const depictIOExamples = <T extends IOSchema>(
   schema: T,
@@ -663,13 +585,19 @@ export const depictRequestParams = ({
       required: !shape[name].isOptional(),
       schema: {
         description: `${method.toUpperCase()} ${path} parameter`,
-        ...depictSchema({ schema: shape[name], isResponse: false }),
+        ...walkSchema({
+          schema: shape[name],
+          isResponse: false,
+          rules: depicters,
+          onEach,
+          onMissing,
+        }),
       },
       ...depictIOParamExamples(schema, false, name),
     }));
 };
 
-const depictHelpers: DepictingRules = {
+export const depicters: HandlingRules<SchemaObject, OpenAPIContext> = {
   ZodString: depictString,
   ZodNumber: depictNumber,
   ZodBigInt: depictBigInt,
@@ -709,33 +637,22 @@ export const hasCoercion = (schema: z.ZodType): boolean =>
     ? schema._def.coerce
     : false;
 
-export const depictSchema: DepictHelper<z.ZodTypeAny> = ({
+export const onEach: Depicter<z.ZodTypeAny, "last"> = ({
   schema,
   isResponse,
 }) => {
-  const initial: SchemaObject = {};
-  if (schema.isNullable()) {
-    if (!(isResponse && hasCoercion(schema))) {
-      initial.nullable = true;
-    }
-  }
-  if (schema.description) {
-    initial.description = `${schema.description}`;
-  }
+  const { description } = schema;
   const examples = getExamples(schema, isResponse);
-  if (examples.length > 0) {
-    initial.example = examples[0];
-  }
-  const nextHelper =
-    "typeName" in schema._def
-      ? depictHelpers[schema._def.typeName as keyof typeof depictHelpers]
-      : null;
-  if (!nextHelper) {
-    throw new OpenAPIError(
-      `Zod type ${schema.constructor.name} is unsupported`
-    );
-  }
-  return nextHelper({ schema, initial, isResponse });
+  return {
+    ...(description && { description }),
+    ...(schema.isNullable() &&
+      !(isResponse && hasCoercion(schema)) && { nullable: true }),
+    ...(examples.length > 0 && { example: examples[0] }),
+  };
+};
+
+export const onMissing = (schema: z.ZodTypeAny) => {
+  throw new OpenAPIError(`Zod type ${schema.constructor.name} is unsupported`);
 };
 
 export const excludeParamsFromDepiction = (
@@ -798,9 +715,12 @@ export const depictResponse = ({
     ? endpoint.getPositiveMimeTypes()
     : endpoint.getNegativeMimeTypes();
   const depictedSchema = excludeExampleFromDepiction(
-    depictSchema({
+    walkSchema({
       schema,
       isResponse: true,
+      rules: depicters,
+      onEach,
+      onMissing,
     })
   );
   const examples = depictIOExamples(schema, true);
@@ -824,7 +744,7 @@ type SecurityHelper<K extends Security["type"]> = (
   security: Security & { type: K }
 ) => SecuritySchemeObject;
 
-const depictBasicSecurity: SecurityHelper<"basic"> = ({}) => ({
+const depictBasicSecurity: SecurityHelper<"basic"> = () => ({
   type: "http",
   scheme: "basic",
 });
@@ -833,7 +753,7 @@ const depictBearerSecurity: SecurityHelper<"bearer"> = ({
 }) => ({
   type: "http",
   scheme: "bearer",
-  ...(bearerFormat ? { bearerFormat } : {}),
+  ...(bearerFormat && { bearerFormat }),
 });
 // @todo add description on actual input placement
 const depictInputSecurity: SecurityHelper<"input"> = ({ name }) => ({
@@ -921,9 +841,12 @@ export const depictRequest = ({
   const pathParams = getRoutePathParams(path);
   const bodyDepiction = excludeExampleFromDepiction(
     excludeParamsFromDepiction(
-      depictSchema({
+      walkSchema({
         schema: endpoint.getInputSchema(),
         isResponse: false,
+        rules: depicters,
+        onEach,
+        onMissing,
       }),
       pathParams
     )
@@ -959,9 +882,8 @@ export const depictTags = <TAG extends string>(
     return {
       name: tag,
       description: typeof def === "string" ? def : def.description,
-      ...(typeof def === "object" && def.url
-        ? { externalDocs: { url: def.url } }
-        : {}),
+      ...(typeof def === "object" &&
+        def.url && { externalDocs: { url: def.url } }),
     };
   });
 
diff --git a/src/open-api.ts b/src/open-api.ts
index acc6d462..caf97274 100644
--- a/src/open-api.ts
+++ b/src/open-api.ts
@@ -18,7 +18,8 @@ import {
   ensureShortDescription,
   reformatParamsInPath,
 } from "./open-api-helpers";
-import { Routing, RoutingCycleParams, routingCycle } from "./routing";
+import { Routing } from "./routing";
+import { RoutingWalkerParams, walkRouting } from "./routing-walker";
 
 interface GeneratorParams {
   title: string;
@@ -66,7 +67,7 @@ export class OpenAPI extends OpenApiBuilder {
   }: GeneratorParams) {
     super();
     this.addInfo({ title, version }).addServer({ url: serverUrl });
-    const endpointCb: RoutingCycleParams["endpointCb"] = (
+    const onEndpoint: RoutingWalkerParams["onEndpoint"] = (
       endpoint,
       path,
       _method
@@ -137,7 +138,7 @@ export class OpenAPI extends OpenApiBuilder {
         [method]: operation,
       });
     };
-    routingCycle({ routing, endpointCb });
+    walkRouting({ routing, onEndpoint });
     this.rootDoc.tags = config.tags ? depictTags(config.tags) : [];
   }
 }
diff --git a/src/routing-walker.ts b/src/routing-walker.ts
new file mode 100644
index 00000000..d45a4fed
--- /dev/null
+++ b/src/routing-walker.ts
@@ -0,0 +1,76 @@
+import { DependsOnMethod } from "./depends-on-method";
+import { AbstractEndpoint } from "./endpoint";
+import { RoutingError } from "./errors";
+import { AuxMethod, Method } from "./method";
+import { Routing } from "./routing";
+import { ServeStatic, StaticHandler } from "./serve-static";
+
+export interface RoutingWalkerParams {
+  routing: Routing;
+  onEndpoint: (
+    endpoint: AbstractEndpoint,
+    path: string,
+    method: Method | AuxMethod
+  ) => void;
+  onStatic?: (path: string, handler: StaticHandler) => void;
+  parentPath?: string;
+  hasCors?: boolean;
+}
+
+export const walkRouting = ({
+  routing,
+  onEndpoint,
+  onStatic,
+  parentPath,
+  hasCors,
+}: RoutingWalkerParams) => {
+  Object.entries(routing).forEach(([segment, element]) => {
+    segment = segment.trim();
+    if (segment.match(/\//)) {
+      throw new RoutingError(
+        "Routing elements should not contain '/' character.\n" +
+          `The error caused by ${
+            parentPath
+              ? `'${parentPath}' route that has a '${segment}'`
+              : `'${segment}'`
+          } entry.`
+      );
+    }
+    const path = `${parentPath || ""}${segment ? `/${segment}` : ""}`;
+    if (element instanceof AbstractEndpoint) {
+      const methods: (Method | AuxMethod)[] = element.getMethods().slice();
+      if (hasCors) {
+        methods.push("options");
+      }
+      methods.forEach((method) => {
+        onEndpoint(element, path, method);
+      });
+    } else if (element instanceof ServeStatic) {
+      if (onStatic) {
+        element.apply(path, onStatic);
+      }
+    } else if (element instanceof DependsOnMethod) {
+      Object.entries<AbstractEndpoint>(element.methods).forEach(
+        ([method, endpoint]) => {
+          onEndpoint(endpoint, path, method as Method);
+        }
+      );
+      if (hasCors && Object.keys(element.methods).length > 0) {
+        const [firstMethod, ...siblingMethods] = Object.keys(
+          element.methods
+        ) as Method[];
+        const firstEndpoint = element.methods[firstMethod]!;
+        firstEndpoint._setSiblingMethods(siblingMethods);
+        onEndpoint(firstEndpoint, path, "options");
+      }
+    } else {
+      walkRouting({
+        onEndpoint,
+        onStatic,
+        hasCors,
+        routing: element,
+        parentPath: path,
+      });
+    }
+  });
+};
diff --git a/src/routing.ts b/src/routing.ts
index fc86682c..a64733d9 100644
--- a/src/routing.ts
+++ b/src/routing.ts
@@ -3,85 +3,14 @@ import { Logger } from "winston";
 import { CommonConfig } from "./config-type";
 import { DependsOnMethod } from "./depends-on-method";
 import { AbstractEndpoint } from "./endpoint";
-import { RoutingError } from "./errors";
-import { AuxMethod, Method } from "./method";
-import { ServeStatic, StaticHandler } from "./serve-static";
+import { walkRouting } from "./routing-walker";
+import { ServeStatic } from "./serve-static";
 import { getStartupLogo } from "./startup-logo";
 
 export interface Routing {
   [SEGMENT: string]: Routing | DependsOnMethod | AbstractEndpoint | ServeStatic;
 }
 
-export interface RoutingCycleParams {
-  routing: Routing;
-  endpointCb: (
-    endpoint: AbstractEndpoint,
-    path: string,
-    method: Method | AuxMethod
-  ) => void;
-  staticCb?: (path: string, handler: StaticHandler) => void;
-  parentPath?: string;
-  hasCors?: boolean;
-}
-
-export const routingCycle = ({
-  routing,
-  endpointCb,
-  staticCb,
-  parentPath,
-  hasCors,
-}: RoutingCycleParams) => {
-  Object.entries(routing).forEach(([segment, element]) => {
-    segment = segment.trim();
-    if (segment.match(/\//)) {
-      throw new RoutingError(
-        "Routing elements should not contain '/' character.\n" +
-          `The error caused by ${
-            parentPath
-              ? `'${parentPath}' route that has a '${segment}'`
-              : `'${segment}'`
-          } entry.`
-      );
-    }
-    const path = `${parentPath || ""}${segment ? `/${segment}` : ""}`;
-    if (element instanceof AbstractEndpoint) {
-      const methods: (Method | AuxMethod)[] = element.getMethods().slice();
-      if (hasCors) {
-        methods.push("options");
-      }
-      methods.forEach((method) => {
-        endpointCb(element, path, method);
-      });
-    } else if (element instanceof ServeStatic) {
-      if (staticCb) {
-        element.apply(path, staticCb);
-      }
-    } else if (element instanceof DependsOnMethod) {
-      Object.entries<AbstractEndpoint>(element.methods).forEach(
-        ([method, endpoint]) => {
-          endpointCb(endpoint, path, method as Method);
-        }
-      );
-      if (hasCors && Object.keys(element.methods).length > 0) {
-        const [firstMethod, ...siblingMethods] = Object.keys(
-          element.methods
-        ) as Method[];
-        const firstEndpoint = element.methods[firstMethod]!;
-        firstEndpoint._setSiblingMethods(siblingMethods);
-        endpointCb(firstEndpoint, path, "options");
-      }
-    } else {
-      routingCycle({
-        routing: element,
-        endpointCb,
-        staticCb,
-        hasCors: hasCors,
-        parentPath: path,
-      });
-    }
-  });
-};
-
 export const initRouting = ({
   app,
   logger,
@@ -96,16 +25,16 @@ export const initRouting = ({
   if (config.startupLogo !== false) {
     console.log(getStartupLogo());
   }
-  routingCycle({
+  walkRouting({
     routing,
     hasCors: !!config.cors,
-    endpointCb: (endpoint, path, method) => {
+    onEndpoint: (endpoint, path, method) => {
       app[method](path, async (request, response) => {
         logger.info(`${request.method}: ${path}`);
         await endpoint.execute({ request, response, logger, config });
       });
     },
-    staticCb: (path, handler) => {
+    onStatic: (path, handler) => {
       app.use(path, handler);
     },
   });
diff --git a/src/schema-walker.ts b/src/schema-walker.ts
new file mode 100644
index 00000000..a020f8ac
--- /dev/null
+++ b/src/schema-walker.ts
@@ -0,0 +1,79 @@
+import { z } from "zod";
+import { ZodDateInDef } from "./date-in-schema";
+import { ZodDateOutDef } from "./date-out-schema";
+import { ZodFileDef } from "./file-schema";
+import { ZodUploadDef } from "./upload-schema";
+
+export type HandlingVariant = "last" | "regular";
+type VariantDependingProps<
+  Variant extends HandlingVariant,
+  U
+> = Variant extends "regular"
+  ? {
+      next: SchemaHandler<z.ZodTypeAny, U, {}, "last">;
+    }
+  : {};
+
+type SchemaHandlingProps<
+  T extends z.ZodTypeAny,
+  U,
+  Context extends object,
+  Variant extends HandlingVariant
+> = {
+  schema: T;
+} & Context &
+  VariantDependingProps<Variant, U>;
+
+export type SchemaHandler<
+  T extends z.ZodTypeAny,
+  U,
+  Context extends object = {},
+  Variant extends HandlingVariant = "regular"
+> = (params: SchemaHandlingProps<T, U, Context, Variant>) => U;
+
+type ProprietaryKinds =
+  | ZodFileDef["typeName"]
+  | ZodUploadDef["typeName"]
+  | ZodDateInDef["typeName"]
+  | ZodDateOutDef["typeName"];
+
+export type HandlingRules<U, Context extends object = {}> = Partial<
+  Record<
+    z.ZodFirstPartyTypeKind | ProprietaryKinds,
+    SchemaHandler<any, U, Context>
+  >
+>;
+
+export const walkSchema = <U, Context extends object = {}>({
+  schema,
+  onEach,
+  rules,
+  onMissing,
+  ...context
+}: SchemaHandlingProps<z.ZodTypeAny, U, Context, "last"> & {
+  onEach?: SchemaHandler<z.ZodTypeAny, U, Context, "last">;
+  rules: HandlingRules<U, Context>;
+  onMissing: (schema: z.ZodTypeAny) => U;
+}): U => {
+  const overrides = onEach && onEach({ schema, ...(context as Context) });
+  const handler =
+    "typeName" in schema._def
+      ? rules[schema._def.typeName as keyof typeof rules]
+      : undefined;
+  const next: SchemaHandler<z.ZodTypeAny, U, {}, "last"> = (params) =>
+    walkSchema({
+      ...params,
+      ...(context as Context),
+      onEach,
+      rules: rules,
+      onMissing,
+    });
+  const result = handler
+    ? handler({
+        schema,
+        ...(context as Context),
+        next,
+      })
+    : onMissing(schema);
+  return overrides ? { ...result, ...overrides } : result;
+};


Test patch:
diff --git a/tests/helpers.ts b/tests/helpers.ts
index 4587a622..b5b376cd 100644
--- a/tests/helpers.ts
+++ b/tests/helpers.ts
@@ -1,5 +1,6 @@
 import jestConfig from "../jest.config";
 import { z } from "../src";
+import { SchemaHandler, walkSchema } from "../src/schema-walker";
 
 export const esmTestPort = 8070;
 
@@ -21,65 +22,73 @@ export const waitFor = async (cb: () => boolean) =>
 export const serializeSchemaForTest = (
   schema: z.ZodTypeAny
 ): Record<string, any> => {
-  return {
-    _type: schema._def.typeName,
-    ...(schema instanceof z.ZodIntersection
-      ? {
-          left: serializeSchemaForTest(schema._def.left),
-          right: serializeSchemaForTest(schema._def.right),
-        }
-      : schema instanceof z.ZodUnion ||
-        schema instanceof z.ZodDiscriminatedUnion
-      ? {
-          options: Array.from(schema.options.values()).map((option) =>
-            serializeSchemaForTest(option as z.ZodTypeAny)
-          ),
-        }
-      : schema instanceof z.ZodObject
-      ? {
-          shape: Object.keys(schema.shape).reduce(
-            (carry, key) => ({
-              ...carry,
-              [key]: serializeSchemaForTest(schema.shape[key]),
-            }),
-            {}
-          ),
-        }
-      : schema instanceof z.ZodOptional || schema instanceof z.ZodNullable
-      ? {
-          value: serializeSchemaForTest(schema.unwrap()),
-        }
-      : schema instanceof z.ZodEffects || schema instanceof z.ZodTransformer
-      ? {
-          value: serializeSchemaForTest(schema._def.schema),
-        }
-      : schema instanceof z.ZodRecord
-      ? {
-          values: serializeSchemaForTest(schema._def.valueType),
-        }
-      : schema instanceof z.ZodArray
-      ? {
-          items: serializeSchemaForTest(schema._def.type),
-        }
-      : schema instanceof z.ZodLiteral
-      ? {
-          value: schema._def.value,
-        }
-      : schema instanceof z.ZodDefault
-      ? {
-          value: serializeSchemaForTest(schema._def.innerType),
-          default: schema._def.defaultValue(),
-        }
-      : schema instanceof z.ZodCatch
-      ? {
-          value: serializeSchemaForTest(schema._def.innerType),
-          fallback: schema._def.defaultValue(),
-        }
-      : schema instanceof z.ZodPipeline
-      ? {
-          from: serializeSchemaForTest(schema._def.in),
-          to: serializeSchemaForTest(schema._def.out),
-        }
-      : {}),
-  };
+  const onSomeUnion: SchemaHandler<
+    z.ZodUnion<any> | z.ZodDiscriminatedUnion<any, any>,
+    object
+  > = ({ schema: subject, next }) => ({
+    options: Array.from(subject.options.values()).map((option) =>
+      next({ schema: option as z.ZodTypeAny })
+    ),
+  });
+  const onOptionalOrNullable: SchemaHandler<
+    z.ZodOptional<any> | z.ZodNullable<any>,
+    object
+  > = ({ schema: subject, next }) => ({
+    value: next({ schema: subject.unwrap() }),
+  });
+  const onPrimitive = () => ({});
+  return walkSchema({
+    schema,
+    rules: {
+      ZodNull: onPrimitive,
+      ZodNumber: onPrimitive,
+      ZodString: onPrimitive,
+      ZodBoolean: onPrimitive,
+      ZodUnion: onSomeUnion,
+      ZodDiscriminatedUnion: onSomeUnion,
+      ZodOptional: onOptionalOrNullable,
+      ZodNullable: onOptionalOrNullable,
+      ZodIntersection: ({ schema: subject, next }) => ({
+        left: next({ schema: subject._def.left }),
+        right: next({ schema: subject._def.right }),
+      }),
+      ZodObject: ({ schema: subject, next }) => ({
+        shape: Object.keys(subject.shape).reduce(
+          (carry, key) => ({
+            ...carry,
+            [key]: next({ schema: subject.shape[key] }),
+          }),
+          {}
+        ),
+      }),
+      ZodEffects: ({ schema: subject, next }) => ({
+        value: next({ schema: subject._def.schema }),
+      }),
+      ZodRecord: ({ schema: subject, next }) => ({
+        keys: next({ schema: subject.keySchema }),
+        values: next({ schema: subject.valueSchema }),
+      }),
+      ZodArray: ({ schema: subject, next }) => ({
+        items: next({ schema: subject.element }),
+      }),
+      ZodLiteral: ({ schema: subject }) => ({ value: subject.value }),
+      ZodDefault: ({ schema: subject, next }) => ({
+        value: next({ schema: subject._def.innerType }),
+        default: schema._def.defaultValue(),
+      }),
+      ZodCatch: ({ schema: subject, next }) => ({
+        value: next({ schema: subject._def.innerType }),
+        fallback: schema._def.defaultValue(),
+      }),
+      ZodPipeline: ({ schema: subject, next }) => ({
+        from: next({ schema: subject._def.in }),
+        to: next({ schema: subject._def.out }),
+      }),
+    },
+    onEach: ({ schema: subject }) => ({ _type: subject._def.typeName }),
+    onMissing: (subject) => {
+      console.warn(`There is no serializer for ${subject._def.typeName}`);
+      return {};
+    },
+  });
 };
diff --git a/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap b/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
index 90213e65..93048fdc 100644
--- a/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
+++ b/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
@@ -1,15 +1,13 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Open API helpers depictAny() should depict ZodAny 1`] = `
+exports[`Open API helpers depictAny() should set format:any 1`] = `
 {
-  "description": "test",
   "format": "any",
 }
 `;
 
-exports[`Open API helpers depictArray() should depict ZodArray 1`] = `
+exports[`Open API helpers depictArray() should set type:array and pass items depiction 1`] = `
 {
-  "description": "test",
   "items": {
     "type": "boolean",
   },
@@ -17,42 +15,39 @@ exports[`Open API helpers depictArray() should depict ZodArray 1`] = `
 }
 `;
 
-exports[`Open API helpers depictBigInt() should depict ZodBigInt 1`] = `
+exports[`Open API helpers depictBigInt() should set type:integer and format:bigint 1`] = `
 {
-  "description": "test",
   "format": "bigint",
   "type": "integer",
 }
 `;
 
-exports[`Open API helpers depictBoolean() should depict ZodBoolean 1`] = `
+exports[`Open API helpers depictBoolean() should set type:boolean 1`] = `
 {
-  "description": "test",
   "type": "boolean",
 }
 `;
 
-exports[`Open API helpers depictBranded should depict the actual schema 1`] = `
+exports[`Open API helpers depictBranded should pass the next depicter 1`] = `
 {
   "minLength": 2,
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictCatch() should depict ZodCatch 1`] = `
+exports[`Open API helpers depictCatch() should pass next depicter 1`] = `
 {
-  "description": "test",
   "type": "boolean",
 }
 `;
 
-exports[`Open API helpers depictDate should throw clear error 1`] = `[OpenAPIError: Using z.date() within output schema is forbidden. Please use z.dateOut() instead. Check out the documentation for details.]`;
+exports[`Open API helpers depictDate should throw clear error 0 1`] = `[OpenAPIError: Using z.date() within output schema is forbidden. Please use z.dateOut() instead. Check out the documentation for details.]`;
 
-exports[`Open API helpers depictDate should throw clear error 2`] = `[OpenAPIError: Using z.date() within input schema is forbidden. Please use z.dateIn() instead. Check out the documentation for details.]`;
+exports[`Open API helpers depictDate should throw clear error 1 1`] = `[OpenAPIError: Using z.date() within input schema is forbidden. Please use z.dateIn() instead. Check out the documentation for details.]`;
 
-exports[`Open API helpers depictDateIn should depict ZodDateIn 1`] = `
+exports[`Open API helpers depictDateIn should set type:string, pattern and format 1`] = `
 {
-  "description": "test",
+  "description": "YYYY-MM-DDTHH:mm:ss.sssZ",
   "externalDocs": {
     "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString",
   },
@@ -64,9 +59,9 @@ exports[`Open API helpers depictDateIn should depict ZodDateIn 1`] = `
 
 exports[`Open API helpers depictDateIn should throw when ZodDateIn in response 1`] = `[OpenAPIError: Please use z.dateOut() for output.]`;
 
-exports[`Open API helpers depictDateOut should depict ZodDateOut 1`] = `
+exports[`Open API helpers depictDateOut should set type:string, description and format 1`] = `
 {
-  "description": "test",
+  "description": "YYYY-MM-DDTHH:mm:ss.sssZ",
   "externalDocs": {
     "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString",
   },
@@ -77,17 +72,15 @@ exports[`Open API helpers depictDateOut should depict ZodDateOut 1`] = `
 
 exports[`Open API helpers depictDateOut should throw when ZodDateOut in request 1`] = `[OpenAPIError: Please use z.dateIn() for input.]`;
 
-exports[`Open API helpers depictDefault() should depict ZodDefault 1`] = `
+exports[`Open API helpers depictDefault() should set default property 1`] = `
 {
   "default": true,
-  "description": "test",
   "type": "boolean",
 }
 `;
 
-exports[`Open API helpers depictDiscriminatedUnion() should depict ZodDiscriminatedUnion 1`] = `
+exports[`Open API helpers depictDiscriminatedUnion() should wrap next depicters in oneOf prop and set discriminator prop 1`] = `
 {
-  "description": "test",
   "discriminator": {
     "propertyName": "status",
   },
@@ -140,25 +133,13 @@ exports[`Open API helpers depictDiscriminatedUnion() should depict ZodDiscrimina
 }
 `;
 
-exports[`Open API helpers depictEffect() should depict ZodEffects preprocess in case of request 1`] = `
-{
-  "format": "string (preprocessed)",
-}
-`;
-
-exports[`Open API helpers depictEffect() should depict ZodEffects transformation in case of request 1`] = `
-{
-  "type": "string",
-}
-`;
-
-exports[`Open API helpers depictEffect() should depict ZodEffects transformation in case of response 1`] = `
+exports[`Open API helpers depictEffect() should depict as number (out) 1`] = `
 {
   "type": "number",
 }
 `;
 
-exports[`Open API helpers depictEffect() should depict refinements 1`] = `
+exports[`Open API helpers depictEffect() should depict as object (refinement) 1`] = `
 {
   "properties": {
     "s": {
@@ -172,9 +153,20 @@ exports[`Open API helpers depictEffect() should depict refinements 1`] = `
 }
 `;
 
-exports[`Open API helpers depictEnum() should depict ZodEnum 1`] = `
+exports[`Open API helpers depictEffect() should depict as string (in) 1`] = `
+{
+  "type": "string",
+}
+`;
+
+exports[`Open API helpers depictEffect() should depict as string (preprocess) 1`] = `
+{
+  "format": "string (preprocessed)",
+}
+`;
+
+exports[`Open API helpers depictEnum() should set type and enum properties 1`] = `
 {
-  "description": "test",
   "enum": [
     "one",
     "two",
@@ -183,9 +175,8 @@ exports[`Open API helpers depictEnum() should depict ZodEnum 1`] = `
 }
 `;
 
-exports[`Open API helpers depictEnum() should depict ZodNativeEnum 1`] = `
+exports[`Open API helpers depictEnum() should set type and enum properties 2`] = `
 {
-  "description": "test",
   "enum": [
     "ONE",
     "TWO",
@@ -194,25 +185,22 @@ exports[`Open API helpers depictEnum() should depict ZodNativeEnum 1`] = `
 }
 `;
 
-exports[`Open API helpers depictFile() should depict ZodFile 0 1`] = `
+exports[`Open API helpers depictFile() should set type:string and format accordingly 0 1`] = `
 {
-  "description": "test",
   "format": "file",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictFile() should depict ZodFile 1 1`] = `
+exports[`Open API helpers depictFile() should set type:string and format accordingly 1 1`] = `
 {
-  "description": "test",
   "format": "binary",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictFile() should depict ZodFile 2 1`] = `
+exports[`Open API helpers depictFile() should set type:string and format accordingly 2 1`] = `
 {
-  "description": "test",
   "format": "byte",
   "type": "string",
 }
@@ -220,7 +208,7 @@ exports[`Open API helpers depictFile() should depict ZodFile 2 1`] = `
 
 exports[`Open API helpers depictFile() should throw when using in input 1`] = `[OpenAPIError: Please use z.file() only within ResultHandler.]`;
 
-exports[`Open API helpers depictIOExamples() should depict examples in case of request 1`] = `
+exports[`Open API helpers depictIOExamples() should pass examples in case of request 1`] = `
 {
   "examples": {
     "example1": {
@@ -239,7 +227,7 @@ exports[`Open API helpers depictIOExamples() should depict examples in case of r
 }
 `;
 
-exports[`Open API helpers depictIOExamples() should depict examples in case of response 1`] = `
+exports[`Open API helpers depictIOExamples() should transform examples in case of response 1`] = `
 {
   "examples": {
     "example1": {
@@ -258,7 +246,7 @@ exports[`Open API helpers depictIOExamples() should depict examples in case of r
 }
 `;
 
-exports[`Open API helpers depictIOParamExamples() should depict examples in case of request 1`] = `
+exports[`Open API helpers depictIOParamExamples() should pass examples in case of request 1`] = `
 {
   "examples": {
     "example1": {
@@ -271,7 +259,7 @@ exports[`Open API helpers depictIOParamExamples() should depict examples in case
 }
 `;
 
-exports[`Open API helpers depictIOParamExamples() should depict examples in case of response 1`] = `
+exports[`Open API helpers depictIOParamExamples() should transform examples in case of response 1`] = `
 {
   "examples": {
     "example1": {
@@ -284,7 +272,7 @@ exports[`Open API helpers depictIOParamExamples() should depict examples in case
 }
 `;
 
-exports[`Open API helpers depictIntersection() should depict ZodIntersection 1`] = `
+exports[`Open API helpers depictIntersection() should wrap next depicters in allOf property 1`] = `
 {
   "allOf": [
     {
@@ -320,13 +308,11 @@ exports[`Open API helpers depictIntersection() should depict ZodIntersection 1`]
       "type": "object",
     },
   ],
-  "description": "test",
 }
 `;
 
-exports[`Open API helpers depictLiteral() should depict ZodLiteral 1`] = `
+exports[`Open API helpers depictLiteral() should set type and involve enum property 1`] = `
 {
-  "description": "test",
   "enum": [
     "testing",
   ],
@@ -334,58 +320,52 @@ exports[`Open API helpers depictLiteral() should depict ZodLiteral 1`] = `
 }
 `;
 
-exports[`Open API helpers depictNull() should depict ZodNull 1`] = `
+exports[`Open API helpers depictNull() should set type:string format:null and nullable:true props 1`] = `
 {
-  "description": "test",
   "format": "null",
   "nullable": true,
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictNullable() should depict ZodNullable 0 1`] = `
+exports[`Open API helpers depictNullable() should set nullable:true 0 1`] = `
 {
-  "description": "test",
   "nullable": true,
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictNullable() should depict ZodNullable 1 1`] = `
+exports[`Open API helpers depictNullable() should set nullable:true 1 1`] = `
 {
-  "description": "test",
   "nullable": true,
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictNumber() should depict ZodNumber with refinements 1`] = `
+exports[`Open API helpers depictNumber() should type:number, min/max, format and exclusiveness props 1`] = `
 {
-  "description": "test",
   "exclusiveMaximum": false,
   "exclusiveMinimum": false,
-  "format": "int64",
-  "maximum": 20,
-  "minimum": 10,
-  "type": "integer",
+  "format": "double",
+  "maximum": 1.7976931348623157e+308,
+  "minimum": 5e-324,
+  "type": "number",
 }
 `;
 
-exports[`Open API helpers depictNumber() should depict regular ZodNumber 1`] = `
+exports[`Open API helpers depictNumber() should type:number, min/max, format and exclusiveness props 2`] = `
 {
-  "description": "test",
   "exclusiveMaximum": false,
   "exclusiveMinimum": false,
-  "format": "double",
-  "maximum": 1.7976931348623157e+308,
-  "minimum": 5e-324,
-  "type": "number",
+  "format": "int64",
+  "maximum": 20,
+  "minimum": 10,
+  "type": "integer",
 }
 `;
 
-exports[`Open API helpers depictObject() should depict ZodObject 0 1`] = `
+exports[`Open API helpers depictObject() should type:object, properties and required props 0 1`] = `
 {
-  "description": "test",
   "properties": {
     "a": {
       "exclusiveMaximum": false,
@@ -407,9 +387,8 @@ exports[`Open API helpers depictObject() should depict ZodObject 0 1`] = `
 }
 `;
 
-exports[`Open API helpers depictObject() should depict ZodObject 1 1`] = `
+exports[`Open API helpers depictObject() should type:object, properties and required props 1 1`] = `
 {
-  "description": "test",
   "properties": {
     "a": {
       "exclusiveMaximum": false,
@@ -431,9 +410,8 @@ exports[`Open API helpers depictObject() should depict ZodObject 1 1`] = `
 }
 `;
 
-exports[`Open API helpers depictObject() should depict ZodObject 2 1`] = `
+exports[`Open API helpers depictObject() should type:object, properties and required props 2 1`] = `
 {
-  "description": "test",
   "properties": {
     "a": {
       "exclusiveMaximum": false,
@@ -452,7 +430,7 @@ exports[`Open API helpers depictObject() should depict ZodObject 2 1`] = `
 }
 `;
 
-exports[`Open API helpers depictObjectProperties() should depict ZodObject shape 1`] = `
+exports[`Open API helpers depictObjectProperties() should wrap next depicters in a shape of object 1`] = `
 {
   "one": {
     "type": "string",
@@ -463,35 +441,50 @@ exports[`Open API helpers depictObjectProperties() should depict ZodObject shape
 }
 `;
 
-exports[`Open API helpers depictOptional() should depict ZodOptional 0 1`] = `
+exports[`Open API helpers depictOptional() should pass the next depicter 0 1`] = `
 {
-  "description": "test",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictOptional() should depict ZodOptional 1 1`] = `
+exports[`Open API helpers depictOptional() should pass the next depicter 1 1`] = `
 {
-  "description": "test",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictPipeline should depict inner schema depending on IO direction 0 1`] = `
+exports[`Open API helpers depictPipeline should depict as boolean (out) 1`] = `
 {
   "type": "boolean",
 }
 `;
 
-exports[`Open API helpers depictPipeline should depict inner schema depending on IO direction 1 1`] = `
+exports[`Open API helpers depictPipeline should depict as string (in) 1`] = `
 {
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictRecord() should depict ZodRecord with key schema enum 1`] = `
+exports[`Open API helpers depictRecord() should set properties+required or additionalProperties props 1`] = `
+{
+  "additionalProperties": {
+    "type": "boolean",
+  },
+  "type": "object",
+}
+`;
+
+exports[`Open API helpers depictRecord() should set properties+required or additionalProperties props 2`] = `
+{
+  "additionalProperties": {
+    "type": "boolean",
+  },
+  "type": "object",
+}
+`;
+
+exports[`Open API helpers depictRecord() should set properties+required or additionalProperties props 3`] = `
 {
-  "description": "test",
   "properties": {
     "one": {
       "type": "boolean",
@@ -508,9 +501,8 @@ exports[`Open API helpers depictRecord() should depict ZodRecord with key schema
 }
 `;
 
-exports[`Open API helpers depictRecord() should depict ZodRecord with key schema literal 1`] = `
+exports[`Open API helpers depictRecord() should set properties+required or additionalProperties props 4`] = `
 {
-  "description": "test",
   "properties": {
     "testing": {
       "type": "boolean",
@@ -523,19 +515,8 @@ exports[`Open API helpers depictRecord() should depict ZodRecord with key schema
 }
 `;
 
-exports[`Open API helpers depictRecord() should depict ZodRecord with key schema string 1`] = `
+exports[`Open API helpers depictRecord() should set properties+required or additionalProperties props 5`] = `
 {
-  "additionalProperties": {
-    "type": "boolean",
-  },
-  "description": "test",
-  "type": "object",
-}
-`;
-
-exports[`Open API helpers depictRecord() should depict ZodRecord with key schema union of literals 1`] = `
-{
-  "description": "test",
   "properties": {
     "one": {
       "type": "boolean",
@@ -552,16 +533,6 @@ exports[`Open API helpers depictRecord() should depict ZodRecord with key schema
 }
 `;
 
-exports[`Open API helpers depictRecord() should depict classic ZodRecord 1`] = `
-{
-  "additionalProperties": {
-    "type": "boolean",
-  },
-  "description": "test",
-  "type": "object",
-}
-`;
-
 exports[`Open API helpers depictRequestParams() should depict none if both query and params are disabled 1`] = `[]`;
 
 exports[`Open API helpers depictRequestParams() should depict only path params if query is disabled 1`] = `
@@ -787,9 +758,8 @@ exports[`Open API helpers depictSecurityRefs() should populate the scopes 1`] =
 ]
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 0 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 0 1`] = `
 {
-  "description": "test",
   "format": "email",
   "maxLength": 20,
   "minLength": 10,
@@ -797,59 +767,52 @@ exports[`Open API helpers depictString() should depict ZodString with refinement
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 1 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 1 1`] = `
 {
-  "description": "test",
   "format": "url",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 2 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 2 1`] = `
 {
-  "description": "test",
   "format": "uuid",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 3 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 3 1`] = `
 {
-  "description": "test",
   "format": "cuid",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 4 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 4 1`] = `
 {
-  "description": "test",
   "format": "date-time",
   "pattern": "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with refinements 5 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 5 1`] = `
 {
-  "description": "test",
   "format": "date-time",
   "pattern": "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(([+-]\\d{2}:\\d{2})|Z)$/",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict ZodString with regex 1`] = `
+exports[`Open API helpers depictString() should set format, pattern and min/maxLength props 6 1`] = `
 {
-  "description": "test",
   "pattern": "/^\\d+.\\d+.\\d+$/",
   "type": "string",
 }
 `;
 
-exports[`Open API helpers depictString() should depict regular ZodString 1`] = `
+exports[`Open API helpers depictString() should set type:string 1`] = `
 {
-  "description": "test",
   "type": "string",
 }
 `;
@@ -883,9 +846,8 @@ exports[`Open API helpers depictTags() should accept plain descriptions 1`] = `
 ]
 `;
 
-exports[`Open API helpers depictTuple() should depict ZodTuple 1`] = `
+exports[`Open API helpers depictTuple() should set type:array, max- and minLength, oneOf, format:tuple and description 1`] = `
 {
-  "description": "test",
   "items": {
     "description": "0: boolean, 1: string, 2: string",
     "format": "tuple",
@@ -910,9 +872,8 @@ exports[`Open API helpers depictTuple() should depict ZodTuple 1`] = `
 }
 `;
 
-exports[`Open API helpers depictUnion() should depict ZodUnion 1`] = `
+exports[`Open API helpers depictUnion() should wrap next depicters into oneOf property 1`] = `
 {
-  "description": "test",
   "oneOf": [
     {
       "type": "string",
@@ -929,9 +890,8 @@ exports[`Open API helpers depictUnion() should depict ZodUnion 1`] = `
 }
 `;
 
-exports[`Open API helpers depictUpload() should depict ZodUpload 1`] = `
+exports[`Open API helpers depictUpload() should set format:binary and type:string 1`] = `
 {
-  "description": "test",
   "format": "binary",
   "type": "string",
 }
@@ -946,9 +906,23 @@ exports[`Open API helpers excludeExampleFromDepiction() should remove example pr
 }
 `;
 
-exports[`Open API helpers excludeParamsFromDepiction() should handle intersection 1`] = `
+exports[`Open API helpers excludeParamsFromDepiction() should omit specified path params 0 1`] = `
 {
-  "allOf": [
+  "properties": {
+    "b": {
+      "type": "string",
+    },
+  },
+  "required": [
+    "b",
+  ],
+  "type": "object",
+}
+`;
+
+exports[`Open API helpers excludeParamsFromDepiction() should omit specified path params 1 1`] = `
+{
+  "oneOf": [
     {
       "properties": {},
       "required": [],
@@ -969,9 +943,9 @@ exports[`Open API helpers excludeParamsFromDepiction() should handle intersectio
 }
 `;
 
-exports[`Open API helpers excludeParamsFromDepiction() should handle union 1`] = `
+exports[`Open API helpers excludeParamsFromDepiction() should omit specified path params 2 1`] = `
 {
-  "oneOf": [
+  "allOf": [
     {
       "properties": {},
       "required": [],
@@ -992,20 +966,6 @@ exports[`Open API helpers excludeParamsFromDepiction() should handle union 1`] =
 }
 `;
 
-exports[`Open API helpers excludeParamsFromDepiction() should omit specified path params 1`] = `
-{
-  "properties": {
-    "b": {
-      "type": "string",
-    },
-  },
-  "required": [
-    "b",
-  ],
-  "type": "object",
-}
-`;
-
 exports[`Open API helpers extractObjectSchema() Feature #600: Top level refinements should handle refined object schema 1`] = `
 {
   "_type": "ZodObject",
diff --git a/tests/unit/__snapshots__/open-api.spec.ts.snap b/tests/unit/__snapshots__/open-api.spec.ts.snap
index f5335d78..bebcd906 100644
--- a/tests/unit/__snapshots__/open-api.spec.ts.snap
+++ b/tests/unit/__snapshots__/open-api.spec.ts.snap
@@ -457,8 +457,6 @@ paths:
                     enum:
                       - success
                   data:
-                    example:
-                      num: 123
                     type: object
                     properties:
                       num:
@@ -470,6 +468,8 @@ paths:
                         exclusiveMaximum: false
                     required:
                       - num
+                    example:
+                      num: 123
                 required:
                   - status
                   - data
@@ -512,22 +512,22 @@ paths:
             schema:
               description: POST /v1/getSomething request body
               allOf:
-                - example:
-                    key: 1234-56789-01
-                  type: object
+                - type: object
                   properties:
                     key:
                       type: string
                   required:
                     - key
-                - example:
-                    str: test
-                  type: object
+                  example:
+                    key: 1234-56789-01
+                - type: object
                   properties:
                     str:
                       type: string
                   required:
                     - str
+                  example:
+                    str: test
             examples:
               example1:
                 value:
@@ -570,8 +570,6 @@ paths:
                     enum:
                       - success
                   data:
-                    example:
-                      numericStr: "123"
                     type: object
                     properties:
                       numericStr:
@@ -583,6 +581,8 @@ paths:
                         exclusiveMaximum: false
                     required:
                       - numericStr
+                    example:
+                      numericStr: "123"
                 required:
                   - status
                   - data
@@ -666,8 +666,6 @@ paths:
                     enum:
                       - success
                   data:
-                    example:
-                      numericStr: "123"
                     type: object
                     properties:
                       numericStr:
@@ -679,6 +677,8 @@ paths:
                         exclusiveMaximum: false
                     required:
                       - numericStr
+                    example:
+                      numericStr: "123"
                 required:
                   - status
                   - data
@@ -770,13 +770,13 @@ paths:
                     type: object
                     properties:
                       numericStr:
-                        example: "123"
                         type: string
                         format: double
                         minimum: 5e-324
                         exclusiveMinimum: false
                         maximum: 1.7976931348623157e+308
                         exclusiveMaximum: false
+                        example: "123"
                     required:
                       - numericStr
                 required:
@@ -815,8 +815,8 @@ paths:
           required: true
           schema:
             description: GET /v1/getSomething parameter
-            example: "123"
             type: string
+            example: "123"
 components:
   schemas: {}
   responses: {}
@@ -857,13 +857,13 @@ paths:
                     type: object
                     properties:
                       result:
-                        description: some positive integer
                         type: integer
                         format: int64
                         minimum: 0
                         exclusiveMinimum: true
                         maximum: 9007199254740991
                         exclusiveMaximum: false
+                        description: some positive integer
                     required:
                       - result
                 required:
@@ -1246,8 +1246,8 @@ paths:
                             enum:
                               - success
                           data:
-                            nullable: true
                             format: any
+                            nullable: true
                         required:
                           - status
                       - type: object
@@ -1774,9 +1774,6 @@ paths:
                     enum:
                       - success
                   data:
-                    example:
-                      name: John Doe
-                      createdAt: 2021-12-31T00:00:00.000Z
                     type: object
                     properties:
                       name:
@@ -1790,6 +1787,9 @@ paths:
                     required:
                       - name
                       - createdAt
+                    example:
+                      name: John Doe
+                      createdAt: 2021-12-31T00:00:00.000Z
                 required:
                   - status
                   - data
@@ -1847,19 +1847,16 @@ paths:
             schema:
               description: POST /v1/user/:id request body
               allOf:
-                - example:
-                    key: 1234-5678-90
-                  type: object
+                - type: object
                   properties:
                     key:
                       type: string
                       minLength: 1
                   required:
                     - key
-                - example:
-                    name: John Doe
-                    birthday: 1963-04-21
-                  type: object
+                  example:
+                    key: 1234-5678-90
+                - type: object
                   properties:
                     name:
                       type: string
@@ -1874,6 +1871,9 @@ paths:
                   required:
                     - name
                     - birthday
+                  example:
+                    name: John Doe
+                    birthday: 1963-04-21
             examples:
               example1:
                 value:
@@ -1971,8 +1971,8 @@ paths:
                       otherInputs:
                         type: object
                         additionalProperties:
-                          nullable: true
                           format: any
+                          nullable: true
                     required:
                       - name
                       - size
@@ -2077,8 +2077,8 @@ paths:
                     type: object
                     properties:
                       "null":
-                        nullable: true
                         type: string
+                        nullable: true
                         format: "null"
                       dateOut:
                         description: YYYY-MM-DDTHH:mm:ss.sssZ
@@ -2895,8 +2895,8 @@ paths:
                     type: object
                     properties:
                       any:
-                        nullable: true
                         format: any
+                        nullable: true
                     required: []
                 required:
                   - status
@@ -2934,8 +2934,8 @@ paths:
           required: false
           schema:
             description: GET /v1/getSomething parameter
-            nullable: true
             format: any
+            nullable: true
 components:
   schemas: {}
   responses: {}
@@ -2976,8 +2976,8 @@ paths:
                     type: object
                     properties:
                       boolean:
-                        nullable: true
                         type: boolean
+                        nullable: true
                     required: []
                 required:
                   - status
@@ -3015,8 +3015,8 @@ paths:
           required: false
           schema:
             description: GET /v1/getSomething parameter
-            nullable: true
             format: string (preprocessed)
+            nullable: true
         - name: number
           in: query
           required: true
diff --git a/tests/unit/open-api-helpers.spec.ts b/tests/unit/open-api-helpers.spec.ts
index 92e678f0..68483c6b 100644
--- a/tests/unit/open-api-helpers.spec.ts
+++ b/tests/unit/open-api-helpers.spec.ts
@@ -1,3 +1,4 @@
+import { SchemaObject } from "openapi3-ts";
 import { IOSchemaError } from "../../src/errors";
 import {
   OpenAPIError,
@@ -7,6 +8,7 @@ import {
 } from "../../src/index";
 import { getMeta } from "../../src/metadata";
 import {
+  OpenAPIContext,
   depictAny,
   depictArray,
   depictBigInt,
@@ -34,7 +36,6 @@ import {
   depictPipeline,
   depictRecord,
   depictRequestParams,
-  depictSchema,
   depictSecurity,
   depictSecurityRefs,
   depictString,
@@ -42,38 +43,45 @@ import {
   depictTuple,
   depictUnion,
   depictUpload,
+  depicters,
   ensureShortDescription,
   excludeExampleFromDepiction,
   excludeParamsFromDepiction,
   extractObjectSchema,
   hasCoercion,
+  onEach,
+  onMissing,
   reformatParamsInPath,
 } from "../../src/open-api-helpers";
+import { SchemaHandler, walkSchema } from "../../src/schema-walker";
 import { serializeSchemaForTest } from "../helpers";
 
 describe("Open API helpers", () => {
+  const requestContext: OpenAPIContext = { isResponse: false };
+  const responseContext: OpenAPIContext = { isResponse: true };
+  const makeNext =
+    (
+      context: OpenAPIContext
+    ): SchemaHandler<z.ZodTypeAny, SchemaObject, {}, "last"> =>
+    ({ schema }) =>
+      walkSchema({
+        schema,
+        rules: depicters,
+        ...context,
+        onEach,
+        onMissing,
+      });
+
   describe("extractObjectSchema()", () => {
     test("should pass the object schema through", () => {
-      const subject = extractObjectSchema(
-        z.object({
-          one: z.string(),
-        })
-      );
+      const subject = extractObjectSchema(z.object({ one: z.string() }));
       expect(subject).toBeInstanceOf(z.ZodObject);
       expect(serializeSchemaForTest(subject)).toMatchSnapshot();
     });
 
     test("should return object schema for the union of object schemas", () => {
       const subject = extractObjectSchema(
-        z
-          .object({
-            one: z.string(),
-          })
-          .or(
-            z.object({
-              two: z.number(),
-            })
-          )
+        z.object({ one: z.string() }).or(z.object({ two: z.number() }))
       );
       expect(subject).toBeInstanceOf(z.ZodObject);
       expect(serializeSchemaForTest(subject)).toMatchSnapshot();
@@ -81,105 +89,49 @@ describe("Open API helpers", () => {
 
     test("should return object schema for the intersection of object schemas", () => {
       const subject = extractObjectSchema(
-        z
-          .object({
-            one: z.string(),
-          })
-          .and(
-            z.object({
-              two: z.number(),
-            })
-          )
+        z.object({ one: z.string() }).and(z.object({ two: z.number() }))
       );
       expect(subject).toBeInstanceOf(z.ZodObject);
       expect(serializeSchemaForTest(subject)).toMatchSnapshot();
     });
 
     test("should preserve examples", () => {
-      const objectSchema = withMeta(
-        z.object({
-          one: z.string(),
-        })
-      ).example({
+      const objectSchema = withMeta(z.object({ one: z.string() })).example({
         one: "test",
       });
       expect(getMeta(extractObjectSchema(objectSchema), "examples")).toEqual([
-        {
-          one: "test",
-        },
+        { one: "test" },
       ]);
 
       const refinedObjSchema = withMeta(
-        z
-          .object({
-            one: z.string(),
-          })
-          .refine(() => true)
-      ).example({
-        one: "test",
-      });
+        z.object({ one: z.string() }).refine(() => true)
+      ).example({ one: "test" });
       expect(
         getMeta(extractObjectSchema(refinedObjSchema), "examples")
-      ).toEqual([
-        {
-          one: "test",
-        },
-      ]);
+      ).toEqual([{ one: "test" }]);
 
       const unionSchema = withMeta(
-        z
-          .object({
-            one: z.string(),
-          })
-          .or(
-            z.object({
-              two: z.number(),
-            })
-          )
+        z.object({ one: z.string() }).or(z.object({ two: z.number() }))
       )
-        .example({
-          one: "test1",
-        })
-        .example({
-          two: 123,
-        });
+        .example({ one: "test1" })
+        .example({ two: 123 });
       expect(getMeta(extractObjectSchema(unionSchema), "examples")).toEqual([
         { one: "test1" },
         { two: 123 },
       ]);
 
       const intersectionSchema = withMeta(
-        z
-          .object({
-            one: z.string(),
-          })
-          .and(
-            z.object({
-              two: z.number(),
-            })
-          )
-      ).example({
-        one: "test1",
-        two: 123,
-      });
+        z.object({ one: z.string() }).and(z.object({ two: z.number() }))
+      ).example({ one: "test1", two: 123 });
       expect(
         getMeta(extractObjectSchema(intersectionSchema), "examples")
-      ).toEqual([
-        {
-          one: "test1",
-          two: 123,
-        },
-      ]);
+      ).toEqual([{ one: "test1", two: 123 }]);
     });
 
     describe("Feature #600: Top level refinements", () => {
       test("should handle refined object schema", () => {
         const subject = extractObjectSchema(
-          z
-            .object({
-              one: z.string(),
-            })
-            .refine(() => true)
+          z.object({ one: z.string() }).refine(() => true)
         );
         expect(subject).toBeInstanceOf(z.ZodObject);
         expect(serializeSchemaForTest(subject)).toMatchSnapshot();
@@ -187,13 +139,7 @@ describe("Open API helpers", () => {
 
       test("should throw when using transformation", () => {
         expect(() =>
-          extractObjectSchema(
-            z
-              .object({
-                one: z.string(),
-              })
-              .transform(() => [])
-          )
+          extractObjectSchema(z.object({ one: z.string() }).transform(() => []))
         ).toThrowError(
           new IOSchemaError(
             "Using transformations on the top level of input schema is not allowed."
@@ -204,45 +150,17 @@ describe("Open API helpers", () => {
   });
 
   describe("excludeParamsFromDepiction()", () => {
-    test("should omit specified path params", () => {
-      const depicted = depictSchema({
-        schema: z.object({
-          a: z.string(),
-          b: z.string(),
-        }),
-        isResponse: false,
-      });
-      expect(excludeParamsFromDepiction(depicted, ["a"])).toMatchSnapshot();
-    });
-
-    test("should handle union", () => {
-      const depicted = depictSchema({
-        schema: z
-          .object({
-            a: z.string(),
-          })
-          .or(
-            z.object({
-              b: z.string(),
-            })
-          ),
-        isResponse: false,
-      });
-      expect(excludeParamsFromDepiction(depicted, ["a"])).toMatchSnapshot();
-    });
-
-    test("should handle intersection", () => {
-      const depicted = depictSchema({
-        schema: z
-          .object({
-            a: z.string(),
-          })
-          .and(
-            z.object({
-              b: z.string(),
-            })
-          ),
-        isResponse: false,
+    test.each<z.ZodTypeAny>([
+      z.object({ a: z.string(), b: z.string() }),
+      z.object({ a: z.string() }).or(z.object({ b: z.string() })),
+      z.object({ a: z.string() }).and(z.object({ b: z.string() })),
+    ])("should omit specified path params %#", (schema) => {
+      const depicted = walkSchema({
+        schema,
+        ...requestContext,
+        onEach,
+        rules: depicters,
+        onMissing,
       });
       expect(excludeParamsFromDepiction(depicted, ["a"])).toMatchSnapshot();
     });
@@ -262,48 +180,48 @@ describe("Open API helpers", () => {
   });
 
   describe("depictDefault()", () => {
-    test("should depict ZodDefault", () => {
+    test("should set default property", () => {
       expect(
         depictDefault({
           schema: z.boolean().default(true),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictCatch()", () => {
-    test("should depict ZodCatch", () => {
+    test("should pass next depicter", () => {
       expect(
         depictCatch({
           schema: z.boolean().catch(true),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictAny()", () => {
-    test("should depict ZodAny", () => {
+    test("should set format:any", () => {
       expect(
         depictAny({
           schema: z.any(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictUpload()", () => {
-    test("should depict ZodUpload", () => {
+    test("should set format:binary and type:string", () => {
       expect(
         depictUpload({
           schema: z.upload(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
@@ -311,8 +229,8 @@ describe("Open API helpers", () => {
       try {
         depictUpload({
           schema: z.upload(),
-          isResponse: true,
-          initial: { description: "test" },
+          ...responseContext,
+          next: makeNext(responseContext),
         });
         fail("Should not be here");
       } catch (e) {
@@ -324,13 +242,13 @@ describe("Open API helpers", () => {
 
   describe("depictFile()", () => {
     test.each([z.file(), z.file().binary(), z.file().base64()])(
-      "should depict ZodFile %#",
+      "should set type:string and format accordingly %#",
       (schema) => {
         expect(
           depictFile({
             schema,
-            isResponse: true,
-            initial: { description: "test" },
+            ...responseContext,
+            next: makeNext(responseContext),
           })
         ).toMatchSnapshot();
       }
@@ -339,8 +257,8 @@ describe("Open API helpers", () => {
       try {
         depictFile({
           schema: z.file().binary(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         });
         fail("Should not be here");
       } catch (e) {
@@ -351,19 +269,19 @@ describe("Open API helpers", () => {
   });
 
   describe("depictUnion()", () => {
-    test("should depict ZodUnion", () => {
+    test("should wrap next depicters into oneOf property", () => {
       expect(
         depictUnion({
           schema: z.string().or(z.number()),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictDiscriminatedUnion()", () => {
-    test("should depict ZodDiscriminatedUnion", () => {
+    test("should wrap next depicters in oneOf prop and set discriminator prop", () => {
       expect(
         depictDiscriminatedUnion({
           schema: z.discriminatedUnion("status", [
@@ -373,22 +291,22 @@ describe("Open API helpers", () => {
               error: z.object({ message: z.string() }),
             }),
           ]),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictIntersection()", () => {
-    test("should depict ZodIntersection", () => {
+    test("should wrap next depicters in allOf property", () => {
       expect(
         depictIntersection({
           schema: z
             .object({ one: z.number() })
             .and(z.object({ two: z.number() })),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
@@ -409,14 +327,14 @@ describe("Open API helpers", () => {
   });
 
   describe("depictOptional()", () => {
-    test.each([{ isResponse: false }, { isResponse: true }])(
-      "should depict ZodOptional %#",
-      ({ isResponse }) => {
+    test.each<OpenAPIContext>([{ isResponse: false }, { isResponse: true }])(
+      "should pass the next depicter %#",
+      (context) => {
         expect(
           depictOptional({
             schema: z.string().optional(),
-            isResponse,
-            initial: { description: "test" },
+            ...context,
+            next: makeNext(context),
           })
         ).toMatchSnapshot();
       }
@@ -424,14 +342,14 @@ describe("Open API helpers", () => {
   });
 
   describe("depictNullable()", () => {
-    test.each([{ isResponse: false }, { isResponse: true }])(
-      "should depict ZodNullable %#",
-      ({ isResponse }) => {
+    test.each<OpenAPIContext>([{ isResponse: false }, { isResponse: true }])(
+      "should set nullable:true %#",
+      (context) => {
         expect(
           depictNullable({
             schema: z.string().nullable(),
-            isResponse,
-            initial: { description: "test" },
+            ...context,
+            next: makeNext(context),
           })
         ).toMatchSnapshot();
       }
@@ -439,46 +357,38 @@ describe("Open API helpers", () => {
   });
 
   describe("depictEnum()", () => {
-    test("should depict ZodEnum", () => {
-      expect(
-        depictEnum({
-          schema: z.enum(["one", "two"]),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodNativeEnum", () => {
-      enum Test {
-        one = "ONE",
-        two = "TWO",
+    enum Test {
+      one = "ONE",
+      two = "TWO",
+    }
+    test.each([z.enum(["one", "two"]), z.nativeEnum(Test)])(
+      "should set type and enum properties",
+      (schema) => {
+        expect(
+          depictEnum({
+            schema,
+            ...requestContext,
+            next: makeNext(requestContext),
+          })
+        ).toMatchSnapshot();
       }
-
-      expect(
-        depictEnum({
-          schema: z.nativeEnum(Test),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
+    );
   });
 
   describe("depictLiteral()", () => {
-    test("should depict ZodLiteral", () => {
+    test("should set type and involve enum property", () => {
       expect(
         depictLiteral({
           schema: z.literal("testing"),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictObject()", () => {
-    test.each([
+    test.each<OpenAPIContext & { shape: z.ZodRawShape }>([
       {
         isResponse: false,
         shape: { a: z.number(), b: z.string() },
@@ -491,136 +401,108 @@ describe("Open API helpers", () => {
         isResponse: true,
         shape: { a: z.coerce.number(), b: z.string({ coerce: true }) },
       },
-    ])("should depict ZodObject %#", ({ isResponse, shape }) => {
-      expect(
-        depictObject({
-          schema: z.object(shape),
-          isResponse,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
+    ])(
+      "should type:object, properties and required props %#",
+      ({ shape, ...context }) => {
+        expect(
+          depictObject({
+            schema: z.object(shape),
+            ...context,
+            next: makeNext(context),
+          })
+        ).toMatchSnapshot();
+      }
+    );
   });
 
   describe("depictNull()", () => {
-    test("should depict ZodNull", () => {
+    test("should set type:string format:null and nullable:true props", () => {
       expect(
         depictNull({
           schema: z.null(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictBoolean()", () => {
-    test("should depict ZodBoolean", () => {
+    test("should set type:boolean", () => {
       expect(
         depictBoolean({
           schema: z.boolean(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictBigInt()", () => {
-    test("should depict ZodBigInt", () => {
+    test("should set type:integer and format:bigint", () => {
       expect(
         depictBigInt({
           schema: z.bigint(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictRecord()", () => {
-    test("should depict classic ZodRecord", () => {
-      expect(
-        depictRecord({
-          schema: z.record(z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodRecord with key schema string", () => {
-      expect(
-        depictRecord({
-          schema: z.record(z.string(), z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodRecord with key schema enum", () => {
-      expect(
-        depictRecord({
-          schema: z.record(z.enum(["one", "two"]), z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodRecord with key schema literal", () => {
-      expect(
-        depictRecord({
-          schema: z.record(z.literal("testing"), z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodRecord with key schema union of literals", () => {
-      expect(
-        depictRecord({
-          schema: z.record(z.literal("one").or(z.literal("two")), z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
+    test.each([
+      z.record(z.boolean()),
+      z.record(z.string(), z.boolean()),
+      z.record(z.enum(["one", "two"]), z.boolean()),
+      z.record(z.literal("testing"), z.boolean()),
+      z.record(z.literal("one").or(z.literal("two")), z.boolean()),
+    ])(
+      "should set properties+required or additionalProperties props",
+      (schema) => {
+        expect(
+          depictRecord({
+            schema,
+            ...requestContext,
+            next: makeNext(requestContext),
+          })
+        ).toMatchSnapshot();
+      }
+    );
   });
 
   describe("depictArray()", () => {
-    test("should depict ZodArray", () => {
+    test("should set type:array and pass items depiction", () => {
       expect(
         depictArray({
           schema: z.array(z.boolean()),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictTuple()", () => {
-    test("should depict ZodTuple", () => {
+    test("should set type:array, max- and minLength, oneOf, format:tuple and description", () => {
       expect(
         depictTuple({
           schema: z.tuple([z.boolean(), z.string(), z.literal("test")]),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictString()", () => {
-    test("should depict regular ZodString", () => {
+    test("should set type:string", () => {
       expect(
         depictString({
           schema: z.string(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
@@ -632,146 +514,105 @@ describe("Open API helpers", () => {
       z.string().cuid(),
       z.string().datetime(),
       z.string().datetime({ offset: true }),
-    ])("should depict ZodString with refinements %#", (schema) => {
+      z.string().regex(/^\d+.\d+.\d+$/),
+    ])("should set format, pattern and min/maxLength props %#", (schema) => {
       expect(
         depictString({
           schema,
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodString with regex", () => {
-      expect(
-        depictString({
-          schema: z.string().regex(/^\d+.\d+.\d+$/),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictNumber()", () => {
-    test("should depict regular ZodNumber", () => {
-      expect(
-        depictNumber({
-          schema: z.number(),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodNumber with refinements", () => {
-      expect(
-        depictNumber({
-          schema: z.number().int().min(10).max(20),
-          isResponse: false,
-          initial: { description: "test" },
-        })
-      ).toMatchSnapshot();
-    });
+    test.each([z.number(), z.number().int().min(10).max(20)])(
+      "should type:number, min/max, format and exclusiveness props",
+      (schema) => {
+        expect(
+          depictNumber({
+            schema,
+            ...requestContext,
+            next: makeNext(requestContext),
+          })
+        ).toMatchSnapshot();
+      }
+    );
   });
 
   describe("depictObjectProperties()", () => {
-    test("should depict ZodObject shape", () => {
+    test("should wrap next depicters in a shape of object", () => {
       expect(
         depictObjectProperties({
           schema: z.object({
             one: z.string(),
             two: z.boolean(),
           }),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictEffect()", () => {
-    test("should depict ZodEffects transformation in case of response", () => {
-      expect(
-        depictEffect({
+    test.each<OpenAPIContext & { schema: z.ZodEffects<any>; expected: string }>(
+      [
+        {
           schema: z.string().transform((v) => parseInt(v, 10)),
           isResponse: true,
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodEffects transformation in case of request", () => {
-      expect(
-        depictEffect({
+          expected: "number (out)",
+        },
+        {
           schema: z.string().transform((v) => parseInt(v, 10)),
           isResponse: false,
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict ZodEffects preprocess in case of request", () => {
-      expect(
-        depictEffect({
+          expected: "string (in)",
+        },
+        {
           schema: z.preprocess((v) => parseInt(`${v}`, 10), z.string()),
           isResponse: false,
-        })
-      ).toMatchSnapshot();
-    });
-
-    test("should depict refinements", () => {
-      expect(
-        depictEffect({
+          expected: "string (preprocess)",
+        },
+        {
           schema: z
             .object({ s: z.string() })
             .refine(() => false, { message: "test" }),
           isResponse: false,
+          expected: "object (refinement)",
+        },
+      ]
+    )("should depict as $expected", ({ schema, ...context }) => {
+      expect(
+        depictEffect({
+          schema,
+          ...context,
+          next: makeNext(context),
         })
       ).toMatchSnapshot();
     });
   });
 
   describe("depictPipeline", () => {
-    test.each([{ isResponse: true }, { isResponse: false }])(
-      "should depict inner schema depending on IO direction %#",
-      ({ isResponse }) => {
-        expect(
-          depictPipeline({
-            isResponse,
-            schema: z.string().pipe(z.coerce.boolean()),
-          })
-        ).toMatchSnapshot();
-      }
-    );
-  });
-
-  describe("depictIOExamples()", () => {
-    test("should depict examples in case of request", () => {
+    test.each<OpenAPIContext & { expected: string }>([
+      { isResponse: true, expected: "boolean (out)" },
+      { isResponse: false, expected: "string (in)" },
+    ])("should depict as $expected", (context) => {
       expect(
-        depictIOExamples(
-          withMeta(
-            z.object({
-              one: z.string().transform((v) => v.length),
-              two: z.number().transform((v) => `${v}`),
-              three: z.boolean(),
-            })
-          )
-            .example({
-              one: "test",
-              two: 123,
-              three: true,
-            })
-            .example({
-              one: "test2",
-              two: 456,
-              three: false,
-            }),
-          false,
-          ["three"]
-        )
+        depictPipeline({
+          schema: z.string().pipe(z.coerce.boolean()),
+          ...context,
+          next: makeNext(context),
+        })
       ).toMatchSnapshot();
     });
+  });
 
-    test("should depict examples in case of response", () => {
+  describe("depictIOExamples()", () => {
+    test.each<OpenAPIContext & Record<"case" | "action", string>>([
+      { isResponse: false, case: "request", action: "pass" },
+      { isResponse: true, case: "response", action: "transform" },
+    ])("should $action examples in case of $case", ({ isResponse }) => {
       expect(
         depictIOExamples(
           withMeta(
@@ -791,7 +632,7 @@ describe("Open API helpers", () => {
               two: 456,
               three: false,
             }),
-          true,
+          isResponse,
           ["three"]
         )
       ).toMatchSnapshot();
@@ -799,7 +640,10 @@ describe("Open API helpers", () => {
   });
 
   describe("depictIOParamExamples()", () => {
-    test("should depict examples in case of request", () => {
+    test.each<OpenAPIContext & Record<"case" | "action", string>>([
+      { isResponse: false, case: "request", action: "pass" },
+      { isResponse: true, case: "response", action: "transform" },
+    ])("should $action examples in case of $case", ({ isResponse }) => {
       expect(
         depictIOParamExamples(
           withMeta(
@@ -819,33 +663,7 @@ describe("Open API helpers", () => {
               two: 456,
               three: false,
             }),
-          false,
-          "two"
-        )
-      ).toMatchSnapshot();
-    });
-
-    test("should depict examples in case of response", () => {
-      expect(
-        depictIOParamExamples(
-          withMeta(
-            z.object({
-              one: z.string().transform((v) => v.length),
-              two: z.number().transform((v) => `${v}`),
-              three: z.boolean(),
-            })
-          )
-            .example({
-              one: "test",
-              two: 123,
-              three: true,
-            })
-            .example({
-              one: "test2",
-              two: 456,
-              three: false,
-            }),
-          true,
+          isResponse,
           "two"
         )
       ).toMatchSnapshot();
@@ -924,12 +742,12 @@ describe("Open API helpers", () => {
   });
 
   describe("depictDateIn", () => {
-    test("should depict ZodDateIn", () => {
+    test("should set type:string, pattern and format", () => {
       expect(
         depictDateIn({
           schema: z.dateIn(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         })
       ).toMatchSnapshot();
     });
@@ -937,8 +755,8 @@ describe("Open API helpers", () => {
       try {
         depictDateIn({
           schema: z.dateIn(),
-          isResponse: true,
-          initial: { description: "test" },
+          ...responseContext,
+          next: makeNext(responseContext),
         });
         fail("should not be here");
       } catch (e) {
@@ -949,12 +767,12 @@ describe("Open API helpers", () => {
   });
 
   describe("depictDateOut", () => {
-    test("should depict ZodDateOut", () => {
+    test("should set type:string, description and format", () => {
       expect(
         depictDateOut({
           schema: z.dateOut(),
-          isResponse: true,
-          initial: { description: "test" },
+          ...responseContext,
+          next: makeNext(responseContext),
         })
       ).toMatchSnapshot();
     });
@@ -962,8 +780,8 @@ describe("Open API helpers", () => {
       try {
         depictDateOut({
           schema: z.dateOut(),
-          isResponse: false,
-          initial: { description: "test" },
+          ...requestContext,
+          next: makeNext(requestContext),
         });
         fail("should not be here");
       } catch (e) {
@@ -974,30 +792,31 @@ describe("Open API helpers", () => {
   });
 
   describe("depictDate", () => {
-    test("should throw clear error", () => {
-      try {
-        depictDate({ isResponse: true, schema: z.date() });
-        fail("should not be here");
-      } catch (e) {
-        expect(e).toBeInstanceOf(OpenAPIError);
-        expect(e).toMatchSnapshot();
+    test.each<OpenAPIContext>([{ isResponse: true }, { isResponse: false }])(
+      "should throw clear error %#",
+      (context) => {
+        try {
+          depictDate({
+            schema: z.date(),
+            ...context,
+            next: makeNext(context),
+          });
+          fail("should not be here");
+        } catch (e) {
+          expect(e).toBeInstanceOf(OpenAPIError);
+          expect(e).toMatchSnapshot();
+        }
       }
-      try {
-        depictDate({ isResponse: false, schema: z.date() });
-        fail("should not be here");
-      } catch (e) {
-        expect(e).toBeInstanceOf(OpenAPIError);
-        expect(e).toMatchSnapshot();
-      }
-    });
+    );
   });
 
   describe("depictBranded", () => {
-    test("should depict the actual schema", () => {
+    test("should pass the next depicter", () => {
       expect(
         depictBranded({
           schema: z.string().min(2).brand<"Test">(),
-          isResponse: true,
+          ...responseContext,
+          next: makeNext(responseContext),
         })
       ).toMatchSnapshot();
     });