Skip to content

Commit

Permalink
fix(schema): Avoid circular ref
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Aug 22, 2021
1 parent a22e241 commit 9bff458
Show file tree
Hide file tree
Showing 43 changed files with 540 additions and 440 deletions.
3 changes: 2 additions & 1 deletion .mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ module.exports = {
reporter: "dot",
spec: [
"packages/**/*.spec.ts"
]
],
timeout: 3000
};
2 changes: 1 addition & 1 deletion packages/formio/test/app/controllers/pages/IndexCtrl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Constant, Controller, Get, HeaderParams, View} from "@tsed/common";
import {Returns} from "@tsed/schema/src";
import {Returns} from "@tsed/schema";
import {Hidden, SwaggerSettings} from "@tsed/swagger";

@Hidden()
Expand Down
2 changes: 1 addition & 1 deletion packages/mongoose/test/discriminators.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {TestMongooseContext} from "@tsed/testing-mongoose";
import {expect} from "chai";
import {Server} from "./helpers/Server";
import {MongooseModel} from "../src/interfaces/MongooseModel";
import {Required} from "@tsed/schema/src";
import {Required} from "@tsed/schema";
import {ObjectID, DiscriminatorKey, Model} from "../src";

describe("Mongoose", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/mongoose/test/versioning.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {TestMongooseContext} from "@tsed/testing-mongoose";
import {expect} from "chai";
import {Server} from "./helpers/Server";
import {MongooseModel} from "../src/interfaces/MongooseModel";
import {CollectionOf, Integer, Required} from "@tsed/schema/src";
import {Integer, Required} from "@tsed/schema";
import {Model, ObjectID, VersionKey} from "../src";

describe("Mongoose", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/platform-express/test/response-filter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Context, Controller, Get, PlatformTest, Res, ResponseFilter} from "@tsed/common";
import {PlatformTestUtils} from "@tsed/platform-test-utils";
import {Returns} from "@tsed/schema/src";
import {Returns} from "@tsed/schema";
import {expect} from "chai";
import * as Sinon from "sinon";
import SuperTest from "supertest";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ export function registerFormioMapper(type: string, mapper: FormioMapper) {
}

export function getFormioMapper(type: string): FormioMapper {
// istanbul ignore next
if (!FormioMappersContainer.has(type)) {
throw new Error("Formio " + type + " mapper doesn't exists");
throw new Error(`Formio ${type} mapper doesn't exists`);
}
return FormioMappersContainer.get(type)!; // || ((schema: string) => schema);
return FormioMappersContainer.get(type)!;
}

export function execMapper(type: string, schema: any, options: any): any {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema-formio/test/datamap.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CollectionOf} from "@tsed/schema/src";
import {CollectionOf} from "@tsed/schema";
import {expect} from "chai";
import {getFormioSchema} from "../src";

Expand Down
2 changes: 1 addition & 1 deletion packages/schema-formio/test/date.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DateFormat, DateTime, Email} from "@tsed/schema/src";
import {DateFormat, DateTime} from "@tsed/schema";
import {expect} from "chai";
import {getFormioSchema} from "../src";

Expand Down
2 changes: 1 addition & 1 deletion packages/schema-formio/test/editgrid.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CollectionOf, Property} from "@tsed/schema/src";
import {CollectionOf, Property} from "@tsed/schema";
import {expect} from "chai";
import {Currency, getFormioSchema} from "../src";

Expand Down
2 changes: 1 addition & 1 deletion packages/schema-formio/test/email.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Email} from "@tsed/schema/src";
import {Email} from "@tsed/schema";
import {expect} from "chai";
import {getFormioSchema} from "../src";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Property} from "@tsed/schema/src";
import {Property} from "@tsed/schema";
import {expect} from "chai";
import {Currency, getFormioSchema, Hidden, Textarea} from "../src";

Expand Down
8 changes: 6 additions & 2 deletions packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
"name": "@tsed/schema",
"version": "6.62.3",
"description": "JsonSchema module for Ts.ED Framework",
"private": false,
"source": "./src/index.ts",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"private": false,
"exports": {
"require": "./lib/index.js",
"default": "./lib/index.modern.js"
},
"keywords": [
"TypeScript",
"decorators",
Expand All @@ -17,7 +21,7 @@
"tsed"
],
"scripts": {
"build": "tsc --build tsconfig.compile.json"
"build": "microbundle --target node --no-compress --format modern,cjs --tsconfig ./tsconfig.compile.json"
},
"dependencies": {
"@tsed/core": "6.62.3",
Expand Down
27 changes: 27 additions & 0 deletions packages/schema/src/components/anyMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {JsonLazyRef} from "../domain/JsonLazyRef";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {mapGenericsOptions} from "../utils/generics";
import {toRef} from "../utils/ref";

export function anyMapper(input: any, options: JsonSchemaOptions = {}): any {
options.schemas = options.schemas || {};

if (typeof input !== "object" || input === null) {
return input;
}

if (input instanceof JsonLazyRef) {
return execMapper("lazyRef", input, options);
}

if ("toJSON" in input) {
const schema = input.toJSON(mapGenericsOptions(options));

return input.canRef ? toRef(input, schema, options) : schema;
}

return execMapper("object", input, options);
}

registerJsonSchemaMapper("any", anyMapper);
50 changes: 50 additions & 0 deletions packages/schema/src/components/classMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {JsonEntityStore} from "../domain/JsonEntityStore";
import {JsonSchema} from "../domain/JsonSchema";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {mapGenericsOptions, popGenerics} from "../utils/generics";
import {createRef, createRefName} from "../utils/ref";

export function classMapper(value: JsonSchema, options: JsonSchemaOptions = {}) {
const store = JsonEntityStore.from(value.class);
const name = createRefName(store.schema.getName() || value.getName(), options);

if (value.hasGenerics) {
// Inline generic
const {type, properties, additionalProperties, items, ...props} = value.toJSON(options);
const schema = {
...execMapper("any", store.schema, {
...options,
...popGenerics(value),
root: false
}),
...props
};

if (schema.title) {
const name = createRefName(schema.title, options);
options.schemas![name] = schema;
delete schema.title;

return createRef(name, value, options);
}

return schema;
}

if (options.schemas && !options.schemas[name]) {
options.schemas[name] = {}; // avoid infinite calls
options.schemas[name] = execMapper(
"any",
store.schema,
mapGenericsOptions({
...options,
root: false
})
);
}

return createRef(name, value, options);
}

registerJsonSchemaMapper("class", classMapper);
69 changes: 69 additions & 0 deletions packages/schema/src/components/genericsMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {isClass, isPrimitiveClass} from "@tsed/core";
import {JsonEntityStore} from "../domain/JsonEntityStore";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {GenericsContext, popGenerics} from "../utils/generics";
import {getJsonType} from "../utils/getJsonType";

/**
* @ignore
*/
export function genericsMapper(obj: any, options: GenericsContext) {
const {generics} = options;

if (generics && obj.$ref) {
if (generics.has(obj.$ref)) {
let type = generics.get(obj.$ref);

if (isPrimitiveClass(type)) {
return {
type: getJsonType(type)
};
}

if (type === Date) {
return {
type: "string",
format: "date-time"
};
}

if (type.toJSON) {
return type.toJSON({
...options,
generics: undefined
});
}

if (type === Object) {
return {
type: "object"
};
}

if (isClass(type)) {
const model = {
class: type
};

if (options.nestedGenerics.length === 0) {
return execMapper("class", model as any, {
...options,
generics: undefined
});
}

const store = JsonEntityStore.from(model.class);

return execMapper("schema", store.schema, {
...options,
...popGenerics(options),
root: false
});
}
}
}

return obj;
}

registerJsonSchemaMapper("generics", genericsMapper);
9 changes: 9 additions & 0 deletions packages/schema/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * from "./anyMapper";
export * from "./classMapper";
export * from "./genericsMapper";
export * from "./inheritedClassMapper";
export * from "./itemMapper";
export * from "./lazyRefMapper";
export * from "./mapMapper";
export * from "./objectMapper";
export * from "./schemaMapper";
23 changes: 23 additions & 0 deletions packages/schema/src/components/inheritedClassMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {classOf, deepMerge} from "@tsed/core";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {getInheritedStores} from "../utils/getInheritedStores";

/**
* @ignore
*/
export function inheritedClassMapper(obj: any, {target, ...options}: JsonSchemaOptions = {}) {
const stores = Array.from(getInheritedStores(target).entries()).filter(([model]) => classOf(model) !== classOf(target));

if (stores.length) {
const schema = stores.reduce((obj, [, store]) => {
return deepMerge(obj, execMapper("schema", store.schema, options));
}, {});

obj = deepMerge(schema, obj);
}

return obj;
}

registerJsonSchemaMapper("inheritedClass", inheritedClassMapper);
8 changes: 8 additions & 0 deletions packages/schema/src/components/itemMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

export function itemMapper(value: any, options: JsonSchemaOptions) {
return value && value.isClass ? execMapper("class", value, options) : execMapper("any", value, options);
}

registerJsonSchemaMapper("item", itemMapper);
21 changes: 21 additions & 0 deletions packages/schema/src/components/lazyRefMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {JsonLazyRef} from "../domain/JsonLazyRef";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {mapGenericsOptions} from "../utils/generics";
import {createRef, toRef} from "../utils/ref";

export function lazyRefMapper(input: JsonLazyRef, options: JsonSchemaOptions) {
const name = input.name;

if (options.$refs?.find((t: any) => t === input.target)) {
return createRef(name, input.schema, options);
}

options.$refs = [...(options.$refs || []), input.target];

const schema = input.toJSON(mapGenericsOptions(options));

return toRef(input.schema, schema, options);
}

registerJsonSchemaMapper("lazyRef", lazyRefMapper);
25 changes: 25 additions & 0 deletions packages/schema/src/components/mapMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {mapGenericsOptions} from "../utils/generics";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

/**
* Serialize class which inherit from Map like JsonMap, JsonOperation, JsonParameter.
* @param input
* @param ignore
* @param options
* @ignore
*/
export function mapMapper(input: Map<string, any>, {ignore = [], ...options}: JsonSchemaOptions = {}): any {
options = mapGenericsOptions(options);

return Array.from(input.entries()).reduce((obj: any, [key, value]) => {
if (ignore.includes(key)) {
return obj;
}

obj[key] = execMapper("item", value, options);
return obj;
}, {});
}

registerJsonSchemaMapper("map", mapMapper);
29 changes: 29 additions & 0 deletions packages/schema/src/components/objectMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {isArray} from "@tsed/core";
import {JsonSchema} from "../domain/JsonSchema";
import {alterIgnore} from "../hooks/alterIgnore";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

/**
* Serialize Any object to a json schema
* @param input
* @param options
* @ignore
*/
export function objectMapper(input: any, options: JsonSchemaOptions) {
const {specType, operationIdFormatter, root, schemas, genericTypes, nestedGenerics, useAlias, genericLabels, ...ctx} = options;

return Object.entries(input).reduce<any>(
(obj, [key, value]: [string, any | JsonSchema]) => {
if (options.withIgnoredProps !== false && !alterIgnore(value, ctx)) {
// remove groups to avoid bad schema generation over children models
obj[key] = execMapper("item", value, {...options, groups: value?.$forwardGroups ? options.groups : undefined});
}

return obj;
},
isArray(input) ? [] : {}
);
}

registerJsonSchemaMapper("object", objectMapper);

0 comments on commit 9bff458

Please sign in to comment.