Skip to content

Commit

Permalink
Merge pull request #217 from asteasolutions/refactor-into-classes
Browse files Browse the repository at this point in the history
Refactor into classes
  • Loading branch information
AGalabov committed Apr 1, 2024
2 parents fedd118 + 201b6b1 commit 2fff075
Show file tree
Hide file tree
Showing 19 changed files with 1,044 additions and 777 deletions.
17 changes: 17 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ export class MissingParameterDataError extends ZodToOpenAPIError {
}
}

export function enhanceMissingParametersError<T>(
action: () => T,
paramsToAdd: Partial<MissingParameterDataErrorProps>
) {
try {
return action();
} catch (error) {
if (error instanceof MissingParameterDataError) {
throw new MissingParameterDataError({
...error.data,
...paramsToAdd,
});
}
throw error;
}
}

interface UnknownZodTypeErrorProps {
schemaName?: string;
currentSchema: any;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/zod-is-type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { z } from 'zod';

type ZodTypes = {
export type ZodTypes = {
ZodAny: z.ZodAny;
ZodArray: z.ZodArray<any>;
ZodBigInt: z.ZodBigInt;
Expand Down
160 changes: 160 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { ZodType, ZodTypeAny } from 'zod';
import { ZodTypes, isZodType } from './lib/zod-is-type';
import { ZodOpenAPIMetadata, ZodOpenApiFullMetadata } from './zod-extensions';
import { isNil, omit, omitBy } from './lib/lodash';
import { ParameterObject, ReferenceObject, SchemaObject } from './types';

export class Metadata {
static getMetadata<T extends any>(
zodSchema: ZodType<T>
): ZodOpenApiFullMetadata<T> | undefined {
const innerSchema = this.unwrapChained(zodSchema);

const metadata = zodSchema._def.openapi
? zodSchema._def.openapi
: innerSchema._def.openapi;

/**
* Every zod schema can receive a `description` by using the .describe method.
* That description should be used when generating an OpenApi schema.
* The `??` bellow makes sure we can handle both:
* - schema.describe('Test').optional()
* - schema.optional().describe('Test')
*/
const zodDescription = zodSchema.description ?? innerSchema.description;

// A description provided from .openapi() should be taken with higher precedence
return {
_internal: metadata?._internal,
metadata: {
description: zodDescription,
...metadata?.metadata,
},
};
}

static getInternalMetadata<T extends any>(zodSchema: ZodType<T>) {
const innerSchema = this.unwrapChained(zodSchema);
const openapi = zodSchema._def.openapi
? zodSchema._def.openapi
: innerSchema._def.openapi;

return openapi?._internal;
}

static getParamMetadata<T extends any>(
zodSchema: ZodType<T>
): ZodOpenApiFullMetadata<T> | undefined {
const innerSchema = this.unwrapChained(zodSchema);

const metadata = zodSchema._def.openapi
? zodSchema._def.openapi
: innerSchema._def.openapi;

/**
* Every zod schema can receive a `description` by using the .describe method.
* That description should be used when generating an OpenApi schema.
* The `??` bellow makes sure we can handle both:
* - schema.describe('Test').optional()
* - schema.optional().describe('Test')
*/
const zodDescription = zodSchema.description ?? innerSchema.description;

return {
_internal: metadata?._internal,
metadata: {
...metadata?.metadata,
// A description provided from .openapi() should be taken with higher precedence
param: {
description: zodDescription,
...metadata?.metadata?.param,
},
},
};
}

/**
* A method that omits all custom keys added to the regular OpenAPI
* metadata properties
*/
static buildSchemaMetadata(metadata: ZodOpenAPIMetadata) {
return omitBy(omit(metadata, ['param']), isNil);
}

static buildParameterMetadata(
metadata: Required<ZodOpenAPIMetadata>['param']
) {
return omitBy(metadata, isNil);
}

static applySchemaMetadata(
initialData: SchemaObject | ParameterObject | ReferenceObject,
metadata: Partial<ZodOpenAPIMetadata>
): SchemaObject | ReferenceObject {
return omitBy(
{
...initialData,
...this.buildSchemaMetadata(metadata),
},
isNil
);
}

static getRefId<T extends any>(zodSchema: ZodType<T>) {
return this.getInternalMetadata(zodSchema)?.refId;
}

static unwrapChained(schema: ZodType): ZodType {
return this.unwrapUntil(schema);
}

static getDefaultValue<T>(zodSchema: ZodTypeAny): T | undefined {
const unwrapped = this.unwrapUntil(zodSchema, 'ZodDefault');

return unwrapped?._def.defaultValue();
}

private static unwrapUntil(schema: ZodType): ZodType;
private static unwrapUntil<TypeName extends keyof ZodTypes>(
schema: ZodType,
typeName: TypeName | undefined
): ZodTypes[TypeName] | undefined;
private static unwrapUntil<TypeName extends keyof ZodTypes>(
schema: ZodType,
typeName?: TypeName
): ZodType | undefined {
if (typeName && isZodType(schema, typeName)) {
return schema;
}

if (
isZodType(schema, 'ZodOptional') ||
isZodType(schema, 'ZodNullable') ||
isZodType(schema, 'ZodBranded')
) {
return this.unwrapUntil(schema.unwrap(), typeName);
}

if (isZodType(schema, 'ZodDefault') || isZodType(schema, 'ZodReadonly')) {
return this.unwrapUntil(schema._def.innerType, typeName);
}

if (isZodType(schema, 'ZodEffects')) {
return this.unwrapUntil(schema._def.schema, typeName);
}

if (isZodType(schema, 'ZodPipeline')) {
return this.unwrapUntil(schema._def.in, typeName);
}

return typeName ? undefined : schema;
}

static isOptionalSchema(zodSchema: ZodTypeAny): boolean {
if (isZodType(zodSchema, 'ZodEffects')) {
return this.isOptionalSchema(zodSchema._def.schema);
}

return zodSchema.isOptional();
}
}
Loading

0 comments on commit 2fff075

Please sign in to comment.