Skip to content

Commit a50c024

Browse files
committed
tRPC implementation
- generate `d.ts` file from MVC routes - introduce `@Trpc`, `@Trpc.Query` and `@Trpc.Mutation` - implement `GET` on APT - ref #3863
1 parent 4674b52 commit a50c024

File tree

23 files changed

+1352
-53
lines changed

23 files changed

+1352
-53
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.annotation;
7+
8+
import java.lang.annotation.Documented;
9+
import java.lang.annotation.ElementType;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
13+
14+
/** Marks a controller class or a specific route method for tRPC TypeScript generation. */
15+
@Target({ElementType.TYPE, ElementType.METHOD})
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Documented
18+
public @interface Trpc {
19+
20+
@Target(ElementType.METHOD)
21+
@Retention(RetentionPolicy.RUNTIME)
22+
@Documented
23+
@interface Mutation {
24+
/**
25+
* Custom name for the tRPC procedure.
26+
*
27+
* <p>If applied to a method, this overrides the generated procedure name. If applied to a
28+
* class, this overrides the generated namespace/router name.
29+
*
30+
* @return The custom procedure name. Empty by default, which means the generator will use the
31+
* Java method or class name.
32+
*/
33+
String value() default "";
34+
}
35+
36+
@Target(ElementType.METHOD)
37+
@Retention(RetentionPolicy.RUNTIME)
38+
@Documented
39+
@interface Query {
40+
/**
41+
* Custom name for the tRPC procedure.
42+
*
43+
* <p>If applied to a method, this overrides the generated procedure name. If applied to a
44+
* class, this overrides the generated namespace/router name.
45+
*
46+
* @return The custom procedure name. Empty by default, which means the generator will use the
47+
* Java method or class name.
48+
*/
49+
String value() default "";
50+
}
51+
52+
/**
53+
* Custom name for the tRPC procedure or namespace.
54+
*
55+
* <p>If applied to a method, this overrides the generated procedure name. If applied to a class,
56+
* this overrides the generated namespace/router name.
57+
*
58+
* @return The custom procedure or namespace name. Empty by default, which means the generator
59+
* will use the Java method or class name.
60+
*/
61+
String value() default "";
62+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.trpc;
7+
8+
import java.util.Map;
9+
10+
public record TrpcError(ErrorDetail error) {
11+
public record ErrorDetail(String message, int code, Map<String, Object> data) {}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.trpc;
7+
8+
public record TrpcResponse<T>(TrpcResult<T> result) {
9+
10+
public static <T> TrpcResponse<T> success(T data) {
11+
return new TrpcResponse<>(new TrpcResult<>(data));
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.trpc;
7+
8+
public record TrpcResult<T>(T data) {}

modules/jooby-apt/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
<scope>test</scope>
2626
</dependency>
2727

28+
<dependency>
29+
<groupId>io.jooby</groupId>
30+
<artifactId>jooby-jackson3</artifactId>
31+
<version>${jooby.version}</version>
32+
<scope>test</scope>
33+
</dependency>
34+
2835
<dependency>
2936
<groupId>jakarta.validation</groupId>
3037
<artifactId>jakarta.validation-api</artifactId>
@@ -76,6 +83,7 @@
7683
<scope>test</scope>
7784
</dependency>
7885

86+
7987
<dependency>
8088
<groupId>io.jooby</groupId>
8189
<artifactId>jooby-test</artifactId>

modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ private void buildRouteRegistry(Map<TypeElement, MvcRouter> registry, TypeElemen
297297
}
298298
});
299299
if (!currentType.equals(superType)) {
300-
// edge-case #1: when controller has no method and extends another class which has.
300+
// edge-case #1: when a controller has no method and extends another class which has.
301301
// edge-case #2: some odd usage a controller could be empty.
302302
// See https://github.com/jooby-project/jooby/issues/3656
303303
if (registry.containsKey(superType)) {

modules/jooby-apt/src/main/java/io/jooby/internal/apt/CodeBlock.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public static CharSequence semicolon(boolean kt) {
3535
return kt ? "" : ";";
3636
}
3737

38+
public static CharSequence var(boolean kt) {
39+
return kt ? "val " : "var ";
40+
}
41+
3842
public static String indent(int count) {
3943
return " ".repeat(count);
4044
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/HttpMethod.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ public enum HttpMethod implements AnnotationSupport {
2727
OPTIONS,
2828
PATCH,
2929
POST,
30-
PUT;
30+
PUT,
31+
// Special
32+
tRPC(
33+
List.of(
34+
"io.jooby.annotation.Trpc",
35+
"io.jooby.annotation.Trpc.Mutation",
36+
"io.jooby.annotation.Trpc.Query"));
3137
private final List<String> annotations;
3238

3339
HttpMethod(String... packages) {
@@ -36,6 +42,10 @@ public enum HttpMethod implements AnnotationSupport {
3642
this.annotations = packageList.stream().map(it -> it + "." + name()).toList();
3743
}
3844

45+
HttpMethod(List<String> annotations) {
46+
this.annotations = annotations;
47+
}
48+
3949
/**
4050
* Look at path attribute over HTTP method annotation (like io.jooby.annotation.GET) or fallback
4151
* to Path annotation.
@@ -76,6 +86,11 @@ public List<String> produces(Element element) {
7686
return mediaType(element, HttpMediaType.Produces, "produces"::equals);
7787
}
7888

89+
public boolean matches(Element element) {
90+
return annotations.stream()
91+
.anyMatch(it -> AnnotationSupport.findAnnotationByName(element, it) != null);
92+
}
93+
7994
private List<String> mediaType(
8095
Element element, HttpMediaType mediaType, Predicate<String> filter) {
8196
var path =

modules/jooby-apt/src/main/java/io/jooby/internal/apt/HttpPath.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,24 @@ public List<String> getAnnotations() {
3232
* @return Path or empty list.
3333
*/
3434
public List<String> path(Collection<TypeElement> hierarchy) {
35+
return path(hierarchy, getAnnotations());
36+
}
37+
38+
/**
39+
* Find path on type hierarchy. It goes back at hierarchy until it finds a Path annotation.
40+
*
41+
* @param hierarchy Type hierarchy.
42+
* @return Path or empty list.
43+
*/
44+
public List<String> trpcPath(Collection<TypeElement> hierarchy) {
45+
return path(hierarchy, List.of("io.jooby.annotation.Trpc"));
46+
}
47+
48+
private List<String> path(Collection<TypeElement> hierarchy, List<String> annotations) {
3549
var prefix = Collections.<String>emptyList();
3650
var it = hierarchy.iterator();
3751
while (prefix.isEmpty() && it.hasNext()) {
38-
prefix = path(it.next());
52+
prefix = path(it.next(), annotations);
3953
}
4054
return prefix;
4155
}
@@ -47,7 +61,17 @@ public List<String> path(Collection<TypeElement> hierarchy) {
4761
* @return Path or empty list.
4862
*/
4963
public List<String> path(Element element) {
50-
return getAnnotations().stream()
64+
return path(element, getAnnotations());
65+
}
66+
67+
/**
68+
* Find Path from method or class.
69+
*
70+
* @param element Method or Class.
71+
* @return Path or empty list.
72+
*/
73+
private List<String> path(Element element, List<String> annotations) {
74+
return annotations.stream()
5175
.map(it -> AnnotationSupport.findAnnotationByName(element, it))
5276
.filter(Objects::nonNull)
5377
.findFirst()

modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcContext.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,26 @@ public ProcessingEnvironment getProcessingEnvironment() {
8787
return processingEnvironment;
8888
}
8989

90+
/**
91+
* Find path from trpc route method and router type. This method scan and expand path base on the
92+
* annotation present at method or class level.
93+
*
94+
* @param owner Router type.
95+
* @param exec Method.
96+
* @param procedure Child path.
97+
* @return List of possible paths.
98+
*/
99+
public List<String> trpcPath(TypeElement owner, ExecutableElement exec, String procedure) {
100+
var prefix = HttpPath.PATH.trpcPath(superTypes(owner));
101+
if (prefix.isEmpty()) {
102+
return procedure.isEmpty() ? Collections.singletonList("/") : List.of(procedure);
103+
}
104+
return prefix.stream()
105+
.map(root -> root.equals("/") ? procedure : root + procedure)
106+
.distinct()
107+
.toList();
108+
}
109+
90110
/**
91111
* Find path from route method and router type. This method scan and expand path base on the
92112
* annotation present at method or class level.

0 commit comments

Comments
 (0)