Skip to content

Commit b9fee8c

Browse files
authored
Merge pull request #7 from sashirestela/developer
Release 0.6.0
2 parents 0397344 + 4034aec commit b9fee8c

File tree

7 files changed

+141
-36
lines changed

7 files changed

+141
-36
lines changed

README.md

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ public class Post {
2323
}
2424
2525
// Interface
26+
@Resource("/posts")
2627
public interface PostService {
2728
28-
@GET("/posts")
29+
@GET
2930
List<Post> readPosts(@Query("_page") Integer page, @Query("_limit") Integer limit);
3031
31-
@GET("/posts/{postId}")
32+
@GET("/{postId}")
3233
Post readPost(@Path("postId") Integer postId);
3334
34-
@POST("/posts")
35+
@POST
3536
Post createPost(@Body Post post);
3637
3738
}
@@ -104,8 +105,8 @@ Example:
104105

105106
```
106107
final var URL_BASE = "https://api.example.com";
107-
final var AUTHORIZATION_HEADER = "Authorization";
108-
final var BEARER_AUTHORIZATION = "Bearer qwertyasdfghzxcvb";
108+
final var HEADER_NAME = "Authorization";
109+
final var HEADER_VALUE = "Bearer qwertyasdfghzxcvb";
109110
final var END_OF_STREAM = "[DONE]";
110111
111112
var httpClient = HttpClient.newBuilder()
@@ -118,24 +119,78 @@ var httpClient = HttpClient.newBuilder()
118119
119120
var cleverClient = CleverClient.builder()
120121
.urlBase(URL_BASE)
121-
.headers(Arrays.asList(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION))
122+
.headers(Arrays.asList(HEADER_NAME, HEADER_VALUE))
122123
.httpClient(httpClient)
123124
.endOfStream(END_OF_STREAM)
124125
.build();
125126
```
126127

127128
### Interface Annotations
128129

129-
| Annotation | Target | Value |
130-
|------------|-----------|--------------------------|
131-
| Resource | Interface | Resource's url |
132-
| GET | Method | GET endpoint's url |
133-
| POST | Method | POST endpoint's url |
134-
| PUT | Method | PUT endpoint's url |
135-
| DELETE | Method | DELETE endpoint's url |
136-
| Multipart | Method | None. Mark for multipart |
137-
| Path | Parameter | Path parameter name |
138-
| Query | Parameter | Query parameter name |
130+
| Annotation | Target | Value | Required Value |
131+
|------------|-----------|-----------------------------|----------------|
132+
| Resource | Interface | Resource's url | optional |
133+
| GET | Method | GET endpoint's url | optional |
134+
| POST | Method | POST endpoint's url | optional |
135+
| PUT | Method | PUT endpoint's url | optional |
136+
| DELETE | Method | DELETE endpoint's url | optional |
137+
| Multipart | Method | (None) | none |
138+
| Path | Parameter | Path parameter name in url | mandatory |
139+
| Query | Parameter | Query parameter name in url | mandatory |
140+
| Body | Parameter | (None) | none |
141+
142+
143+
* ```Resource``` could be used to separate the repeated part of the endpoints' url in an interface.
144+
* ```GET, POST, PUT, DELETE``` are used to mark the typical http methods (endpoints).
145+
* ```Multipart``` is used to mark an endpoint with a multipart/form-data request. This is required when tou need to upload files.
146+
* ```Path``` is used to replace the path parameter name in url with the matched method parameter's value.
147+
* ```Query``` is used to add a query parameter to the url in the way: [?]queryName=methodParameterValue[&...].
148+
* ```Body``` is used to mark a custom object method parameter as the endpoint's payload request, so the request will be application/json at least the endpoint is annotated with Multipart.
149+
* Check the above [Description's example](#💡-description) and the [Test](https://github.com/sashirestela/cleverclient/tree/main/src/test/java/io/github/sashirestela/cleverclient) folder to see most of these interface annotations in action.
150+
151+
### Interface Default Methods
152+
You can create interface default methods to execute special requirements such as pre/post processing before/after calling annotated regular methods. For example in the following interface definition, we have two regular methods with POST annotation which are called from anoother two default methods. In those defaults methods we are making some pre processing (in this case, modifying the request) before calling the annotated methods:
153+
154+
```
155+
@Resource("/v1/completions")
156+
interface Completions {
157+
158+
default CompletableFuture<CompletionResponse> create(CompletionRequest completionRequest) {
159+
completionRequest.setStream(false);
160+
return notUseCreate(completionRequest);
161+
}
162+
163+
@POST
164+
CompletableFuture<CompletionResponse> notUseCreate(@Body CompletionRequest completionRequest);
165+
166+
default CompletableFuture<Stream<CompletionResponse>> createStream(CompletionRequest completionRequest) {
167+
completionRequest.setStream(true);
168+
return notUseCreateStream(completionRequest);
169+
}
170+
171+
@POST
172+
CompletableFuture<Stream<CompletionResponse>> notUseCreateStream(@Body CompletionRequest completionRequest);
173+
174+
}
175+
```
176+
177+
### Supported Response Type
178+
The reponse types are determined from the method responses. You don't need any annotation for that.
179+
180+
We have five response types: Stream of objects, List of objects, Generic of object, Single object, and Plain Text, and all of them can be synchronous or asynchronous:
181+
182+
| Method Response | Sync/Async | Response Type |
183+
|-------------------------------------|------------|-----------------------------------------|
184+
| CompletableFuture<Stream\<Object>> | Async | Server sent events as Stream of Objects |
185+
| Stream\<Object> | Sync | Server sent events as Stream of Objects |
186+
| CompletableFuture<List\<Object>> | Async | Collection of Objects |
187+
| List\<Object> | Sync | Collection of Objects |
188+
| CompletableFuture<Generic\<Object>> | Async | Custom Generic Class of Object |
189+
| Generic\<Object> | Sync | Custom Generic Class of Object |
190+
| CompletableFuture\<Object> | Async | Single Object |
191+
| Object | Sync | Single Object |
192+
| CompletableFuture\<String> | Async | Plain Text |
193+
| String | Sync | Plain Text |
139194

140195

141196
## ✳ Examples

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.sashirestela</groupId>
88
<artifactId>cleverclient</artifactId>
9-
<version>0.5.0</version>
9+
<version>0.6.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>cleverclient</name>

src/example/java/io/github/sashirestela/cleverclient/example/jsonplaceholder/PostService.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@
99
import io.github.sashirestela.cleverclient.annotation.PUT;
1010
import io.github.sashirestela.cleverclient.annotation.Path;
1111
import io.github.sashirestela.cleverclient.annotation.Query;
12+
import io.github.sashirestela.cleverclient.annotation.Resource;
1213

14+
@Resource("/posts")
1315
public interface PostService {
1416

15-
@GET("/posts")
17+
@GET
1618
List<Post> readPosts(@Query("_page") Integer page, @Query("_limit") Integer limit);
1719

18-
@GET("/posts/{postId}")
20+
@GET("/{postId}")
1921
Post readPost(@Path("postId") Integer postId);
2022

21-
@POST("/posts")
23+
@POST
2224
Post createPost(@Body Post post);
2325

24-
@PUT("/posts/{postId}")
26+
@PUT("/{postId}")
2527
Post updatePost(@Path("postId") Integer postId, @Body Post post);
2628

27-
@DELETE("/posts/{postId}")
29+
@DELETE("/{postId}")
2830
Post deletePost(@Path("postId") Integer postId);
2931

3032
}

src/main/java/io/github/sashirestela/cleverclient/CleverClient.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111
import io.github.sashirestela.cleverclient.support.CleverClientSSE;
1212
import lombok.Builder;
1313
import lombok.Getter;
14-
import lombok.NoArgsConstructor;
1514
import lombok.NonNull;
1615

17-
@NoArgsConstructor
1816
@Getter
1917
public class CleverClient {
2018
private static Logger logger = LoggerFactory.getLogger(CleverClient.class);

src/test/java/io/github/sashirestela/cleverclient/CleverClientTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ void shouldSetPropertiesToDefaultValuesWhenBuilderIsCalledWithoutThoseProperties
2020
.build();
2121
assertEquals(List.of(), cleverClient.getHeaders());
2222
assertEquals(HttpClient.Version.HTTP_2, cleverClient.getHttpClient().version());
23+
assertNotNull(cleverClient.getUrlBase());
2324
assertNotNull(cleverClient.getHttpProcessor());
2425
}
2526

src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ void shouldReturnAStringSyncWhenMethodReturnTypeIsAString() throws IOException,
7474
assertEquals(expectedDemo, actualDemo);
7575
}
7676

77+
@Test
78+
void shouldThrownExceptionWhenMethodReturnTypeIsAString() throws IOException, InterruptedException {
79+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
80+
.thenThrow(new InterruptedException("The operation was interrupted"));
81+
82+
var service = httpProcessor.createProxy(ITest.SyncService.class);
83+
assertThrows(CleverClientException.class, () -> service.getDemoPlain(100));
84+
}
85+
7786
@Test
7887
void shouldReturnAnObjectSyncWhenMethodReturnTypeIsAnObject() throws IOException, InterruptedException {
7988
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
@@ -88,6 +97,15 @@ void shouldReturnAnObjectSyncWhenMethodReturnTypeIsAnObject() throws IOException
8897
assertEquals(expectedDemo, actualDemo);
8998
}
9099

100+
@Test
101+
void shouldThrownExceptionWhenMethodReturnTypeIsAnObject() throws IOException, InterruptedException {
102+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
103+
.thenThrow(new InterruptedException("The operation was interrupted"));
104+
105+
var service = httpProcessor.createProxy(ITest.SyncService.class);
106+
assertThrows(CleverClientException.class, () -> service.getDemo(100));
107+
}
108+
91109
@Test
92110
void shouldReturnAGenericSyncWhenMethodReturnTypeIsAnObject() throws IOException, InterruptedException {
93111
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
@@ -103,6 +121,15 @@ void shouldReturnAGenericSyncWhenMethodReturnTypeIsAnObject() throws IOException
103121
assertEquals(expectedDemo, actualDemo);
104122
}
105123

124+
@Test
125+
void shouldThrownExceptionWhenMethodReturnTypeIsAGenericObject() throws IOException, InterruptedException {
126+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
127+
.thenThrow(new InterruptedException("The operation was interrupted"));
128+
129+
var service = httpProcessor.createProxy(ITest.SyncService.class);
130+
assertThrows(CleverClientException.class, () -> service.getGenericDemo(1));
131+
}
132+
106133
@Test
107134
void shouldReturnAListSyncWhenMethodReturnTypeIsAList() throws IOException, InterruptedException {
108135
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
@@ -118,6 +145,15 @@ void shouldReturnAListSyncWhenMethodReturnTypeIsAList() throws IOException, Inte
118145
assertEquals(expectedDemo, actualDemo);
119146
}
120147

148+
@Test
149+
void shouldThrownExceptionWhenMethodReturnTypeIsAList() throws IOException, InterruptedException {
150+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
151+
.thenThrow(new InterruptedException("The operation was interrupted"));
152+
153+
var service = httpProcessor.createProxy(ITest.SyncService.class);
154+
assertThrows(CleverClientException.class, () -> service.getDemos());
155+
}
156+
121157
@Test
122158
void shouldReturnAStreamSyncWhenMethodReturnTypeIsAStream() throws IOException, InterruptedException {
123159
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofLines().getClass())))
@@ -133,6 +169,16 @@ void shouldReturnAStreamSyncWhenMethodReturnTypeIsAStream() throws IOException,
133169
assertEquals(expectedDemo, actualDemo);
134170
}
135171

172+
@Test
173+
void shouldThrownExceptionWhenMethodReturnTypeIsAStream() throws IOException, InterruptedException {
174+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofLines().getClass())))
175+
.thenThrow(new InterruptedException("The operation was interrupted"));
176+
177+
var service = httpProcessor.createProxy(ITest.SyncService.class);
178+
var requestDemo = new ITest.RequestDemo("Descr", null);
179+
assertThrows(CleverClientException.class, () -> service.getDemoStream(requestDemo));
180+
}
181+
136182
@Test
137183
void shouldReturnAStringAsyncWhenMethodReturnTypeIsAString() {
138184
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))

src/test/java/io/github/sashirestela/cleverclient/http/ITest.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.github.sashirestela.cleverclient.annotation.Multipart;
1111
import io.github.sashirestela.cleverclient.annotation.POST;
1212
import io.github.sashirestela.cleverclient.annotation.Path;
13+
import io.github.sashirestela.cleverclient.annotation.Resource;
1314
import lombok.AllArgsConstructor;
1415
import lombok.EqualsAndHashCode;
1516
import lombok.Getter;
@@ -37,50 +38,52 @@ interface BadPathParamService {
3738

3839
}
3940

41+
@Resource("/demos")
4042
interface AsyncService {
4143

42-
@GET("/demos")
44+
@GET
4345
Set<Demo> unsupportedMethod();
4446

45-
@GET("/demos/{demoId}")
47+
@GET("/{demoId}")
4648
CompletableFuture<String> getDemoPlain(@Path("demoId") Integer demoId);
4749

48-
@GET("/demos/{demoId}")
50+
@GET("/{demoId}")
4951
CompletableFuture<Demo> getDemo(@Path("demoId") Integer demoId);
5052

51-
@GET("/demos/{genericDemoId}")
53+
@GET("/{genericDemoId}")
5254
CompletableFuture<Generic<Demo>> getGenericDemo(@Path("genericDemoId") Integer genericDemoId);
5355

54-
@GET("/demos")
56+
@GET
5557
CompletableFuture<List<Demo>> getDemos();
5658

57-
@POST("/demos")
59+
@POST
5860
CompletableFuture<Stream<Demo>> getDemoStream(@Body RequestDemo request);
5961

6062
@Multipart
61-
@POST("/demos")
63+
@POST
6264
CompletableFuture<Demo> getFile(@Body RequestDemo request);
6365

6466
default String defaultMethod(String name) {
6567
return "Hello " + name;
6668
}
6769
}
6870

71+
@Resource("/demos")
6972
interface SyncService {
7073

71-
@GET("/demos/{demoId}")
74+
@GET("/{demoId}")
7275
String getDemoPlain(@Path("demoId") Integer demoId);
7376

74-
@GET("/demos/{demoId}")
77+
@GET("/{demoId}")
7578
Demo getDemo(@Path("demoId") Integer demoId);
7679

77-
@GET("/demos/{genericDemoId}")
80+
@GET("/{genericDemoId}")
7881
Generic<Demo> getGenericDemo(@Path("genericDemoId") Integer genericDemoId);
7982

80-
@GET("/demos")
83+
@GET
8184
List<Demo> getDemos();
8285

83-
@POST("/demos")
86+
@POST
8487
Stream<Demo> getDemoStream(@Body RequestDemo request);
8588
}
8689

0 commit comments

Comments
 (0)