From 3cbc000f549fca9a85913188689529363e377bf6 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Tue, 9 Jul 2024 11:34:48 +0200 Subject: [PATCH] adds support for @imports and improves nested fields and relationships validations. --- pom.xml | 2 +- .../io.github.zenwave360.zdl.antlr/Zdl.g4 | 16 +++-- .../zenwave360/zdl/antlr/ZdlListenerImpl.java | 67 ++++++++++++++++--- .../zdl/antlr/ZdlListenerUtils.java | 17 +++++ .../github/zenwave360/zdl/antlr/ZdlModel.java | 1 + .../zenwave360/zdl/antlr/ZdlListenerTest.java | 4 ++ src/test/resources/complete.zdl | 6 +- 7 files changed, 96 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 371a318..d3cbd4b 100644 --- a/pom.xml +++ b/pom.xml @@ -152,7 +152,7 @@ org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.12 **/Inflector.* diff --git a/src/main/antlr4/io.github.zenwave360.zdl.antlr/Zdl.g4 b/src/main/antlr4/io.github.zenwave360.zdl.antlr/Zdl.g4 index 1c3fa60..42143df 100644 --- a/src/main/antlr4/io.github.zenwave360.zdl.antlr/Zdl.g4 +++ b/src/main/antlr4/io.github.zenwave360.zdl.antlr/Zdl.g4 @@ -47,6 +47,7 @@ ARRAY: '[]'; OPTIONAL: '?'; // Keywords +IMPORT: 'import'; CONFIG: 'config'; APIS: 'apis'; PLUGINS: 'plugins'; @@ -116,7 +117,10 @@ PATTERN_REGEX: '/' .*? '/' ; // TODO: improve regex ERRCHAR: . -> channel(HIDDEN); // Rules -zdl: global_javadoc? legacy_constants config? apis? (policies | aggregate | entity | enum | input | output | event | relationships | service | service_legacy)* EOF; +zdl: imports global_javadoc? legacy_constants config? apis? (policies | aggregate | entity | enum | input | output | event | relationships | service | service_legacy)* EOF; + +imports: ('@import' LPAREN import_value RPAREN)*; +import_value: string; global_javadoc: JAVADOC; javadoc: JAVADOC; suffix_javadoc: JAVADOC; @@ -124,10 +128,11 @@ suffix_javadoc: JAVADOC; legacy_constants: LEGACY_CONSTANT*; // values -keyword: ID | CONFIG | APIS | PLUGINS | DISABLED | ASYNCAPI | OPENAPI | ENTITY | AGGREGATE | INPUT | OUTPUT | EVENT | RELATIONSHIP | SERVICE | PARAM_ID | FOR | TO | WITH_EVENTS | WITH | REQUIRED | UNIQUE | MIN | MAX | MINLENGTH | MAXLENGTH | PATTERN; +keyword: ID | IMPORT | CONFIG | APIS | PLUGINS | DISABLED | ASYNCAPI | OPENAPI | ENTITY | AGGREGATE | INPUT | OUTPUT | EVENT | RELATIONSHIP | SERVICE | PARAM_ID | FOR | TO | WITH_EVENTS | WITH | REQUIRED | UNIQUE | MIN | MAX | MINLENGTH | MAXLENGTH | PATTERN; complex_value: value | array | object; value: simple | object; +string: keyword | SINGLE_QUOTED_STRING | DOUBLE_QUOTED_STRING; simple: keyword | SINGLE_QUOTED_STRING | DOUBLE_QUOTED_STRING | INT | NUMBER | TRUE | FALSE | NULL; pair: keyword COLON value; object: LBRACE pair (COMMA pair)* RBRACE; @@ -192,7 +197,7 @@ field_validations: field_validation_name (LPAREN field_validation_value RPAREN)? field_validation_name: REQUIRED | UNIQUE | MIN | MAX | MINLENGTH | MAXLENGTH | PATTERN; field_validation_value: INT | ID | PATTERN_REGEX; nested_field_validations: nested_field_validation_name (LPAREN nested_field_validation_value RPAREN)?; -nested_field_validation_name: REQUIRED | UNIQUE; +nested_field_validation_name: REQUIRED | UNIQUE | MIN | MAX; nested_field_validation_value: INT | ID | PATTERN_REGEX; // enums @@ -222,11 +227,14 @@ relationship_type: MANY_TO_MANY | MANY_TO_ONE| ONE_TO_MANY | ONE_TO_ONE; relationship: relationship_from TO relationship_to; relationship_from: javadoc? annotations relationship_definition; relationship_to: javadoc? annotations relationship_definition; -relationship_definition: relationship_entity_name (LBRACE relationship_field_name (LPAREN relationship_description_field RPAREN)? relationship_field_required? RBRACE)?; +relationship_definition: relationship_entity_name (LBRACE relationship_field_name (LPAREN relationship_description_field RPAREN)? relationship_field_validations RBRACE)?; relationship_entity_name: ID; relationship_field_name: keyword; relationship_description_field: ID; +relationship_field_validations: relationship_field_required? relationship_field_min? relationship_field_max?; relationship_field_required: REQUIRED; +relationship_field_min: MIN(INT); +relationship_field_max: MAX(INT); // aggregates aggregate: javadoc? annotations AGGREGATE aggregate_name LPAREN aggregate_root RPAREN LBRACE aggregate_command* RBRACE; diff --git a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerImpl.java b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerImpl.java index 215f4f3..0cc44b5 100644 --- a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerImpl.java +++ b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerImpl.java @@ -61,6 +61,13 @@ public void enterLegacy_constants(io.github.zenwave360.zdl.antlr.ZdlParser.Legac ctx.LEGACY_CONSTANT().stream().map(TerminalNode::getText).map(c -> c.split(" *= *")).forEach(c -> model.appendTo("constants", c[0], c[1])); } + @Override + public void enterImports(ZdlParser.ImportsContext ctx) { + for (ZdlParser.Import_valueContext importValue : ctx.import_value()) { + model.appendToList("imports", getValueText(importValue.string())); + } + } + @Override public void enterConfig_option(io.github.zenwave360.zdl.antlr.ZdlParser.Config_optionContext ctx) { var name = ctx.field_name().getText(); @@ -106,7 +113,7 @@ public void exitApi(io.github.zenwave360.zdl.antlr.ZdlParser.ApiContext ctx) { } @Override - public void enterPlugin(ZdlParser.PluginContext ctx) { + public void enterPlugin(io.github.zenwave360.zdl.antlr.ZdlParser.PluginContext ctx) { var name = getText(ctx.plugin_name()); var javadoc = javadoc(ctx.javadoc()); var disabled = ctx.plugin_disabled().DISABLED() != null; @@ -135,21 +142,21 @@ public void enterPlugin(ZdlParser.PluginContext ctx) { } @Override - public void enterPlugin_config_option(ZdlParser.Plugin_config_optionContext ctx) { + public void enterPlugin_config_option(io.github.zenwave360.zdl.antlr.ZdlParser.Plugin_config_optionContext ctx) { var name = getText(ctx.field_name()); var value = getComplexValue(ctx.complex_value()); currentStack.peek().appendTo("config", name, value); } @Override - public void enterPlugin_config_cli_option(ZdlParser.Plugin_config_cli_optionContext ctx) { + public void enterPlugin_config_cli_option(io.github.zenwave360.zdl.antlr.ZdlParser.Plugin_config_cli_optionContext ctx) { var keyword = getText(ctx.keyword()); var value = getText(ctx.simple()); currentStack.peek().appendTo("cliOptions", keyword, value); } @Override - public void exitPlugin(ZdlParser.PluginContext ctx) { + public void exitPlugin(io.github.zenwave360.zdl.antlr.ZdlParser.PluginContext ctx) { currentStack.pop(); } @@ -283,7 +290,7 @@ public void enterNested_field(io.github.zenwave360.zdl.antlr.ZdlParser.Nested_fi String entityJavadoc = javadoc(parent.javadoc()); String tableName = getText(parent.entity_table_name()); var validations = processNestedFieldValidations(ctx.nested_field_validations()); - ((Map)parentField).put("validations", validations); + ((FluentMap) parentField).appendTo("validations", validations); currentStack.push(processEntity(entityName, entityJavadoc, tableName).with("type", currentCollection.split("\\.")[0])); currentStack.peek().appendTo("options", "embedded", true); var parenFieldOptions = JSONPath.get(parentField, "options", Map.of()); @@ -361,14 +368,21 @@ public void enterRelationship(io.github.zenwave360.zdl.antlr.ZdlParser.Relations var fromField = getText(ctx.relationship_from().relationship_definition().relationship_field_name()); var commentInFrom = javadoc(ctx.relationship_from().javadoc()); var fromOptions = relationshipOptions(ctx.relationship_from().annotations().option()); - var isInjectedFieldInFromRequired = ctx.relationship_from().relationship_definition().relationship_field_required() != null; + var isInjectedFieldInFromRequired = isRequired(ctx.relationship_from().relationship_definition()); var injectedFieldInFromDescription = getText(ctx.relationship_from().relationship_definition().relationship_description_field()); + var relationshipValidations = relationshipValidations(ctx.relationship_from().relationship_definition()); model.setLocation(location + ".from.entity", getLocations(ctx.relationship_from().relationship_definition().relationship_entity_name())); model.setLocation(location + ".from.field", getLocations(ctx.relationship_from().relationship_definition().relationship_field_name())); + if (ctx.relationship_from().relationship_definition().relationship_field_validations() != null) { + model.setLocation(location + ".from.validations", getLocations(ctx.relationship_from().relationship_definition().relationship_field_validations())); + model.setLocation(location + ".from.validations.min", getLocations(ctx.relationship_from().relationship_definition().relationship_field_validations().relationship_field_min())); + model.setLocation(location + ".from.validations.max", getLocations(ctx.relationship_from().relationship_definition().relationship_field_validations().relationship_field_max())); + } relationship.with("from", from) .with("commentInFrom", commentInFrom) .with("injectedFieldInFrom", fromField) .with("fromOptions", fromOptions) + .with("fromValidations", relationshipValidations) .with("injectedFieldInFromDescription", injectedFieldInFromDescription) .with("isInjectedFieldInFromRequired", isInjectedFieldInFromRequired); } @@ -378,14 +392,21 @@ public void enterRelationship(io.github.zenwave360.zdl.antlr.ZdlParser.Relations var toField = getText(ctx.relationship_to().relationship_definition().relationship_field_name()); var commentInTo = javadoc(ctx.relationship_to().javadoc()); var toOptions = relationshipOptions(ctx.relationship_to().annotations().option()); - var isInjectedFieldInToRequired = ctx.relationship_to().relationship_definition().relationship_field_required() != null; + var isInjectedFieldInToRequired = isRequired(ctx.relationship_to().relationship_definition()); var injectedFieldInToDescription = getText(ctx.relationship_to().relationship_definition().relationship_description_field()); + var relationshipValidations = relationshipValidations(ctx.relationship_to().relationship_definition()); model.setLocation(location + ".to.entity", getLocations(ctx.relationship_to().relationship_definition().relationship_entity_name())); model.setLocation(location + ".to.field", getLocations(ctx.relationship_to().relationship_definition().relationship_field_name())); + if (ctx.relationship_to().relationship_definition().relationship_field_validations() != null) { + model.setLocation(location + ".to.validations", getLocations(ctx.relationship_to().relationship_definition().relationship_field_validations())); + model.setLocation(location + ".to.validations.min", getLocations(ctx.relationship_to().relationship_definition().relationship_field_validations().relationship_field_min())); + model.setLocation(location + ".to.validations.max", getLocations(ctx.relationship_to().relationship_definition().relationship_field_validations().relationship_field_max())); + } relationship.with("to", to) .with("commentInTo", commentInTo) .with("injectedFieldInTo", toField) .with("toOptions", toOptions) + .with("toValidations", relationshipValidations) .with("injectedFieldInToDescription", injectedFieldInToDescription) .with("isInjectedFieldInToRequired", isInjectedFieldInToRequired); } @@ -393,6 +414,32 @@ public void enterRelationship(io.github.zenwave360.zdl.antlr.ZdlParser.Relations model.getRelationships().appendTo(relationshipType, relationshipName, relationship); } + private boolean isRequired(io.github.zenwave360.zdl.antlr.ZdlParser.Relationship_definitionContext relationshipDefinitionContext) { + return relationshipDefinitionContext.relationship_field_validations() != null + && relationshipDefinitionContext.relationship_field_validations().relationship_field_required() != null; + } + + private Object relationshipValidations(io.github.zenwave360.zdl.antlr.ZdlParser.Relationship_definitionContext relationshipDefinitionContext) { + var validations = new FluentMap(); + if (relationshipDefinitionContext.relationship_field_validations() != null) { + if (relationshipDefinitionContext.relationship_field_validations().relationship_field_required() != null) { + var name = "required"; + validations.with(name, Map.of("name", name, "value", true)); + } + if (relationshipDefinitionContext.relationship_field_validations().relationship_field_min() != null) { + var name = "min"; + var value = getText(relationshipDefinitionContext.relationship_field_validations().relationship_field_min()); + validations.with(name, Map.of("name", name, "value", value)); + } + if (relationshipDefinitionContext.relationship_field_validations().relationship_field_max() != null) { + var name = "max"; + var value = getText(relationshipDefinitionContext.relationship_field_validations().relationship_field_min()); + validations.with(name, Map.of("name", name, "value", value)); + } + } + return validations; + } + private String removeJavadoc(String text) { final String regex = "(/\\*\\*.+?\\*/)"; text = text.replace("\r\n", ""); @@ -400,7 +447,7 @@ private String removeJavadoc(String text) { return text.replaceAll(regex, ""); } - private String relationshipDescription(ZdlParser.Relationship_definitionContext ctx) { + private String relationshipDescription(io.github.zenwave360.zdl.antlr.ZdlParser.Relationship_definitionContext ctx) { var description = ""; if(ctx != null) { description = description + getText(ctx.relationship_entity_name()); @@ -440,7 +487,7 @@ public void exitService_legacy(io.github.zenwave360.zdl.antlr.ZdlParser.Service_ } @Override - public void enterAggregate(ZdlParser.AggregateContext ctx) { + public void enterAggregate(io.github.zenwave360.zdl.antlr.ZdlParser.AggregateContext ctx) { var aggregateName = getText(ctx.aggregate_name()); var javadoc = javadoc(ctx.javadoc()); var aggregateRoot = getText(ctx.aggregate_root()); @@ -491,7 +538,7 @@ public void enterAggregate_command(io.github.zenwave360.zdl.antlr.ZdlParser.Aggr } @Override - public void exitAggregate_command(ZdlParser.Aggregate_commandContext ctx) { + public void exitAggregate_command(io.github.zenwave360.zdl.antlr.ZdlParser.Aggregate_commandContext ctx) { currentStack.pop(); } diff --git a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerUtils.java b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerUtils.java index 2dc6445..0b57e9d 100644 --- a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerUtils.java +++ b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlListenerUtils.java @@ -35,6 +35,23 @@ static Object getValueText(io.github.zenwave360.zdl.antlr.ZdlParser.ValueContext return getText(ctx); } + static Object getValueText(io.github.zenwave360.zdl.antlr.ZdlParser.StringContext ctx) { + if(ctx == null) { + return null; + } + if(ctx.keyword() != null) { + return ctx.keyword().getText(); + } + if(ctx.SINGLE_QUOTED_STRING() != null) { + return unquote(ctx.SINGLE_QUOTED_STRING().getText(), "'"); + } + if(ctx.DOUBLE_QUOTED_STRING() != null) { + return unquote(ctx.DOUBLE_QUOTED_STRING().getText(), "\""); + } + return getText(ctx); + } + + static Object getValueText(io.github.zenwave360.zdl.antlr.ZdlParser.SimpleContext ctx) { if(ctx == null) { return null; diff --git a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlModel.java b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlModel.java index 2e129f6..8906c33 100644 --- a/src/main/java/io/github/zenwave360/zdl/antlr/ZdlModel.java +++ b/src/main/java/io/github/zenwave360/zdl/antlr/ZdlModel.java @@ -6,6 +6,7 @@ public class ZdlModel extends FluentMap { public ZdlModel() { + put("imports", new ArrayList<>()); put("config", new FluentMap()); put("apis", new FluentMap()); put("aggregates", new FluentMap()); diff --git a/src/test/java/io/github/zenwave360/zdl/antlr/ZdlListenerTest.java b/src/test/java/io/github/zenwave360/zdl/antlr/ZdlListenerTest.java index 2f9e221..2877dc6 100644 --- a/src/test/java/io/github/zenwave360/zdl/antlr/ZdlListenerTest.java +++ b/src/test/java/io/github/zenwave360/zdl/antlr/ZdlListenerTest.java @@ -34,6 +34,8 @@ public void parseZdl_CompleteZdl() throws Exception { ZdlModel model = parseZdl("src/test/resources/complete.zdl"); assertEquals("ZenWave Online Food Delivery - Orders Module.", get(model, "$.javadoc")); + assertEquals("com.example:artifact:RELEASE", get(model, "$.imports[0]")); + // CONFIG assertEquals("io.zenwave360.example.orders", get(model, "$.config.basePackage")); assertEquals("mongodb", get(model, "$.config.persistence")); @@ -90,6 +92,8 @@ public void parseZdl_CompleteZdl() throws Exception { assertEquals("OrderItem", get(model, "$.entities.CustomerOrder.fields.orderItems.type")); assertNull(get(model, "$.entities.CustomerOrder.fields.orderItems.initialValue")); assertNull(get(model, "$.entities.CustomerOrder.fields.orderItems.validations.required")); + assertEquals("1", get(model, "$.entities.CustomerOrder.fields.orderItems.validations.min.value")); + assertEquals("200", get(model, "$.entities.CustomerOrder.fields.orderItems.validations.max.value")); assertEquals(false, get(model, "$.entities.CustomerOrder.fields.orderItems.isEnum")); assertEquals(true, get(model, "$.entities.CustomerOrder.fields.orderItems.isEntity")); assertEquals(true, get(model, "$.entities.CustomerOrder.fields.orderItems.isArray")); diff --git a/src/test/resources/complete.zdl b/src/test/resources/complete.zdl index 07c4bcb..58fecf0 100644 --- a/src/test/resources/complete.zdl +++ b/src/test/resources/complete.zdl @@ -1,3 +1,5 @@ +@import("com.example:artifact:RELEASE") + /** * ZenWave Online Food Delivery - Orders Module. */ @@ -108,13 +110,13 @@ entity CustomerOrder { /** * orderItems javadoc */ - orderItems OrderItem[] { + orderItems OrderItem[] min(1) max(100) { menuItemId String required name String required description String price BigDecimal required quantity Integer required - } + } max(200) } @aggregate