Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller and interfaces with generic methods result in wrong generated client #1025

Open
SomMeri opened this issue Sep 11, 2023 · 1 comment

Comments

@SomMeri
Copy link

SomMeri commented Sep 11, 2023

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

}
@SomMeri
Copy link
Author

SomMeri commented Sep 11, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant