vert-x3 / vertx-rx

Reactive Extensions for Vert.x
Apache License 2.0
147 stars 73 forks source link

Generated code for abstract interfaces pushes towards using the Future API #315

Open tsegismont opened 1 month ago

tsegismont commented 1 month ago

In various places of the Vert.x codebase, we have abstract interfaces. For example:

/**
 * A {@link HttpProxy} interceptor.
 */
@VertxGen(concrete = false)
public interface ProxyInterceptor {

  /**
   * Handle the proxy request at the stage of this interceptor.
   *
   * @param context the proxy context
   * @return when the request has actually been sent to the origin
   */
  default Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
    return context.sendRequest();
  }

  /**
   * Handle the proxy response at the stage of this interceptor.
   *
   * @param context the proxy context
   * @return when the response has actually been sent to the user-agent
   */
  default Future<Void> handleProxyResponse(ProxyContext context) {
    return context.sendResponse();
  }

  /**
   * Used to set whether to apply the interceptor to the WebSocket
   * handshake packet. The default value is false.
   * @return the boolean value
   */
  default boolean allowApplyToWebSocket() {
    return false;
  }
}

The generated code is:

@RxGen(io.vertx.httpproxy.ProxyInterceptor.class)
public interface ProxyInterceptor {

  default io.vertx.httpproxy.ProxyInterceptor getDelegate() {
    return new io.vertx.httpproxy.ProxyInterceptor() {
      @Override
      public Future<io.vertx.httpproxy.ProxyResponse> handleProxyRequest(io.vertx.httpproxy.ProxyContext context) {
        return ProxyInterceptor.this.handleProxyRequest();
      }

      @Override
      public Future<Void> handleProxyResponse(io.vertx.httpproxy.ProxyContext context) {
        return io.vertx.httpproxy.ProxyInterceptor.super.handleProxyResponse(context);
      }

      @Override
      public boolean allowApplyToWebSocket() {
        return io.vertx.httpproxy.ProxyInterceptor.super.allowApplyToWebSocket();
      }
    };
  }

  /**
   * Handle the proxy request at the stage of this interceptor.
   * @param context the proxy context
   * @return when the request has actually been sent to the origin
   */
  public io.reactivex.rxjava3.core.Single<io.vertx.rxjava3.httpproxy.ProxyResponse> handleProxyRequest(io.vertx.rxjava3.httpproxy.ProxyContext context);

  /**
   * Handle the proxy request at the stage of this interceptor.
   * @param context the proxy context
   * @return when the request has actually been sent to the origin
   */
  public io.reactivex.rxjava3.core.Single<io.vertx.rxjava3.httpproxy.ProxyResponse> rxHandleProxyRequest(io.vertx.rxjava3.httpproxy.ProxyContext context);

  /**
   * Handle the proxy response at the stage of this interceptor.
   * @param context the proxy context
   * @return when the response has actually been sent to the user-agent
   */
  public io.reactivex.rxjava3.core.Completable handleProxyResponse(io.vertx.rxjava3.httpproxy.ProxyContext context);

  /**
   * Handle the proxy response at the stage of this interceptor.
   * @param context the proxy context
   * @return when the response has actually been sent to the user-agent
   */
  public io.reactivex.rxjava3.core.Completable rxHandleProxyResponse(io.vertx.rxjava3.httpproxy.ProxyContext context);

  /**
   * Used to set whether to apply the interceptor to the WebSocket
   * handshake packet. The default value is false.
   * @return the boolean value
   */
  public boolean allowApplyToWebSocket();

  public static ProxyInterceptor newInstance(io.vertx.httpproxy.ProxyInterceptor arg) {
    return arg != null ? new ProxyInterceptorImpl(arg) : null;
  }

}

class ProxyInterceptorImpl implements ProxyInterceptor {
  private final io.vertx.httpproxy.ProxyInterceptor delegate;

  public ProxyInterceptorImpl(io.vertx.httpproxy.ProxyInterceptor delegate) {
    this.delegate = delegate;
  }

  public ProxyInterceptorImpl(Object delegate) {
    this.delegate = (io.vertx.httpproxy.ProxyInterceptor)delegate;
  }

  public io.vertx.httpproxy.ProxyInterceptor getDelegate() {
    return delegate;
  }

  private static final TypeArg<io.vertx.rxjava3.httpproxy.ProxyResponse> TYPE_ARG_0 = new TypeArg<io.vertx.rxjava3.httpproxy.ProxyResponse>(o1 -> io.vertx.rxjava3.httpproxy.ProxyResponse.newInstance((io.vertx.httpproxy.ProxyResponse)o1), o1 -> o1.getDelegate());

  /**
   * Handle the proxy request at the stage of this interceptor.
   * @param context the proxy context
   * @return when the request has actually been sent to the origin
   */
  public io.reactivex.rxjava3.core.Single<io.vertx.rxjava3.httpproxy.ProxyResponse> handleProxyRequest(io.vertx.rxjava3.httpproxy.ProxyContext context) {
    io.reactivex.rxjava3.core.Single<io.vertx.rxjava3.httpproxy.ProxyResponse> ret = rxHandleProxyRequest(context);
    ret = ret.cache();
    ret.subscribe(io.vertx.rxjava3.SingleHelper.nullObserver());
    return ret;
  }

  /**
   * Handle the proxy request at the stage of this interceptor.
   * @param context the proxy context
   * @return when the request has actually been sent to the origin
   */
  public io.reactivex.rxjava3.core.Single<io.vertx.rxjava3.httpproxy.ProxyResponse> rxHandleProxyRequest(io.vertx.rxjava3.httpproxy.ProxyContext context) {
    return AsyncResultSingle.toSingle(() -> delegate.handleProxyRequest(context.getDelegate()), __value -> io.vertx.rxjava3.httpproxy.ProxyResponse.newInstance((io.vertx.httpproxy.ProxyResponse)__value));
  }

  /**
   * Handle the proxy response at the stage of this interceptor.
   * @param context the proxy context
   * @return when the response has actually been sent to the user-agent
   */
  public io.reactivex.rxjava3.core.Completable handleProxyResponse(io.vertx.rxjava3.httpproxy.ProxyContext context) {
    io.reactivex.rxjava3.core.Completable ret = rxHandleProxyResponse(context);
    ret = ret.cache();
    ret.subscribe(io.vertx.rxjava3.CompletableHelper.nullObserver());
    return ret;
  }

  /**
   * Handle the proxy response at the stage of this interceptor.
   * @param context the proxy context
   * @return when the response has actually been sent to the user-agent
   */
  public io.reactivex.rxjava3.core.Completable rxHandleProxyResponse(io.vertx.rxjava3.httpproxy.ProxyContext context) {
    return AsyncResultCompletable.toCompletable(() -> delegate.handleProxyResponse(context.getDelegate()));
  }

  /**
   * Used to set whether to apply the interceptor to the WebSocket
   * handshake packet. The default value is false.
   * @return the boolean value
   */
  public boolean allowApplyToWebSocket() {
    boolean ret = delegate.allowApplyToWebSocket();
    return ret;
  }

}

The generated code pushes users towards using the Future API: the user must invoke io.vertx.rxjava3.httpproxy.ProxyInterceptor#newInstance and implement a ProxyInterceptor with the bare API.

The same problem can be seen with Mutiny bindings, and is of course not limited to ProxyInterceptor.

tsegismont commented 1 month ago

Perhaps we could generate a default implementation for the getDelegate() method:

  default io.vertx.httpproxy.ProxyInterceptor getDelegate() {
    return new io.vertx.httpproxy.ProxyInterceptor() {
      @Override
      public Future<io.vertx.httpproxy.ProxyResponse> handleProxyRequest(io.vertx.httpproxy.ProxyContext context) {
        Single<ProxyResponse> single = ProxyInterceptor.this.handleProxyRequest(ProxyContext.newInstance(context));
        // Adapt to Future<ProxyResponse>
        return single;
      }

      @Override
      public Future<Void> handleProxyResponse(io.vertx.httpproxy.ProxyContext context) {
        Completable completable = ProxyInterceptor.this.rxHandleProxyResponse(ProxyContext.newInstance(context));
        // Adapt to Future<Void>
        return completable;
      }

      @Override
      public boolean allowApplyToWebSocket() {
        return ProxyInterceptor.this.allowApplyToWebSocket();
      }
    };
  }

And then the user could create a Rx ProxyInterceptor that is compatible with a Rx ReverseProxy.

Thoughts @vietj @jponge ?

vietj commented 1 month ago

I think such interface should not be code generated actually, they are not meant to be used they are meant to extend the library.

vietj commented 1 month ago

so we could remove VertxGen for such cases

tsegismont commented 1 month ago

It's arguable that implementing a ProxyInterceptor is not extending the HTTP Proxy, but one of its core usages.

Can you elaborate about why we shouldn't change the generated code for getDelegate()?