Skip to content

Commit 76cab73

Browse files
committed
refactor(client): migrate to io.github.bsayli namespace and update docs
- Moved all packages from com.example.demo.client → io.github.bsayli.openapi.client - Updated pom.xml with new groupId and OpenAPI generator package paths - Adjusted api_wrapper.mustache to use FQN base class - Fixed OpenAPI spec (201 response, server URL) for alignment with demo service - Updated README.md to reflect new package structure and usage examples - Replaced generated-client-wrapper.png with updated image reflecting new namespace
1 parent f1b2001 commit 76cab73

File tree

12 files changed

+107
-70
lines changed

12 files changed

+107
-70
lines changed

customer-service-client/README.md

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
# customer-service-client
22

3-
Generated Java client for the demo **customer-service**, showcasing **type‑safe generic responses** with OpenAPI + a tiny custom template (wrapping payloads in a reusable `ApiClientResponse<T>`).
3+
Generated Java client for the demo **customer-service**, showcasing **type‑safe generic responses** with OpenAPI + a custom Mustache template (wrapping payloads in a reusable `ApiClientResponse<T>`).
44

5-
---
6-
7-
## ✅ What you get
8-
9-
* Generated code using **OpenAPI Generator** (`restclient` with Spring Framework `RestClient`)
10-
* A thin wrapper class per endpoint (e.g. `ApiResponseCustomerCreateResponse`) that **extends**:
5+
This module demonstrates how to evolve OpenAPI Generator with minimal customization to support generic response envelopes — avoiding duplicated wrappers and preserving strong typing.
116

12-
* `src/main/java/com/example/demo/client/common/ApiClientResponse.java`
13-
* Minimal Spring wiring to expose the generated API as beans:
7+
---
148

15-
* `com.example.demo.client.adapter.config.CustomerApiClientConfig`
16-
* A focused integration test with **OkHttp MockWebServer**:
9+
## ✅ What You Get
1710

18-
* `com.example.demo.client.adapter.CustomerClientIT`
11+
* Generated code using **OpenAPI Generator** (`restclient` with Spring Framework `RestClient`).
12+
* A reusable generic base: `io.github.bsayli.openapi.client.common.ApiClientResponse<T>`.
13+
* Thin wrappers per endpoint (e.g. `ApiResponseCustomerCreateResponse`) that extend the base.
14+
* Spring Boot configuration to auto‑expose the client as beans.
15+
* A focused integration test using **OkHttp MockWebServer**.
1916

2017
---
2118

22-
## 🧪 Quick pipeline (3 steps)
19+
## 🚀 Quick Pipeline (3 Steps)
2320

2421
1. **Run the sample service**
2522

@@ -37,118 +34,158 @@ curl -s http://localhost:8084/customer/v3/api-docs.yaml \
3734
-o src/main/resources/customer-api-docs.yaml
3835
```
3936

40-
> You can also skip this if the spec is already checked in.
41-
4237
3. **Generate & build the client**
4338

4439
```bash
4540
mvn clean install
4641
```
4742

48-
Generated sources will land under:
43+
Generated sources will be placed under:
4944

5045
```
5146
target/generated-sources/openapi/src/gen/java/main
5247
```
5348

5449
---
5550

56-
## 🚀 Using the client in your application
51+
## 🧩 Using the Client
5752

58-
### Option A — Spring configuration (recommended)
53+
### Option A — Spring Configuration (recommended)
5954

60-
Add this module as a dependency and set the base URL. The module contributes a small configuration:
55+
Include this module as a dependency and configure the base URL:
6156

6257
```java
6358
@Configuration
6459
public class CustomerApiClientConfig {
60+
6561
@Bean
6662
public RestClient customerRestClient(RestClient.Builder builder,
6763
@Value("${customer.api.base-url}") String baseUrl) {
6864
return builder.baseUrl(baseUrl).build();
6965
}
7066

7167
@Bean
72-
public com.example.demo.client.generated.invoker.ApiClient customerApiClient(
68+
public io.github.bsayli.openapi.client.generated.invoker.ApiClient customerApiClient(
7369
RestClient customerRestClient,
7470
@Value("${customer.api.base-url}") String baseUrl) {
75-
return new com.example.demo.client.generated.invoker.ApiClient(customerRestClient)
71+
return new io.github.bsayli.openapi.client.generated.invoker.ApiClient(customerRestClient)
7672
.setBasePath(baseUrl);
7773
}
7874

7975
@Bean
80-
public com.example.demo.client.generated.api.CustomerControllerApi customerControllerApi(
81-
com.example.demo.client.generated.invoker.ApiClient apiClient) {
82-
return new com.example.demo.client.generated.api.CustomerControllerApi(apiClient);
76+
public io.github.bsayli.openapi.client.generated.api.CustomerControllerApi customerControllerApi(
77+
io.github.bsayli.openapi.client.generated.invoker.ApiClient apiClient) {
78+
return new io.github.bsayli.openapi.client.generated.api.CustomerControllerApi(apiClient);
8379
}
8480
}
8581
```
8682

87-
**Configure the base URL** in your app:
83+
**application.properties:**
8884

8985
```properties
9086
customer.api.base-url=http://localhost:8084/customer
9187
```
9288

93-
**Call the API**:
89+
**Usage example:**
9490

9591
```java
9692
@Autowired
97-
private com.example.demo.client.generated.api.CustomerControllerApi customerApi;
93+
private io.github.bsayli.openapi.client.generated.api.CustomerControllerApi customerApi;
9894

9995
public void createCustomer() {
100-
var req = new com.example.demo.client.generated.dto.CustomerCreateRequest()
96+
var req = new io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest()
10197
.name("Jane Doe")
10298
.email("jane@example.com");
10399

104100
var resp = customerApi.create(req); // ApiResponseCustomerCreateResponse
101+
105102
System.out.println(resp.getStatus()); // 201
106103
System.out.println(resp.getData().getCustomer().getName()); // "Jane Doe"
107104
}
108105
```
109106

110-
### Option B — Manual wiring (no Spring context)
107+
### Option B — Manual Wiring (no Spring context)
111108

112109
```java
113110
var rest = RestClient.builder().baseUrl("http://localhost:8084/customer").build();
114-
var apiClient = new com.example.demo.client.generated.invoker.ApiClient(rest)
111+
var apiClient = new io.github.bsayli.openapi.client.generated.invoker.ApiClient(rest)
115112
.setBasePath("http://localhost:8084/customer");
116-
var customerApi = new com.example.demo.client.generated.api.CustomerControllerApi(apiClient);
113+
var customerApi = new io.github.bsayli.openapi.client.generated.api.CustomerControllerApi(apiClient);
114+
```
115+
116+
---
117+
118+
## 🧩 Adapter Pattern Example
119+
120+
For larger applications, encapsulate the generated API in an adapter:
121+
122+
```java
123+
package io.github.bsayli.openapi.client.adapter.impl;
124+
125+
import io.github.bsayli.openapi.client.adapter.CustomerClientAdapter;
126+
import io.github.bsayli.openapi.client.common.ApiClientResponse;
127+
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
128+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
129+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse;
130+
import org.springframework.stereotype.Service;
131+
132+
@Service
133+
public class CustomerClientAdapterImpl implements CustomerClientAdapter {
134+
135+
private final CustomerControllerApi customerControllerApi;
136+
137+
public CustomerClientAdapterImpl(CustomerControllerApi customerControllerApi) {
138+
this.customerControllerApi = customerControllerApi;
139+
}
140+
141+
@Override
142+
public ApiClientResponse<CustomerCreateResponse> create(CustomerCreateRequest request) {
143+
return customerControllerApi.create(request);
144+
}
145+
}
117146
```
118147

148+
This ensures:
149+
150+
* Generated code stays isolated.
151+
* Business code depends only on the adapter interface.
152+
119153
---
120154

121-
## 🧩 How the generics work
155+
## 🧩 How the Generics Work
122156

123-
The template at `src/main/resources/openapi-templates/api_wrapper.mustache` emits thin wrappers like:
157+
The template at `src/main/resources/openapi-templates/api_wrapper.mustache` emits wrappers like:
124158

125159
```java
160+
import io.github.bsayli.openapi.client.common.ApiClientResponse;
161+
126162
// e.g., ApiResponseCustomerCreateResponse
127163
public class ApiResponseCustomerCreateResponse
128-
extends com.example.demo.client.common.ApiClientResponse<CustomerCreateResponse> { }
164+
extends ApiClientResponse<CustomerCreateResponse> {
165+
}
129166
```
130167

131-
Only `api_wrapper.mustache` is customized for this demo; **all other models** use the stock templates/behavior.
168+
Only this Mustache partial is customized. All other models use stock templates.
132169

133170
---
134171

135172
## 🧪 Tests
136173

137-
Run the integration-style test with MockWebServer:
174+
Integration test with MockWebServer:
138175

139176
```bash
140177
mvn -q -DskipITs=false test
141178
```
142179

143-
It enqueues a `201` response and asserts mapping into `ApiResponseCustomerCreateResponse`.
180+
It enqueues a `201` response and asserts correct mapping into `ApiResponseCustomerCreateResponse`.
144181

145182
---
146183

147184
## 📚 Notes
148185

149-
* Dependencies like `spring-web`, `spring-context`, `jackson-*`, `jakarta.*` are marked **provided**. Your host app supplies them.
186+
* Dependencies like `spring-web`, `spring-context`, `jackson-*`, `jakarta.*` are marked **provided**; your host app supplies them.
150187
* Generator options: Spring 6 `RestClient`, Jakarta EE, Jackson, Java 21.
151-
* OpenAPI spec path used by the build:
188+
* OpenAPI spec path:
152189

153190
```
154191
src/main/resources/customer-api-docs.yaml
@@ -158,4 +195,4 @@ src/main/resources/customer-api-docs.yaml
158195

159196
## 🛡 License
160197

161-
This repository is licensed under **MIT** (root `LICENSE`). Submodules don’t duplicate license files; the root license applies.
198+
This repository is licensed under **MIT** (root `LICENSE`). Submodules inherit the license.

customer-service-client/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
55
<modelVersion>4.0.0</modelVersion>
66

7-
<groupId>com.example.demo</groupId>
7+
<groupId>io.github.bsayli</groupId>
88
<artifactId>customer-service-client</artifactId>
99
<version>0.1.0</version>
1010
<name>customer-service-client</name>
@@ -124,9 +124,9 @@
124124
<output>${project.build.directory}/generated-sources/openapi</output>
125125

126126
<!-- Packages for generated code -->
127-
<apiPackage>com.example.demo.client.generated.api</apiPackage>
128-
<modelPackage>com.example.demo.client.generated.dto</modelPackage>
129-
<invokerPackage>com.example.demo.client.generated.invoker</invokerPackage>
127+
<apiPackage>io.github.bsayli.openapi.client.generated.api</apiPackage>
128+
<modelPackage>io.github.bsayli.openapi.client.generated.dto</modelPackage>
129+
<invokerPackage>io.github.bsayli.openapi.client.generated.invoker</invokerPackage>
130130

131131
<!-- Use our custom templates that implement generics-aware wrappers -->
132132
<templateDirectory>${project.basedir}/src/main/resources/openapi-templates</templateDirectory>

customer-service-client/src/main/java/com/example/demo/client/adapter/CustomerClientAdapter.java

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.bsayli.openapi.client.adapter;
2+
3+
import io.github.bsayli.openapi.client.common.ApiClientResponse;
4+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
5+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse;
6+
7+
public interface CustomerClientAdapter {
8+
ApiClientResponse<CustomerCreateResponse> create(CustomerCreateRequest request);
9+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
package com.example.demo.client.adapter.config;
1+
package io.github.bsayli.openapi.client.adapter.config;
22

3-
import com.example.demo.client.generated.api.CustomerControllerApi;
4-
import com.example.demo.client.generated.invoker.ApiClient;
3+
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
4+
import io.github.bsayli.openapi.client.generated.invoker.ApiClient;
55
import org.springframework.beans.factory.annotation.Value;
66
import org.springframework.context.annotation.Bean;
77
import org.springframework.context.annotation.Configuration;
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
package com.example.demo.client.adapter.impl;
1+
package io.github.bsayli.openapi.client.adapter.impl;
22

3-
import com.example.demo.client.adapter.CustomerClientAdapter;
4-
import com.example.demo.client.common.ApiClientResponse;
5-
import com.example.demo.client.generated.api.CustomerControllerApi;
6-
import com.example.demo.client.generated.dto.CustomerCreateRequest;
7-
import com.example.demo.client.generated.dto.CustomerCreateResponse;
3+
import io.github.bsayli.openapi.client.adapter.CustomerClientAdapter;
4+
import io.github.bsayli.openapi.client.common.ApiClientResponse;
5+
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
6+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
7+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse;
88
import org.springframework.stereotype.Service;
99

1010
@Service
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.example.demo.client.common;
1+
package io.github.bsayli.openapi.client.common;
22

33
public record ApiClientError(String errorCode, String message) {
44
}

customer-service-client/src/main/java/com/example/demo/client/common/ApiClientResponse.java renamed to customer-service-client/src/main/java/io/github/bsayli/openapi/client/common/ApiClientResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.example.demo.client.common;
1+
package io.github.bsayli.openapi.client.common;
22

33
import java.util.List;
44
import java.util.Objects;

customer-service-client/src/main/resources/customer-api-docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ info:
44
description: "Demo: type-safe generic API responses with OpenAPI"
55
version: 0.1.0
66
servers:
7-
- url: http://localhost:8084/customer/customer
7+
- url: http://localhost:8084/customer
88
description: Local service URL
99
paths:
1010
/v1/customers:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import com.example.demo.client.common.ApiClientResponse;
1+
import io.github.bsayli.openapi.client.common.ApiClientResponse;
22

33
public class {{classname}} extends ApiClientResponse<{{vendorExtensions.x-api-wrapper-datatype}}> {
44
}

0 commit comments

Comments
 (0)