Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: adds extend in common, meta, and constrained models #1613

Merged
merged 2 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/helpers/CommonModelToMetaModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,16 @@ export function convertToObjectModel(
metaModel.properties[String(propertyName)] = propertyModel;
}

if (jsonSchemaModel.extend?.length) {
metaModel.options.extend = [];

for (const extend of jsonSchemaModel.extend) {
metaModel.options.extend.push(
convertToMetaModel(extend, alreadySeenModels)
);
}
}

if (
jsonSchemaModel.additionalProperties !== undefined ||
jsonSchemaModel.patternProperties !== undefined
Expand Down
63 changes: 50 additions & 13 deletions src/helpers/ConstrainHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
ConstrainedEnumModel,
ConstrainedDictionaryModel,
ConstrainedEnumValueModel,
ConstrainedObjectPropertyModel
ConstrainedObjectPropertyModel,
ConstrainedMetaModelOptions
} from '../models/ConstrainedMetaModel';
import {
AnyModel,
Expand Down Expand Up @@ -97,6 +98,20 @@ const placeHolderConstrainedObject = new ConstrainedAnyModel(
''
);

function getConstrainedMetaModelOptions(
metaModel: MetaModel
): ConstrainedMetaModelOptions {
const options: ConstrainedMetaModelOptions = {};

options.const = metaModel.options.const;
options.isNullable = metaModel.options.isNullable;
options.discriminator = metaModel.options.discriminator;
options.format = metaModel.options.format;
options.isExtended = metaModel.options.isExtended;

return options;
}

function constrainReferenceModel<
Options,
DependencyManager extends AbstractDependencyManager
Expand All @@ -109,7 +124,7 @@ function constrainReferenceModel<
const constrainedModel = new ConstrainedReferenceModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
placeHolderConstrainedObject
);
Expand Down Expand Up @@ -148,7 +163,7 @@ function constrainAnyModel<
const constrainedModel = new ConstrainedAnyModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
''
);
constrainedModel.type = getTypeFromMapping(typeMapping, {
Expand All @@ -169,7 +184,7 @@ function constrainFloatModel<
const constrainedModel = new ConstrainedFloatModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
''
);
constrainedModel.type = getTypeFromMapping(typeMapping, {
Expand All @@ -190,7 +205,7 @@ function constrainIntegerModel<
const constrainedModel = new ConstrainedIntegerModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
''
);
constrainedModel.type = getTypeFromMapping(typeMapping, {
Expand All @@ -211,7 +226,7 @@ function constrainStringModel<
const constrainedModel = new ConstrainedStringModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
''
);
constrainedModel.type = getTypeFromMapping(typeMapping, {
Expand All @@ -232,7 +247,7 @@ function constrainBooleanModel<
const constrainedModel = new ConstrainedBooleanModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
''
);
constrainedModel.type = getTypeFromMapping(typeMapping, {
Expand All @@ -255,7 +270,7 @@ function constrainTupleModel<
const constrainedModel = new ConstrainedTupleModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
[]
);
Expand Down Expand Up @@ -291,7 +306,7 @@ function constrainArrayModel<
const constrainedModel = new ConstrainedArrayModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
placeHolderConstrainedObject
);
Expand Down Expand Up @@ -360,7 +375,7 @@ function constrainUnionModel<
const constrainedModel = new ConstrainedUnionModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
[]
);
Expand Down Expand Up @@ -399,7 +414,7 @@ function constrainDictionaryModel<
const constrainedModel = new ConstrainedDictionaryModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
placeHolderConstrainedObject,
placeHolderConstrainedObject,
Expand Down Expand Up @@ -443,10 +458,31 @@ function constrainObjectModel<
context: ConstrainContext<Options, ObjectModel, DependencyManager>,
alreadySeenModels: Map<MetaModel, ConstrainedMetaModel>
): ConstrainedObjectModel {
const options = getConstrainedMetaModelOptions(context.metaModel);

if (context.metaModel.options.extend?.length) {
options.extend = [];

for (const extend of context.metaModel.options.extend) {
options.extend.push(
constrainMetaModel(
typeMapping,
constrainRules,
{
...context,
metaModel: extend,
partOfProperty: undefined
},
alreadySeenModels
)
);
}
}

const constrainedModel = new ConstrainedObjectModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
options,
'',
{}
);
Expand Down Expand Up @@ -481,6 +517,7 @@ function constrainObjectModel<
constrainedModel.properties[String(constrainedPropertyName)] =
constrainedPropertyModel;
}

constrainedModel.type = getTypeFromMapping(typeMapping, {
constrainedModel,
options: context.options,
Expand All @@ -501,7 +538,7 @@ function ConstrainEnumModel<
const constrainedModel = new ConstrainedEnumModel(
context.constrainedName,
context.metaModel.originalInput,
context.metaModel.options,
getConstrainedMetaModelOptions(context.metaModel),
'',
[]
);
Expand Down
37 changes: 36 additions & 1 deletion src/helpers/Splitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,31 @@ const trySplitModel = (
(options.splitDictionary === true && model instanceof DictionaryModel);

if (shouldSplit) {
if (!models.includes(model)) {
let hasModel: boolean = false;

for (const m of models) {
if (m === model) {
hasModel = true;
}

// If a model with the same name is not extended somewhere, we have to force both not to be extended
if (m.name === model.name) {
// if both are extended we can continue
if (m.options.isExtended && model.options.isExtended) {
continue;
}

if (m.options.isExtended || model.options.isExtended) {
m.options.isExtended = false;
model.options.isExtended = false;
}
}
}

if (!hasModel) {
models.push(model);
}

return new ReferenceModel(
model.name,
model.originalInput,
Expand Down Expand Up @@ -95,6 +117,19 @@ export const split = (
);
split(propertyModel, options, models, alreadySeenModels);
}

if (model.options.extend?.length) {
for (let index = 0; index < model.options.extend.length; index++) {
const extendModel = model.options.extend[Number(index)];
extendModel.options.isExtended = true;
model.options.extend[Number(index)] = trySplitModel(
extendModel,
options,
models
);
split(extendModel, options, models, alreadySeenModels);
}
}
} else if (model instanceof UnionModel) {
for (let index = 0; index < model.union.length; index++) {
const unionModel = model.union[Number(index)];
Expand Down
52 changes: 30 additions & 22 deletions src/interpreter/InterpretAllOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,39 @@ export default function interpretAllOf(

for (const allOfSchema of schema.allOf) {
const allOfModel = interpreter.interpret(allOfSchema, interpreterOptions);

if (allOfModel === undefined) {
continue;
}
if (
isModelObject(allOfModel) === true &&
interpreterOptions.allowInheritance === true
) {
Logger.info(
`Processing allOf, inheritance is enabled, ${model.$id} inherits from ${allOfModel.$id}`,
model,
allOfModel
);
model.addExtendedModel(allOfModel);
} else {
Logger.info(
'Processing allOf, inheritance is not enabled. AllOf model is merged together with already interpreted model',
model,
allOfModel
);
interpreter.interpretAndCombineSchema(
allOfSchema,
model,
schema,
interpreterOptions
);

if (interpreterOptions.allowInheritance === true) {
const allOfModelWithoutCache = interpreter.interpret(allOfSchema, {
...interpreterOptions,
disableCache: true
});

if (allOfModelWithoutCache && isModelObject(allOfModelWithoutCache)) {
Logger.info(
`Processing allOf, inheritance is enabled, ${model.$id} inherits from ${allOfModelWithoutCache.$id}`,
model,
allOfModel
);

model.addExtendedModel(allOfModelWithoutCache);
}
}

Logger.info(
'Processing allOf, inheritance is not enabled. AllOf model is merged together with already interpreted model',
model,
allOfModel
);

interpreter.interpretAndCombineSchema(
allOfSchema,
model,
schema,
interpreterOptions
);
}
}
13 changes: 10 additions & 3 deletions src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export type InterpreterOptions = {
* When interpreting a schema with discriminator set, this property will be set best by the individual interpreters to make sure the discriminator becomes an enum.
*/
discriminator?: string;
/**
* Use this option to disable cache when interpreting schemas. This will affect merging of schemas.
kennethaasan marked this conversation as resolved.
Show resolved Hide resolved
*/
disableCache?: boolean;
};
export type InterpreterSchemas =
| Draft6Schema
Expand All @@ -64,7 +68,8 @@ export class Interpreter {
static defaultInterpreterOptions: InterpreterOptions = {
allowInheritance: false,
ignoreAdditionalProperties: false,
ignoreAdditionalItems: false
ignoreAdditionalItems: false,
disableCache: false
};

private anonymCounter = 1;
Expand All @@ -80,7 +85,7 @@ export class Interpreter {
schema: InterpreterSchemaType,
options: InterpreterOptions = Interpreter.defaultInterpreterOptions
): CommonModel | undefined {
if (this.seenSchemas.has(schema)) {
if (!options.disableCache && this.seenSchemas.has(schema)) {
const cachedModel = this.seenSchemas.get(schema);
if (cachedModel !== undefined) {
return cachedModel;
Expand All @@ -92,7 +97,9 @@ export class Interpreter {
}
const model = new CommonModel();
model.originalInput = schema;
this.seenSchemas.set(schema, model);
if (!options.disableCache) {
this.seenSchemas.set(schema, model);
}
this.interpretSchema(model, schema, options);
return model;
}
Expand Down
16 changes: 11 additions & 5 deletions src/models/CommonModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const defaultMergingOptions: MergingOptions = {
* Common internal representation for a model.
*/
export class CommonModel {
extend?: string[];
extend?: CommonModel[];
originalInput?: any;
$id?: string;
type?: string | string[];
Expand Down Expand Up @@ -426,24 +426,30 @@ export class CommonModel {
* @param extendedModel
*/
addExtendedModel(extendedModel: CommonModel): void {
if (extendedModel.$id === undefined) {
if (
extendedModel.$id === undefined ||
CommonModel.idIncludesAnonymousSchema(extendedModel)
) {
Logger.error(
'Found no $id for allOf model and cannot extend the existing model, this should never happen.',
this,
extendedModel
);
return;
}
this.extend = this.extend || [];
if (this.extend.includes(extendedModel.$id)) {

if (
this.extend?.find((commonModel) => commonModel.$id === extendedModel.$id)
) {
Logger.info(
`${this.$id} model already extends model ${extendedModel.$id}.`,
this,
extendedModel
);
return;
}
this.extend.push(extendedModel.$id);
this.extend = this.extend ?? [];
this.extend.push(extendedModel);
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/models/ConstrainedMetaModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class ConstrainedMetaModelOptions extends MetaModelOptions {
const?: ConstrainedMetaModelOptionsConst;
discriminator?: ConstrainedMetaModelOptionsDiscriminator;
parents?: ConstrainedMetaModel[];
extend?: ConstrainedMetaModel[];
}

export abstract class ConstrainedMetaModel extends MetaModel {
Expand Down
Loading
Loading