diff --git a/services-api/src/main/java/io/scalecube/services/Reflect.java b/services-api/src/main/java/io/scalecube/services/Reflect.java index 0cfe00471..854efd7fb 100644 --- a/services-api/src/main/java/io/scalecube/services/Reflect.java +++ b/services-api/src/main/java/io/scalecube/services/Reflect.java @@ -25,7 +25,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.reactivestreams.Publisher; @@ -220,10 +219,10 @@ private static Map transformArrayToMap(Tag[] array) { * @param serviceInterface with {@link Service} annotation * @return service name */ - public static Map serviceMethods(Class serviceInterface) { + public static Collection serviceMethods(Class serviceInterface) { return Arrays.stream(serviceInterface.getMethods()) .filter(method -> method.isAnnotationPresent(ServiceMethod.class)) - .collect(Collectors.toMap(Reflect::methodName, Function.identity())); + .toList(); } /** diff --git a/services-api/src/test/java/io/scalecube/services/methods/ServiceMethodInvokerTest.java b/services-api/src/test/java/io/scalecube/services/methods/ServiceMethodInvokerTest.java index 7aadff2a8..458041256 100644 --- a/services-api/src/test/java/io/scalecube/services/methods/ServiceMethodInvokerTest.java +++ b/services-api/src/test/java/io/scalecube/services/methods/ServiceMethodInvokerTest.java @@ -368,7 +368,11 @@ private static Consumer assertError(int errorCode, String errorM private static MethodInfo getMethodInfo(Object serviceInstance, String methodName) { final var serviceInstanceClass = serviceInstance.getClass(); final Class serviceInterface = Reflect.serviceInterfaces(serviceInstance).toList().get(0); - final var method = Reflect.serviceMethods(serviceInterface).get(methodName); + final var method = + Reflect.serviceMethods(serviceInterface).stream() + .filter(m -> m.getName().equals(methodName)) + .findFirst() + .get(); // get service instance method Method serviceMethod; diff --git a/services-gateway/src/test/java/io/scalecube/services/gateway/rest/ServiceRegistrationTest.java b/services-gateway/src/test/java/io/scalecube/services/gateway/rest/ServiceRegistrationTest.java new file mode 100644 index 000000000..379e99ce4 --- /dev/null +++ b/services-gateway/src/test/java/io/scalecube/services/gateway/rest/ServiceRegistrationTest.java @@ -0,0 +1,162 @@ +package io.scalecube.services.gateway.rest; + +import static io.scalecube.services.api.ServiceMessage.HEADER_REQUEST_METHOD; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; + +import io.scalecube.services.Microservices; +import io.scalecube.services.Microservices.Context; +import io.scalecube.services.annotations.RestMethod; +import io.scalecube.services.annotations.Service; +import io.scalecube.services.annotations.ServiceMethod; +import io.scalecube.services.api.ServiceMessage; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import reactor.core.publisher.Mono; + +public class ServiceRegistrationTest { + + @ParameterizedTest + @ValueSource( + classes = { + EchoService.class, + GoodRestService.class, + CreateRestService.class, + UpdateRestService.class + }) + void registerDuplicateService(Class serviceInterface) { + try (final var microservices = + Microservices.start( + new Context().services(mock(serviceInterface), mock(serviceInterface)))) { + fail("Expected exception"); + } catch (Exception e) { + assertInstanceOf(IllegalStateException.class, e, e::getMessage); + assertThat(e.getMessage(), Matchers.startsWith("MethodInvoker already exists")); + } + } + + @Test + void registerInvalidRestService() { + try (final var microservices = + Microservices.start(new Context().services(mock(BadRestService.class)))) { + fail("Expected exception"); + } catch (Exception e) { + assertInstanceOf(IllegalStateException.class, e, e::getMessage); + assertThat(e.getMessage(), Matchers.startsWith("MethodInvoker already exists")); + } + } + + @Test + void registerSingleValidRestService() { + try (final var microservices = + Microservices.start(new Context().services(mock(GoodRestService.class)))) { + final var serviceRegistry = microservices.serviceRegistry(); + + final var foo = System.nanoTime(); + final var methodInvokerWithoutRestMethod = + serviceRegistry.lookupInvoker( + ServiceMessage.builder().qualifier("v1/service/echo/" + foo).build()); + assertNull(methodInvokerWithoutRestMethod); + + final var methodInvokerByGet = + serviceRegistry.lookupInvoker( + ServiceMessage.builder() + .header(HEADER_REQUEST_METHOD, "GET") + .qualifier("v1/service/echo/" + foo) + .build()); + assertNotNull(methodInvokerByGet); + + final var methodInvokerByPost = + serviceRegistry.lookupInvoker( + ServiceMessage.builder() + .header(HEADER_REQUEST_METHOD, "POST") + .qualifier("v1/service/echo/" + foo) + .build()); + assertNotNull(methodInvokerByPost); + } + } + + @Test + void registerMultipleValidRestServices() { + try (final var microservices = + Microservices.start( + new Context().services(mock(CreateRestService.class), mock(UpdateRestService.class)))) { + final var serviceRegistry = microservices.serviceRegistry(); + + final var foo = System.nanoTime(); + final var methodInvokerWithoutRestMethod = + serviceRegistry.lookupInvoker( + ServiceMessage.builder().qualifier("v1/service/account/" + foo).build()); + assertNull(methodInvokerWithoutRestMethod); + + final var methodInvokerByPost = + serviceRegistry.lookupInvoker( + ServiceMessage.builder() + .header(HEADER_REQUEST_METHOD, "POST") + .qualifier("v1/service/account/" + foo) + .build()); + assertNotNull(methodInvokerByPost); + + final var methodInvokerByPut = + serviceRegistry.lookupInvoker( + ServiceMessage.builder() + .header(HEADER_REQUEST_METHOD, "PUT") + .qualifier("v1/service/account/" + foo) + .build()); + assertNotNull(methodInvokerByPut); + } + } + + @Service("v1/service") + interface EchoService { + + @ServiceMethod("get/:foo") + Mono echo(); + } + + @Service("v1/service") + interface BadRestService { + + @RestMethod("GET") + @ServiceMethod("get/:foo") + Mono echo(); + + @RestMethod("GET") + @ServiceMethod("get/:foo") + Mono ping(); + } + + @Service("v1/service") + interface GoodRestService { + + @RestMethod("GET") + @ServiceMethod("echo/:foo") + Mono echo(); + + @RestMethod("POST") + @ServiceMethod("echo/:foo") + Mono ping(); + } + + @Service("v1/service") + interface CreateRestService { + + @RestMethod("POST") + @ServiceMethod("account/:foo") + Mono account(); + } + + @Service("v1/service") + interface UpdateRestService { + + @RestMethod("PUT") + @ServiceMethod("account/:foo") + Mono account(); + } +} diff --git a/services/src/main/java/io/scalecube/services/ServiceScanner.java b/services/src/main/java/io/scalecube/services/ServiceScanner.java index 148a8c656..156b9a753 100644 --- a/services/src/main/java/io/scalecube/services/ServiceScanner.java +++ b/services/src/main/java/io/scalecube/services/ServiceScanner.java @@ -32,7 +32,7 @@ public static List toServiceRegistrations(ServiceInfo servi final var namespace = Reflect.serviceName(serviceInterface); final var methodDefinitions = - Reflect.serviceMethods(serviceInterface).values().stream() + Reflect.serviceMethods(serviceInterface).stream() .map( method -> { // validate method @@ -113,7 +113,7 @@ public static Collection collectServiceRoles( serviceInterface -> Reflect.serviceMethods(serviceInterface) .forEach( - (key, method) -> { + method -> { // validate method Reflect.validateMethodOrThrow(method); diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 4d84a9f16..abb04dcdd 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -152,7 +152,7 @@ public void registerService( serviceInterface -> Reflect.serviceMethods(serviceInterface) .forEach( - (key, method) -> { + method -> { // validate method Reflect.validateMethodOrThrow(method);