Skip to content

Commit

Permalink
feat: allow rpc calls (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-thorel-of authored Oct 4, 2024
1 parent 7921c65 commit ae69589
Show file tree
Hide file tree
Showing 19 changed files with 591 additions and 64 deletions.
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,78 @@ public class UserService {

> Bulk Operations are allowed on `Post` ,`Patch`, `Delete` and `Upsert`
#### Rpc Calls

Supports of [rpc function calls](https://postgrest.org/en/v12/references/api/functions.html#functions-as-rpc)

**Configuration**
Its use a PostgrestRpcClient which use the PostgrestClient

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.ouestfrance.querydsl.postgrest.PostgrestClient;
import fr.ouestfrance.querydsl.postgrest.PostgrestWebClient;
import fr.ouestfrance.querydsl.postgrest.PostgrestRpcClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class PostgrestConfiguration {

@Bean
public PostgrestRpcClient rpcClient() {
String serviceUrl = "http://localhost:9000";
WebClient webclient = WebClient.builder()
.baseUrl(serviceUrl)
// Here you can add any filters or default configuration you want
.build();

return new PostgrestRpcClient(PostgrestWebClient.of(webclient));
}
}
```

then you can call your rpc method using this call

```java
public class Example{
private PostgrestRpcClient rpcClient;

public List<Coordinate> getCoordinates(){
// call getCoordinates_v1 and expect to return a list of Coordinates
return rpcClient.exectureRpc("getCoordinates_v1", TypeUtils.parameterize(List.class, Coordinate.class));
// CALL => ${base_url}/rpc/getCoordinates_v1
}

public Coordinate getCoordinate(Point point){
// call findClosestCoordinate_v1 with body {x:?, y:?}
// expect to return a single coordinate
return rpcClient.executeRpc("findClosestCoordinate_v1", point, Coordinate.class);
// CALL => ${base_url}/rpc/findClosestCoordinate_v1
// => with body {x: point.x, y: point.y}
}

public SimpleCoordinate getCoordinateX(Point point){
// call findClosestCoordinate_v1 with body {x:?, y:?}
// add Criteria that add select=x,y and z=gte.0.0
// and return the result as a SimpleCoordinate class
return rpcClient.executeRpc("findClosestCoordinate_v1", new CoordinateCriteria(0.0), point, SimpleCoordinate.class);
// CALL => ${base_url}/rpc/findClosestCoordinate_v1?z=gte.0.0&select=x,y
// => with body {x: point.x, y: point.y}
}


@Select({"x", "y"})
record CoordinateCriteria(
@FilterField(key = "z", operation = FilterOperation.GTE.class)
private Float z
){}

record SimpleCoordinate(Float x, Float y){}
}
```

## Need Help ?

If you need help with the library please start a new thread QA / Issue on github
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import fr.ouestfrance.querydsl.postgrest.model.RangeResponse;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
Expand All @@ -15,6 +16,7 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.lang.reflect.Type;
import java.net.URI;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -66,7 +68,7 @@ public <T> RangeResponse<T> search(String resource, Map<String, List<String>> pa
}

@Override
public <T> BulkResponse<T> post(String resource, Map<String, List<String>> params, List<Object> value, Map<String, List<String>> headers, Class<T> clazz) {
public <T> BulkResponse<T> post(String resource, Map<String, List<String>> params, Object value, Map<String, List<String>> headers, Class<T> clazz) {
ResponseEntity<List<T>> response = restTemplate.exchange(
getUri(resource, params),
HttpMethod.POST, new HttpEntity<>(value, toHeaders(headers)), listRef(clazz));
Expand Down Expand Up @@ -95,6 +97,12 @@ public List<CountItem> count(String resource, Map<String, List<String>> map) {
getUri(resource, map), HttpMethod.GET, new HttpEntity<>(null, new HttpHeaders()), listRef(CountItem.class)).getBody();
}

@Override
public <V> V rpc(String rpcName, Map<String, List<String>> map, Object body, Type type) {
return (V) restTemplate.exchange(
getUri(rpcName, map), HttpMethod.POST, new HttpEntity<>(body, new HttpHeaders()), ParameterizedTypeReference.forType(type)).getBody();
}


/**
* Convert map to MultiValueMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import fr.ouestfrance.querydsl.postgrest.model.Page;
import fr.ouestfrance.querydsl.postgrest.model.Pageable;
import lombok.SneakyThrows;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -31,13 +32,16 @@
class PostgrestRestTemplateRepositoryTest {

private PostgrestRepository<Post> repository;
private PostgrestRpcClient rpcClient;

@BeforeEach
void beforeEach(MockServerClient client) {
client.reset();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()));
repository = new PostRepository(PostgrestRestTemplate.of(restTemplate, "http://localhost:8007"));
PostgrestRestTemplate postgrestRestTemplate = PostgrestRestTemplate.of(restTemplate, "http://localhost:8007");
repository = new PostRepository(postgrestRestTemplate);
rpcClient = new PostgrestRpcClient(postgrestRestTemplate);
}

@Test
Expand Down Expand Up @@ -185,6 +189,37 @@ void shouldDeleteBulkPost(MockServerClient client) {
assertTrue(result.isEmpty());
}


@Test
void shouldCallRpc(MockServerClient client) {
client.when(HttpRequest.request().withPath("/rpc/testV1"))
.respond(jsonResponse("""
{"id": 1, "title": "test"}
"""));

Post result = rpcClient.executeRpc("testV1", null, Post.class);
assertNotNull(result);
System.out.println(result);
}


@Test
void shouldCallRpcResultIsList(MockServerClient client) {
client.when(HttpRequest.request().withPath("/rpc/testV1"))
.respond(jsonResponse("""
[{"id": 1, "title": "test"}]
"""));

List<Post> result = rpcClient.executeRpc("testV1", null, TypeUtils.parameterize(List.class, Post.class));
assertNotNull(result);
System.out.println(result);
}

private HttpResponse jsonResponse(String content) {
return HttpResponse.response().withContentType(MediaType.APPLICATION_JSON)
.withBody(content);
}

private HttpResponse jsonFileResponse(String resourceFileName) {
return HttpResponse.response().withContentType(MediaType.APPLICATION_JSON)
.withBody(jsonOf(resourceFileName));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package fr.ouestfrance.querydsl.postgrest;

import fr.ouestfrance.querydsl.postgrest.app.Post;
import fr.ouestfrance.querydsl.postgrest.app.PostRequestWithSelect;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.client.MockServerClient;
import org.mockserver.junit.jupiter.MockServerSettings;
import org.mockserver.model.HttpRequest;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.List;

import static fr.ouestfrance.querydsl.postgrest.TestUtils.jsonResponse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@MockServerSettings(ports = 8007)
class PostgrestRpcTest {

private PostgrestRpcClient rpcClient;

@BeforeEach
void beforeEach(MockServerClient client) {
client.reset();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()));
PostgrestRestTemplate postgrestRestTemplate = PostgrestRestTemplate.of(restTemplate, "http://localhost:8007");
rpcClient = new PostgrestRpcClient(postgrestRestTemplate);
}

@Test
void shouldCallRpc(MockServerClient client) {
client.when(HttpRequest.request().withPath("/rpc/testV1"))
.respond(jsonResponse("""
{"id": 1, "title": "test"}
"""));
Post result = rpcClient.executeRpc("testV1", Post.class);
assertNotNull(result);
}

@Test
void shouldCallRpcWithCriteria(MockServerClient client) {
client.when(HttpRequest.request().withPath("/rpc/testV1")
.withQueryStringParameter("userId", "eq.1")
.withQueryStringParameter("select", "title,userId"))
.respond(jsonResponse("""
{"id": 1, "title": "test"}
"""));

PostRequestWithSelect criteria = new PostRequestWithSelect();
criteria.setUserId(1);
Post result = rpcClient.executeRpc("testV1", criteria,null, Post.class);
assertNotNull(result);
System.out.println(result);
}

@Test
void shouldCallRpcWithCriteriaResultIsList(MockServerClient client) {
client.when(HttpRequest.request().withPath("/rpc/testV1")
.withQueryStringParameter("userId", "eq.1")
.withQueryStringParameter("select", "title,userId"))
.respond(jsonResponse("""
[{"id": 1, "title": "test"}]
"""));

PostRequestWithSelect criteria = new PostRequestWithSelect();
criteria.setUserId(1);
List<Post> result = rpcClient.executeRpc("testV1", criteria, null, TypeUtils.parameterize(List.class, Post.class));
assertNotNull(result);
System.out.println(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fr.ouestfrance.querydsl.postgrest;

import lombok.SneakyThrows;
import org.mockserver.model.HttpResponse;
import org.mockserver.model.MediaType;
import shaded_package.org.apache.commons.io.IOUtils;

import java.nio.charset.Charset;

import static org.mockserver.model.HttpResponse.response;

public class TestUtils {

public static HttpResponse jsonResponse(String content) {
return HttpResponse.response().withContentType(MediaType.APPLICATION_JSON)
.withBody(content);
}


public static HttpResponse jsonFileResponse(String resourceFileName) {
return response().withContentType(MediaType.APPLICATION_JSON)
.withBody(jsonOf(resourceFileName));
}

@SneakyThrows
public static String jsonOf(String name) {
return IOUtils.resourceToString(name, Charset.defaultCharset(), TestUtils.class.getClassLoader());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fr.ouestfrance.querydsl.postgrest.app;

import fr.ouestfrance.querydsl.FilterField;
import fr.ouestfrance.querydsl.FilterOperation;
import fr.ouestfrance.querydsl.postgrest.annotations.Select;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Select({"title", "userId"})
public class PostRequestWithSelect {

@FilterField
private Integer userId;
@FilterField(operation = FilterOperation.NEQ.class)
private Integer id;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import fr.ouestfrance.querydsl.postgrest.model.RangeResponse;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
Expand All @@ -14,6 +15,8 @@
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -83,9 +86,29 @@ public List<CountItem> count(String resource, Map<String, List<String>> params)
return Optional.ofNullable(response).map(HttpEntity::getBody).orElse(List.of());
}

@Override
public <V> V rpc(String rpcName, Map<String, List<String>> params, Object body, Type clazz) {
WebClient.RequestBodySpec request = webClient.post().uri(uriBuilder -> {
uriBuilder.path(rpcName);
uriBuilder.queryParams(toMultiMap(params));
return uriBuilder.build();
});
if(body != null){
request.bodyValue(body);
}
Object result = request
.retrieve()
.bodyToMono(ParameterizedTypeReference.forType(clazz))
.block();
if(result != null) {
return (V) result;
}
return null;
}


@Override
public <T> BulkResponse<T> post(String resource, Map<String, List<String>> params, List<Object> value, Map<String, List<String>> headers, Class<T> clazz) {
public <T> BulkResponse<T> post(String resource, Map<String, List<String>> params, Object value, Map<String, List<String>> headers, Class<T> clazz) {
ResponseEntity<List<T>> response = webClient.post().uri(uriBuilder -> {
uriBuilder.path(resource);
uriBuilder.queryParams(toMultiMap(params));
Expand Down Expand Up @@ -119,7 +142,8 @@ public <T> BulkResponse<T> delete(String resource, Map<String, List<String>> par
uriBuilder.path(resource);
uriBuilder.queryParams(toMultiMap(params));
return uriBuilder.build();
}).headers(httpHeaders -> safeAdd(headers, httpHeaders))
})
.headers(httpHeaders -> safeAdd(headers, httpHeaders))
.retrieve()
.toEntity(listRef(clazz)).block();
return toBulkResponse(response);
Expand Down
Loading

0 comments on commit ae69589

Please sign in to comment.