Skip to content

Commit

Permalink
feat: support circular refs in custom formatters (#829)
Browse files Browse the repository at this point in the history
  • Loading branch information
gadicc authored Jul 3, 2021
1 parent 2c9312c commit b509b7f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,14 @@ import { BaseType, Definition, FunctionType, SubTypeFormatter } from "ts-json-sc
import ts from "typescript";

export class MyFunctionTypeFormatter implements SubTypeFormatter {
// You can skip this line if you don't need childTypeFormatter
public constructor(private childTypeFormatter: TypeFormatter) {}

public supportsType(type: FunctionType): boolean {
return type instanceof FunctionType;
}

public getDefinition(_type: FunctionType): Definition {
public getDefinition(type: FunctionType): Definition {
// Return a custom schema for the function property.
return {
type: "object",
Expand All @@ -79,9 +82,16 @@ export class MyFunctionTypeFormatter implements SubTypeFormatter {
};
}

public getChildren(_type: FunctionType): BaseType[] {
// If this type does NOT HAVE children, generally all you need is:
public getChildren(type: FunctionType): BaseType[] {
return [];
}

// However, if children ARE supported, you'll need something similar to
// this (see src/TypeFormatter/{Array,Definition,etc}.ts for some examples):
public getChildren(type: FunctionType): BaseType[] {
return this.childTypeFormatter.getChildren(type.getType());
}
}
```

Expand All @@ -99,8 +109,11 @@ const config = {
};

// We configure the formatter an add our custom formatter to it.
const formatter = createFormatter(config, (fmt) => {
const formatter = createFormatter(config, (fmt, circularReferenceTypeFormatter) => {
// If your formatter DOES NOT support children, e.g. getChildren() { return [] }:
fmt.addTypeFormatter(new MyFunctionTypeFormatter());
// If your formatter DOES support children, you'll need this reference too:
fmt.addTypeFormatter(new MyFunctionTypeFormatter(circularReferenceTypeFormatter));
});

const program = createProgram(config);
Expand Down
7 changes: 5 additions & 2 deletions factory/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ import { UnknownTypeFormatter } from "../src/TypeFormatter/UnknownTypeFormatter"
import { VoidTypeFormatter } from "../src/TypeFormatter/VoidTypeFormatter";
import { MutableTypeFormatter } from "../src/MutableTypeFormatter";

export type FormatterAugmentor = (formatter: MutableTypeFormatter) => void;
export type FormatterAugmentor = (
formatter: MutableTypeFormatter,
circularReferenceTypeFormatter: CircularReferenceTypeFormatter
) => void;

export function createFormatter(config: Config, augmentor?: FormatterAugmentor): TypeFormatter {
const chainTypeFormatter = new ChainTypeFormatter([]);
const circularReferenceTypeFormatter = new CircularReferenceTypeFormatter(chainTypeFormatter);

if (augmentor) {
augmentor(chainTypeFormatter);
augmentor(chainTypeFormatter, circularReferenceTypeFormatter);
}

chainTypeFormatter
Expand Down
32 changes: 31 additions & 1 deletion test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import addFormats from "ajv-formats";
import { readFileSync } from "fs";
import { resolve } from "path";
import ts from "typescript";
import { BaseType, Context, ReferenceType, SubNodeParser } from "../index";
import { BaseType, Context, DefinitionType, ReferenceType, SubNodeParser } from "../index";
import { createFormatter, FormatterAugmentor } from "../factory/formatter";
import { createParser, ParserAugmentor } from "../factory/parser";
import { createProgram } from "../factory/program";
Expand All @@ -14,6 +14,8 @@ import { SubTypeFormatter } from "../src/SubTypeFormatter";
import { EnumType } from "../src/Type/EnumType";
import { FunctionType } from "../src/Type/FunctionType";
import { StringType } from "../src/Type/StringType";
import { TypeFormatter } from "../src/TypeFormatter";
import { uniqueArray } from "../src/Utils/uniqueArray";

const basePath = "test/config";

Expand Down Expand Up @@ -108,6 +110,21 @@ export class ExampleEnumTypeFormatter implements SubTypeFormatter {
}
}

// Just like DefinitionFormatter but adds { $comment: "overriden" }
export class ExampleDefinitionOverrideFormatter implements SubTypeFormatter {
public constructor(private childTypeFormatter: TypeFormatter) {}
public supportsType(type: DefinitionType): boolean {
return type instanceof DefinitionType;
}
public getDefinition(type: DefinitionType): Definition {
const ref = type.getName();
return { $ref: `#/definitions/${ref}`, $comment: "overriden" };
}
public getChildren(type: DefinitionType): BaseType[] {
return uniqueArray([type, ...this.childTypeFormatter.getChildren(type.getType())]);
}
}

export class ExampleConstructorParser implements SubNodeParser {
supportsNode(node: ts.Node): boolean {
return node.kind === ts.SyntaxKind.ConstructorType;
Expand Down Expand Up @@ -356,6 +373,19 @@ describe("config", () => {
)
);

it(
"custom-formatter-configuration-circular",
assertSchema(
"custom-formatter-configuration-circular",
{
type: "MyObject",
},
false,
(formatter, circularReferenceTypeFormatter) =>
formatter.addTypeFormatter(new ExampleDefinitionOverrideFormatter(circularReferenceTypeFormatter))
)
);

it(
"custom-parser-configuration",
assertSchema(
Expand Down
7 changes: 7 additions & 0 deletions test/config/custom-formatter-configuration-circular/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface InnerInterface {
exportValue: string;
}

export interface MyObject {
inner: InnerInterface;
}
28 changes: 28 additions & 0 deletions test/config/custom-formatter-configuration-circular/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$comment": "overriden",
"$ref": "#/definitions/MyObject",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"InnerInterface": {
"additionalProperties": false,
"properties": {
"exportValue": {
"type": "string"
}
},
"required": ["exportValue"],
"type": "object"
},
"MyObject": {
"additionalProperties": false,
"properties": {
"inner": {
"$comment": "overriden",
"$ref": "#/definitions/InnerInterface"
}
},
"required": ["inner"],
"type": "object"
}
}
}

0 comments on commit b509b7f

Please sign in to comment.