From 857f9c63a6d7679592ef258b9f67be4c5607ce33 Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Tue, 18 Mar 2025 21:27:26 +0100 Subject: [PATCH 1/6] Added types declarations --- parser/pom.xml | 1 + .../main/java/nu/zoom/tapir/DataTypeNode.java | 6 + .../main/java/nu/zoom/tapir/Generator.java | 16 ++- .../java/nu/zoom/tapir/NodeTransformer.java | 113 +++++++++++++++--- .../java/nu/zoom/tapir/TargetGenerator.java | 41 ++++--- parser/src/main/jjtree/tapir.jjt | 41 ++++--- 6 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 parser/src/main/java/nu/zoom/tapir/DataTypeNode.java diff --git a/parser/pom.xml b/parser/pom.xml index e5601dc..b29ad05 100755 --- a/parser/pom.xml +++ b/parser/pom.xml @@ -32,6 +32,7 @@ --> jjtree-javacc + generate-sources jjtree-javacc diff --git a/parser/src/main/java/nu/zoom/tapir/DataTypeNode.java b/parser/src/main/java/nu/zoom/tapir/DataTypeNode.java new file mode 100644 index 0000000..923214e --- /dev/null +++ b/parser/src/main/java/nu/zoom/tapir/DataTypeNode.java @@ -0,0 +1,6 @@ +package nu.zoom.tapir; + +import java.util.List; + +public record DataTypeNode(String name, List fields) { +} diff --git a/parser/src/main/java/nu/zoom/tapir/Generator.java b/parser/src/main/java/nu/zoom/tapir/Generator.java index c48d20d..a068ca0 100755 --- a/parser/src/main/java/nu/zoom/tapir/Generator.java +++ b/parser/src/main/java/nu/zoom/tapir/Generator.java @@ -39,19 +39,25 @@ public class Generator implements Callable { validateTemplateDirectory(); validateInputFile(); validateOutputDirectory(); - var rootNode = new TapirParser(Files.newBufferedReader(this.file)).endpoints(); + var rootNode = new TapirParser(Files.newBufferedReader(this.file)).specification(); if (this.verbose) { System.out.println("====== Parse Tree ======"); rootNode.dump(""); } - var endpoints = NodeTransformer.transform(rootNode); - if (endpoints.isEmpty()) { + NodeTransformer transformer = new NodeTransformer(); + transformer.transform(rootNode); + if (transformer.getEndpoints().isEmpty()) { System.err.println("No tapir endpoints found."); return 2; } if (this.verbose) { System.out.println("\n====== AST ======"); - endpoints.forEach(endpoint -> { + System.out.println("\n====== Types ======"); + transformer.getDataTypes().forEach(type -> { + System.out.println(type); + }); + System.out.println("\n====== Endpoints ======"); + transformer.getEndpoints().forEach(endpoint -> { System.out.println(endpoint); }); } @@ -59,7 +65,7 @@ public class Generator implements Callable { this.verbose, this.outputDir, this.templateDir, - endpoints + transformer.getEndpoints() ); targetGenerator.generate(); return 0; diff --git a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java index c4ed179..ffa5469 100644 --- a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java +++ b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java @@ -9,14 +9,95 @@ import java.util.ArrayList; import java.util.List; public class NodeTransformer { + private final List endpoints = new ArrayList<>(); + private final List dataTypes = new ArrayList<>(); - public static List transform(SimpleNode rootNode) throws ParseException { - ArrayList endpoints = new ArrayList<>(); - for (int i = 0; i < rootNode.jjtGetNumChildren(); i++) { - SimpleNode endpoint = assertSimpleNode(rootNode.jjtGetChild(i)); - endpoints.add(handleEndpoint(endpoint)); + public List getEndpoints() { + return endpoints; + } + + public List getDataTypes() { + return dataTypes; + } + + public void transform(SimpleNode rootNode) throws ParseException { + assertSimpleNodeType(rootNode, TapirParserTreeConstants.JJTSPECIFICATION); + int numChildren = rootNode.jjtGetNumChildren(); + if (numChildren == 2) { + this.dataTypes.addAll( + handleDataTypes( + assertSimpleNodeType( + rootNode.jjtGetChild(0), + TapirParserTreeConstants.JJTDATATYPES + ) + ) + ); + this.endpoints.addAll( + handleEndpoints( + assertSimpleNodeType( + rootNode.jjtGetChild(1), + TapirParserTreeConstants.JJTENDPOINTS + ) + ) + ); + } else if (numChildren == 1) { + this.endpoints.addAll( + handleEndpoints( + assertSimpleNodeType( + rootNode.jjtGetChild(1), + TapirParserTreeConstants.JJTENDPOINTS + ) + ) + ); + } else { + throw new ParseException("Expected specification to have 1 or 2 children but had " + numChildren); } - return endpoints ; + } + + private static List handleEndpoints(SimpleNode endpoints) throws ParseException { + ArrayList endpointNodes = new ArrayList<>(); + for (int i = 0; i < endpoints.jjtGetNumChildren(); i++) { + endpointNodes.add( + handleEndpoint( + assertSimpleNodeType( + endpoints.jjtGetChild(i), + TapirParserTreeConstants.JJTENDPOINT + ) + ) + ); + } + return endpointNodes; + } + + private static List handleDataTypes(SimpleNode dataTypesDeclaration) throws ParseException { + List dataTypes = new ArrayList<>(); + for (int i = 0; i < dataTypesDeclaration.jjtGetNumChildren(); i++) { + dataTypes.add( + handleCompoundDataType( + assertSimpleNodeType( + dataTypesDeclaration.jjtGetChild(i), + TapirParserTreeConstants.JJTCOMPOUNDDATATYPE + ) + ) + ); + } + return dataTypes; + } + + private static DataTypeNode handleCompoundDataType(SimpleNode dataTypeNode) throws ParseException { + String typename = getStringValue( + assertSimpleNodeType( + dataTypeNode.jjtGetChild(0), + TapirParserTreeConstants.JJTCOMPUNDDATATYPENAME + ) + ); + List fields = handleFields( + assertSimpleNodeType( + dataTypeNode.jjtGetChild(1), + TapirParserTreeConstants.JJTDATATYPEFIELDS + ) + ); + return new DataTypeNode(typename, fields); } private static EndpointNode handleEndpoint(SimpleNode node) throws ParseException { @@ -27,13 +108,13 @@ public class NodeTransformer { SimpleNode pathsParseNode = assertSimpleNodeType( node.jjtGetChild(0), - TapirParserTreeConstants.JJTPATHS + TapirParserTreeConstants.JJTPATH ); PathsNode pathsNode = handlePaths(pathsParseNode); SimpleNode handlerParseNode = assertSimpleNodeType( node.jjtGetChild(1), - TapirParserTreeConstants.JJTHANDLERSPEC + TapirParserTreeConstants.JJTCOMPOUNDDATATYPE ); HandlerNode handlerNode = handleHandler(handlerParseNode); return new EndpointNode(pathsNode, handlerNode); @@ -47,25 +128,25 @@ public class NodeTransformer { String handlerName = getStringValue( assertSimpleNodeType( handlerSpec.jjtGetChild(0), - TapirParserTreeConstants.JJTHANDLERNAME + TapirParserTreeConstants.JJTCOMPUNDDATATYPENAME ) ); SimpleNode payloadFieldsParseNode = assertSimpleNodeType( handlerSpec.jjtGetChild(1), - TapirParserTreeConstants.JJTPAYLOADFIELDS + TapirParserTreeConstants.JJTDATATYPEFIELDS ); List fields = handleFields(payloadFieldsParseNode); return new HandlerNode(handlerName, fields); } - private static List handleFields(SimpleNode payloadFieldsParseNode) throws ParseException { + private static List handleFields(SimpleNode compoundDatatTypeFields) throws ParseException { ArrayList fields = new ArrayList<>(); - for (int i = 0; i < payloadFieldsParseNode.jjtGetNumChildren(); i++) { + for (int i = 0; i < compoundDatatTypeFields.jjtGetNumChildren(); i++) { SimpleNode payloadFieldParseNode = assertSimpleNodeType( - payloadFieldsParseNode.jjtGetChild(i), - TapirParserTreeConstants.JJTPAYLOADFIELD + compoundDatatTypeFields.jjtGetChild(i), + TapirParserTreeConstants.JJTDATATYPEFIELDS ); int numFieldNodes = payloadFieldParseNode.jjtGetNumChildren(); if (numFieldNodes != 2) { @@ -74,13 +155,13 @@ public class NodeTransformer { String fieldName = getStringValue( assertSimpleNodeType( payloadFieldParseNode.jjtGetChild(0), - TapirParserTreeConstants.JJTPAYLOADFIELDNAME + TapirParserTreeConstants.JJTDATATYPEFIELDNAME ) ); String fieldType = getStringValue( assertSimpleNodeType( payloadFieldParseNode.jjtGetChild(1), - TapirParserTreeConstants.JJTPAYLOADFIELDTYPE + TapirParserTreeConstants.JJTDATATYPEFIELDTYPE ) ); fields.add(new FieldNode(fieldName, fieldType)); diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index 7c95f5a..307a063 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -24,6 +24,7 @@ public class TargetGenerator { public TargetGeneratorException(String message) { super(message); } + public TargetGeneratorException(Exception cause) { super(cause); } @@ -44,26 +45,30 @@ public class TargetGenerator { templatePath, "Template path is required" ); - this.endpoints = Objects.requireNonNull(endpoints) ; + this.endpoints = Objects.requireNonNull(endpoints); } - 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); + public void generate() throws TargetGeneratorException { + try { + 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); + } + } catch (TemplateException | IOException ex) { + throw new TargetGeneratorException(ex); } } } diff --git a/parser/src/main/jjtree/tapir.jjt b/parser/src/main/jjtree/tapir.jjt index 72355da..efa5e5b 100755 --- a/parser/src/main/jjtree/tapir.jjt +++ b/parser/src/main/jjtree/tapir.jjt @@ -23,6 +23,7 @@ SKIP: { TOKEN : { | + | | "> | | @@ -32,63 +33,75 @@ TOKEN : { | ()* > } -void path() : +void pathSegment() : {Token t;} { t={jjtThis.value = t.image;} } -void paths() : +void path() : {} { - path() (path())* + pathSegment() (pathSegment())* } -void payloadFieldName() : +void dataTypeFieldType() : {Token t;} { t={jjtThis.value = t.image;} } -void payloadFieldType() : +void dataTypeFieldName() : {Token t;} { t={jjtThis.value = t.image;} } -void payloadField() : +void dataTypeField() : {} { - payloadFieldName() payloadFieldType() + dataTypeFieldName() dataTypeFieldType() } -void payloadFields() : +void dataTypeFields() : {} { - payloadField() ( payloadField() )* + dataTypeField() ( dataTypeField() )* } -void handlerName() : +void compundDataTypeName() : {Token t;} { t={jjtThis.value = t.image;} } -void handlerSpec() : +void compoundDataType() : {} { - handlerName() payloadFields() + compundDataTypeName() dataTypeFields() +} + +void dataTypes() : +{} +{ + (compoundDataType() )* } void endpoint() : {} { - paths() handlerSpec() + path() compoundDataType() } -SimpleNode endpoints() : +void endpoints() : {} { (endpoint() )* +} + +SimpleNode specification() : +{} +{ + dataTypes() endpoints() { return jjtThis; } } From 127e4013e0ea0330f0387e1131b6ac3ec27027cc Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Wed, 19 Mar 2025 21:19:56 +0100 Subject: [PATCH 2/6] Added types declarations --- .gitignore | 23 +------------ endpoints.tapir | 10 ++++-- .../main/java/nu/zoom/tapir/Generator.java | 3 +- .../java/nu/zoom/tapir/NodeTransformer.java | 4 +-- .../java/nu/zoom/tapir/TargetGenerator.java | 8 +++-- parser/src/main/jjtree/tapir.jjt | 2 +- tapir-templates/endpoints.ftl | 33 ++++++++++++------- 7 files changed, 42 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 6efeca9..2b562cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +tapir-out/** # ---> Scala *.class *.log @@ -39,25 +40,6 @@ replay_pid* .idea/** *.iml -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using @@ -74,9 +56,6 @@ replay_pid* # CMake cmake-build-*/ -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - # File-based project format *.iws diff --git a/endpoints.tapir b/endpoints.tapir index 108382e..3edbd87 100644 --- a/endpoints.tapir +++ b/endpoints.tapir @@ -1,9 +1,15 @@ -projekt/create/ -> createProjekt( +ProjektProperties( + id: String, + title: String, + description: String +) + +/projekt/create -> createProjekt( id: ProjektId, properties: ProjektProperties ) -projekt/update/ -> updateProjekt( +/projekt/update -> updateProjekt( id: ProjektId, properties: ProjektProperties ) \ No newline at end of file diff --git a/parser/src/main/java/nu/zoom/tapir/Generator.java b/parser/src/main/java/nu/zoom/tapir/Generator.java index a068ca0..8488a2b 100755 --- a/parser/src/main/java/nu/zoom/tapir/Generator.java +++ b/parser/src/main/java/nu/zoom/tapir/Generator.java @@ -65,7 +65,8 @@ public class Generator implements Callable { this.verbose, this.outputDir, this.templateDir, - transformer.getEndpoints() + transformer.getEndpoints(), + transformer.getDataTypes() ); targetGenerator.generate(); return 0; diff --git a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java index ffa5469..f0d3462 100644 --- a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java +++ b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java @@ -146,7 +146,7 @@ public class NodeTransformer { SimpleNode payloadFieldParseNode = assertSimpleNodeType( compoundDatatTypeFields.jjtGetChild(i), - TapirParserTreeConstants.JJTDATATYPEFIELDS + TapirParserTreeConstants.JJTDATATYPEFIELD ); int numFieldNodes = payloadFieldParseNode.jjtGetNumChildren(); if (numFieldNodes != 2) { @@ -173,7 +173,7 @@ public class NodeTransformer { int numPathSegments = pathsParseNode.jjtGetNumChildren(); ArrayList segments = new ArrayList<>(); for (int i = 0; i < numPathSegments; i++) { - SimpleNode segmentParseNode = assertSimpleNodeType(pathsParseNode.jjtGetChild(i), TapirParserTreeConstants.JJTPATH); + SimpleNode segmentParseNode = assertSimpleNodeType(pathsParseNode.jjtGetChild(i), TapirParserTreeConstants.JJTPATHSEGMENT); segments.add(getStringValue(segmentParseNode)); } return new PathsNode(segments); diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index 307a063..4dbc1f6 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -19,6 +19,7 @@ public class TargetGenerator { private final boolean verbose; public static String ENDPOINTS_TEMPLATE_NAME = "endpoints.ftl"; private final List endpoints; + private final List dataTypes; public static class TargetGeneratorException extends Exception { public TargetGeneratorException(String message) { @@ -34,7 +35,8 @@ public class TargetGenerator { final boolean verbose, Path outputPath, Path templatePath, - List endpoints + List endpoints, + List dataTypes ) { this.verbose = verbose; this.outputPath = Objects.requireNonNull( @@ -46,6 +48,7 @@ public class TargetGenerator { "Template path is required" ); this.endpoints = Objects.requireNonNull(endpoints); + this.dataTypes = Objects.requireNonNull(dataTypes); } public void generate() throws TargetGeneratorException { @@ -63,8 +66,9 @@ public class TargetGenerator { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING )) { - HashMap> templateData = new HashMap<>(); + HashMap templateData = new HashMap<>(); templateData.put("endpoints", endpoints); + templateData.put("datatypes", dataTypes); temp.process(templateData, outputFile); } } catch (TemplateException | IOException ex) { diff --git a/parser/src/main/jjtree/tapir.jjt b/parser/src/main/jjtree/tapir.jjt index efa5e5b..55ce140 100755 --- a/parser/src/main/jjtree/tapir.jjt +++ b/parser/src/main/jjtree/tapir.jjt @@ -36,7 +36,7 @@ TOKEN : { void pathSegment() : {Token t;} { - t={jjtThis.value = t.image;} + t={jjtThis.value = t.image;} } void path() : diff --git a/tapir-templates/endpoints.ftl b/tapir-templates/endpoints.ftl index d0c8157..64805ae 100644 --- a/tapir-templates/endpoints.ftl +++ b/tapir-templates/endpoints.ftl @@ -10,6 +10,14 @@ 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 datatypes as datatype> + case class ${datatype.name}( + <#list datatype.fields as field> + ${field.name} : ${field.type}, + + ) + + <#list endpoints as endpoint> case class ${endpoint.handler.name?cap_first}( <#list endpoint.handler.fields as field> @@ -18,20 +26,23 @@ class Endpoints(override val config: OAuthUtils.OAuthConfig) extends framework.s ) + <#list datatypes as datatype> + given Codec[${datatype.name?cap_first}] = deriveCodec + <#list endpoints as endpoint> given Codec[${endpoint.handler.name?cap_first}] = deriveCodec <#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}") - - .post - .in(jsonBody[${endpoint.handler.name?cap_first}]) - .out(jsonBody[VersionedResponse]) - + val ${endpoint.handler.name}Endpoint = ApiEndpoint[${endpoint.handler.name?cap_first}, VersionedResponse] = + <#list endpoint.paths.paths> + apiV1Endpoint + .post + <#items as segment> + .in("${segment}") + + .post + .in(jsonBody[${endpoint.handler.name?cap_first}]) + .out(jsonBody[VersionedResponse]) + From e4e6b6fc256698a710b60caf0e796c9334651cd9 Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Wed, 19 Mar 2025 21:31:32 +0100 Subject: [PATCH 3/6] Support several templates --- .../java/nu/zoom/tapir/TargetGenerator.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index 4dbc1f6..c4031c3 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -61,18 +61,30 @@ public class TargetGenerator { 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); - templateData.put("datatypes", dataTypes); - temp.process(templateData, outputFile); + List templates = Files + .list(this.templatePath) + .filter(Files::isRegularFile) + .filter(f -> f.getFileName().toString().endsWith(".ftl")) + .toList() ; + for (Path template : templates) { + try (var outputFile = Files.newBufferedWriter( + outputName(template), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + )) { + HashMap templateData = new HashMap<>(); + templateData.put("endpoints", endpoints); + templateData.put("datatypes", dataTypes); + temp.process(templateData, outputFile); + } } } catch (TemplateException | IOException ex) { throw new TargetGeneratorException(ex); } } + + private Path outputName(Path templatePath) { + String name = templatePath.getFileName().toString().replace(".ftl", ".scala") ; + return this.outputPath.resolve(name) ; + } } From 145a8672aba842387218f2494bd7455ee17e1a33 Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Wed, 19 Mar 2025 21:33:36 +0100 Subject: [PATCH 4/6] Support several templates --- parser/src/main/java/nu/zoom/tapir/TargetGenerator.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index c4031c3..de207a7 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -16,16 +16,11 @@ 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; private final List dataTypes; public static class TargetGeneratorException extends Exception { - public TargetGeneratorException(String message) { - super(message); - } - public TargetGeneratorException(Exception cause) { super(cause); } @@ -38,7 +33,6 @@ public class TargetGenerator { List endpoints, List dataTypes ) { - this.verbose = verbose; this.outputPath = Objects.requireNonNull( outputPath, "Output path is required" From 92098b1a3b855da89af6c39748461385a49e5e5f Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Wed, 19 Mar 2025 21:35:50 +0100 Subject: [PATCH 5/6] Support several templates --- parser/src/main/java/nu/zoom/tapir/TargetGenerator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index de207a7..537d132 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -19,6 +19,7 @@ public class TargetGenerator { public static String ENDPOINTS_TEMPLATE_NAME = "endpoints.ftl"; private final List endpoints; private final List dataTypes; + private final boolean verbose ; public static class TargetGeneratorException extends Exception { public TargetGeneratorException(Exception cause) { @@ -27,12 +28,13 @@ public class TargetGenerator { } public TargetGenerator( - final boolean verbose, + boolean verbose, Path outputPath, Path templatePath, List endpoints, List dataTypes ) { + this.verbose = verbose; this.outputPath = Objects.requireNonNull( outputPath, "Output path is required" @@ -66,6 +68,9 @@ public class TargetGenerator { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING )) { + if (this.verbose) { + System.out.println("Processing " + template); + } HashMap templateData = new HashMap<>(); templateData.put("endpoints", endpoints); templateData.put("datatypes", dataTypes); From 20aa2becfb5b85d6bb131e581a6d2a75ce502d44 Mon Sep 17 00:00:00 2001 From: Johan Maasing Date: Thu, 20 Mar 2025 19:22:41 +0100 Subject: [PATCH 6/6] Remove handler name, use the last path segment to name the payload class --- endpoints.tapir | 5 ++--- .../main/java/nu/zoom/tapir/NodeTransformer.java | 5 +++-- .../main/java/nu/zoom/tapir/TargetGenerator.java | 11 ++++++----- parser/src/main/jjtree/tapir.jjt | 2 +- tapir-templates/codec.ftl | 8 ++++++++ tapir-templates/endpoints.ftl | 15 ++++----------- 6 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 tapir-templates/codec.ftl diff --git a/endpoints.tapir b/endpoints.tapir index 3edbd87..19eeef3 100644 --- a/endpoints.tapir +++ b/endpoints.tapir @@ -1,15 +1,14 @@ ProjektProperties( - id: String, title: String, description: String ) -/projekt/create -> createProjekt( +/createProject -> ( id: ProjektId, properties: ProjektProperties ) -/projekt/update -> updateProjekt( +/updateProject -> ( id: ProjektId, properties: ProjektProperties ) \ No newline at end of file diff --git a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java index f0d3462..942465d 100644 --- a/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java +++ b/parser/src/main/java/nu/zoom/tapir/NodeTransformer.java @@ -114,9 +114,10 @@ public class NodeTransformer { SimpleNode handlerParseNode = assertSimpleNodeType( node.jjtGetChild(1), - TapirParserTreeConstants.JJTCOMPOUNDDATATYPE + TapirParserTreeConstants.JJTDATATYPEFIELDS ); - HandlerNode handlerNode = handleHandler(handlerParseNode); + List fields = handleFields(handlerParseNode); + HandlerNode handlerNode = new HandlerNode(pathsNode.paths().getLast(), fields); return new EndpointNode(pathsNode, handlerNode); } diff --git a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java index 537d132..c47d4e2 100644 --- a/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java +++ b/parser/src/main/java/nu/zoom/tapir/TargetGenerator.java @@ -56,25 +56,26 @@ public class TargetGenerator { cfg.setLogTemplateExceptions(false); cfg.setWrapUncheckedExceptions(true); cfg.setFallbackOnNullLoopVariable(false); - Template temp = cfg.getTemplate(ENDPOINTS_TEMPLATE_NAME); List templates = Files .list(this.templatePath) .filter(Files::isRegularFile) .filter(f -> f.getFileName().toString().endsWith(".ftl")) .toList() ; - for (Path template : templates) { + for (Path templatePath : templates) { try (var outputFile = Files.newBufferedWriter( - outputName(template), + outputName(templatePath), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING )) { if (this.verbose) { - System.out.println("Processing " + template); + System.out.println("Processing " + templatePath); } HashMap templateData = new HashMap<>(); templateData.put("endpoints", endpoints); templateData.put("datatypes", dataTypes); - temp.process(templateData, outputFile); + cfg.getTemplate( + templatePath.getFileName().toString() + ).process(templateData, outputFile); } } } catch (TemplateException | IOException ex) { diff --git a/parser/src/main/jjtree/tapir.jjt b/parser/src/main/jjtree/tapir.jjt index 55ce140..4f38e04 100755 --- a/parser/src/main/jjtree/tapir.jjt +++ b/parser/src/main/jjtree/tapir.jjt @@ -90,7 +90,7 @@ void dataTypes() : void endpoint() : {} { - path() compoundDataType() + path() dataTypeFields() } void endpoints() : diff --git a/tapir-templates/codec.ftl b/tapir-templates/codec.ftl new file mode 100644 index 0000000..ec48ffe --- /dev/null +++ b/tapir-templates/codec.ftl @@ -0,0 +1,8 @@ +object Codecs: + +<#list datatypes as datatype> + given Codec[${datatype.name?cap_first}] = deriveCodec + +<#list endpoints as endpoint> + given Codec[${endpoint.handler.name?cap_first}Payload] = deriveCodec + \ No newline at end of file diff --git a/tapir-templates/endpoints.ftl b/tapir-templates/endpoints.ftl index 64805ae..7a01e51 100644 --- a/tapir-templates/endpoints.ftl +++ b/tapir-templates/endpoints.ftl @@ -1,4 +1,4 @@ -package se.senashdev.projekt.api +package se.senashdev.project.api import se.rutdev.projekt.api.HttpProtocol.VersionedResponse import se.rutdev.framework.json.circe.RutUtilsCodec @@ -19,22 +19,15 @@ class Endpoints(override val config: OAuthUtils.OAuthConfig) extends framework.s <#list endpoints as endpoint> - case class ${endpoint.handler.name?cap_first}( + case class ${endpoint.handler.name?cap_first}Payload( <#list endpoint.handler.fields as field> ${field.name} : ${field.type}, ) - <#list datatypes as datatype> - given Codec[${datatype.name?cap_first}] = deriveCodec - <#list endpoints as endpoint> - given Codec[${endpoint.handler.name?cap_first}] = deriveCodec - - - <#list endpoints as endpoint> - val ${endpoint.handler.name}Endpoint = ApiEndpoint[${endpoint.handler.name?cap_first}, VersionedResponse] = + val ${endpoint.handler.name}Endpoint = ApiEndpoint[${endpoint.handler.name?cap_first}Payload, VersionedResponse] = <#list endpoint.paths.paths> apiV1Endpoint .post @@ -42,7 +35,7 @@ class Endpoints(override val config: OAuthUtils.OAuthConfig) extends framework.s .in("${segment}") .post - .in(jsonBody[${endpoint.handler.name?cap_first}]) + .in(jsonBody[${endpoint.handler.name?cap_first}Payload]) .out(jsonBody[VersionedResponse])