Skip to content

Commit

Permalink
Merge pull request #87 from camunda-community-hub/86-duration
Browse files Browse the repository at this point in the history
fix: Return temporal values in FEEL-compatible format
  • Loading branch information
saig0 authored Jan 20, 2025
2 parents 6ab64c5 + 11a31c1 commit 1c9e17f
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
package org.camunda.feel.playground.sevice;

import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.camunda.feel.FeelEngine;
import org.camunda.feel.api.EvaluationResult;
import org.camunda.feel.api.FeelEngineApi;
import org.camunda.feel.impl.JavaValueMapper;
import org.camunda.feel.syntaxtree.*;
import org.camunda.feel.valuemapper.CustomValueMapper;
import org.camunda.feel.valuemapper.JavaCustomValueMapper;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -21,7 +27,9 @@ public final class FeelEvaluationService {

private FeelEngineApi buildFeelEngine() {
final var feelEngine =
new FeelEngine.Builder().customValueMapper(new JavaValueMapper()).build();
new FeelEngine.Builder()
.customValueMapper(new JavaValueMapperWithTemporalStringDeserialization())
.build();
return new FeelEngineApi(feelEngine);
}

Expand All @@ -33,4 +41,36 @@ public EvaluationResult evaluateUnaryTests(
String expression, Object inputValue, Map<String, Object> context) {
return feelEngineApi.evaluateUnaryTests(expression, inputValue, context);
}

private static class JavaValueMapperWithTemporalStringDeserialization
extends JavaCustomValueMapper {

private final CustomValueMapper baseValueMapper = new JavaValueMapper();

@Override
public Optional<Val> toValue(Object x, Function<Object, Val> innerValueMapper) {
return baseValueMapper.toVal(x, innerValueMapper::apply).fold(Optional::empty, Optional::of);
}

@Override
public Optional<Object> unpackValue(Val value, Function<Val, Object> innerValueMapper) {
if (isTemporalValue(value)) {
return Optional.of(value.toString());
} else {
return baseValueMapper
.unpackVal(value, innerValueMapper::apply)
.fold(Optional::empty, Optional::of);
}
}

private static boolean isTemporalValue(Val value) {
return value instanceof ValDate
|| value instanceof ValTime
|| value instanceof ValLocalTime
|| value instanceof ValDateTime
|| value instanceof ValLocalDateTime
|| value instanceof ValDayTimeDuration
|| value instanceof ValYearMonthDuration;
}
}
}
50 changes: 26 additions & 24 deletions src/test/java/org/camunda/feel/playground/FeelApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
Expand All @@ -35,34 +38,33 @@ void shouldReturnResult() throws Exception {
.andExpect(content().json("{'result': 3}"));
}

@Test
void shouldReturnNull() throws Exception {
mvc.perform(
post("/api/v1/feel/evaluate")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"expression\": \"null\", \"context\": {}}"))
.andExpect(status().isOk())
.andExpect(content().json("{'result': null}"));
}

@Test
void shouldReturnList() throws Exception {
mvc.perform(
post("/api/v1/feel/evaluate")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"expression\": \"[1,2,3]\", \"context\": {}}"))
.andExpect(status().isOk())
.andExpect(content().json("{'result': [1,2,3]}"));
}

@Test
void shouldReturnContext() throws Exception {
@CsvSource(
value = {
"3;3",
"\\\"feel\\\";\"feel\"",
"true;true",
"null;null",
"[1,2,3];[1,2,3]",
"{x:1};{'x': 1}",
"[{x:1}];[{'x': 1}]",
"{x:[1,2]};{'x': [1,2]}",
"@\\\"2025-01-20\\\";\"2025-01-20\"",
"@\\\"10:41:30\\\";\"10:41:30\"",
"@\\\"10:41:30+02:00\\\";\"10:41:30+02:00\"",
"@\\\"2025-01-20T10:41:30\\\";\"2025-01-20T10:41:30\"",
"@\\\"2025-01-20T10:41:30+02:00\\\";\"2025-01-20T10:41:30+02:00\"",
"@\\\"P5D\\\";\"P5D\"",
"@\\\"P2Y\\\";\"P2Y\""
},
delimiter = ';')
@ParameterizedTest
void shouldReturnResultValue(final String expression, final String expected) throws Exception {
mvc.perform(
post("/api/v1/feel/evaluate")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"expression\": \"{x:1}\", \"context\": {}}"))
.content("{\"expression\": \"" + expression + "\", \"context\": {}}"))
.andExpect(status().isOk())
.andExpect(content().json("{'result': {'x': 1}}"));
.andExpect(content().json("{'result': " + expected + "}"));
}

@Test
Expand Down

0 comments on commit 1c9e17f

Please sign in to comment.