Skip to content

Commit afb8003

Browse files
authored
Merge pull request #96 from SentryMan/nima-jsonb
(Nima) Avaje JsonB support
2 parents 07b13d7 + c2e9c5a commit afb8003

File tree

20 files changed

+959
-132
lines changed

20 files changed

+959
-132
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ http-generator-nima/.settings/org.eclipse.jdt.apt.core.prefs
99
http-generator-nima/.settings/org.eclipse.core.resources.prefs
1010
http-generator-nima/.project
1111
http-generator-nima/.classpath
12+
*.prefs
13+
tests/test-nima/.classpath
14+
tests/test-nima/.factorypath
15+
tests/test-nima/.project
16+
*.class

README.md

Lines changed: 203 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,78 +6,94 @@ Http server and client libraries and code generation.
66

77
A jax-rs style controllers with annotations (`@Path`, `@Get` ...)
88
that is lightweight by using source code generation (annotation processors)
9-
to generate adapter code for Javalin and Helidon SE.
9+
to generate adapter code for Javalin and Helidon SE/Nima.
1010

1111
- Lightweight as in 65Kb library + generated source code
12-
- Full use of Javalin or Helidon SE as desired
12+
- Full use of Javalin or Helidon SE/Nima as desired
1313

1414

15-
## Define a Controller
15+
## Define a Controller (Note that these APT processors works with both Java and Kotlin.)
1616
```java
17-
package org.example.hello
17+
package org.example.hello;
1818

19-
import io.avaje.http.api.Controller
20-
import io.avaje.http.api.Get
21-
import io.avaje.http.api.Path
19+
import io.avaje.http.api.Controller;
20+
import io.avaje.http.api.Get;
21+
import io.avaje.http.api.Path;
22+
import java.util.List;
2223

2324
@Path("/widgets")
2425
@Controller
25-
class WidgetController(private val hello: HelloComponent) {
26+
public class WidgetController {
27+
private final HelloComponent hello;
28+
public WidgetController(HelloComponent hello) {
29+
this.hello = hello;
30+
}
31+
32+
@Get("/{id}")
33+
Widget getById(int id) {
34+
return new Widget(id, "you got it"+ hello.hello());
35+
}
2636

27-
@Get("/:id")
28-
fun getById(id : Int): Widget {
29-
return Widget(id, "you got it${hello.hello()}")
37+
@Get()
38+
List<Widget> getAll() {
39+
return List.of(new Widget(1, "Rob"), new Widget(2, "Fi"));
3040
}
3141

32-
@Get
33-
fun getAll(): MutableList<Widget> {
42+
record Widget(int id, String name){};
43+
}
44+
45+
```
3446

35-
val list = mutableListOf<Widget>()
36-
list.add(Widget(1, "Rob"))
37-
list.add(Widget(2, "Fi"))
47+
## Usage with Javalin
3848

39-
return list
40-
}
49+
The annotation processor will generate controller classes implementing the WebRoutes interface, which means we can
50+
get all the WebRoutes and register them with Javalin using:
4151

42-
data class Widget(var id: Int, var name: String)
43-
}
52+
```java
53+
var routes = BeanScope.builder().build().list(WebRoutes.class);
4454

55+
Javalin.create()
56+
.routes(() -> routes.forEach(WebRoutes::registerRoutes))
57+
.start();
4558
```
4659

47-
## Generated source
4860

49-
The annotation processor will generate a `$Route` for the controller like below.
61+
## Usage with Helidon SE
5062

51-
Note that this class implements the WebRoutes interface, which means we can
52-
get all the WebRoutes and register them with Javalin using.
63+
The annotation processor will generate controller classes implementing the Helidon Service interface, which we can use
64+
get all the Services and register them with Helidon `RoutingBuilder`.
5365

5466
```java
55-
fun main(args: Array<String>) {
56-
57-
// get all the webRoutes
58-
val webRoutes = ApplicationScope.list(WebRoutes::class.java)
67+
var routes = BeanScope.builder().build().list(Service.class);
68+
var routingBuilder = Routing.builder().register(routes.stream().toArray(Service[]::new));
69+
WebServer.builder()
70+
.addMediaSupport(JacksonSupport.create())
71+
.routing(routingBuilder)
72+
.build()
73+
.start();
74+
```
5975

60-
val javalin = Javalin.create()
76+
## Usage with Helidon Nima
6177

62-
javalin.routes {
63-
// register all the routes with Javalin
64-
webRoutes.forEach { it.registerRoutes() }
78+
The annotation processor will generate controller classes implementing the Helidon HttpService interface, which we can use
79+
get all the services and register them with the Helidon `HttpRouting`.
6580

66-
// other routes etc as desired
67-
ApiBuilder.get("/foo") { ctx ->
68-
ctx.html("bar")
69-
ctx.status(200)
70-
}
71-
...
72-
}
81+
```java
82+
var routes = BeanScope.builder().build().list(HttpService.class);
83+
final var builder = HttpRouting.builder();
7384

74-
javalin.start(7000)
85+
for (final HttpService httpService : routes) {
86+
httpService.routing(builder);
7587
}
7688

89+
WebServer.builder()
90+
.addRouting(builder.build())
91+
.build()
92+
.start();
7793
```
94+
## Generated sources
7895

79-
### The generated WidgetController$Route.java is:
80-
96+
### (Javalin) The generated WidgetController$Route.java is:
8197

8298
```java
8399
package org.example.hello;
@@ -102,7 +118,7 @@ public class WidgetController$Route implements WebRoutes {
102118
@Override
103119
public void registerRoutes() {
104120

105-
ApiBuilder.get("/widgets/:id", ctx -> {
121+
ApiBuilder.get("/widgets/{id}", ctx -> {
106122
int id = asInt(ctx.pathParam("id"));
107123
ctx.json(controller.getById(id));
108124
ctx.status(200);
@@ -117,6 +133,149 @@ public class WidgetController$Route implements WebRoutes {
117133
}
118134
```
119135

120-
Note that this APT processor works with both Java and Kotlin.
136+
### (Helidon SE) The generated WidgetController$Route.java is:
137+
```java
138+
package org.example.hello;
139+
140+
import static io.avaje.http.api.PathTypeConversion.*;
141+
142+
import io.avaje.http.api.*;
143+
import io.helidon.common.http.FormParams;
144+
import io.helidon.webserver.Handler;
145+
import io.helidon.webserver.Routing;
146+
import io.helidon.webserver.ServerRequest;
147+
import io.helidon.webserver.ServerResponse;
148+
import io.helidon.webserver.Service;
149+
import jakarta.inject.Singleton;
150+
import org.example.hello.WidgetController;
151+
152+
@Generated("io.dinject.helidon-generator")
153+
@Singleton
154+
public class WidgetController$Route implements Service {
155+
156+
private final WidgetController controller;
157+
158+
public WidgetController$Route(WidgetController controller) {
159+
this.controller = controller;
160+
}
161+
162+
@Override
163+
public void update(Routing.Rules rules) {
164+
165+
rules.get("/widgets/{id}", this::_getById);
166+
rules.post("/widgets", this::_getAll);
167+
}
168+
169+
private void _getById(ServerRequest req, ServerResponse res) {
170+
int id = asInt(req.path().param("id"));
171+
res.send(controller.getById(id));
172+
}
173+
174+
private void _getAll(ServerRequest req, ServerResponse res) {
175+
res.send(controller.getAll());
176+
}
177+
178+
}
179+
```
180+
181+
### (Helidon Nima) The generated WidgetController$Route.java is:
182+
183+
```java
184+
package org.example.hello;
185+
186+
import static io.avaje.http.api.PathTypeConversion.*;
187+
188+
import io.avaje.http.api.*;
189+
import io.avaje.inject.Component;
190+
import io.helidon.nima.webserver.http.HttpRouting;
191+
import io.helidon.nima.webserver.http.HttpRules;
192+
import io.helidon.nima.webserver.http.HttpService;
193+
import io.helidon.nima.webserver.http.ServerRequest;
194+
import io.helidon.nima.webserver.http.ServerResponse;
195+
import org.example.hello.WidgetController;
196+
197+
@Generated("avaje-helidon-nima-generator")
198+
@Component
199+
public class WidgetController$Route implements HttpService {
200+
201+
private final WidgetController controller;
202+
public WidgetController$Route(WidgetController controller) {
203+
this.controller = controller;
204+
}
205+
206+
@Override
207+
public void routing(HttpRules rules) {
208+
rules.get("/widgets/{id}", this::_getById);
209+
rules.get("/widgets", this::_getAll);
210+
}
211+
212+
private void _getById(ServerRequest req, ServerResponse res) {
213+
var pathParams = req.path().pathParameters();
214+
int id = asInt(pathParams.first("id").get());
215+
var result = controller.getById(id);
216+
res.send(result);
217+
}
218+
219+
private void _getAll(ServerRequest req, ServerResponse res) {
220+
var pathParams = req.path().pathParameters();
221+
var result = controller.getAll();
222+
res.send(result);
223+
}
224+
225+
}
226+
```
227+
228+
### (Helidon Nima with Avaje-Jsonb) The generated WidgetController$Route.java is:
121229

230+
```java
231+
package org.example.hello;
232+
233+
import static io.avaje.http.api.PathTypeConversion.*;
234+
235+
import io.avaje.http.api.*;
236+
import io.avaje.inject.Component;
237+
import io.helidon.nima.webserver.http.HttpRouting;
238+
import io.helidon.nima.webserver.http.HttpRules;
239+
import io.helidon.nima.webserver.http.HttpService;
240+
import io.helidon.nima.webserver.http.ServerRequest;
241+
import io.helidon.nima.webserver.http.ServerResponse;
242+
import org.example.hello.WidgetController;
243+
244+
@Generated("avaje-helidon-nima-generator")
245+
@Component
246+
public class WidgetController$Route implements HttpService {
247+
248+
249+
private final WidgetController controller;
250+
private final JsonType<org.example.hello.WidgetController.Widget> getByIdReturnedJsonType;
251+
private final JsonType<java.util.List<org.example.hello.WidgetController.Widget>> getAllReturnedJsonType;
252+
253+
public WidgetController$Route(WidgetController controller, Jsonb jsonB) {
254+
this.controller = controller;
255+
this.getByIdReturnedJsonType = jsonB.type(org.example.hello.WidgetController.Widget.class);
256+
this.getAllReturnedJsonType = jsonB.type(org.example.hello.WidgetController.Widget.class).list();
257+
}
122258

259+
@Override
260+
public void routing(HttpRules rules) {
261+
rules.get("/widgets/{id}", this::_getById);
262+
rules.get("/widgets", this::_getAll);
263+
}
264+
265+
private void _getById(ServerRequest req, ServerResponse res) {
266+
var pathParams = req.path().pathParameters();
267+
int id = asInt(pathParams.first("id").get());
268+
var result = controller.getById(id);
269+
res.headers().contentType(io.helidon.common.http.HttpMediaType.APPLICATION_JSON);
270+
getByIdReturnedJsonType.toJson(result, res.outputStream());
271+
}
272+
273+
private void _getAll(ServerRequest req, ServerResponse res) {
274+
var pathParams = req.path().pathParameters();
275+
var result = controller.getAll();
276+
res.headers().contentType(io.helidon.common.http.HttpMediaType.APPLICATION_JSON);
277+
getAllReturnedJsonType.toJson(result, res.outputStream());
278+
}
279+
280+
}
281+
```

http-api/src/main/java/io/avaje/http/api/MediaType.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public interface MediaType {
3535
*/
3636
String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
3737

38-
3938
/**
4039
* A {@code String} constant representing {@value #MULTIPART_FORM_DATA} media type.
4140
*/
@@ -51,7 +50,6 @@ public interface MediaType {
5150
*/
5251
String TEXT_PLAIN = "text/plain";
5352

54-
5553
/**
5654
* A {@code String} constant representing {@value #TEXT_XML} media type.
5755
*/
@@ -71,5 +69,35 @@ public interface MediaType {
7169
* {@link String} representation of {@value #APPLICATION_JSON_PATCH_JSON} media type..
7270
*/
7371
String APPLICATION_JSON_PATCH_JSON = "application/json-patch+json";
74-
72+
73+
/**
74+
* {@link String} representation of {@value #APPLICATION_PDF} media type.
75+
*/
76+
String APPLICATION_PDF = "application/pdf";
77+
78+
/**
79+
* {@link String} representation of {@value #IMAGE_GIF} media type.
80+
*/
81+
String IMAGE_GIF = "image/gif";
82+
83+
/**
84+
* {@link String} representation of {@value #IMAGE_JPEG} media type.
85+
*/
86+
String IMAGE_JPEG = "image/jpeg";
87+
88+
/**
89+
* {@link String} representation of {@value #IMAGE_PNG} media type.
90+
*/
91+
String IMAGE_PNG = "image/png";
92+
93+
/**
94+
* {@link String} representation of {@value #MULTIPART_MIXED} media type.
95+
*/
96+
String MULTIPART_MIXED = "multipart/mixed";
97+
98+
/**
99+
* {@link String} representation of {@value #MULTIPART_RELATED} media type.
100+
*/
101+
String MULTIPART_RELATED = "multipart/related";
102+
75103
}

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/SchemaDocBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ private Schema<?> buildIterableSchema(TypeMirror type) {
177177

178178
private Schema<?> buildArraySchema(TypeMirror type) {
179179

180-
ArrayType arrayType = types.getArrayType(type);
180+
ArrayType arrayType = (ArrayType) type;
181181
Schema<?> itemSchema = toSchema(arrayType.getComponentType());
182182

183183
ArraySchema arraySchema = new ArraySchema();

0 commit comments

Comments
 (0)