Skip to content

Latest commit

 

History

History
603 lines (457 loc) · 25.4 KB

presets.md

File metadata and controls

603 lines (457 loc) · 25.4 KB

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:

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; }
}

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:

{
  // `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.

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.

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; }
}

Ap/pre-pending to existng rendered content

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; }
}

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:

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!"
      }
    }
  ]
});

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.

...
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').

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:

const DEFAULT_PRESET = {
  // implementation
}

const 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.

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.

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.

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