Skip to content
This repository has been archived by the owner on May 6, 2021. It is now read-only.

Latest commit

 

History

History
343 lines (292 loc) · 10.7 KB

typescript-endpoints-generator.asciidoc

File metadata and controls

343 lines (292 loc) · 10.7 KB
title order layout
Appendix: TypeScript Endpoints Generator
120
page

Appendix: TypeScript Endpoints Generator

The TypeScript generator produces TypeScript files based on the information from an OpenApi document which is generated from Java files in src/main/java folder by default.

Note

Vaadin uses OpenAPI Specification as a middle layer between Java endpoints and TypeScript endpoint clients. The current implementation is based on OpenAPI specification 3.0. For details, please refer to the appendix at the end of this page.

Examples

A simple generated TypeScript files will look like the following snippet:

UserEndpoint.ts
/**
 * User endpoints.
 *
 * This module has been generated from UserEndpoints.java
 * @module UserEndpoints
 */
import client from './connect-client.default';

/**
 * Check if a user is admin or not.
 *
 * @param id User id to be checked
 * Return Return true if the given user is an admin, otherwise false.
 */
export async function isAdmin(
  id: number
) {
  return await client.call('UserEndpoints', 'isAdmin', {id});
}

The import client from './connect-client.default' is a static part of any generated file. connect-client.default.ts is another generated file which includes default configurations for the ConnectClient and exports its instance as client.

Each method in the generated modules is corresponding to a Java method in @Endpoint annotated classes. For example, the following Java code is corresponding to the generated UserEndpoints.ts:

UserEndpoint.ts
/**
 * User endpoints.
 */
@Endpoint
public class UserEndpoints {
    /**
     * Check if a user is admin or not.
     *
     * @param id
     *            User id to be checked
     * @return Return true if the given user is an admin, otherwise false.
     */
    public boolean isAdmin(long id) {
        return id == 0;
    }
}

By default, all Java types are mapped and generated to a non-nullable type in TypeScript. Therefore, returning a null value to TypeScript in a non-optional endpoint method causes a validation exception in runtime.

Note
For more information about type mapping between Java and TypeScript, please refer to type conversion page.

Optional values

If a parameter, a bean property or a method in Java method is annotated with @Nullable or has Optional type, the corresponding TypeScript code is declared as optional.

Optional parameter

Optional parameter in Java endpoint
public void setName(String firstName, String lastName, @Nullable String middleName) {
    // omitted code
}

public void setNameOptional(String firstName, String lastName, Optional<String> middleName) {
    // omitted code
}
Generated optional parameter in TypeScript
export async function setName(
  firstName: string,
  lastName: string,
  middleName?: string
) {
  return await client.call('UserEndpoints', 'setName', {firstName, lastName, middleName});
}

export async function setNameOptional(
  firstName: string,
  lastName: string,
  middleName?: string
) {
  return await client.call('UserEndpoints', 'setNameOptional', {firstName, lastName, middleName});
}

In this case, if user calls UserEndpoints.setName('first', 'last') in TypeScript, the middle name is omitted and set as null when receiving in Java UserEndpoints.setName method. If the JavaType is Optional, the missing parameter is Optional.empty();

Optional property

Optional properties in Java
public class MyBean {
    private long id;
    private String value;
    @Nullable
    private String description;
    private Optional<String> optionalDescription;
}
Generated optional properties in TypeScript
export default interface MyBean {
  id: number;
  value: string;
  description?: string;
  optionalDescription?: string;
}

Optional return type

Optional return type in Java
@Nullable
public String getPhoneNumber() {
    // omitted code
}

public Optional<String> getPhoneNumberOptional() {
    // omitted code
}
Generated optional return type in TypeScript
export async function getPhoneNumber() {
  return await client.call('UserEndpoints', 'getPhoneNumber');
}

export async function getPhoneNumberOptional() {
  return await client.call('UserEndpoints', 'getPhoneNumberOptional');
}

Appendix: how the generator generate TypeScript from OpenAPI specification.

Modules

The generator will collect all the tags field of all operations in the OpenAPI document. Each tag will generate a corresponding TypeScript file. The tag name is used for TypeScript module name as well as the file name. TsDoc of the class will be fetched from description field of the tag object which has the same name as the class.

Methods

Each exported method in a module is corresponding to a POST operation of a path item in paths object.

Note

Currently, the generator only supports POST operation. If a path item contains other operations than POST, the generator will stop processing.

The path must start with / as described in Patterned Fields. It is parsed as /<endpoint name>/<method name> which are used as parameters to call to Java endpoints in the backend. Method name from the path is also reused as the method name in the generated TypeScript file.

Method’s Parameters

Parameters of the method are taken from the application/json content of request body object. To get the result as [UserEndpoint.ts], the request body content should be:

Request Body
{
 "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "User id to be checked"
          }
        }
      }
    }
  }
}

Type and description of each property are used for TsDoc that describes the parameter in more details.

Note

All the other content types of request body object are not ignored by the Vaadin Generator. It means that without the application/json content type, the method is considered as a no parameter one.

Method’s Return Type

Return type and its description are taken from the 200 response object. As same as request body object, the generator is only interested at application/json content type. The schema type indicates the return type and the description describes the result. Here is an example of a responses objects:

Responses Object
{
  "200": {
    "description": "Return true if the given user is an admin, otherwise false.",
    "content": {
      "application/json": {
        "schema": {
          "type": "boolean"
        }
      }
    }
  }
}
Note

At this point, the generator only takes the advantage of 200 response objects. Other response objects are ignored.

Method’s TsDoc

The TsDoc of the generated method is stored as description value of the POST operation in path item. A valid POST operation combined with [request-body] and [response-object] would look like:

Post Operation
{
  "tags": ["UserEndpoint"], // (1)
  "description": "Check if a user is admin or not.",
  "requestBody": {
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "id": {
              "type": "number",
              "description": "User id to be checked"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Return true if the given user is an admin, otherwise false.",
      "content": {
        "application/json": {
          "schema": {
            "type": "boolean"
          }
        }
      }
    }
  }
}
  1. As mentioned in operation object specification, in Vaadin Generator, tags are used to classify operations into TypeScript files. It means each tag will have a corresponding generated TypeScript file. The operations, which contain more than one tag, will appear in all generated files. Empty tags operations will be placed in Default.ts file.

Note

Although multiple tags do not break the generator, it might be confusing in the development time when there are two exact same methods in different TypeScript files. It is recommended to have only one tag per operation.

Here is an example OpenAPI document which could generate the above [UserEndpoint.ts].

User endpoint OpenApi document
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "My example application",
    "version" : "1.0.0"
  },
  "servers" : [ {
    "url" : "https://myhost.com/myendpoint",
    "description" : "Vaadin backend server"
  } ],
  "tags" : [ {
    "name" : "UserEndpoint",
    "description" : "User endpoint class."
  } ],
  "paths" : {
    "/UserEndpoint/isAdmin" : {
      "post": {
        "tags": ["UserEndpoint"],
        "description": "Check if a user is admin or not.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [ "id" ]
                "properties": {
                  "id": {
                    "type": "number",
                    "description": "User id to be checked"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return true if the given user is an admin, otherwise false.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      }
    }
  }
}