diff --git a/endpoints.tapir b/endpoints.tapir
index e69de29..108382e 100644
--- a/endpoints.tapir
+++ b/endpoints.tapir
@@ -0,0 +1,9 @@
+projekt/create/ -> createProjekt(
+ id: ProjektId,
+ properties: ProjektProperties
+)
+
+projekt/update/ -> updateProjekt(
+ id: ProjektId,
+ properties: ProjektProperties
+)
\ No newline at end of file
diff --git a/parser/pom.xml b/parser/pom.xml
index 5062a5f..e5601dc 100755
--- a/parser/pom.xml
+++ b/parser/pom.xml
@@ -53,5 +53,9 @@
info.picocli
picocli
+
+ org.freemarker
+ freemarker
+
diff --git a/parser/src/main/java/nu/zoom/tapir/Generator.java b/parser/src/main/java/nu/zoom/tapir/Generator.java
index 2282e1d..c48d20d 100755
--- a/parser/src/main/java/nu/zoom/tapir/Generator.java
+++ b/parser/src/main/java/nu/zoom/tapir/Generator.java
@@ -1,70 +1,99 @@
-package nu.zoom.tapir;
-
-import nu.zoom.tapir.parser.*;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.Callable;
-
-import picocli.CommandLine;
-import picocli.CommandLine.Command;
-import picocli.CommandLine.Option;
-import picocli.CommandLine.Parameters;
-
-@Command(name = "tapirgen", mixinStandardHelpOptions = true, description = "Generate source code from a tapir endpoint specification file.")
-public class Generator implements Callable {
- @Parameters(index = "0", description = "The source tapir file.")
- private Path file;
-
- @Option(names = {"-t", "--template"}, description = "The template directory. Default is ~/tapir-templates")
- private Path templateDir = Paths.get(System.getProperty("user.dir"), "tapir-templates");
-
- @Option(names = {"-o", "--output"}, description = "The directory to write the gerenated code to. Default is ~/tapir-output")
- private Path outputDir = Paths.get(System.getProperty("user.dir"), "tapir-output");
-
- public static void main(String[] args) throws ParseException {
- int exitCode = new CommandLine(new Generator()).execute(args);
- System.exit(exitCode);
- }
-
- @Override
- public Integer call() {
- try {
- validateTemplateDirectory();
- validateInputFile();
- StringReader reader = new StringReader("foo/bar/baz/ -> fooHandler(id:String, name:NonEmptyString)foo/baz/->bazHandler(nothing:Any)");
- var rootNode = new TapirParser(reader).endpoints();
- var endpoints = NodeTransformer.transform(rootNode);
- rootNode.dump("");
- endpoints.forEach(endpoint -> {
- System.out.println(endpoint);
- });
- return 0;
- } catch (Exception e) {
- System.err.println(e.getMessage());
- return 1;
- }
- }
-
- private void validateTemplateDirectory() throws IOException {
- if (!Files.isDirectory(this.templateDir)) {
- throw new IllegalArgumentException("Template directory '" + this.templateDir + "' does not exist.");
- }
- Path endpointTemplate = this.templateDir.resolve("endpoints.ftl") ;
- if (Files.notExists(endpointTemplate)) {
- throw new IllegalArgumentException("Can not find: '" + endpointTemplate + "'.");
- }
- }
-
- private void validateInputFile() throws IOException {
- if (Files.notExists(this.file)) {
- throw new IllegalArgumentException("Input file '" + this.file + "' does not exist.");
- }
- if (!Files.isReadable(this.file) || !Files.isRegularFile(this.file)) {
- throw new IllegalArgumentException("Input file '" + this.file + "' is not a readable file.");
- }
- }
-}
+package nu.zoom.tapir;
+
+import nu.zoom.tapir.parser.*;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.Callable;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+@Command(name = "tapirgen", mixinStandardHelpOptions = true, description = "Generate source code from a tapir endpoint specification file.")
+public class Generator implements Callable {
+ @Parameters(index = "0", description = "The source tapir file.")
+ private Path file;
+
+ @Option(names = {"-t", "--template"}, description = "The template directory. Default is ~/tapir-templates")
+ private Path templateDir = Paths.get(System.getProperty("user.dir"), "tapir-templates");
+
+ @Option(names = {"-o", "--output"}, description = "The directory to write the gerenated code to. Default is ~/tapir-output")
+ private Path outputDir = Paths.get(System.getProperty("user.dir"), "tapir-output");
+
+ @Option(names = {"-v", "--verbose"}, description = "Print verbose debug messages.")
+ private Boolean verbose = false;
+
+ public static void main(String[] args) throws ParseException {
+ int exitCode = new CommandLine(new Generator()).execute(args);
+ System.exit(exitCode);
+ }
+
+ @Override
+ public Integer call() {
+ try {
+ validateTemplateDirectory();
+ validateInputFile();
+ validateOutputDirectory();
+ var rootNode = new TapirParser(Files.newBufferedReader(this.file)).endpoints();
+ if (this.verbose) {
+ System.out.println("====== Parse Tree ======");
+ rootNode.dump("");
+ }
+ var endpoints = NodeTransformer.transform(rootNode);
+ if (endpoints.isEmpty()) {
+ System.err.println("No tapir endpoints found.");
+ return 2;
+ }
+ if (this.verbose) {
+ System.out.println("\n====== AST ======");
+ endpoints.forEach(endpoint -> {
+ System.out.println(endpoint);
+ });
+ }
+ TargetGenerator targetGenerator = new TargetGenerator(
+ this.verbose,
+ this.outputDir,
+ this.templateDir,
+ endpoints
+ );
+ targetGenerator.generate();
+ return 0;
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ return 1;
+ }
+ }
+
+ private void validateOutputDirectory() throws IOException {
+ if (Files.notExists(this.outputDir)) {
+ Files.createDirectories(this.outputDir);
+ }
+ if (!Files.isDirectory(this.outputDir)) {
+ throw new IllegalArgumentException("Output directory: '" + this.outputDir + " 'is not a directory.");
+ }
+ }
+
+ private void validateTemplateDirectory() throws IOException {
+ if (!Files.isDirectory(this.templateDir)) {
+ throw new IllegalArgumentException("Template directory '" + this.templateDir + "' does not exist.");
+ }
+ Path endpointTemplate = this.templateDir.resolve(TargetGenerator.ENDPOINTS_TEMPLATE_NAME);
+ if (Files.notExists(endpointTemplate)) {
+ throw new IllegalArgumentException("Can not find: '" + endpointTemplate + "'.");
+ }
+ }
+
+ private void validateInputFile() throws IOException {
+ if (Files.notExists(this.file)) {
+ throw new IllegalArgumentException("Input file '" + this.file + "' does not exist.");
+ }
+ if (!Files.isReadable(this.file) || !Files.isRegularFile(this.file)) {
+ throw new IllegalArgumentException("Input file '" + this.file + "' is not a readable file.");
+ }
+ }
+}
diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java
index f4bcfa8..7c95f5a 100644
--- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java
+++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java
@@ -1,12 +1,24 @@
package nu.zoom.tapir;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
import java.util.List;
import java.util.Objects;
public class TargetGenerator {
private final Path outputPath;
private final Path templatePath;
+ private final boolean verbose;
+ public static String ENDPOINTS_TEMPLATE_NAME = "endpoints.ftl";
+ private final List endpoints;
public static class TargetGeneratorException extends Exception {
public TargetGeneratorException(String message) {
@@ -18,10 +30,12 @@ public class TargetGenerator {
}
public TargetGenerator(
+ final boolean verbose,
Path outputPath,
Path templatePath,
List endpoints
) {
+ this.verbose = verbose;
this.outputPath = Objects.requireNonNull(
outputPath,
"Output path is required"
@@ -30,9 +44,26 @@ public class TargetGenerator {
templatePath,
"Template path is required"
);
+ this.endpoints = Objects.requireNonNull(endpoints) ;
}
- public void generate() throws TargetGeneratorException {
-
+ public void generate() throws TargetGeneratorException, IOException, TemplateException {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
+ cfg.setDirectoryForTemplateLoading(this.templatePath.toFile());
+ cfg.setDefaultEncoding("UTF-8");
+ cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ cfg.setLogTemplateExceptions(false);
+ cfg.setWrapUncheckedExceptions(true);
+ cfg.setFallbackOnNullLoopVariable(false);
+ Template temp = cfg.getTemplate(ENDPOINTS_TEMPLATE_NAME);
+ try (var outputFile = Files.newBufferedWriter(
+ outputPath.resolve("endpoints.scala"),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING
+ )) {
+ HashMap> templateData = new HashMap<>();
+ templateData.put("endpoints", endpoints);
+ temp.process(templateData, outputFile);
+ }
}
}
diff --git a/parser/src/main/jjtree/tapir.jjt b/parser/src/main/jjtree/tapir.jjt
index 83babd3..72355da 100755
--- a/parser/src/main/jjtree/tapir.jjt
+++ b/parser/src/main/jjtree/tapir.jjt
@@ -27,8 +27,9 @@ TOKEN : {
|
|
|
- |
- | )+ >
+ |
+ |
+ | ()* >
}
void path() :
diff --git a/pom.xml b/pom.xml
index 0e81479..6d0f4a6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,11 @@
picocli
4.7.6
+
+ org.freemarker
+ freemarker
+ 2.3.34
+
diff --git a/tapir-templates/endpoints.ftl b/tapir-templates/endpoints.ftl
index e69de29..d0c8157 100644
--- a/tapir-templates/endpoints.ftl
+++ b/tapir-templates/endpoints.ftl
@@ -0,0 +1,37 @@
+package se.senashdev.projekt.api
+
+import se.rutdev.projekt.api.HttpProtocol.VersionedResponse
+import se.rutdev.framework.json.circe.RutUtilsCodec
+import se.rutdev.framework
+import se.rutdev.framework.service.api.{OAuthUtils, RequestMeta, RutTapir}
+import se.rutdev.pd.ProblemDetailProtocol.ProblemDetail
+import sttp.tapir.Schema
+
+class Endpoints(override val config: OAuthUtils.OAuthConfig) extends framework.service.api.Endpoints with RutTapir with RutUtilsCodec:
+ type ApiEndpoint[I, O] = OAuthEndpoint[RequestMeta.OAuthRequestMeta, I, ProblemDetail, O]
+
+ <#list endpoints as endpoint>
+ case class ${endpoint.handler.name?cap_first}(
+ <#list endpoint.handler.fields as field>
+ ${field.name} : ${field.type},
+ #list>
+ )
+ #list>
+
+ <#list endpoints as endpoint>
+ given Codec[${endpoint.handler.name?cap_first}] = deriveCodec
+ #list>
+
+ <#list endpoints as endpoint>
+ val ${endpoint.handler.name}Endpoint = ApiEndpoint[${endpoint.handler.name?cap_first}, VersionedResponse] =
+ <#list endpoint.paths.paths>
+ apiV1Endpoint
+ .post
+ <#items as segment>
+ .in("${segment}")
+ #items>
+ .post
+ .in(jsonBody[${endpoint.handler.name?cap_first}])
+ .out(jsonBody[VersionedResponse])
+ #list>
+ #list>