Skip to content

Commit

Permalink
feat: adds extend in common, meta, and constrained models
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethaasan committed Nov 16, 2023
1 parent ef56f3e commit f813492
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 48 deletions.
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.
*/
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

0 comments on commit f813492

Please sign in to comment.