diff --git a/http-generator-sigma/pom.xml b/http-generator-sigma/pom.xml new file mode 100644 index 000000000..77014be46 --- /dev/null +++ b/http-generator-sigma/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + io.avaje + avaje-http-parent + 2.8-RC1 + + + avaje-http-sigma-generator + + + 17 + UTF-8 + + + + + io.avaje + avaje-http-generator-core + ${project.version} + + + + io.avaje + avaje-prisms + ${avaje.prisms.version} + provided + + + diff --git a/http-generator-sigma/src/etc/activate-shade-module b/http-generator-sigma/src/etc/activate-shade-module new file mode 100644 index 000000000..949f3d244 --- /dev/null +++ b/http-generator-sigma/src/etc/activate-shade-module @@ -0,0 +1 @@ +Remove from module-info all content after: // SHADED: diff --git a/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerMethodWriter.java b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerMethodWriter.java new file mode 100644 index 000000000..787820979 --- /dev/null +++ b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerMethodWriter.java @@ -0,0 +1,128 @@ +package io.avaje.http.generator.sigma; + +import static io.avaje.http.generator.core.ProcessingContext.*; + +import java.util.List; + +import io.avaje.http.generator.core.*; +import io.avaje.http.generator.core.openapi.MediaType; + +/** Write code to register Web route for a given controller method. */ +class ControllerMethodWriter { + + private final MethodReader method; + private final Append writer; + private final WebMethod webMethod; + private final boolean instrumentContext; + private final boolean customMethod; + + ControllerMethodWriter(MethodReader method, Append writer) { + this.method = method; + this.writer = writer; + final var webM = method.webMethod(); + this.webMethod = webM == CoreWebMethod.FILTER ? SigmaWebMethod.BEFORE : webM; + this.instrumentContext = method.instrumentContext(); + customMethod = !(webMethod instanceof CoreWebMethod); + } + + void write() { + final var segments = method.pathSegments(); + final var fullPath = segments.fullPath(); + + writeMethod(fullPath); + + final var params = writeParams(segments); + writer.append(" "); + if (!method.isVoid() && !customMethod) { + writer.append("var result = "); + } + + if (instrumentContext) { + method.writeContext(writer, "ctx", "ctx"); + } + + writer.append("controller."); + + writer.append(method.simpleName()).append("("); + for (var i = 0; i < params.size(); i++) { + if (i > 0) { + writer.append(", "); + } + final var param = params.get(i); + if (isAssignable2Interface(param.utype().mainType(), "java.lang.Exception")) { + writer.append("ex"); + } else { + param.buildParamName(writer); + } + } + + if (instrumentContext) { + writer.append(")"); + } + + writer.append(");").eol(); + if (!method.isVoid() && !customMethod) { + writeContextReturn(); + writer.eol(); + } + + writer.append(" }"); + writer.append(");").eol().eol(); + } + + private void writeMethod(final String fullPath) { + if (method.isErrorMethod()) { + writer + .append(" router.exception(%s.class, (ex, ctx) -> {", method.exceptionShortName()) + .eol(); + } else { + var methodName = webMethod.name().toLowerCase().replace("_m", "M"); + writer.append(" router.%s(\"%s\", ctx -> {", methodName, fullPath).eol(); + } + if (!customMethod) { + int statusCode = method.statusCode(); + if (statusCode > 0) { + writer.append(" ctx.status(%d);", statusCode).eol(); + } + } + } + + private List writeParams(final PathSegments segments) { + final var matrixSegments = segments.matrixSegments(); + for (final PathSegments.Segment matrixSegment : matrixSegments) { + matrixSegment.writeCreateSegment(writer, platform()); + } + + final var params = method.params(); + for (final MethodParam param : params) { + if (!isAssignable2Interface(param.utype().mainType(), "java.lang.Exception")) { + param.writeCtxGet(writer, segments); + } + } + if (method.includeValidate()) { + for (final MethodParam param : params) { + param.writeValidate(writer); + } + } + return params; + } + + private void writeContextReturn() { + var produces = method.produces(); + boolean applicationJson = + produces == null || MediaType.APPLICATION_JSON.getValue().equalsIgnoreCase(produces); + if (applicationJson || JsonBUtil.isJsonMimeType(produces)) { + if (applicationJson) { + writer.append(" ctx.json(result);"); + } else { + writer.append(" ctx.contentType(\"%s\").result(result);", produces); + } + } else if (MediaType.TEXT_HTML.getValue().equalsIgnoreCase(produces)) { + writer.append(" ctx.html(result);"); + } else if (MediaType.TEXT_PLAIN.getValue().equalsIgnoreCase(produces)) { + writer.append(" ctx.text(result);"); + } else { + writer.append(" ctx.contentType(\"%s\").result(%s);", produces); + } + } +} diff --git a/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerWriter.java b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerWriter.java new file mode 100644 index 000000000..f0ec980df --- /dev/null +++ b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/ControllerWriter.java @@ -0,0 +1,92 @@ +package io.avaje.http.generator.sigma; + +import static io.avaje.http.generator.core.ProcessingContext.diAnnotation; + +import java.io.IOException; + +import io.avaje.http.generator.core.BaseControllerWriter; +import io.avaje.http.generator.core.ControllerReader; +import io.avaje.http.generator.core.MethodReader; + +/** + * Write Sigma specific Controller WebRoute handling adapter. + */ +class ControllerWriter extends BaseControllerWriter { + + private static final String AT_GENERATED = "@Generated(\"avaje-sigma-generator\")"; + + ControllerWriter(ControllerReader reader) throws IOException { + super(reader); + reader.addImportType("io.avaje.sigma.HttpService"); + reader.addImportType("io.avaje.sigma.Router"); + } + + void write() { + writePackage(); + writeImports(); + writeClassStart(); + writeAddRoutes(); + writeClassEnd(); + } + + private void writeAddRoutes() { + writer.append(" @Override").eol(); + writer.append(" public void setup(Router router) {").eol().eol(); + + + for (final MethodReader method : reader.methods()) { + if (method.isWebMethod()) { + writeForMethod(method); + } + } + writer.append(" }").eol().eol(); + } + + private void writeForMethod(MethodReader method) { + new ControllerMethodWriter(method, writer).write(); + if (!reader.isDocHidden()) { + method.buildApiDocumentation(); + } + } + + private void writeClassStart() { + writer.append(AT_GENERATED).eol(); + writer.append(diAnnotation()).eol(); + writer + .append("public class ") + .append(shortName) + .append("$Route implements HttpService {") + .eol() + .eol(); + + var controllerName = "controller"; + var controllerType = shortName; + writer.append(" private final %s %s;", controllerType, controllerName).eol(); + + if (reader.isIncludeValidator()) { + writer.append(" private final Validator validator;").eol(); + } + + if (instrumentContext) { + writer.append(" private final RequestContextResolver resolver;").eol(); + } + writer.eol(); + + writer.append(" public %s$Route(%s %s", shortName, controllerType, controllerName); + if (reader.isIncludeValidator()) { + writer.append(", Validator validator"); + } + if (instrumentContext) { + writer.append(", RequestContextResolver resolver"); + } + writer.append(") {").eol(); + writer.append(" this.%s = %s;", controllerName, controllerName).eol(); + if (reader.isIncludeValidator()) { + writer.append(" this.validator = validator;").eol(); + } + if (instrumentContext) { + writer.append(" this.resolver = resolver;").eol(); + } + writer.append(" }").eol().eol(); + } +} diff --git a/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaAdapter.java b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaAdapter.java new file mode 100644 index 000000000..4b45afc7e --- /dev/null +++ b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaAdapter.java @@ -0,0 +1,141 @@ +package io.avaje.http.generator.sigma; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import javax.lang.model.element.Element; + +import io.avaje.http.generator.core.Append; +import io.avaje.http.generator.core.Constants; +import io.avaje.http.generator.core.ControllerReader; +import io.avaje.http.generator.core.CustomWebMethod; +import io.avaje.http.generator.core.ParamType; +import io.avaje.http.generator.core.PlatformAdapter; +import io.avaje.http.generator.core.UType; + +class SigmaAdapter implements PlatformAdapter { + + static final String CONTEXT = "io.avaje.sigma.HttpContext"; + + @Override + public boolean isContextType(String rawType) { + return CONTEXT.equals(rawType); + } + + @Override + public String platformVariable(String rawType) { + return "ctx"; + } + + @Override + public boolean isBodyMethodParam() { + return false; + } + + @Override + public String bodyAsClass(UType type) { + if ("java.lang.String".equals(type.full())) { + return "ctx.body()"; + } else { + return "ctx.bodyAsClass(" + type.mainType() + ".class)"; + } + } + + @Override + public String indent() { + return " "; + } + + @Override + public void writeReadParameter(Append writer, ParamType paramType, String paramName) { + writer.append("ctx.%s(\"%s\")", paramType, paramName); + } + + @Override + public void writeReadParameter(Append writer, ParamType paramType, String paramName, String paramDefault) { + writer.append("withDefault(ctx.%s(\"%s\"), \"%s\")", paramType, paramName, paramDefault); + } + + @Override + public void writeReadMapParameter(Append writer, ParamType paramType) { + + switch (paramType) { + case QUERYPARAM: + writer.append("ctx.queryParamMap()"); + break; + case FORM: + case FORMPARAM: + writer.append("ctx.formParamMap()"); + break; + default: + throw new UnsupportedOperationException( + "Only Query/Form Params have Map> supported in Javalin"); + } + } + + @Override + public void writeReadCollectionParameter(Append writer, ParamType paramType, String paramName) { + switch (paramType) { + case QUERYPARAM: + writer.append("ctx.queryParams(\"%s\")", paramName); + break; + case HEADER: + writer.append("ctx.headers(\"%s\")", paramName); + break; + case FORMPARAM: + writer.append("ctx.formParams(\"%s\")", paramName); + break; + default: + throw new UnsupportedOperationException( + "Only MultiValue Form/Query Params are supported in Javalin"); + } + } + + @Override + public void writeReadCollectionParameter( + Append writer, ParamType paramType, String paramName, List paramDefault) { + + switch (paramType) { + case QUERYPARAM: + writer.append( + "withDefault(ctx.queryParams(\"%s\"), java.util.List.of(\"%s\"))", + paramName, String.join(",", paramDefault)); + break; + case HEADER: + writer.append( + "withDefault(ctx.headers(\"%s\"), java.util.List.of(\"%s\"))", + paramName, String.join(",", paramDefault)); + break; + case FORMPARAM: + writer.append( + "withDefault(ctx.formParams(\"%s\"), java.util.List.of(\"%s\"))", + paramName, String.join(",", paramDefault)); + break; + default: + throw new UnsupportedOperationException( + "Only MultiValue Form/Header/Query Params are supported in Javalin"); + } + } + + @Override + public void writeAcceptLanguage(Append writer) { + writer.append("ctx.header(\"%s\")", Constants.ACCEPT_LANGUAGE); + } + + @Override + public List>> customHandlers() { + + return List.of(); + } + + @Override + public void controllerRoles(List roles, ControllerReader controller) { + + } + + @Override + public void methodRoles(List roles, ControllerReader controller) { + + } +} diff --git a/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaProcessor.java b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaProcessor.java new file mode 100644 index 000000000..9a79145b2 --- /dev/null +++ b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaProcessor.java @@ -0,0 +1,24 @@ +package io.avaje.http.generator.sigma; + +import java.io.IOException; + +import javax.annotation.processing.Processor; + +import io.avaje.http.generator.core.BaseProcessor; +import io.avaje.http.generator.core.ControllerReader; +import io.avaje.http.generator.core.PlatformAdapter; +import io.avaje.spi.ServiceProvider; + +@ServiceProvider(Processor.class) +public class SigmaProcessor extends BaseProcessor { + + @Override + protected PlatformAdapter providePlatformAdapter() { + return new SigmaAdapter(); + } + + @Override + public void writeControllerAdapter(ControllerReader reader) throws IOException { + new ControllerWriter(reader).write(); + } +} diff --git a/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaWebMethod.java b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaWebMethod.java new file mode 100644 index 000000000..471b00da4 --- /dev/null +++ b/http-generator-sigma/src/main/java/io/avaje/http/generator/sigma/SigmaWebMethod.java @@ -0,0 +1,21 @@ +package io.avaje.http.generator.sigma; + +import io.avaje.http.generator.core.WebMethod; + +public enum SigmaWebMethod implements WebMethod { + BEFORE(0), + BEFORE_MATCHED(0), + AFTER(0), + AFTER_MATCHED(0); + + private final int statusCode; + + SigmaWebMethod(int statusCode) { + this.statusCode = statusCode; + } + + @Override + public int statusCode(boolean isVoid) { + return statusCode; + } +} diff --git a/http-generator-sigma/src/main/java/module-info.java b/http-generator-sigma/src/main/java/module-info.java new file mode 100644 index 000000000..37c1b3fbb --- /dev/null +++ b/http-generator-sigma/src/main/java/module-info.java @@ -0,0 +1,12 @@ +module io.avaje.http.sigma.generator { + + provides javax.annotation.processing.Processor with io.avaje.http.generator.sigma.SigmaProcessor; + + requires java.compiler; + requires java.sql; + + // SHADED: All content after this line will be removed at package time + requires io.avaje.http.generator.core; + requires static io.avaje.prism; + requires static io.avaje.spi; +} diff --git a/pom.xml b/pom.xml index e569bebd2..a2333d507 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,7 @@ http-generator-core http-generator-javalin http-generator-jex + http-generator-sigma http-generator-client diff --git a/tests/pom.xml b/tests/pom.xml index cdec44f34..0f7523f12 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -27,6 +27,7 @@ test-jex test-client test-client-generation + test-sigma diff --git a/tests/test-javalin-jsonb/avaje-processors.txt b/tests/test-javalin-jsonb/avaje-processors.txt new file mode 100644 index 000000000..6ee501129 --- /dev/null +++ b/tests/test-javalin-jsonb/avaje-processors.txt @@ -0,0 +1 @@ +avaje-jsonb-generator \ No newline at end of file diff --git a/tests/test-javalin-jsonb/io.avaje.jsonb.spi.JsonbExtension b/tests/test-javalin-jsonb/io.avaje.jsonb.spi.JsonbExtension new file mode 100644 index 000000000..05a41ae93 --- /dev/null +++ b/tests/test-javalin-jsonb/io.avaje.jsonb.spi.JsonbExtension @@ -0,0 +1 @@ +org.example.myapp.web.jsonb.GeneratedJsonComponent \ No newline at end of file diff --git a/tests/test-sigma/.gitignore b/tests/test-sigma/.gitignore new file mode 100644 index 000000000..0c9d542d8 --- /dev/null +++ b/tests/test-sigma/.gitignore @@ -0,0 +1,5 @@ +target/ +build/ +.idea/ +*.iml +.gradle diff --git a/tests/test-sigma/README.md b/tests/test-sigma/README.md new file mode 100644 index 000000000..7463daa77 --- /dev/null +++ b/tests/test-sigma/README.md @@ -0,0 +1,7 @@ +# example-javalin-java-jsonb + +Very simple Java Maven Javalin example with Avaje JsonB instead of Jackson. + +- Run the main method - org.example.myapp.Main + +- `curl http://localhost:7000/hello/42/Roberto?otherParam=banana` diff --git a/tests/test-sigma/pom.xml b/tests/test-sigma/pom.xml new file mode 100644 index 000000000..78e59fa6c --- /dev/null +++ b/tests/test-sigma/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + io.avaje + tests + 2.8-RC1 + + + test-sigma + + + 17 + true + 2.2.23 + 1.3.71 + + + + + + org.avaje + logback + 1.0 + + + + io.avaje + avaje-sigma + 0.2 + + + + io.avaje + avaje-inject + ${avaje-inject.version} + + + + io.avaje + avaje-http-api + ${project.version} + + + + io.avaje + avaje-http-hibernate-validator + 3.5-RC3 + + + + io.swagger.core.v3 + swagger-annotations + ${swagger.version} + + + + + io.avaje + avaje-inject-generator + ${avaje-inject.version} + provided + + + + io.avaje + avaje-http-sigma-generator + ${project.version} + provided + + + + io.avaje + avaje-jsonb + 2.1 + + + io.avaje + avaje-jsonb-generator + 2.1 + provided + + + + + io.avaje + junit + 1.5 + test + + + + + app + + + + io.avaje + openapi-maven-plugin + 1.0 + + + main + process-classes + + openapi + + + + + + + io.repaint.maven + tiles-maven-plugin + 2.40 + true + + + org.avaje.tile:lib-classpath:1.1 + + + + + + + + + + HEAD + scm:git:git@github.com:avaje/avaje-http.git + + diff --git a/tests/test-sigma/src/main/java/org/example/myapp/service/BazRepo.java b/tests/test-sigma/src/main/java/org/example/myapp/service/BazRepo.java new file mode 100644 index 000000000..226df54b9 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/service/BazRepo.java @@ -0,0 +1,31 @@ +package org.example.myapp.service; + +import org.example.myapp.web.Baz; +import org.example.myapp.web.Repository; + +import jakarta.inject.Singleton; +import java.util.ArrayList; +import java.util.List; + +@Singleton +public class BazRepo implements Repository { + + @Override + public Baz findById(Long id) { + Baz baz = new Baz(); + baz.id = id; + baz.name = "Baz" + id; + //baz.startDate = LocalDate.of(2020, 1, 1); + return baz; + } + + @Override + public List findAll() { + return new ArrayList<>(); + } + + @Override + public Long save(Baz bean) { + return 42L; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/service/MyDependency.java b/tests/test-sigma/src/main/java/org/example/myapp/service/MyDependency.java new file mode 100644 index 000000000..394f48d76 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/service/MyDependency.java @@ -0,0 +1,11 @@ +package org.example.myapp.service; + +import jakarta.inject.Singleton; + +@Singleton +public class MyDependency { + + public String hello() { + return "my dependency"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/service/MyService.java b/tests/test-sigma/src/main/java/org/example/myapp/service/MyService.java new file mode 100644 index 000000000..e21da48fa --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/service/MyService.java @@ -0,0 +1,20 @@ +package org.example.myapp.service; + +import org.example.myapp.web.HelloDto; + +import jakarta.inject.Singleton; +import java.util.ArrayList; +import java.util.List; + +@Singleton +public class MyService { + + public List findAll() { + + List list = new ArrayList<>(); + list.add(new HelloDto(12, "Jim", "asd")); + list.add(new HelloDto(13, "Spock", "456456")); + + return list; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/Bar.java b/tests/test-sigma/src/main/java/org/example/myapp/web/Bar.java new file mode 100644 index 000000000..5d044cbf6 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/Bar.java @@ -0,0 +1,10 @@ +package org.example.myapp.web; + +import io.avaje.jsonb.Json; + +@Json +public class Bar { + + public long id; + public String name; +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/BarController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/BarController.java new file mode 100644 index 000000000..2c6e281e4 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/BarController.java @@ -0,0 +1,28 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Controller; + +import java.util.ArrayList; +import java.util.List; + +@Controller +public class BarController implements BarInterface { + + @Override + public Bar getById(long id) { + Bar bar = new Bar(); + bar.id = id; + bar.name = "Rob" + id; + return bar; + } + + @Override + public List findByCode(String code) { + return new ArrayList<>(); + } + + @Override + public String barMessage() { + return "Hello"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/BarInterface.java b/tests/test-sigma/src/main/java/org/example/myapp/web/BarInterface.java new file mode 100644 index 000000000..4572236ae --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/BarInterface.java @@ -0,0 +1,25 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Get; +import io.avaje.http.api.MediaType; +import io.avaje.http.api.Path; +import io.avaje.http.api.Produces; +import io.swagger.v3.oas.annotations.links.Link; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import java.util.List; + +@Path("/bars") +public interface BarInterface { + + @Get(":id") + @ApiResponse(links = @Link(name="find", ref ="/find/:code", description="find by code")) + Bar getById(long id); + + @Get("/find/:code") + List findByCode(String code); + + @Produces(MediaType.TEXT_PLAIN) + @Get + String barMessage(); +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/BaseController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/BaseController.java new file mode 100644 index 000000000..d342015b6 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/BaseController.java @@ -0,0 +1,31 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Get; +import io.avaje.http.api.Post; + +import java.util.List; + +abstract class BaseController { + + protected final Repository repository; + + BaseController(Repository repository) { + this.repository = repository; + } + + @Get(":id") + T getById(I id) { + return repository.findById(id); + } + + @Get + List findAll() { + return repository.findAll(); + } + + @Post + I save(T bean) { + return repository.save(bean); + } + +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/Baz.java b/tests/test-sigma/src/main/java/org/example/myapp/web/Baz.java new file mode 100644 index 000000000..7fbf3d1fc --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/Baz.java @@ -0,0 +1,15 @@ +package org.example.myapp.web; + +import java.time.LocalDate; + +import io.avaje.jsonb.Json; +@Json +public class Baz { + + public Long id; + + public String name; + + public LocalDate startDate; + +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/BazController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/BazController.java new file mode 100644 index 000000000..96923c60f --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/BazController.java @@ -0,0 +1,43 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Get; +import io.avaje.http.api.Path; + +import java.util.Arrays; +import java.util.List; + +@Controller +@Path("/baz") +class BazController extends BaseController { + + BazController(Repository repository) { + super(repository); + } + + /** + * Find the baz by name. + *

+ * This is some more comments about this method. + * + * @return The list of baz + */ + @Get("findbyname/{name}") + List searchByName(String name) { + + Baz b1 = new Baz(); + b1.id = 1L; + b1.name = "baz1-" + name; + + Baz b2 = new Baz(); + b2.id = 2L; + b2.name = "baz2"; + + return Arrays.asList(b1, b2); + } + + @Get("checkparams/{id}") + String checkParams(int id, String p1, Double p2, Integer p3, Float p4, String body) { + return "dummy-response"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/GetBeanForm.java b/tests/test-sigma/src/main/java/org/example/myapp/web/GetBeanForm.java new file mode 100644 index 000000000..70818500d --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/GetBeanForm.java @@ -0,0 +1,88 @@ +package org.example.myapp.web; + +import java.util.List; +import java.util.Set; + +import io.avaje.http.api.Header; +import io.avaje.http.api.Ignore; +import io.avaje.http.api.QueryParam; +import io.avaje.jsonb.Json; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +@Json +@Valid +public class GetBeanForm { + + @NotNull + @Size(min = 2, max = 150) + private String name; + + @Email + @Size(max = 100) + private String email; + + private List addresses; + + @Header private String head; + + @QueryParam private Set type; + + @Json.Ignore @Ignore private String ignored; + + public String getIgnored() { + return ignored; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public GetBeanForm(String name, String email) { + this.name = name; + this.email = email; + } + + @Override + public String toString() { + return "HelloForm{" + "name='" + name + '\'' + ", email='" + email + '\'' + '}'; + } + + public List getAddresses() { + return addresses; + } + + public void setAddresses(List addresses) { + this.addresses = addresses; + } + + public String getHead() { + return head; + } + + public void setHead(String head) { + this.head = head; + } + + public Set getType() { + return type; + } + + public void setType(Set type) { + this.type = type; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/Hallo.java b/tests/test-sigma/src/main/java/org/example/myapp/web/Hallo.java new file mode 100644 index 000000000..441676e7b --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/Hallo.java @@ -0,0 +1,13 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Path; + +@Controller +@Path("hallo") +public class Hallo { + + public String getStuff() { + return "Hallo"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/HelloController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloController.java new file mode 100644 index 000000000..6beaa9b3d --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloController.java @@ -0,0 +1,190 @@ +package org.example.myapp.web; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; + +import org.example.myapp.service.MyService; +import org.example.myapp.web.other.Foo; + +import io.avaje.http.api.BeanParam; +import io.avaje.http.api.Controller; +import io.avaje.http.api.Default; +import io.avaje.http.api.Delete; +import io.avaje.http.api.Form; +import io.avaje.http.api.Get; +import io.avaje.http.api.MediaType; +import io.avaje.http.api.Path; +import io.avaje.http.api.Post; +import io.avaje.http.api.Produces; +import io.avaje.http.api.QueryParam; +import io.avaje.http.api.Valid; +import io.avaje.sigma.HttpContext; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.inject.Inject; + +/** + * Hello resource manager. + *

+ * Simple API for Hello resources. + */ +//@Hidden +@Valid +@Controller +@Path("/hello") +class HelloController { + + private final MyService myService; + + @Inject + HelloController(MyService myService) { + this.myService = myService; + } + + @Produces(MediaType.TEXT_PLAIN) + @Get("message") + String getPlainMessage() { + return "hello world"; + } + + /** + * Return the Hello DTO. + * + * @param id The hello Id. + * @param date The name of the hello + * @param otherParam Optional other parameter + * @return The Hello DTO given the id and name. + * @deprecated Please migrate away + */ + @Deprecated + @Get("/:id/:date") + HelloDto hello(int id, LocalDate date, String otherParam) { + return new HelloDto(id, date.toString(), otherParam); + } + + /** + * Find Hellos by name. + * + * @param name The name to search for + * @param myParam My option parameter + * @return The Hellos that we found. + */ + @Get("/findbyname/{name}") + List findByName(String name, @QueryParam("my-param") @Default("one") String myParam) { + return new ArrayList<>(); + } + + /** + * Simple example post with JSON body response. + */ + @Produces(MediaType.APPLICATION_JSON_PATCH_JSON) + @Post + HelloDto post(HelloDto dto) { + dto.name = "posted"; + return dto; + } + + /** + * Save the hello using json body. + * + * @param foo The hello doo id + * @param dto The hello body as json + */ + @Post("/savebean/:foo") + void saveBean(String foo, HelloDto dto, HttpContext HttpContext) { + // save hello data ... + System.out.println("save " + foo + " dto:" + dto); + requireNonNull(foo); + requireNonNull(dto); + requireNonNull(HttpContext); + } + + /** + * Create the new Hello using a form. + */ + @Post("saveform") + @Form + void saveForm(HelloForm helloForm) { + System.out.println("saving " + helloForm); + } + + @Form @Post("mySave") + void saveForm324(@Default("junk") String name, String email, String url) { + System.out.println("name " + name + " email:" + email + " url:" + url); + } + + + @Post("saveform2") + @Form + void saveForm2(String name, String email, String url) { + System.out.println("name " + name + " email:" + email + " url:" + url); + } + + @Post("saveform3") + @Form + HelloDto saveForm3(HelloForm helloForm) { + return new HelloDto(52, helloForm.name, helloForm.email); + } + + @Produces("text/plain") + @Get("withValidBean") + String getGetBeanForm(@BeanParam GetBeanForm bean) { + return "ok name:" + bean.getName(); + } + + @Hidden + @Get + List getAll() { + return myService.findAll(); + } + + @Get("/async") + CompletableFuture> getAllAsync() { + return CompletableFuture.supplyAsync(() -> { + // Simulate a delay as if an actual IO operation is being executed. + // This also helps ensure that we aren't just getting lucky with timings. + try { + Thread.sleep(10L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + return myService.findAll(); + }, Executors.newSingleThreadExecutor()); // Example of how to use a custom executor. + } + + // @Hidden + @Delete(":id") + void deleteById(int id) { + System.out.println("deleting " + id); + } + + @Produces("text/plain") + @Get("/withMatrix/:year;author;country/:other") + String getWithMatrixParam(int year, String author, String country, String other, String extra) { + return "yr:" + year + " au:" + author + " co:" + country + " other:" + other + " extra:" + extra; + } + + @Produces("text/plain") + @Get("slash/{name}//other/") + String slashAccepting(String name, String nam0, String nam1) { + return "got name:" + name + " splat0:" + nam0 + " splat1:" + nam1; + } + + @Produces(value = "text/plain") + @Get("controlStatusCode") + String controlStatusCode(HttpContext ctx) { + ctx.status(201); + return "controlStatusCode"; + } + + @Produces(value = "text/plain") + @Get("takesNestedEnum") + String takesNestedEnum(Foo.NestedEnum myEnum) { + return "takesNestedEnum-" + myEnum; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/HelloDto.java b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloDto.java new file mode 100644 index 000000000..ac3762420 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloDto.java @@ -0,0 +1,56 @@ +package org.example.myapp.web; + +import java.time.Instant; +import java.util.UUID; + +import io.avaje.jsonb.Json; +@Json +public class HelloDto { + + public int id; + /** + * This is a comment. + */ + public String name; + /** + * This is a comment + */ + public String otherParam; + private UUID gid; + + private Instant whenAction; + + public HelloDto(int id, String name, String otherParam) { + this.id = id; + this.name = name; + this.otherParam = otherParam; + } + + /** + * Jackson constructor. + */ + public HelloDto() { + } + + public UUID getGid() { + return gid; + } + + public void setGid(UUID gid) { + this.gid = gid; + } + + public Instant getWhenAction() { + return whenAction; + } + + public void setWhenAction(Instant whenAction) { + this.whenAction = whenAction; + } + + @Override + public String toString() { + return "id:" + id + " name:" + name + " other:" + otherParam; + } + +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/HelloForm.java b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloForm.java new file mode 100644 index 000000000..51dcfb085 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/HelloForm.java @@ -0,0 +1,83 @@ +package org.example.myapp.web; + +import java.time.LocalDate; + +import org.hibernate.validator.constraints.URL; + +import io.avaje.jsonb.Json; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +@Json +@Valid +public class HelloForm { + + @NotNull + @Size(min = 2, max = 150) + String name; + + @Email + @Size(max = 100) + String email; +@URL + private String url; +@Future + public LocalDate startDate; + + public HelloForm(String name, String email) { + this.name = name; + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public LocalDate getStartDate() { + return startDate; + } + + public void setStartDate(LocalDate startDate) { + this.startDate = startDate; + } + + @Override + public String toString() { + return "HelloForm{" + + "name='" + + name + + '\'' + + ", email='" + + email + + '\'' + + ", url='" + + url + + '\'' + + ", startDate=" + + startDate + + '}'; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/Repository.java b/tests/test-sigma/src/main/java/org/example/myapp/web/Repository.java new file mode 100644 index 000000000..de24d26ca --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/Repository.java @@ -0,0 +1,13 @@ +package org.example.myapp.web; + +import java.util.List; + +public interface Repository { + + T findById(I id); + + List findAll(); + + I save(T bean); +} + diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/SecurityController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/SecurityController.java new file mode 100644 index 000000000..4668a9b91 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/SecurityController.java @@ -0,0 +1,22 @@ +package org.example.myapp.web; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Get; +import io.avaje.http.api.Path; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Controller +@Path("/security") +class SecurityController { + + @Get("/first") + @SecurityRequirement(name = "JWT") + String first() { + return "simple"; + } + + @Get("/second") + String second() { + return "simple"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/ServerType.java b/tests/test-sigma/src/main/java/org/example/myapp/web/ServerType.java new file mode 100644 index 000000000..244f00545 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/ServerType.java @@ -0,0 +1,7 @@ +package org.example.myapp.web; + +public enum ServerType { + PROXY, + HIDE_N_SEEK, + FFA +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/other/Foo.java b/tests/test-sigma/src/main/java/org/example/myapp/web/other/Foo.java new file mode 100644 index 000000000..f3ed0507a --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/other/Foo.java @@ -0,0 +1,7 @@ +package org.example.myapp.web.other; + +public class Foo { + public enum NestedEnum { + A, B, C + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/ErrorResponse.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/ErrorResponse.java new file mode 100644 index 000000000..4611cc712 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/ErrorResponse.java @@ -0,0 +1,7 @@ +package org.example.myapp.web.test; + +public class ErrorResponse { + + public String id; + public String text; +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthController.java new file mode 100644 index 000000000..8cdbea60a --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthController.java @@ -0,0 +1,30 @@ +package org.example.myapp.web.test; + +import io.avaje.http.api.Get; +import io.avaje.http.api.MediaType; +import io.avaje.http.api.OpenAPIResponse; +import io.avaje.http.api.Path; +import io.avaje.http.api.Produces; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Path("javalin") +@OpenAPIDefinition( + info = + @Info( + title = "Example service showing off the Path extension method of controller", + description = "")) +@OpenAPIResponse(responseCode = 403, description = "Not Authorized") +public interface HealthController { + /** + * Standard Get + * + * @return a health check + */ + @Get("/health") + @Produces(MediaType.TEXT_PLAIN) + @Tag(name = "tag1", description = "it's somethin") + @OpenAPIResponse(responseCode = 500, type = ErrorResponse.class) + String health(); +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthControllerImpl.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthControllerImpl.java new file mode 100644 index 000000000..e89195d97 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/HealthControllerImpl.java @@ -0,0 +1,13 @@ +package org.example.myapp.web.test; + +import io.avaje.http.api.Controller; + +@Controller +public class HealthControllerImpl implements HealthController { + + @Override + public String health() { + + return "this feels like a picnic *chew*"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/MyForm.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/MyForm.java new file mode 100644 index 000000000..8b7207843 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/MyForm.java @@ -0,0 +1,8 @@ +package org.example.myapp.web.test; + +public class MyForm { + + public String name; + public String email; + public String url; +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/OpenAPIController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/OpenAPIController.java new file mode 100644 index 000000000..39764584b --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/OpenAPIController.java @@ -0,0 +1,102 @@ +package org.example.myapp.web.test; + +import java.util.List; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Get; +import io.avaje.http.api.MediaType; +import io.avaje.http.api.OpenAPIResponse; +import io.avaje.http.api.OpenAPIResponses; +import io.avaje.http.api.Path; +import io.avaje.http.api.Post; +import io.avaje.http.api.Produces; +import io.avaje.http.api.Put; +import io.avaje.sigma.HttpContext; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.annotations.tags.Tag; + +@OpenAPIDefinition( + info = + @Info( + title = "Example service", + description = "Example Javalin controllers with Java and Maven")) +@Controller +@Path("openapi/") +@SecurityScheme( + type = SecuritySchemeType.APIKEY, + in = SecuritySchemeIn.QUERY, + name = "JWT", + paramName = "access_token", + description = + "JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.") +public class OpenAPIController { + + /** + * Example of Open API Get (up to the first period is the summary). When using Javalin HttpContext + * only
+ * This Javadoc description is added to the generated openapi.json + * + * @return funny phrase (this part of the javadoc is added to the response desc) + */ + @Get("/get") + @Produces(MediaType.TEXT_PLAIN) + @OpenAPIResponse(responseCode = 200, type = String.class) + void ctxEndpoint(HttpContext ctx) { + ctx.contentType(MediaType.TEXT_PLAIN).result("healthlmao"); + } + + /** + * Standard Post. uses tag annotation to add tags to openapi json + * + * @param b the body (this is used for generated request body desc) + * @return the response body (from javadoc) + */ + @Post("/post") + @Tag(name = "tag1", description = "this is added to openapi tags") + @OpenAPIResponse(responseCode = 200, description = "overrides @return javadoc description") + @OpenAPIResponse(responseCode = 201) + @OpenAPIResponse( + responseCode = 400, + description = "User not found (Will not have an associated response schema)") + @OpenAPIResponse( + responseCode = 500, + description = "Some other Error (Will have this error class as the response class)", + type = ErrorResponse.class) + Person testPost(Person b) { + return new Person(0, "baby"); + } + + /** + * Standard Post. The Deprecated annotation adds "deprecacted:true" to the generated json + * + * @param m the body + * @return the response body (from javadoc) + */ + @Deprecated + @Post("/post1") + @OpenAPIResponses({ + @OpenAPIResponse(responseCode = 400, description = "User not found"), + @OpenAPIResponse( + responseCode = 500, + description = "Some other Error", + type = ErrorResponse.class) + }) + Person testPostList(List m) { + return new Person(0, "baby"); + } + + @Put("/put") + @Produces(value = MediaType.TEXT_PLAIN, statusCode = 203) + @OpenAPIResponse(responseCode = 204, type = String.class) + String testDefaultStatus(HttpContext ctx) { + if (ctx.contentType().equals(MediaType.APPLICATION_PDF)) { + ctx.status(204); + return ""; + } + return "only partial info"; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/Person.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/Person.java new file mode 100644 index 000000000..434251ba0 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/Person.java @@ -0,0 +1,31 @@ +package org.example.myapp.web.test; + +import io.avaje.jsonb.Json; + +@Json +public class Person { + + long id; + String name; + + public Person(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController.java new file mode 100644 index 000000000..036777241 --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController.java @@ -0,0 +1,130 @@ +package org.example.myapp.web.test; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import org.example.myapp.web.HelloDto; +import org.example.myapp.web.ServerType; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Default; +import io.avaje.http.api.Form; +import io.avaje.http.api.Get; +import io.avaje.http.api.Header; +import io.avaje.http.api.MatrixParam; +import io.avaje.http.api.MediaType; +import io.avaje.http.api.Path; +import io.avaje.http.api.Post; +import io.avaje.http.api.Produces; +import io.avaje.http.api.Put; +import io.avaje.http.api.QueryParam; +import io.avaje.sigma.HttpContext; + +@Controller +@Path("test/") +public class TestController { + + @Get + @Produces(MediaType.TEXT_PLAIN) + String basic() { + return "Hello world - index"; + } + + @Get("hey") + @Produces(MediaType.TEXT_PLAIN) + String helloWorld() { + return "Hello world"; + } + + @Get("/ctx") + void testVoid(HttpContext ctx) { + ctx.result("success path:" + ctx.path()); + } + + @Get("/header") + String testHeader(@Header String head) { + return head; + } + + @Get("person/{name}") + Person testParamAndBody(String name) { + return new Person(42, name + " hello"); + } + + @Post("/person") + Person testPostPerson(Person body) { + return new Person(42, "Returning " + body.getName()); + } + + @Get("person/{sortBy}/list") + List testPersonList(String sortBy) { + return List.of(new Person(42, "fooList"), new Person(43, "barList")); + } + + // curl -v localhost:8081/person/foo/set + @Get("person/{sortBy}/set") + Set testPersonSet(String sortBy) { + return Set.of(new Person(42, "fooSet"), new Person(43, "barSet")); + } + + @Get("person/{sortBy}/map") + Map testPersonMap(String sortBy) { + return Map.of("one", new Person(42, "fooMap"), "two", new Person(43, "barMap")); + } + + @Put("person/update") + String testPersonListBody(List newGuys) { + return "New Guys Added"; + } + + @Put("int") + int testIntReturn() { + return 422; + } + + @Put("long") + long testLongReturn() { + return 69; + } + + // curl -X POST http://localhost:8081/form + // -H "Content-Type: application/x-www-form-urlencoded" + // -d "name=Jimmy&email=jim@foo&url=notaurl" + @Form + @Post("form") + String testForm(String name, String email, String url) { + return name + "-" + email + "-" + url; + } + + // curl -X POST http://localhost:8081/formBean + // -H "Content-Type:application/x-www-form-urlencoded" + // -d "name=FormBeanJimmy&email=jim@foo&url=notaurl" + @Form + @Post("formBean") + String testFormBean(MyForm form) { + return form.name + "|" + form.email + "|" + form.url; + } + + @Get("/withMatrixParam/{type-1;category;vendor-34}/{range;style}") + void neo( + @MatrixParam("type-1") String type, + String category, + @MatrixParam("vendor-34") String vendor, + String range, + String style) { + + System.out.println("Ever have that feeling where you're not sure if you're awake or dreaming?"); + } + + @Get("/async") + CompletableFuture getAllAsync() { + return CompletableFuture.supplyAsync(() -> new HelloDto(12, "Jim", "asd")); + } + + @Get("/enumQuery2") + String enumMultiQuery(@QueryParam @Default({"FFA", "PROXY"}) Set type) { + return type.toString(); + } +} diff --git a/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController2.java b/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController2.java new file mode 100644 index 000000000..b4b20804b --- /dev/null +++ b/tests/test-sigma/src/main/java/org/example/myapp/web/test/TestController2.java @@ -0,0 +1,95 @@ +package org.example.myapp.web.test; + +import java.util.Set; + +import org.example.myapp.web.ServerType; + +import io.avaje.http.api.BodyString; +import io.avaje.http.api.Controller; +import io.avaje.http.api.Default; +import io.avaje.http.api.ExceptionHandler; +import io.avaje.http.api.Filter; +import io.avaje.http.api.Form; +import io.avaje.http.api.FormParam; +import io.avaje.http.api.Get; +import io.avaje.http.api.InstrumentServerContext; +import io.avaje.http.api.Path; +import io.avaje.http.api.Post; +import io.avaje.http.api.QueryParam; +import io.avaje.sigma.HttpContext; + +@Path("test/") +@Controller +public class TestController2 { + + @Form + @Get("/enumForm") + @InstrumentServerContext + void enumForm(String s, ServerType type, HttpContext ctx) { + ctx.result(s); + } + + @Get("/enumFormParam") + @InstrumentServerContext + String enumFormParam(@FormParam String s, @FormParam ServerType type) throws Exception { + return type.name(); + } + + @Get("/enumQuery") + String enumQuery(@QueryParam @Default("FFA") ServerType type) { + return type.name(); + } + + @Post("/enumQueryImplied") + String enumQueryImplied(String s, @QueryParam ServerType type) { + return type.name(); + } + + @Post("/enumPath/{type}") + String enumPath(ServerType type) { + return type.name(); + } + + @Post("/strBody") + @InstrumentServerContext + String strBody(@BodyString String body) { + return body; + } + + @ExceptionHandler + String exception(Exception ex) { + + return ""; + } + + @ExceptionHandler + String exceptionCtx(Exception ex, HttpContext ctx) { + + return ""; + } + + @ExceptionHandler(RuntimeException.class) + void exceptionVoid(HttpContext ctx) { + System.err.println("do nothing lmao"); + } + + // @After + void after(String s, ServerType type, HttpContext ctx) { + ctx.result(s); + } + + // @Before + void before(String s, ServerType type, HttpContext ctx) { + ctx.result(s); + } + + @Filter + void filter(HttpContext ctx) { + } + + @Form + @Get("/formMulti") + String formMulti(Set strings) { + return strings.toString(); + } +} diff --git a/tests/test-sigma/src/main/resources/logback.xml b/tests/test-sigma/src/main/resources/logback.xml new file mode 100644 index 000000000..1119a0c85 --- /dev/null +++ b/tests/test-sigma/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + diff --git a/tests/test-sigma/src/test/java/io/avaje/http/generator/SigmaProcessorTest.java b/tests/test-sigma/src/test/java/io/avaje/http/generator/SigmaProcessorTest.java new file mode 100644 index 000000000..dda2e39b7 --- /dev/null +++ b/tests/test-sigma/src/test/java/io/avaje/http/generator/SigmaProcessorTest.java @@ -0,0 +1,102 @@ +package io.avaje.http.generator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import io.avaje.http.generator.sigma.SigmaProcessor; +import io.avaje.jsonb.generator.JsonbProcessor; + +class SigmaProcessorTest { + + @AfterEach + void deleteGeneratedFiles() throws IOException { + + Paths.get("openapi.json").toAbsolutePath().toFile().delete(); + + Files.walk(Paths.get("org").toAbsolutePath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + + @Test + public void runAnnotationProcessor() throws Exception { + final var source = Paths.get("src").toAbsolutePath().toString(); + + final var files = getSourceFiles(source); + + final var compiler = ToolProvider.getSystemJavaCompiler(); + + final var task = + compiler.getTask( + new PrintWriter(System.out), + null, + null, + List.of("--release=11", "-AdisableDirectWrites=true"), + null, + files); + task.setProcessors(List.of(new SigmaProcessor())); + + assertThat(task.call()).isTrue(); + assert Files.readString( + Paths.get("org/example/myapp/web/BarController$Route.java").toAbsolutePath()) + .contains("io.avaje.inject.Component"); + } + + @Test + void runAnnotationProcessorJakarta() throws Exception { + final var source = Paths.get("src").toAbsolutePath().toString(); + + final var files = getSourceFiles(source); + + final var compiler = ToolProvider.getSystemJavaCompiler(); + + final var task = + compiler.getTask( + new PrintWriter(System.out), + null, + null, + List.of( + "--release=11", + "-AuseJavax=false", + "-AuseSingleton=true", + "-AdisableDirectWrites=true"), + null, + files); + task.setProcessors(List.of(new SigmaProcessor(), new JsonbProcessor())); + + assertThat(task.call()).isTrue(); + + assert Files.readString( + Paths.get("org/example/myapp/web/BarController$Route.java").toAbsolutePath()) + .contains("jakarta.inject.Singleton"); + } + + private Iterable getSourceFiles(String source) throws Exception { + final var compiler = ToolProvider.getSystemJavaCompiler(); + final var files = compiler.getStandardFileManager(null, null, null); + + files.setLocation(StandardLocation.SOURCE_PATH, List.of(new File(source))); + + final Set fileKinds = Collections.singleton(Kind.SOURCE); + return files.list(StandardLocation.SOURCE_PATH, "", fileKinds, true); + } +} diff --git a/tests/test-sigma/src/test/resources/application-test.yaml b/tests/test-sigma/src/test/resources/application-test.yaml new file mode 100644 index 000000000..ae43f3a1f --- /dev/null +++ b/tests/test-sigma/src/test/resources/application-test.yaml @@ -0,0 +1,6 @@ +ebean: + test: +# shutdown: remove # stop | remove + platform: h2 # h2, postgres, mysql, mariadb, sqlserver, oracle, hana, clickhouse, sqlite + ddlMode: dropCreate # none | dropCreate | create | migration | createOnly | migrationDropCreate + dbName: my_app diff --git a/tests/test-sigma/src/test/resources/logback-test.xml b/tests/test-sigma/src/test/resources/logback-test.xml new file mode 100644 index 000000000..8467abcc9 --- /dev/null +++ b/tests/test-sigma/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + TRACE + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + +