Presets
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.
Hello world!
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:
1class Root { 2 private _email?: string; 3 4 constructor(input: { 5 email?: string, 6 }) { 7 this._email = input.email; 8 } 9 10 get email(): string | undefined { return this._email; } 11 set email(email: string | undefined) { this._email = email; } 12}
The generator renderes the TypeScript class by calling preset hooks, which is callbacks that is called for rendering parts of the class.
1<self> 2 <properties /> 3 4 <ctor /> 5 6 <getter /> 7 <setter /> 8 9 <additionalContent /> 10</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:
1import { TypeScriptGenerator } from '@asyncapi/modelina'; 2 3const generator = new TypeScriptGenerator({ 4 presets: [ 5 { 6 class: { 7 property({ content }) { 8 const description = '// Hello world!' 9 return `${description}\n${content}`; 10 } 11 } 12 } 13 ] 14});
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!
1class Root { 2 // Hello world! 3 private _email?: string; 4 5 constructor(input: { 6 email?: string, 7 }) { 8 this._email = input.email; 9 } 10 11 get email(): string | undefined { return this._email; } 12 set email(email: string | undefined) { this._email = email; } 13}
Presets in depth
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:
1{ 2 // `class` model type 3 class: { 4 self(...arguments) { /* logic */ }, 5 // `setter` customization method 6 setter(...arguments) { /* logic */ }, 7 }, 8 interface: { 9 // `property` customization method 10 property(...arguments) { /* logic */ }, 11 additionalContent(...arguments) { /* logic */ }, 12 }, 13}
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.
Overwriting existing rendered content
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.
1import { TypeScriptGenerator } from '@asyncapi/modelina'; 2 3const generator = new TypeScriptGenerator({ 4 presets: [ 5 { 6 class: { 7 property({ property }) { 8 return `public ${property.propertyName}${!property.required ? '?' : ''}: ${property.type};`; 9 } 10 } 11 } 12 ] 13});
It would render the following class:
1class Root { 2 public _email?: string; 3 4 constructor(input: { 5 email?: string, 6 }) { 7 this._email = input.email; 8 } 9 10 get email(): string | undefined { return this._email; } 11 set email(email: string | undefined) { this._email = email; } 12}
Ap/pre-pending to existing rendered content
As the hello world example appended content, this time lets prepend some content to the properties.
1import { TypeScriptGenerator } from '@asyncapi/modelina'; 2 3const generator = new TypeScriptGenerator({ 4 presets: [ 5 { 6 class: { 7 property({ content }) { 8 const description = '// Hello world!' 9 return `${content}\n${description}`; 10 } 11 } 12 } 13 ] 14});
It would render the following class:
1class Root { 2 private _email?: string; 3 // Hello world! 4 5 constructor(input: { 6 email?: string, 7 }) { 8 this._email = input.email; 9 } 10 11 get email(): string | undefined { return this._email; } 12 set email(email: string | undefined) { this._email = email; } 13}
Reusing presets (options)
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:
1import { TypeScriptGenerator } from '@asyncapi/modelina'; 2 3const generator = new TypeScriptGenerator({ 4 presets: [ 5 { 6 preset: { 7 class: { 8 property({ content, options }) { 9 const description = options.description !== undefined ? options.description : '// Hello world!' 10 return `${description}\n${content}`; 11 } 12 } 13 }, 14 options: { 15 description: "Hello dear customizer!" 16 } 17 } 18 ] 19});
This enables you to reuse presets (even expose them) to multiple generators
1import { TypeScriptGenerator } from '@asyncapi/modelina'; 2interface DescriptionOption = { 3 description: string 4} 5const descriptionPreset: TypeScriptPreset<DescriptionOption> = { 6 class: { 7 property({ content, options }) { 8 const description = options.description !== undefined ? options.description : '// Hello world!' 9 return `${description}\n${content}`; 10 } 11 } 12} 13 14// One generator prepends `Hello dear customizer!` 15const generator = new TypeScriptGenerator({ 16 presets: [ 17 { 18 preset: descriptionPreset, 19 options: { 20 description: "Hello dear customizer!" 21 } 22 } 23 ] 24}); 25 26// Second generator prepends `Hello from beyond!` 27const generator2 = new TypeScriptGenerator({ 28 presets: [ 29 { 30 preset: descriptionPreset, 31 options: { 32 description: "Hello from beyond!" 33 } 34 } 35 ] 36});
Adding new dependencies
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
.
1... 2self({ dependencyManager, content }) { 3 dependencyManager.addDependency('import java.util.*;'); 4 return content; 5} 6...
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 provides a specific function addTypeScriptDependency
that takes care of that logic, and you just have to remember addTypeScriptDependency('Import what', 'From where')
.
Overriding the default preset
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:
1const DEFAULT_PRESET = { 2 // implementation 3} 4 5const generator = new TypeScriptGenerator({ defaultPreset: DEFAULT_PRESET });
NOTE: Default presets MUST implement all preset hooks for a given model type!
Preset's shape
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.
Java
Class
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. |
Enum
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. |
JavaScript
Class
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. |
TypeScript
Class
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. |
Interface
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. |
Enum
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. |
Type
This preset is a generator for all meta models ConstrainedMetaModel
and can be accessed through the model
argument.
There are no additional methods.
Go
Struct
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. |
Enum
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 rendering the enum items. | item object as a ConstrainedEnumValueModel instance. index as number , the current enum item being rendered. |
C#
Class
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. |
Enum
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. |
Rust
Struct
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. |
Enum
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 . |
Package
This preset is a generator for the crate package file.
Method | Description | Additional arguments |
---|---|---|
manifest | packageOptions , InputMetaModel | |
lib | packageOptions , inputModel |
Union
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 |
Tuple
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 . |
Dart
Class
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. |
Enum
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. |
Python
Class
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. |
Enum
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. |
C++ (csplusplus)
Class
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. |
Enum
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. |
Kotlin
Class
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. |
Enum
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. |
PHP
Class
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. |
Enum
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. |
Limitations
With features natually comes limitations, and same applies for presets, so here are the known limitations the architecture of presets for Modelina.
Hard for two presets to write to the exact same location within a class
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.
1class Root { 2 @something 3 @something_else 4 private _email?: string; 5}
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: https://github.com/asyncapi/modelina/issues/628