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

feat: allow rpc calls #44

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading