Skip to content

Commit

Permalink
feat!: adds extend in common, meta, and constrained models (#1613)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethaasan authored Nov 18, 2023
1 parent 280904c commit 0e9f0e7
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 48 deletions.
16 changes: 16 additions & 0 deletions docs/migrations/version-2-to-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

This document contains all the breaking changes and migrations guidelines for adapting your code to the new version.

## allowInheritance set to true will enable inheritance

This feature introduces a new option called `allowInheritance` in the interpreter options, which controls whether the generated models should inherit when the schema includes an `allOf`. By default, this option is set to false, which means that you'll not be affected if this property is not set. In the `MetaModel` and the `ConstrainedMetaModel` options, there is now an `extend` property (a list of models) and an `isExtended` property (boolean).

Here is an example of how to use the new feature and the `allowInheritance` option in your code:

```ts
const generator = new JavaFileGenerator({
processorOptions: {
interpreter: {
allowInheritance: true
}
}
});
```

### TypeScript

Is not affected by this change.
Expand Down
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
);
}
}
16 changes: 13 additions & 3 deletions src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ 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;
/**
* This options disables the seenSchemas cache in the Interpreter.
* Use this option to disable the seenSchemas cache when interpreting schemas.
* This will affect merging of schemas, and should only be used by the internal interpreters when allowInheritance is set to true.
* This allows the internal interpreters to keep clean copies of the schemas as CommonModel's.
*/
disableCache?: boolean;
};
export type InterpreterSchemas =
| Draft6Schema
Expand All @@ -64,7 +71,8 @@ export class Interpreter {
static defaultInterpreterOptions: InterpreterOptions = {
allowInheritance: false,
ignoreAdditionalProperties: false,
ignoreAdditionalItems: false
ignoreAdditionalItems: false,
disableCache: false
};

private anonymCounter = 1;
Expand All @@ -80,7 +88,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 +100,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
Loading

0 comments on commit 0e9f0e7

Please sign in to comment.