システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

typescript-generatorのメモ

参考リンク

GitHub - vojtechhabarta/typescript-generator: Generates TypeScript from Java - JSON declarations, REST service client

JavaのクラスからTypeScriptの型定義を生成するtypescript-generatorを使ってみた

typescript-generator カテゴリーの記事一覧 - 毎日へっぽこ

生成例

コントローラー

@Path("/users")
@EntryPoint
public class UserController {

  private SearchUser searchUser;

  @Inject
  UserController(SearchUser searchUser) {
    this.searchUser = searchUser;
  }

  @GetEntry
  @Schema(description = "ユーザー情報を検索します")
  @Path("{id}")
  public UserResourse getUsersById(@PathParam("id") String id) {
    var model = searchUser.findById(UserId.of(Integer.valueOf(id))).orElseThrow();
    return UserResourse.from(model);
  }

  @GetEntry
  @Schema(description = "ユーザー情報を全件取得します")
  public List<UserResourse> getUsers() {
    var models = searchUser.findAll();
    return UserResourse.from(models);
  }
}

レスポンスクラス

/**
 * UserResourse.
 */
@lombok.Builder(access = AccessLevel.PRIVATE)
@lombok.Data
public class UserResourse {

  private final Gender gender;
  private final Optional<Integer> id;
  private final Optional<String> name;

  static UserResourse from(User user) {
    var res = UserResourse.builder()
        .gender(user.getGender())
        .id(Optional.ofNullable(user.getUserId().getId()))
        .name(user.getName().getValue())
        .build();
    return res;
  }

  static List<UserResourse> from(Users users) {
    var resList = users.getItems().stream()
        .map(UserResourse::from)
        .sorted(Comparator.comparing(res -> res.getId().get(), nullsFirst(naturalOrder())))
        .collect(Collectors.toList());

    return resList;
  }
}

Enumクラス

@lombok.AllArgsConstructor
@lombok.Getter
public enum Gender {

  MALE("0"), FEMALE("1"), OTHER("2");

  private final String cd;

  public static final EnumReverseLookup<Gender, String> byCd
      = new EnumReverseLookup<>(Gender.class, Gender::getCd);

}

pom

<plugin>
    <groupId>cz.habarta.typescript-generator</groupId>
    <artifactId>typescript-generator-maven-plugin</artifactId>
    <version>${typescript-generator.version}</version>
    <executions>
        <execution>
            <id>typescript-generator-generate</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <phase>process-classes</phase>
        </execution>
    </executions>
    <configuration>
        <jsonLibrary>jackson2</jsonLibrary>
        <noFileComment>true</noFileComment>
        <optionalPropertiesDeclaration>questionMarkAndNullableType</optionalPropertiesDeclaration>
        <sortTypeDeclarations>true</sortTypeDeclarations>
        <outputKind>module</outputKind>
        <outputFileType>implementationFile</outputFileType>
        <generateJaxrsApplicationClient>true</generateJaxrsApplicationClient>
        <generateJaxrsApplicationInterface>true</generateJaxrsApplicationInterface>
        <namespace>rest</namespace>

        <extensions>
            <extension>org.vermeerlab.plugin.typescript.generator.ext.EnumToConstConverter</extension>
        </extensions>

        <classPatterns>
            <classPattern>**.presentation.**</classPattern>
        </classPatterns>
        <outputFile>type-script/rest.ts</outputFile>
    
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.verneermlab</groupId>
            <artifactId>ts-generator-plugin</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</plugin>

生成されたTypeScriptファイル

export namespace rest {

    export interface HttpClient {

        request<R>(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; }): RestResponse<R>;
    }

    export interface RestApplication {

        /**
         * HTTP GET /users
         * Java method: ee.sample.apps.context.user.presentation.UserController.getUsers
         */
        getUsers(): RestResponse<UserResourse[]>;

        /**
         * HTTP GET /users/{id}
         * Java method: ee.sample.apps.context.user.presentation.UserController.getUsersById
         */
        getUsersById(id: string): RestResponse<UserResourse>;
    }

    export class RestApplicationClient implements RestApplication {

        constructor(protected httpClient: HttpClient) {
        }

        /**
         * HTTP GET /users
         * Java method: ee.sample.apps.context.user.presentation.UserController.getUsers
         */
        getUsers(): RestResponse<UserResourse[]> {
            return this.httpClient.request({ method: "GET", url: uriEncoding`users` });
        }

        /**
         * HTTP GET /users/{id}
         * Java method: ee.sample.apps.context.user.presentation.UserController.getUsersById
         */
        getUsersById(id: string): RestResponse<UserResourse> {
            return this.httpClient.request({ method: "GET", url: uriEncoding`users/${id}` });
        }
    }

    export interface UserResourse {
        gender: Gender;
        id?: number | null;
        name?: string | null;
    }

    export interface UserResourseBuilder {
    }

    export type Gender = "MALE" | "FEMALE" | "OTHER";

    export type RestResponse<R> = Promise<R>;

    function uriEncoding(template: TemplateStringsArray, ...substitutions: any[]): string {
        let result = "";
        for (let i = 0; i < substitutions.length; i++) {
            result += template[i];
            result += encodeURIComponent(substitutions[i]);
        }
        result += template[template.length - 1];
        return result;
    }


    // Added by 'EnumToConstConverter' extension

    export const Gender = {
        MALE: "MALE",
        FEMALE: "FEMALE",
        OTHER: "OTHER",
    } as const;

}

org.vermeerlab.plugin.typescript.generator.ext.EnumToConstConverter
enumの変換用につくった拡張(別プロジェクトで作成して dependencyしているもののコード)
EE(Payara)で同一プロジェクトで準備をすると、起動時にエラーになるので別プロジェクトにしています(Jackson2あたりが競合している様子)

/**
 * Enumクラスからconstを作成します.
 */
public class EnumToConstConverter extends EmitterExtension {

  @Override
  public EmitterExtensionFeatures getFeatures() {
    var emitterExcensionFeatures = new EmitterExtensionFeatures();
    emitterExcensionFeatures.generatesRuntimeCode = true;
    return emitterExcensionFeatures;
  }

  @Override
  public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) {
    var exportStr = exportKeyword ? "export " : "";
    var tsEnums = model.getOriginalStringEnums();

    tsEnums.forEach((var tsEnum) -> {
      writer.writeIndentedLine("");

      var constName = new StringBuilder()
          .append(exportStr)
          .append("const ")
          .append(tsEnum.getName().getSimpleName())
          .append(" = {").toString();

      writer.writeIndentedLine(constName);

      tsEnum.getMembers().forEach((var member) -> {
        var memberProp = new StringBuilder()
            .append(settings.indentString)
            .append(member.getPropertyName())
            .append(": \"")
            .append(member.getEnumValue())
            .append("\",")
            .toString();
        writer.writeIndentedLine(memberProp);
      });

      writer.writeIndentedLine("} as const;");

    });
  }
}

その他

コードの全量はいずれ公開予定