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 existng 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 `${description}\n${content}`;
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 provies a specific function addTypeScriptDependency that takes care of that logic, and you just have to remember addTypeScriptDependency('ImportanWhat', 'FromWhere').

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 - a ConstrainedMetaModel variation which depends on the preset type.
  • inputModel - an instance of the InputMetaModel 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 the presets 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
propertyA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
fieldA method to extend rendered given field.field object as a ConstrainedObjectPropertyModel instance.

C#

Class

This preset is a generator for the meta model ConstrainedObjectModel and can be accessed through the model argument.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
accessorA method to extend rendered given property accessor.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
fieldA method to extend rendered given field.field object as a ConstrainedObjectPropertyModel instance.
fieldMacrofield object as a ConstrainedObjectPropertyModel instance.
structMacrofield object as a ConstrainedObjectPropertyModel instance.

Enum

This preset is a generator for the meta model ConstrainedEnumModel and can be accessed through the model argument.

MethodDescriptionAdditional arguments
itemA method to extend enum's item.item object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item, itemIndex.
itemMacroitem object as a ConstrainedEnumValueModel instance, which contains the value and key of enum's item, itemIndex.
structMacroitem 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.

MethodDescriptionAdditional arguments
manifestpackageOptions, InputMetaModel
libpackageOptions, inputModel

Union

This preset is a generator for the meta model ConstrainedUnionModel and can be accessed through the model argument.

MethodDescriptionAdditional arguments
itemConstrainedMetaModel
itemMacroConstrainedMetaModel
structMacroConstrainedMetaModel

Tuple

This preset is a generator for the meta model ConstrainedTupleModel and can be accessed through the model argument.

MethodDescriptionAdditional arguments
fieldfield object as a ConstrainedTupleValueModel instance, fieldIndex.
structMacrofield 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
accessorA method to extend rendered given property accessor.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
propertyA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA 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.

MethodDescriptionAdditional arguments
itemA 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.

MethodDescriptionAdditional arguments
ctorA method to extend rendered constructor for a given class.-
propertyA method to extend rendered given property.property object as a ConstrainedObjectPropertyModel instance.
setterA method to extend setter for a given property.property object as a ConstrainedObjectPropertyModel instance.
getterA 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.

MethodDescriptionAdditional arguments
itemA 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