Demo ready
This commit is contained in:
parent
743432e071
commit
620999a992
7 changed files with 190 additions and 74 deletions
|
@ -0,0 +1,9 @@
|
|||
projekt/create/ -> createProjekt(
|
||||
id: ProjektId,
|
||||
properties: ProjektProperties
|
||||
)
|
||||
|
||||
projekt/update/ -> updateProjekt(
|
||||
id: ProjektId,
|
||||
properties: ProjektProperties
|
||||
)
|
|
@ -53,5 +53,9 @@
|
|||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -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<Integer> {
|
||||
@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<Integer> {
|
||||
@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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<EndpointNode> 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<EndpointNode> 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<String, List<EndpointNode>> templateData = new HashMap<>();
|
||||
templateData.put("endpoints", endpoints);
|
||||
temp.process(templateData, outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ TOKEN : {
|
|||
| <SLASH: "/">
|
||||
| <COLON: ":">
|
||||
| <COMMA: ",">
|
||||
| <LETTER: [ "A"-"Z", "a"-"z" ]>
|
||||
| <IDENTIFIER: ( <LETTER>)+ >
|
||||
| <FIRST_LETTER: [ "A"-"Z", "a"-"z" ]>
|
||||
| <LETTER: [ "A"-"Z", "a"-"z", "0"-"9", "[", "]"] >
|
||||
| <IDENTIFIER: <FIRST_LETTER> (<LETTER>)* >
|
||||
}
|
||||
|
||||
void path() :
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -30,6 +30,11 @@
|
|||
<artifactId>picocli</artifactId>
|
||||
<version>4.7.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
<version>2.3.34</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue