Skip to content
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
422 changes: 181 additions & 241 deletions README.md

Large diffs are not rendered by default.

616 changes: 126 additions & 490 deletions customer-service-client/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion customer-service-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.bsayli</groupId>
<artifactId>customer-service-client</artifactId>
<version>0.6.8</version>
<version>0.7.0</version>
<name>customer-service-client</name>
<description>Generated client (RestClient) using generics-aware OpenAPI templates</description>
<packaging>jar</packaging>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package io.github.bsayli.openapi.client.adapter;

import io.github.bsayli.openapi.client.common.Page;
import io.github.bsayli.openapi.client.common.ServiceClientResponse;
import io.github.bsayli.openapi.client.generated.dto.*;
import io.github.bsayli.openapi.client.common.sort.SortDirection;
import io.github.bsayli.openapi.client.common.sort.SortField;
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
import io.github.bsayli.openapi.client.generated.dto.CustomerDeleteResponse;
import io.github.bsayli.openapi.client.generated.dto.CustomerDto;
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateRequest;

public interface CustomerClientAdapter {
ServiceClientResponse<CustomerCreateResponse> createCustomer(CustomerCreateRequest request);

ServiceClientResponse<CustomerDto> createCustomer(CustomerCreateRequest request);

ServiceClientResponse<CustomerDto> getCustomer(Integer customerId);

ServiceClientResponse<CustomerListResponse> getCustomers();
ServiceClientResponse<Page<CustomerDto>> getCustomers();

ServiceClientResponse<Page<CustomerDto>> getCustomers(
String name,
String email,
Integer page,
Integer size,
SortField sortBy,
SortDirection direction);

ServiceClientResponse<CustomerUpdateResponse> updateCustomer(
ServiceClientResponse<CustomerDto> updateCustomer(
Integer customerId, CustomerUpdateRequest request);

ServiceClientResponse<CustomerDeleteResponse> deleteCustomer(Integer customerId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
package io.github.bsayli.openapi.client.adapter.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.bsayli.openapi.client.common.error.ClientProblemException;
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
import io.github.bsayli.openapi.client.generated.dto.ProblemDetail;
import io.github.bsayli.openapi.client.generated.invoker.ApiClient;
import java.time.Duration;
import java.util.List;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

@Configuration
public class CustomerApiClientConfig {

@Bean
RestClientCustomizer problemDetailStatusHandler(ObjectMapper om) {
return builder ->
builder.defaultStatusHandler(
HttpStatusCode::isError,
(request, response) -> {
ProblemDetail pd = null;
try (var is = response.getBody()) {
pd = om.readValue(is, ProblemDetail.class);
} catch (Exception ignore) {
}
throw new ClientProblemException(pd, response.getStatusCode().value());
});
}

@Bean(destroyMethod = "close")
CloseableHttpClient customerHttpClient(
@Value("${customer.api.max-connections-total:64}") int maxTotal,
Expand Down Expand Up @@ -51,8 +72,14 @@ HttpComponentsClientHttpRequestFactory customerRequestFactory(

@Bean
RestClient customerRestClient(
RestClient.Builder builder, HttpComponentsClientHttpRequestFactory customerRequestFactory) {
return builder.requestFactory(customerRequestFactory).build();
RestClient.Builder builder,
HttpComponentsClientHttpRequestFactory customerRequestFactory,
List<RestClientCustomizer> customizers) {
builder.requestFactory(customerRequestFactory);
if (customizers != null) {
customizers.forEach(c -> c.customize(builder));
}
return builder.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,64 @@
package io.github.bsayli.openapi.client.adapter.impl;

import io.github.bsayli.openapi.client.adapter.CustomerClientAdapter;
import io.github.bsayli.openapi.client.common.Page;
import io.github.bsayli.openapi.client.common.ServiceClientResponse;
import io.github.bsayli.openapi.client.common.sort.SortDirection;
import io.github.bsayli.openapi.client.common.sort.SortField;
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse;
import io.github.bsayli.openapi.client.generated.dto.CustomerDeleteResponse;
import io.github.bsayli.openapi.client.generated.dto.CustomerDto;
import io.github.bsayli.openapi.client.generated.dto.CustomerListResponse;
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateRequest;
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateResponse;
import io.github.bsayli.openapi.client.generated.dto.*;
import org.springframework.stereotype.Service;

@Service
public class CustomerClientAdapterImpl implements CustomerClientAdapter {

private final CustomerControllerApi customerControllerApi;
private final CustomerControllerApi api;

public CustomerClientAdapterImpl(CustomerControllerApi customerControllerApi) {
this.customerControllerApi = customerControllerApi;
this.api = customerControllerApi;
}

@Override
public ServiceClientResponse<CustomerCreateResponse> createCustomer(
CustomerCreateRequest request) {
return customerControllerApi.createCustomer(request);
public ServiceClientResponse<CustomerDto> createCustomer(CustomerCreateRequest request) {
return api.createCustomer(request);
}

@Override
public ServiceClientResponse<CustomerDto> getCustomer(Integer customerId) {
return customerControllerApi.getCustomer(customerId);
return api.getCustomer(customerId);
}

@Override
public ServiceClientResponse<CustomerListResponse> getCustomers() {
return customerControllerApi.getCustomers();
public ServiceClientResponse<Page<CustomerDto>> getCustomers() {
return getCustomers(null, null, 0, 5, SortField.CUSTOMER_ID, SortDirection.ASC);
}

@Override
public ServiceClientResponse<CustomerUpdateResponse> updateCustomer(
public ServiceClientResponse<Page<CustomerDto>> getCustomers(
String name,
String email,
Integer page,
Integer size,
SortField sortBy,
SortDirection direction) {

return api.getCustomers(
name,
email,
page,
size,
sortBy != null ? sortBy.value() : SortField.CUSTOMER_ID.value(),
direction != null ? direction.value() : SortDirection.ASC.value());
}

@Override
public ServiceClientResponse<CustomerDto> updateCustomer(
Integer customerId, CustomerUpdateRequest request) {
return customerControllerApi.updateCustomer(customerId, request);
return api.updateCustomer(customerId, request);
}

@Override
public ServiceClientResponse<CustomerDeleteResponse> deleteCustomer(Integer customerId) {
return customerControllerApi.deleteCustomer(customerId);
return api.deleteCustomer(customerId);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.bsayli.openapi.client.common;

import io.github.bsayli.openapi.client.common.sort.ClientSort;
import java.time.Instant;
import java.util.List;

public record ClientMeta(Instant serverTime, List<ClientSort> sort) {
public ClientMeta {
sort = (sort == null) ? List.of() : List.copyOf(sort);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.bsayli.openapi.client.common;

import java.util.List;

public record Page<T>(
List<T> content,
int page,
int size,
long totalElements,
int totalPages,
boolean hasNext,
boolean hasPrev) {

public Page {
content = (content == null) ? List.of() : List.copyOf(content);
}
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,21 @@
package io.github.bsayli.openapi.client.common;

import java.util.List;
import java.util.Objects;

public class ServiceClientResponse<T> {

private Integer status;
private String message;
private List<ClientErrorDetail> errors;
private T data;
private ClientMeta meta;

public ServiceClientResponse() {}

public ServiceClientResponse(
Integer status, String message, List<ClientErrorDetail> errors, T data) {
this.status = status;
this.message = message;
this.errors = errors;
public ServiceClientResponse(T data, ClientMeta meta) {
this.data = data;
this.meta = meta;
}

public static <T> ServiceClientResponse<T> from(
Integer status, String message, List<ClientErrorDetail> errors, T data) {
return new ServiceClientResponse<>(status, message, errors, data);
}

public Integer getStatus() {
return status;
}

public void setStatus(Integer status) {
this.status = status;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public List<ClientErrorDetail> getErrors() {
return errors;
}

public void setErrors(List<ClientErrorDetail> errors) {
this.errors = errors;
public static <T> ServiceClientResponse<T> of(T data, ClientMeta meta) {
return new ServiceClientResponse<>(data, meta);
}

public T getData() {
Expand All @@ -57,33 +26,28 @@ public void setData(T data) {
this.data = data;
}

public ClientMeta getMeta() {
return meta;
}

public void setMeta(ClientMeta meta) {
this.meta = meta;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ServiceClientResponse<?> that)) return false;
return Objects.equals(status, that.status)
&& Objects.equals(message, that.message)
&& Objects.equals(errors, that.errors)
&& Objects.equals(data, that.data);
return Objects.equals(data, that.data) && Objects.equals(meta, that.meta);
}

@Override
public int hashCode() {
return Objects.hash(status, message, errors, data);
return Objects.hash(data, meta);
}

@Override
public String toString() {
return "ServiceClientResponse{"
+ "status="
+ status
+ ", message='"
+ message
+ '\''
+ ", errors="
+ errors
+ ", data="
+ data
+ '}';
return "ServiceClientResponse{data=" + data + ", meta=" + meta + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.bsayli.openapi.client.common.error;

import io.github.bsayli.openapi.client.generated.dto.ProblemDetail;
import java.io.Serial;
import java.io.Serializable;

/**
* Wraps non-2xx HTTP responses decoded into RFC7807-style {@link ProblemDetail}. Thrown by
* RestClient defaultStatusHandler in client config.
*/
public class ClientProblemException extends RuntimeException implements Serializable {

@Serial private static final long serialVersionUID = 1L;

private final transient ProblemDetail problem;
private final int status;

public ClientProblemException(ProblemDetail problem, int status) {
super(buildMessage(problem, status));
this.problem = problem;
this.status = status;
}

public ClientProblemException(ProblemDetail problem, int status, Throwable cause) {
super(buildMessage(problem, status), cause);
this.problem = problem;
this.status = status;
}

private static String buildMessage(ProblemDetail pd, int status) {
if (pd == null) {
return "HTTP " + status + " (no problem body)";
}
StringBuilder sb = new StringBuilder("HTTP ").append(status);
if (pd.getTitle() != null && !pd.getTitle().isBlank()) {
sb.append(" - ").append(pd.getTitle());
}
if (pd.getDetail() != null && !pd.getDetail().isBlank()) {
sb.append(" | ").append(pd.getDetail());
}
if (pd.getErrorCode() != null && !pd.getErrorCode().isBlank()) {
sb.append(" [code=").append(pd.getErrorCode()).append(']');
}
return sb.toString();
}

public ProblemDetail getProblem() {
return problem;
}

public int getStatus() {
return status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.bsayli.openapi.client.common.sort;

public record ClientSort(SortField field, SortDirection direction) {

public ClientSort {
if (field == null) {
field = SortField.CUSTOMER_ID;
}
if (direction == null) {
direction = SortDirection.ASC;
}
}
}
Loading