vojtechhabarta / typescript-generator

Generates TypeScript from Java - JSON declarations, REST service client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Controller and interfaces with generic methods result in wrong generated client

SomMeri opened this issue · comments

I have a controller that inherits form a generic interface:

public interface GenericInterface<T extends MyInterface> {
  void doSomething(T thing);
}

The controller itself is using a concrete implemenations of the interface:

  public void doSomething(@RequestBody MyDto thing) {}

This results in two different client methods generated by typescript generator:

    doSomething$GET$something(arg0: MyDto): RestResponse<void> { }
    doSomething$GET$something(arg0: MyInterface): RestResponse<void> { }

However, in java, the controller is able to deal only with MyDto instances. The controller is NOT accepting an arbitrary MyInterface instance. The doSomething method is available only once - with the MyDto parameter.

How to reproduce

// data structures
public interface MyInterface {} 
public class MyDto implements MyInterface {}


// controller and its interface
public interface GenericInterface<T extends MyInterface> {
  void doSomething(T thing);
}

@RequestMapping("/something")
@RestController
public class ConcreteController implements GenericInterface<MyDto> {

  @GetMapping
  @Override
  public void doSomething(@RequestBody MyDto thing) {

  }
}

Main class:

public static void main(String[] args) {
    Settings settings = new Settings();
    settings.outputKind = TypeScriptOutputKind.module;
    settings.outputFileType = TypeScriptFileType.implementationFile;
    settings.jsonLibrary = JsonLibrary.jackson2;
    settings.generateSpringApplicationClient = true;
    settings.generateSpringApplicationInterface = true;

    TypeScriptGenerator generator = new TypeScriptGenerator(settings);

    String result = generator.generateTypeScript(
      Input.from(ConcreteController.class)
    );

    System.out.println(result);
  }

Actual Result

export class RestApplicationClient implements RestApplication {

    constructor(protected httpClient: HttpClient) {
    }

    /**
     * HTTP GET /something
     * Java method: com.meri.ConcreteController.doSomething
     */
    doSomething$GET$something(arg0: MyDto): RestResponse<void> {
        return this.httpClient.request({ method: "GET", url: uriEncoding`something`, data: arg0 });
    }

    /**
     * HTTP GET /something
     * Java method: com.meri.ConcreteController.doSomething
     */
    doSomething$GET$something(arg0: MyInterface): RestResponse<void> {
        return this.httpClient.request({ method: "GET", url: uriEncoding`something`, data: arg0 });
    }
}

Expected Result

export class RestApplicationClient implements RestApplication {

    constructor(protected httpClient: HttpClient) {
    }

    /**
     * HTTP GET /something
     * Java method: com.meri.ConcreteController.doSomething
     */
    doSomething(arg0: MyDto): RestResponse<void> {
        return this.httpClient.request({ method: "GET", url: uriEncoding`something`, data: arg0 });
    }

}

Workaround

Create an extension so that typescript generator ignores bridge methods:

class WorkaroundExtension extends Extension {

  public List<TransformerDefinition> getTransformers() {
    return Arrays.asList(new TransformerDefinition(ModelCompiler.TransformationPhase.BeforeTsModel, new Transformer()));
  }

  @Override
  public EmitterExtensionFeatures getFeatures() {
    return new EmitterExtensionFeatures();
  }
}

class Transformer implements ModelTransformer {
  public Model transformModel(SymbolTable symbolTable, Model model) {

    List<RestApplicationModel> newRestApplications = model.getRestApplications().stream().map(restApplication ->
      new RestApplicationModel(
        restApplication.getType(),
        restApplication.getApplicationPath(),
        restApplication.getApplicationName(),
        restApplication.getMethods().stream().filter(it -> !it.getOriginalMethod().isBridge()).collect(Collectors.toList()))
    ).collect(Collectors.toList());

    return new Model(model.getBeans(), model.getEnums(), newRestApplications);
  }
}

And configure it:

  public static void main(String[] args) {
    Settings settings = new Settings();
    settings.outputKind = TypeScriptOutputKind.module;
    settings.outputFileType = TypeScriptFileType.implementationFile;
    settings.jsonLibrary = JsonLibrary.jackson2;
    settings.generateSpringApplicationClient = true;
    settings.generateSpringApplicationInterface = true;
    settings.extensions.add(new WorkaroundExtension()); //<-- here

    TypeScriptGenerator generator = new TypeScriptGenerator(settings);

    String result = generator.generateTypeScript(
      Input.from(ConcreteController.class)
    );

    System.out.println(result);
  }