Modelina uses something called presets to extend the rendered model. You can see it as layers you add ontop of each other which either adds new code to render, or completely overwrite existing generated code.
Lets try to look at an example, every generator start with a bare minimal model called the default preset and for the TypeScript that preset would render a class to look something like this:
class Root {
private _email?: string;
constructor(input: {
email?: string,
}) {
this._email = input.email;
}
get email(): string | undefined { return this._email; }
set email(email: string | undefined) { this._email = email; }
}
The generator renderes the TypeScript class by calling preset hooks, which is callbacks that is called for rendering parts of the class.
<self>
<properties />
<ctor />
<getter />
<setter />
<additionalContent />
</self>
This is what Modelina leverage to customize what is being rendered, because these preset hooks can be extended or overwritten by one or more presets.
Lets take a look at an example, say we wanted to a description for each property of the class, maybe just to say hallo to the world. To do this we pass a custom preset to our generator:
import { TypeScriptGenerator } from '@asyncapi/modelina';
const generator = new TypeScriptGenerator({
presets: [
{
class: {
property({ content }) {
const description = '// Hello world!'
return `${description}\n${content}`;
}
}
}
]
});
This adds a new preset for classes where for each property it runs our callback. The callback then prepends, to the existing content
that have been rendered by other presets, our comment // Hello world!
. This now renders all class properties with a comment from us!
class Root {
// Hello world!
private _email?: string;
constructor(input: {
email?: string,
}) {
this._email = input.email;
}
get email(): string | undefined { return this._email; }
set email(email: string | undefined) { this._email = email; }
}
A preset is a pure JavaScript object with format key: value
, where key
is the name of a model type and value
is an object containing callbacks that extend a given rendered part for a given model type, like below example:
{
// `class` model type
class: {
self(...arguments) { /* logic */ },
// `setter` customization method
setter(...arguments) { /* logic */ },
},
interface: {
// `property` customization method
property(...arguments) { /* logic */ },
additionalContent(...arguments) { /* logic */ },
},
}
Each output has different model types, which results in different implementable methods in a single preset. The different model types can be found in the preset's shape section.
For each custom preset, the implementation of methods for a given model type is optional. It means that you can implement one or all, depending on your use-case.
The order of extending a given part of the model is consistent with the order of presets in the array passed as a presets
option parameter in the generator's constructor.
As shown in the Hello world! example, there are many ways to customize the model generation, this section covers the the different parts.
Since the preset renders in a form of layers, one of the usecases is to overwrite an already existing rendering of some part of the generated model. Lets try an adapt out hello world example, and instead of prepending comments, we can overwrite the already rendered content, for example lets use public property initializer.
import { TypeScriptGenerator } from '@asyncapi/modelina';
const generator = new TypeScriptGenerator({
presets: [
{
class: {
property({ property }) {
return `public ${property.propertyName}${!property.required ? '?' : ''}: ${property.type};`;
}
}
}
]
});
It would render the following class:
class Root {
public _email?: string;
constructor(input: {
email?: string,
}) {
this._email = input.email;
}
get email(): string | undefined { return this._email; }
set email(email: string | undefined) { this._email = email; }
}
As the hello world example appended content, this time lets prepend some content to the properties.
import { TypeScriptGenerator } from '@asyncapi/modelina';
const generator = new TypeScriptGenerator({
presets: [
{
class: {
property({ content }) {
const description = '// Hello world!'
return `${description}\n${content}`;
}
}
}
]
});
It would render the following class:
class Root {
private _email?: string;
// Hello world!
constructor(input: {
email?: string,
}) {
this._email = input.email;
}
get email(): string | undefined { return this._email; }
set email(email: string | undefined) { this._email = email; }
}
Sometimes you might want to create different behavior based on user input, this can be done through options that can be provided with the preset.
Say we want to create a preset with a customizable description that is provided by the use of the preset. To do this we can adapt the hello world! example to this:
import { TypeScriptGenerator } from '@asyncapi/modelina';
const generator = new TypeScriptGenerator({
presets: [
{
preset: {
class: {
property({ content, options }) {
const description = options.description !== undefined ? options.description : '// Hello world!'
return `${description}\n${content}`;
}
}
},
options: {
description: "Hello dear customizer!"
}
}
]
});
This enables you to reuse presets (even expose them) to multiple generators
import { TypeScriptGenerator } from '@asyncapi/modelina';
interface DescriptionOption = {
description: string
}
const descriptionPreset: TypeScriptPreset<DescriptionOption> = {
class: {
property({ content, options }) {
const description = options.description !== undefined ? options.description : '// Hello world!'
return `${description}\n${content}`;
}
}
}
// One generator prepends `Hello dear customizer!`
const generator = new TypeScriptGenerator({
presets: [
{
preset: descriptionPreset,
options: {
description: "Hello dear customizer!"
}
}
]
});
// Second generator prepends `Hello from beyond!`
const generator2 = new TypeScriptGenerator({
presets: [
{
preset: descriptionPreset,
options: {
description: "Hello from beyond!"
}
}
]
});
Sometimes the preset might need to use some kind of foreign dependency. To achieve this each preset hook has the possibility of adding its own dependencies through a dependency manager, which can be accessed in dependencyManager
.
...
self({ dependencyManager, content }) {
dependencyManager.addDependency('import java.util.*;');
return content;
}
...
Some languages has specific helper functions, and some very basic interfaces, such as for Java.
In TypeScript because you can have different import syntaxes based on the module system such as CJS or ESM, therefore it provies a specific function addTypeScriptDependency
that takes care of that logic, and you just have to remember addTypeScriptDependency('ImportanWhat', 'FromWhere')
.
Each implemented generator must have defined a default preset which forms is minimal generated model, that the rest of the presets add to or removes from. This can be overwritten by passing the defaultPreset
parameter in the generator options. Check the example for TypeScript generator:
const DEFAULT_PRESET = {
// implementation
}
const generator = new TypeScriptGenerator({ defaultPreset: DEFAULT_PRESET });
NOTE: Default presets MUST implement all preset hooks for a given model type!
For each model type, you can implement two basic preset hooks:
self
- the method for extending the model shape, this is what calls all additional preset hooks.additionalContent
- the method which adds additional content to the model.
Each preset hook method receives the following arguments:
model
- aConstrainedMetaModel
variation which depends on the preset type.inputModel
- an instance of theInputMetaModel
class.renderer
- an instance of the class with common helper functions to render appropriate model type.content
- rendered content from previous preset.options
- options passed to preset defined in thepresets
array, it's type depends on the specific preset.
Below is a list of supported languages with their model types and corresponding additional preset's methods with extra arguments based on the character of the customization method.
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for all meta models ConstrainedMetaModel
and can be accessed through the model
argument.
There are no additional methods.
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
field |
A method to extend rendered given field. | field object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
accessor |
A method to extend rendered given property accessor. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
field |
A method to extend rendered given field. | field object as a ConstrainedObjectPropertyModel instance. |
fieldMacro |
field object as a ConstrainedObjectPropertyModel instance. |
|
structMacro |
field object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item, itemIndex . |
itemMacro |
item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item, itemIndex . |
|
structMacro |
item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item, itemIndex . |
This preset is a generator for the crate package file.
Method | Description | Additional arguments |
---|---|---|
manifest |
packageOptions , InputMetaModel |
|
lib |
packageOptions , inputModel |
This preset is a generator for the meta model ConstrainedUnionModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
ConstrainedMetaModel |
|
itemMacro |
ConstrainedMetaModel |
|
structMacro |
ConstrainedMetaModel |
This preset is a generator for the meta model ConstrainedTupleModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
field |
field object as a ConstrainedTupleValueModel instance, fieldIndex . |
|
structMacro |
field object as a ConstrainedTupleValueModel instance, fieldIndex . |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
accessor |
A method to extend rendered given property accessor. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
This preset is a generator for the meta model ConstrainedObjectModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
ctor |
A method to extend rendered constructor for a given class. | - |
property |
A method to extend rendered given property. | property object as a ConstrainedObjectPropertyModel instance. |
setter |
A method to extend setter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
getter |
A method to extend getter for a given property. | property object as a ConstrainedObjectPropertyModel instance. |
This preset is a generator for the meta model ConstrainedEnumModel
and can be accessed through the model
argument.
Method | Description | Additional arguments |
---|---|---|
item |
A method to extend enum's item. | item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item. |
With features natually comes limitations, and same applies for presets, so here are the known limitations the architecture of presets for Modelina.
Say you developed two presets, and you wanted to use both at the same time, but they both to add something right before a property. Example could be one wanted to add @something
and the other @something_else
. With the way presets work, one will always be rendered before the other.
class Root {
@something
@something_else
private _email?: string;
}
There are no easy way for those two presets to properly together, and there is no easy way to solve this. You can read more about the issue here: asyncapi#628