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 all commits
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
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
}
}
});
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, if we show in the subsequent PRs how that code will look in the different generators, that would be nice 👌

### 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
Loading