GithubHelp home page GithubHelp logo

isabella232 / infobip-typescript-generator-extension Goto Github PK

View Code? Open in Web Editor NEW

This project forked from infobip/infobip-typescript-generator-extension

0.0 0.0 0.0 298 KB

Library which provides new features on top of TypeScript Generator.

License: Apache License 2.0

Java 99.01% TypeScript 0.99%

infobip-typescript-generator-extension's Introduction

Infobip TypeScript Generator Extension

Maven Central Coverage Status Known Vulnerabilities

Library which provides new features on top of TypeScript Generator: annotation processor support (which eliminates the requirement for a maven plugin) and bean validation Java annotations to TypeScript decorators translation.

Contents

  1. Bean Validation to class-validator translation
  2. Custom Validation to class-validator translation
  3. Localization.ts & CommonValidationMessages.ts
  4. Annotation processor
  5. Contributing
  6. License

Bean Validation to class-validator translation

Simple Object

Input:

@Value
public class Foo {

    @Size(min = 1, max = 2)
    @NotEmpty
    @NotNull
    @Valid
    private final String bar;

}

Result:

/* tslint:disable */
/* eslint-disable */
import { IsDefined, IsNotEmpty, MinLength, ValidateNested, MaxLength } from 'class-validator';
import { CommonValidationMessages } from './CommonValidationMessages';

export class Foo {
    @MaxLength(2, { message: CommonValidationMessages.MaxLength(2) })
    @MinLength(1, { message: CommonValidationMessages.MinLength(1) })
    @IsNotEmpty({ message: CommonValidationMessages.IsNotEmpty })
    @IsDefined({ message: CommonValidationMessages.IsDefined })
    @ValidateNested()
    bar: string;
}

Hierarchy

class-transformer together with Infobip Jackson Extension is used to handle hierarchies.

Example for a hierarchy is a multi level hierarchy for inbound and outbound messages.

Input:

@Getter
@AllArgsConstructor
enum Channel {
    SMS(InboundSmsMessage.class, OutboundSmsMessage.class);

    private final Class<? extends InboundMessage> inboundMessageType;
    private final Class<? extends OutboundMessage> outboundMessageType;
}

@Getter
@AllArgsConstructor
enum Direction implements TypeProvider {
    INBOUND(InboundMessage.class),
    OUTBOUND(OutboundMessage.class);

    private final Class<? extends Message> type;
}

@Getter
@AllArgsConstructor
public enum CommonContentType implements TypeProvider, ContentType {
    TEXT(TextContent.class);

    private final Class<? extends CommonContent> type;
}

@JsonTypeResolveWith(InboundMessageJsonTypeResolver.class)
interface InboundMessage extends Message {

    @Override
    default Direction getDirection() {
        return Direction.INBOUND;
    }

}

@JsonTypeResolveWith(MessageJsonTypeResolver.class)
interface Message {

    Direction getDirection();

    Channel getChannel();

}

@JsonTypeResolveWith(OutboundMessageJsonTypeResolver.class)
interface OutboundMessage extends Message {

    @Override
    default Direction getDirection() {
        return Direction.OUTBOUND;
    }

}

public interface CommonContent extends SimpleJsonHierarchy<CommonContentType>, Content<CommonContentType> {

}

public interface Content<T extends ContentType> {

    T getType();

}

public interface ContentType {

}

@Value
class TextContent implements CommonContent {

    @NotNull
    @NotEmpty
    private final String text;

    @Override
    public CommonContentType getType() {
        return CommonContentType.TEXT;
    }

}

@Value
class InboundSmsMessage implements InboundMessage {

    private final CommonContent content;

    @Override
    public Channel getChannel() {
        return Channel.SMS;
    }

}

@Value
class OutboundSmsMessage implements OutboundMessage {

    private final CommonContent content;

    @Override
    public Channel getChannel() {
        return Channel.SMS;
    }

}

Result:

/* tslint:disable */
/* eslint-disable */
import 'reflect-metadata';
import { Type } from 'class-transformer';
import { IsDefined, IsNotEmpty } from 'class-validator';
import { CommonValidationMessages } from './CommonValidationMessages';

export enum Channel {
    SMS = 'SMS',
}

export enum Direction {
    INBOUND = 'INBOUND',
    OUTBOUND = 'OUTBOUND',
}

export enum CommonContentType {
    TEXT = 'TEXT',
}

export interface InboundMessage extends Message {
}

export interface Message {
    channel: Channel;
    direction: Direction;
}

export interface OutboundMessage extends Message {
}

export interface CommonContent extends Content<CommonContentType> {
    type: CommonContentType;
}

export interface Content<T> {
    type: T;
}

export interface ContentType {
}

export class TextContent implements CommonContent {
    readonly type: CommonContentType = CommonContentType.TEXT;
    @IsDefined({ message: CommonValidationMessages.IsDefined })
    @IsNotEmpty({ message: CommonValidationMessages.IsNotEmpty })
    text: string;
}

export class InboundSmsMessage implements InboundMessage {
    readonly channel: Channel = Channel.SMS;
    direction: Direction;
    @Type(() => Object, {
        discriminator: {
            property: 'type', subTypes: [
                { value: TextContent, name: CommonContentType.TEXT }
            ]
        }
    })
    content: CommonContent;
}

export class OutboundSmsMessage implements OutboundMessage {
    readonly channel: Channel = Channel.SMS;
    direction: Direction;
    @Type(() => Object, {
        discriminator: {
            property: 'type', subTypes: [
                { value: TextContent, name: CommonContentType.TEXT }
            ]
        }
    })
    content: CommonContent;
}

Custom Validation to class-validator translation

@CustomTypeScriptDecorator

In order to link custom java validation annotation with appropriate decorator, java validation annotation must be marked @CustomTypeScriptDecorator annotation.

  • @CustomTypeScriptDecorator has:
    • typeScriptDecorator optional parameter:
      • if a provided annotation is going to be linked to a decorator with a specified name
      • else it is going to be linked to a decorator with the same name as an annotation
    • decoratorParameterListExtractor optional parameter:
      • provides an implementation of a class which extracts annotation parameters
      • provided class must implement DecoratorParameterListExtractor interface

Also in class which extends from TypeScriptFileGenerator two methods must be overridden:

  • getCustomValidationAnnotationSettings:
    • Which will provide settings needed for locating custom java annotations. Setting will provide name of java package from where annotation scanning will begin.
  • getCustomDecoratorBasePath:
    • Which will provide base path to TypeScript decorators

After providing the above information, TypeScriptFileGenerator will take a scan project for custom annotations and will perform logic to link annotations wit appropriate TypeScript decorators.

Limitations

  1. From class-validation only Custom Validation Decorators are supported, reason behind supporting only Custom Validation Decorators and not supporting [Custom Validation Classes](Custom Validation Decorators) as well, is that in first approach we are able to link custom java annotation with TypescriptDecorator by just looking at decorator's file name, while in second approach we would need to parse a whole file in order to find where is a decorator defined.
  2. TypeScript decorator file name and decorator must be the same, reason behind this is similar to previous point, if we decide to not follow given convention we would need to parse the whole Typescript file in order to find a given decorator. This would introduce additional complexity. This also means that one decorator is located in one TypeScript file.
  3. All decorators will be copied in dist location under validation folder.
  4. getCustomValidationAnnotationSettings must be overridden in class which extends from TypeScriptFileGenerator. This is done to restrict scope of ** ClassGraph** annotation scanning. By default, ClassGraph will scan all classes in the class path and will try to extract annotations from them, if restriction is not performed given operation could result in OutOfMemoryError.

Example

Annotation implementation:

@CustomTypeScriptDecorator(
    typeScriptDecorator = "ComplexValidator",
    decoratorParameterListExtractor = DecoratorParameterListExtractorImpl.class,
    type = ComplexCustomValidation.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ComplexCustomValidator.class)
public @interface ComplexCustomValidation {

    String message() default "must be valid element";

    int length();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Input:

@Value
public class Foo {

    @ComplexCustomValidation(length = 100)
    private final String bar;

}

Result:

/* tslint:disable */
/* eslint-disable */
import { CommonValidationMessages } from './CommonValidationMessages';
import { localize } from './Localization';

export class Foo {
    @ComplexValidator(100, { message: localize('must be valid element') })
    bar: string;
}

Localization.ts & CommonValidationMessages.ts

Running TypeScript Generator Extension will result in two additional files:

  • Localization.ts
    • Blueprint for Localization functionality
  • CommonValidationMessages.ts
    • Blueprint for common validation messages

After initial generation, this two files can be changed in order to adjust custom validation messages, or to implement a way how localization is supported. After manually making changes, you can tell TypeScript Generator Extension not to generate this files and more in the future. You can achieve this by overriding:

  • writeCommonValidationMessagesTypeScriptFile for CommonValidationMessages.ts:
    • @Override
      protected void writeCommonValidationMessagesTypeScriptFile(String code, Path filePath) {}
  • writeLocalization Localization.ts:
    • @Override
      protected void writeLocalization(String code, Path filePath) {}

Annotation processor

Disclaimer: in order for annotation processor to work model classes and generator configuration have to be compiled before annotation processor is run. In practice this means that they have to be in separate modules.

Main advantage of this approach: easier extension, reusability and no requirement to run Maven to generate TypeScript!

Most, if not all, options available to TypeScript Generator Maven Plugin are also available to the annotation processor.

Setup:

  1. In Maven module where Java model is defined add the following dependency:

    <dependency>
       <groupId>com.infobip</groupId>
       <artifactId>infobip-typescript-generator-extension-api</artifactId>
       <version>${infobip-typescript-generator-extension.version}</version>
    </dependency>
  2. Configure the generator by extending TypeScriptFileGenerator:

    public class SimpleTypeScriptFileGenerator extends TypeScriptFileGenerator {
    
        public SimpleTypeScriptFileGenerator(Path basePath) {
            super(basePath);
        }
    
        @Override
        public Input getInput() {
            return Input.from(Foo.class);
        }
    
        @Override
        public Path outputFilePath(Path basePath) {
            Path lib = basePath.getParent().getParent().resolve("dist");
    
            try {
                Files.createDirectories(lib);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
    
            return lib.resolve("Simple.ts");
        }
    
        @Override
         protected Path getDecoratorBasePath() {
             return getBasePath().getParent().getParent().resolve("src/main/typescript/decorators");
         }
    }
  3. Custom java validation annotations must be marked with @CustomTSDecorator annotation. If a custom validation annotation name is not the same as a decorator name, you can specify decorator by using typeScriptDecorator annotation property.

  4. Project only supports class-validator custom decorators custom decorators

  5. By overriding getDecoratorBasePath() you are specifying path to typescript decorators which relates to custom java validations:

         @Override
         protected Path getDecoratorBasePath() {
             return getBasePath().getParent().getParent().resolve("src/main/typescript/decorators");
         }
  6. Define a separate module where annotation processing will occur (this module depends on model module) with following dependency:

    <dependency>
       <groupId>com.infobip</groupId>
       <artifactId>infobip-typescript-generator-extension-api</artifactId>
       <version>${infobip-typescript-generator-extension.version}</version>
    </dependency>
  7. Add the annotation configuration class (this is only used to trigger the annotation processing with annotation):

    @GenerateTypescript(generator = SimpleTypeScriptFileGenerator.class)
    public class SimpleTypeScriptFileGeneratorConfiguration {
    }

For more complex examples look at infobip-typescript-generator-extension-model-showcase and at infobip-typescript-generator-extension-annotation-processor-showcase.

Generated typescript can be seen in dist folder. In production you'd probably add dist to .gitignore, here it's not mainly to be used a an showcase of how the end result looks like.

Since there's no maven plugin it's possible to run TypeScript Generator with multiple different configurations in same project! Aforementioned showcase folders use this to test and showcase different parts of functionality.

Contributing

If you have an idea for a new feature or want to report a bug please use the issue tracker.

Pull requests are welcome!

License

This library is licensed under the Apache License, Version 2.0.

infobip-typescript-generator-extension's People

Contributors

lpandzic avatar marek-sontag avatar msertic1 avatar sebue avatar slawekib avatar slo avatar snyk-bot avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.