Repository: serverlessworkflow/sdk-java (java)

Issue:
Support const keyword in generator
**What would you like to be added**:
Currently the const word is ignored

**Why is this needed**:
To distinghish, in CallTask, the right class when there is no param. 

Pull request:
[Fix #418] Adding const support
Fix https://github.com/serverlessworkflow/sdk-java/issues/418

Patch:
diff --git a/api/pom.xml b/api/pom.xml
index 37524b3..d5128d5 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -33,7 +33,14 @@
             <groupId>jakarta.validation</groupId>
             <artifactId>jakarta.validation-api</artifactId>
         </dependency>
- 
+        <dependency>
+          <groupId>org.hibernate.validator</groupId>
+          <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+          <groupId>org.glassfish.expressly</groupId>
+          <artifactId>expressly</artifactId>
+        </dependency>
         <!-- test -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -65,11 +72,6 @@
             <artifactId>logback-classic</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.assertj</groupId>
-            <artifactId>assertj-core</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
 
@@ -101,6 +103,7 @@
                     <usePrimitives>true</usePrimitives>
                     <useJakartaValidation>true</useJakartaValidation>
                     <customRuleFactory>io.serverlessworkflow.generator.UnreferencedFactory</customRuleFactory>
+                    <customAnnotator>io.serverlessworkflow.generator.ConstAnnotator</customAnnotator>
                 </configuration>
                 <dependencies>
                     <dependency>
diff --git a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java
index 7bc8414..850e7da 100644
--- a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java
+++ b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java
@@ -17,8 +17,10 @@ package io.serverlessworkflow.api;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
+import io.serverlessworkflow.serialization.BeanDeserializerModifierWithValidation;
 
 class ObjectMapperFactory {
 
@@ -36,10 +38,13 @@ class ObjectMapperFactory {
   }
 
   private static ObjectMapper configure(ObjectMapper mapper) {
+    SimpleModule validationModule = new SimpleModule();
+    validationModule.setDeserializerModifier(new BeanDeserializerModifierWithValidation());
     return mapper
         .configure(SerializationFeature.INDENT_OUTPUT, true)
         .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false)
-        .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
+        .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false)
+        .registerModule(validationModule);
   }
 
   private ObjectMapperFactory() {}
diff --git a/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java
new file mode 100644
index 0000000..e4e019a
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.serverlessworkflow.serialization;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.deser.BeanDeserializer;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+
+public class BeanDeserializerModifierWithValidation extends BeanDeserializerModifier {
+
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  public JsonDeserializer<?> modifyDeserializer(
+      DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+    return deserializer instanceof BeanDeserializer
+        ? new BeanDeserializerWithValidation((BeanDeserializer) deserializer)
+        : deserializer;
+  }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java
new file mode 100644
index 0000000..a77e117
--- /dev/null
+++ b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.serverlessworkflow.serialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.BeanDeserializer;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import java.io.IOException;
+import java.util.Set;
+
+public class BeanDeserializerWithValidation extends BeanDeserializer {
+  private static final long serialVersionUID = 1L;
+  private static final Validator validator =
+      Validation.buildDefaultValidatorFactory().getValidator();
+
+  protected BeanDeserializerWithValidation(BeanDeserializerBase src) {
+    super(src);
+  }
+
+  private <T> void validate(T t) throws IOException {
+    Set<ConstraintViolation<T>> violations = validator.validate(t);
+    if (!violations.isEmpty()) {
+      throw new ConstraintViolationException(violations);
+    }
+  }
+
+  @Override
+  public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+    Object instance = super.deserialize(p, ctxt);
+    validate(instance);
+    return instance;
+  }
+}
diff --git a/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java b/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java
index fc5390f..72b4cce 100644
--- a/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java
+++ b/api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.TreeNode;
 import com.fasterxml.jackson.databind.JsonMappingException;
+import jakarta.validation.ConstraintViolationException;
 import java.io.IOException;
 import java.util.Collection;
 
@@ -33,7 +34,7 @@ public class DeserializeHelper {
       try {
         Object object = p.getCodec().treeToValue(node, unionType);
         return targetClass.getConstructor(unionType).newInstance(object);
-      } catch (IOException | ReflectiveOperationException io) {
+      } catch (IOException | ReflectiveOperationException | ConstraintViolationException io) {
         ex.addSuppressed(io);
       }
     }
diff --git a/custom-generator/src/main/java/io/serverlessworkflow/generator/ConstAnnotator.java b/custom-generator/src/main/java/io/serverlessworkflow/generator/ConstAnnotator.java
new file mode 100644
index 0000000..a893cfb
--- /dev/null
+++ b/custom-generator/src/main/java/io/serverlessworkflow/generator/ConstAnnotator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.serverlessworkflow.generator;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JFieldVar;
+import jakarta.validation.constraints.Pattern;
+import org.jsonschema2pojo.AbstractAnnotator;
+import org.jsonschema2pojo.GenerationConfig;
+
+public class ConstAnnotator extends AbstractAnnotator {
+
+  private static final String CONST = "const";
+
+  public ConstAnnotator(GenerationConfig generationConfig) {
+    super(generationConfig);
+  }
+
+  @Override
+  public void propertyField(
+      JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) {
+    if (propertyNode.has(CONST)) {
+      field.annotate(Pattern.class).param("regexp", propertyNode.get(CONST).asText());
+    }
+  }
+}
diff --git a/pom.xml b/pom.xml
index 4206462..5311c09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,8 @@
     <version.org.junit.jupiter>5.11.0</version.org.junit.jupiter>
     <version.org.mockito>5.12.0</version.org.mockito>
     <version.org.slf4j>2.0.16</version.org.slf4j>
-    
+    <version.org.hibernate.validator>8.0.1.Final</version.org.hibernate.validator>
+    <version.org.glassfish.expressly>5.0.0</version.org.glassfish.expressly>
 
     <!-- Checkstyle props -->
     <checkstyle.failOnViolation>true</checkstyle.failOnViolation>
@@ -124,6 +125,17 @@
         <artifactId>json-schema-validator</artifactId>
         <version>${version.com.networknt}</version>
       </dependency>
+      <dependency>
+        <groupId>org.hibernate.validator</groupId>
+        <artifactId>hibernate-validator</artifactId>
+        <version>${version.org.hibernate.validator}</version>
+      </dependency>
+       <dependency>
+         <groupId>org.glassfish.expressly</groupId>
+         <artifactId>expressly</artifactId>
+         <version>${version.org.glassfish.expressly}</version>
+        </dependency>
+
       <dependency>
         <groupId>com.fasterxml.jackson.dataformat</groupId>
         <artifactId>jackson-dataformat-yaml</artifactId>


Test patch:
diff --git a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java
index ac5f753..20248f3 100644
--- a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java
+++ b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java
@@ -18,6 +18,7 @@ package io.serverlessworkflow.api;
 import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import io.serverlessworkflow.api.types.CallFunction;
 import io.serverlessworkflow.api.types.CallHTTP;
 import io.serverlessworkflow.api.types.CallTask;
 import io.serverlessworkflow.api.types.Task;
@@ -44,4 +45,22 @@ public class ApiTest {
       assertThat(httpCall.getWith().getMethod()).isEqualTo("get");
     }
   }
+
+  @Test
+  void testCallFunctionAPIWithoutArguments() throws IOException {
+    Workflow workflow = readWorkflowFromClasspath("features/callFunction.yaml");
+    assertThat(workflow.getDo()).isNotEmpty();
+    assertThat(workflow.getDo().get(0).getName()).isNotNull();
+    assertThat(workflow.getDo().get(0).getTask()).isNotNull();
+    Task task = workflow.getDo().get(0).getTask();
+    CallTask callTask = task.getCallTask();
+    assertThat(callTask).isNotNull();
+    assertThat(callTask.get()).isInstanceOf(CallFunction.class);
+    if (callTask.get() instanceof CallFunction) {
+      CallFunction functionCall = callTask.getCallFunction();
+      assertThat(functionCall).isNotNull();
+      assertThat(callTask.getCallAsyncAPI()).isNull();
+      assertThat(functionCall.getWith()).isNull();
+    }
+  }
 }
diff --git a/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java b/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java
index 8e77673..af11a49 100644
--- a/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java
+++ b/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java
@@ -43,7 +43,8 @@ public class FeaturesTest {
         "features/set.yaml",
         "features/switch.yaml",
         "features/try.yaml",
-        "features/listen.yaml"
+        "features/listen.yaml",
+        "features/callFunction.yaml"
       })
   public void testSpecFeaturesParsing(String workflowLocation) throws IOException {
     Workflow workflow = readWorkflowFromClasspath(workflowLocation);
diff --git a/api/src/test/resources/features/callFunction.yaml b/api/src/test/resources/features/callFunction.yaml
new file mode 100644
index 0000000..95a3a98
--- /dev/null
+++ b/api/src/test/resources/features/callFunction.yaml
@@ -0,0 +1,19 @@
+document:
+  dsl: 1.0.0-alpha1
+  namespace: default
+  name: http-call-with-response-output
+  version: 1.0.0
+
+use:
+  functions:
+    getPet:
+      call: http
+      with:
+        method: get
+        endpoint:
+          uri: https://petstore.swagger.io/v2/pet/{petId}
+        output: response
+
+do:
+  - getPetFunctionCall:
+      call: getPet
\ No newline at end of file
diff --git a/api/src/test/resources/features/callHttp.yaml b/api/src/test/resources/features/callHttp.yaml
index 9b48c78..4022e38 100644
--- a/api/src/test/resources/features/callHttp.yaml
+++ b/api/src/test/resources/features/callHttp.yaml
@@ -2,6 +2,7 @@ document:
   dsl: 1.0.0-alpha1
   namespace: default
   name: http-call-with-response-output
+  version: 1.0.0
 do:
   - getPet:
       call: http
diff --git a/api/src/test/resources/features/callOpenAPI.yaml b/api/src/test/resources/features/callOpenAPI.yaml
index 46ecc92..1a1d0c5 100644
--- a/api/src/test/resources/features/callOpenAPI.yaml
+++ b/api/src/test/resources/features/callOpenAPI.yaml
@@ -2,6 +2,7 @@ document:
   dsl: 1.0.0-alpha1
   namespace: default
   name: openapi-call-with-content-output
+  version: 1.0.0
 do:
   - findPet:
       call: openapi
diff --git a/api/src/test/resources/features/composite.yaml b/api/src/test/resources/features/composite.yaml
index 71b0dea..515cda2 100644
--- a/api/src/test/resources/features/composite.yaml
+++ b/api/src/test/resources/features/composite.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: do
+      version: 1.0.0
 do:
   - compositeExample:
       do:
diff --git a/api/src/test/resources/features/data-flow.yaml b/api/src/test/resources/features/data-flow.yaml
index d66d784..bebb212 100644
--- a/api/src/test/resources/features/data-flow.yaml
+++ b/api/src/test/resources/features/data-flow.yaml
@@ -2,6 +2,7 @@ document:
   dsl: 1.0.0-alpha1
   namespace: default
   name: output-filtering
+  version: 1.0.0
 do:
   - getPet:
       call: http
diff --git a/api/src/test/resources/features/emit.yaml b/api/src/test/resources/features/emit.yaml
index 488feed..983407d 100644
--- a/api/src/test/resources/features/emit.yaml
+++ b/api/src/test/resources/features/emit.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: emit
+      version: 1.0.0
 do:
   - emitEvent:
       emit:
diff --git a/api/src/test/resources/features/flow.yaml b/api/src/test/resources/features/flow.yaml
index 83baf04..6bd8a6e 100644
--- a/api/src/test/resources/features/flow.yaml
+++ b/api/src/test/resources/features/flow.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: implicit-sequence
+      version: 1.0.0
 do:
   - setRed:
       set:
diff --git a/api/src/test/resources/features/for.yaml b/api/src/test/resources/features/for.yaml
index f8ae826..662dff9 100644
--- a/api/src/test/resources/features/for.yaml
+++ b/api/src/test/resources/features/for.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: for
+      version: 1.0.0
 do:
   - loopColors:
       for:
diff --git a/api/src/test/resources/features/listen.yaml b/api/src/test/resources/features/listen.yaml
index 393355d..1c56c22 100644
--- a/api/src/test/resources/features/listen.yaml
+++ b/api/src/test/resources/features/listen.yaml
@@ -2,6 +2,7 @@ document:
   dsl: 1.0.0-alpha1
   namespace: default
   name: listen-task
+  version: 1.0.0
 do:
   - listenToSomething:
       listen:
diff --git a/api/src/test/resources/features/raise.yaml b/api/src/test/resources/features/raise.yaml
index 9dd6b4f..7745162 100644
--- a/api/src/test/resources/features/raise.yaml
+++ b/api/src/test/resources/features/raise.yaml
@@ -2,10 +2,12 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: raise-custom-error
+      version: 1.0.0
 do:
   - raiseError:
       raise:
         error:
           status: 400
           type: https://serverlessworkflow.io/errors/types/compliance
-          title: Compliance Error
\ No newline at end of file
+          title: Compliance Error
+          instance: raiseError
\ No newline at end of file
diff --git a/api/src/test/resources/features/set.yaml b/api/src/test/resources/features/set.yaml
index 1589792..dfebbf2 100644
--- a/api/src/test/resources/features/set.yaml
+++ b/api/src/test/resources/features/set.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: set
+      version: 1.0.0
 do:
   - setShape:
       set:
diff --git a/api/src/test/resources/features/switch.yaml b/api/src/test/resources/features/switch.yaml
index 74d046c..aa073fe 100644
--- a/api/src/test/resources/features/switch.yaml
+++ b/api/src/test/resources/features/switch.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: switch-match
+      version: 1.0.0
 do:
   - switchColor:
       switch:
diff --git a/api/src/test/resources/features/try.yaml b/api/src/test/resources/features/try.yaml
index 7f9ba59..ec19194 100644
--- a/api/src/test/resources/features/try.yaml
+++ b/api/src/test/resources/features/try.yaml
@@ -2,6 +2,7 @@ document:
       dsl: 1.0.0-alpha1
       namespace: default
       name: try-catch-404
+      version: 1.0.0
 do:
   - tryGetPet:
       try: