feign-reactive icon indicating copy to clipboard operation
feign-reactive copied to clipboard

Generalized target-type Inference

Open rjbgaspar opened this issue 2 years ago • 1 comments

Hi,

I do not understand why we cannot set the returned target type on a method…

This is my feign reactive interface for accessing the Fargo API

package com.gv.zeppelin.service.fargo;

import com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO;
import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;

@Headers({"Accept: application/json"})
public interface FargoApi {
    @RequestLine("GET /api/v1/query/{slug}")
    <T> Mono<T> queryForList(@Param("slug") String slug,
                             @QueryMap MultiValueMap<String, Object> queryMap,
                             Class<T> elementClass);

    @RequestLine("GET /api/v1/query/{slug}")
    Mono<ACL247StarterProcessDTO> queryForList(@Param("slug") String slug,
                                               @QueryMap MultiValueMap<String, Object> queryMap);
}

This is my test class

package com.gv.zeppelin.service.fargo;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gv.zeppelin.service.dto.XConverterRequestDTO;
import com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO;
import com.gv.zeppelin.service.fargo.query.Query;
import com.gv.zeppelin.service.fargo.query.QuerySlug;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactivefeign.webclient.WebReactiveFeign;
import reactor.test.StepVerifier;

import java.util.List;


class FargoApiTest {
    private static ObjectMapper baseConfig = new ObjectMapper();
    private static FargoApi fargoApi;
    private static XConverterRequestDTO xConverterRequestDTO = new XConverterRequestDTO();

    @BeforeAll
    public static void beforeAll() {
        var builder = fargoWebClient(baseConfig);

        fargoApi = WebReactiveFeign
            .<FargoApi>builder(builder)
            .target(FargoApi.class, "http://172.16.9.5:50010");

        xConverterRequestDTO.setWorkOrders(List.of("ZPH-V121-00122"));
        xConverterRequestDTO.setWorkOrders(List.of("MFG-V121-008485", "MFG-V121-008486"));
    }

    private static WebClient.Builder fargoWebClient(ObjectMapper baseConfig) {
        return WebClient.builder()
            .exchangeStrategies(ExchangeStrategies.builder()
                .codecs(FargoApiTest::configureClientCodecs)
                .build()
            );
    }

    private static void configureClientCodecs(ClientCodecConfigurer configurer) {
        configurer
            .defaultCodecs()
            .maxInMemorySize(16 * 1024 * 1024);

        configurer
            .defaultCodecs()
            .jackson2JsonEncoder(
                new Jackson2JsonEncoder(baseConfig, MediaType.APPLICATION_JSON));
        configurer
            .defaultCodecs()
            .jackson2JsonDecoder(
                new Jackson2JsonDecoder(baseConfig, MediaType.APPLICATION_JSON));
    }

    /**
     * Works
     */
    @Test
    public void queryForList() {
        var mono  = fargoApi.queryForList(
            QuerySlug.ERP_SAGE_X3_ACL_247_STARTER_PROCESSOR.value(),
            Query.toQueryMap(xConverterRequestDTO)
        );

        StepVerifier.create(mono)
            .expectNextMatches(response -> response.getData().size() == 1)
            .verifyComplete();
    }

    /**
     * Do not works
     */
    @Test
    public void queryForListWithGeneralizedTargetType() {
        var mono  = fargoApi.queryForList(
            QuerySlug.ERP_SAGE_X3_ACL_247_STARTER_PROCESSOR.value(),
            Query.toQueryMap(xConverterRequestDTO),
            ACL247StarterProcessDTO.class
        );

        StepVerifier.create(mono)
            .expectNextMatches(response -> response.getData().size() == 1)
            .verifyComplete();
    }
}

This is the stack trace ouput by the queryForListWithGeneralizedTargetType test

2022-03-19 15:14:32.099 DEBUG   --- [           main] r.client.log.DefaultReactiveLogger       : [FargoApi#queryForList(String,MultiValueMap,Class)]--->GET http://172.16.9.5:50010/api/v1/query/erp-sage-x3-acl-247-starter-processor?manufacturingPlan=&workOrder=MFG-V121-008485&workOrder=MFG-V121-008486&tag= HTTP/1.1
2022-03-19 15:14:33.333 DEBUG   --- [actor-tcp-nio-2] r.client.log.DefaultReactiveLogger       : [FargoApi#queryForList(String,MultiValueMap,Class)]<--- headers takes 1234 milliseconds

java.lang.AssertionError: expectation failed (failed running expectation on signal [onNext({data=[{order_number=ENC-V121/00929, order_line=6000, order_sequence=6000, manufacturing_plan=ZPH-V121-00122, kit_number=KIT-V121-02113, work_order=MFG-V121-008486, planned_quantity=1.0, product=0021237, description=Porta I19V1G Favo Mdf 28 + Ptx 3 OF Faia Vap 2000.0x400.0x35.0, exterior_color=FV, interior_color=FV, opening_side=ESQR, access=IN, access_val_1=IN, access_val_2=push, ariporta_type=AB01A.LM.2, product_family_code=PI0, product_family_description=, length=2000.0, width=400.0, thickness=35.0, expected_lot=ZLT-0000001921, glass_open_type=G, hinge_quantity=3, lock_height=0, edge_shape_t=1, edge_shape_description_t=0° S/boleado (lacagem), edge_shape_r=2, edge_shape_description_r=0° C/boleado (standard), edge_shape_b=1, edge_shape_description_b=0° S/boleado (lacagem), edge_shape_l=2, edge_shape_description_l=0° C/boleado (standard), edge_shape_mechanization_t={"rebated_width":0,"rebated_depth":0}, edge_shape_mechanization_r={"rebated_width":0,"rebated_depth":0}, edge_shape_mechanization_b={"rebated_width":0,"rebated_depth":0}, edge_shape_mechanization_l={"rebated_width":0,"rebated_depth":0}, hinges=[{"type":"hinge","item":"0009195","description":"400 Rev.Esq NI","model":"Q37","macro_name":"zConcealedHinge1","macro_value":"\/\/ 400\ndouble hingePositionY = openingSide.ToUpper().StartsWith(\"ESQ\") ? DZ - (7.5 + 20 \/ 2) : (7.5 + 20 \/ 2);\ndouble hingeAngle = 0;\ndouble hingeLength1 = 100;\ndouble hingeWidth = 20;\ndouble hingeRadius = 10;\ndouble hingeDepth1 = 2.6;\ndouble hingeInputFeed = 0;\ndouble hingeFeed = 0;\ndouble hingeRotation = 0;\nint hingeHood = 0;\nstring hingeTool = \"031\";\nstring hingeHead = \"1\";\nstring hingeFace = \"Back\";"}]}]})] with [java.lang.ClassCastException]:
class java.util.LinkedHashMap cannot be cast to class com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO is in unnamed module of loader 'app'))

	at reactor.test.DefaultStepVerifierBuilder.failPrefix(DefaultStepVerifierBuilder.java:2171)
	at reactor.test.DefaultStepVerifierBuilder.fail(DefaultStepVerifierBuilder.java:2167)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1252)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onNext(DefaultStepVerifierBuilder.java:905)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:249)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295)
	at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
	at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159)
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
	at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400)
	at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419)
	at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:473)
	at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702)
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:327)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:299)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:722)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)
	Suppressed: java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.gv.zeppelin.service.dto.fargo.ACL247StarterProcessDTO is in unnamed module of loader 'app')
		at reactor.test.DefaultStepVerifierBuilder.lambda$expectNextMatches$10(DefaultStepVerifierBuilder.java:430)
		at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:1909)
		at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1270)
		at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1215)
		... 46 more

Is it possible to make it work?

Thanks in advance.

rjbgaspar avatar Mar 19 '22 15:03 rjbgaspar

We support only features that are present in OpenFeign. I doubt if OpenFeign have this feature.

kptfh avatar Mar 29 '22 15:03 kptfh