Skip to content

Commit

Permalink
🐛 Refactor ref-handling, main API types and fix definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanTerdell committed Dec 12, 2022
1 parent 87fe261 commit 9a4a59a
Show file tree
Hide file tree
Showing 38 changed files with 428 additions and 472 deletions.
36 changes: 36 additions & 0 deletions src/Options.ts
@@ -0,0 +1,36 @@
import { ZodSchema } from "zod";

export type Options = {
name: string | undefined;
$refStrategy: "root" | "relative" | "none";
basePath: string[];
effectStrategy: "input" | "any";
target: "jsonSchema7" | "openApi3";
strictUnions: boolean;
definitionPath: string;
definitions: Record<string, ZodSchema>;
};

export const defaultOptions: Options = {
name: undefined,
$refStrategy: "root",
basePath: ["#"],
effectStrategy: "input",
definitionPath: "definitions",
target: "jsonSchema7",
strictUnions: false,
definitions: {},
};

export const getDefaultOptions = (
options: Partial<Options> | string | undefined
): Options =>
typeof options === "string"
? {
...defaultOptions,
name: options,
}
: {
...defaultOptions,
...options,
};
62 changes: 0 additions & 62 deletions src/References.ts

This file was deleted.

29 changes: 29 additions & 0 deletions src/Refs.ts
@@ -0,0 +1,29 @@
import { ZodTypeDef } from "zod";
import { getDefaultOptions, Options } from "./Options";
import { JsonSchema7Type } from "./parseDef";

export type Refs = {
seen: Seen[];
currentPath: string[];
propertyPath: string[] | undefined;
} & Options;

export type Seen = {
def: ZodTypeDef;
path: string[];
jsonSchema: JsonSchema7Type | undefined;
};

export const getRefs = (options?: string | Partial<Options>): Refs => {
const _options = getDefaultOptions(options);
const currentPath =
_options.name !== undefined
? [..._options.basePath, _options.definitionPath, _options.name]
: _options.basePath;
return {
..._options,
currentPath: currentPath,
propertyPath: undefined,
seen: [],
};
};
93 changes: 16 additions & 77 deletions src/parseDef.ts
@@ -1,4 +1,4 @@
import { ZodFirstPartyTypeKind, ZodSchema, ZodTypeDef } from "zod";
import { ZodFirstPartyTypeKind, ZodTypeDef } from "zod";
import { JsonSchema7AnyType, parseAnyDef } from "./parsers/any";
import { JsonSchema7ArrayType, parseArrayDef } from "./parsers/array";
import { JsonSchema7BigintType, parseBigintDef } from "./parsers/bigint";
Expand Down Expand Up @@ -35,9 +35,10 @@ import {
} from "./parsers/undefined";
import { JsonSchema7UnionType, parseUnionDef } from "./parsers/union";
import { JsonSchema7UnknownType, parseUnknownDef } from "./parsers/unknown";
import { Item, References } from "./References";
import { Refs, Seen } from "./refs";

type JsonSchema7RefType = { $ref: string };
type JsonSchema7Meta = { default?: any; description?: string };

export type JsonSchema7Type = (
| JsonSchema7StringType
Expand All @@ -64,51 +65,37 @@ export type JsonSchema7Type = (
| JsonSchema7AllOfType
| JsonSchema7UnknownType
| JsonSchema7SetType
) & { default?: any; description?: string }
) &
JsonSchema7Meta;

export function parseDef(
def: ZodTypeDef,
refs: References,
preloadDefinitions?: {
definitions: Record<string, ZodSchema<any>>;
definitionsPath: string;
basePath: string[];
}
refs: Refs
): JsonSchema7Type | undefined {
const definitionSchemas = getPreloadedDefinitionSchemas(
refs,
preloadDefinitions
);

const seenItem = refs.items.find((x) => Object.is(x.def, def));
const seenItem = refs.seen.find((x) => Object.is(x.def, def));

if (seenItem) {
return select$refStrategy(seenItem, refs);
return get$ref(seenItem, refs);
}

const newItem: Item = { def, path: refs.currentPath, jsonSchema: undefined };
const newItem: Seen = { def, path: refs.currentPath, jsonSchema: undefined };

refs.items.push(newItem);
refs.seen.push(newItem);

const jsonSchema = selectParser(def, (def as any).typeName, refs);

if (jsonSchema) {
addMeta(def, jsonSchema);
addDefinitionSchemas(
jsonSchema,
definitionSchemas,
preloadDefinitions?.definitionsPath
);
}

newItem.jsonSchema = jsonSchema;

return jsonSchema;
}

const select$refStrategy = (
item: Item,
refs: References
const get$ref = (
item: Seen,
refs: Refs
):
| {
$ref: string;
Expand All @@ -126,7 +113,7 @@ const select$refStrategy = (
: item.path.join("/"),
};
case "relative":
return { $ref: makeRelativePath(refs.currentPath, item.path) };
return { $ref: getRelativePath(refs.currentPath, item.path) };
case "none": {
if (
item.path.length < refs.currentPath.length &&
Expand All @@ -145,7 +132,7 @@ const select$refStrategy = (
}
};

const makeRelativePath = (pathA: string[], pathB: string[]) => {
const getRelativePath = (pathA: string[], pathB: string[]) => {
let i = 0;
for (; i < pathA.length && i < pathB.length; i++) {
if (pathA[i] !== pathB[i]) break;
Expand All @@ -156,7 +143,7 @@ const makeRelativePath = (pathA: string[], pathB: string[]) => {
const selectParser = (
def: any,
typeName: ZodFirstPartyTypeKind,
refs: References
refs: Refs
): JsonSchema7Type | undefined => {
switch (typeName) {
case ZodFirstPartyTypeKind.ZodString:
Expand Down Expand Up @@ -232,51 +219,3 @@ const addMeta = (
if (def.description) jsonSchema.description = def.description;
return jsonSchema;
};

const getPreloadedDefinitionSchemas = (
refs: References,
options:
| {
basePath: string[];
definitionsPath: string;
definitions: Record<string, ZodSchema<any>>;
}
| undefined
) =>
options
? Object.entries(options.definitions).reduce(
(acc: Record<string, JsonSchema7Type>, [key, schema]) => {
const jsonSchema = selectParser(
schema._def,
(schema._def as any).typeName,
refs
);

if (jsonSchema) {
refs.items.push({
def: schema._def,
path: [...options.basePath, options.definitionsPath, key],
jsonSchema,
});

acc[key] = jsonSchema;
}

return acc;
},
{}
)
: undefined;

const addDefinitionSchemas = (
jsonSchema: JsonSchema7Type,
definitionSchemas: Record<string, JsonSchema7Type> | undefined,
definitionsPath: string | undefined
) => {
if (definitionSchemas && definitionsPath !== undefined) {
(jsonSchema as any)[definitionsPath] = {
...definitionSchemas,
...(jsonSchema as any)[definitionsPath],
};
}
};
9 changes: 6 additions & 3 deletions src/parsers/array.ts
@@ -1,6 +1,6 @@
import { ZodArrayDef, ZodFirstPartyTypeKind } from "zod";
import { JsonSchema7Type, parseDef } from "../parseDef";
import { References } from "../References";
import { Refs } from "../refs";

export type JsonSchema7ArrayType = {
type: "array";
Expand All @@ -9,12 +9,15 @@ export type JsonSchema7ArrayType = {
maxItems?: number;
};

export function parseArrayDef(def: ZodArrayDef, refs: References) {
export function parseArrayDef(def: ZodArrayDef, refs: Refs) {
const res: JsonSchema7ArrayType = {
type: "array",
};
if (def.type?._def?.typeName !== ZodFirstPartyTypeKind.ZodAny) {
res.items = parseDef(def.type._def, refs.addToPath("items"));
res.items = parseDef(def.type._def, {
...refs,
currentPath: [...refs.currentPath, "items"],
});
}
if (def.minLength) {
res.minItems = def.minLength.value;
Expand Down
9 changes: 3 additions & 6 deletions src/parsers/branded.ts
@@ -1,10 +1,7 @@
import { ZodBrandedDef } from "zod";
import { parseDef } from "../parseDef";
import { References } from "../References";
import { Refs } from "../refs";

export function parseBrandedDef(
_def: ZodBrandedDef<any>,
refs: References
) {
return parseDef(_def.type._def, refs)
export function parseBrandedDef(_def: ZodBrandedDef<any>, refs: Refs) {
return parseDef(_def.type._def, refs);
}
4 changes: 2 additions & 2 deletions src/parsers/default.ts
@@ -1,10 +1,10 @@
import { ZodDefaultDef } from "zod";
import { JsonSchema7Type, parseDef } from "../parseDef";
import { References } from "../References";
import { Refs } from "../refs";

export function parseDefaultDef(
_def: ZodDefaultDef,
refs: References
refs: Refs
): JsonSchema7Type & { default: any } {
return {
...parseDef(_def.innerType._def, refs),
Expand Down
4 changes: 2 additions & 2 deletions src/parsers/effects.ts
@@ -1,10 +1,10 @@
import { ZodEffectsDef } from "zod";
import { JsonSchema7Type, parseDef } from "../parseDef";
import { References } from "../References";
import { Refs } from "../refs";

export function parseEffectsDef(
_def: ZodEffectsDef,
refs: References
refs: Refs
): JsonSchema7Type | undefined {
return refs.effectStrategy === "input"
? parseDef(_def.schema._def, refs)
Expand Down
14 changes: 10 additions & 4 deletions src/parsers/intersection.ts
@@ -1,18 +1,24 @@
import { ZodIntersectionDef } from "zod";
import { JsonSchema7Type, parseDef } from "../parseDef";
import { References } from "../References";
import { Refs } from "../refs";

export type JsonSchema7AllOfType = {
allOf: JsonSchema7Type[];
};

export function parseIntersectionDef(
def: ZodIntersectionDef,
refs: References
refs: Refs
): JsonSchema7AllOfType | JsonSchema7Type | undefined {
const allOf = [
parseDef(def.left._def, refs.addToPath("allOf", "0")),
parseDef(def.right._def, refs.addToPath("allOf", "1")),
parseDef(def.left._def, {
...refs,
currentPath: [...refs.currentPath, "allOf", "0"],
}),
parseDef(def.right._def, {
...refs,
currentPath: [...refs.currentPath, "allOf", "1"],
}),
].filter((x): x is JsonSchema7Type => !!x);

return allOf.length ? { allOf } : undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/parsers/literal.ts
@@ -1,5 +1,5 @@
import { ZodLiteralDef } from "zod";
import { References } from "../References";
import { Refs } from "../refs";

export type JsonSchema7LiteralType =
| {
Expand All @@ -12,7 +12,7 @@ export type JsonSchema7LiteralType =

export function parseLiteralDef(
def: ZodLiteralDef,
refs: References
refs: Refs
): JsonSchema7LiteralType {
const parsedType = typeof def.value;
if (
Expand Down

0 comments on commit 9a4a59a

Please sign in to comment.