Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
.vscode/
46 changes: 46 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yetanalytics</groupId>
<artifactId>cel-demo</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>cel-demo</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0-M2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.cel</groupId>
<artifactId>cel</artifactId>
<version>0.11.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>com.yetanalytics</groupId>
<artifactId>xapi-tools</artifactId>
<version>0.0.3-beta</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
</plugins>
</build>
</project>
63 changes: 63 additions & 0 deletions src/main/java/com/yetanalytics/CelExecutor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.yetanalytics;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.types.CelType;
import dev.cel.common.types.SimpleType;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerBuilder;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.parser.CelStandardMacro;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeFactory;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelLiteRuntime.Program;

public class CelExecutor {
private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);
private CelCompiler celCompiler = CelCompilerFactory.standardCelCompilerBuilder().build();

public void runExpression(String expression) {
System.out.println(expression);
}

CelAbstractSyntaxTree compile(String expression, Map<String,CelType> varTypeMap) {
// Compile (parse + type-check) the expression
// CelCompiler is immutable and when statically configured can be moved to a
// static final
CelCompilerBuilder builder = CelCompilerFactory.standardCelCompilerBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.setResultType(SimpleType.BOOL);

varTypeMap.forEach((k, v) -> builder.addVar(k, v));

CelCompiler celCompiler = builder.build();

try {
return celCompiler.compile(expression).getAst();
} catch (CelValidationException e) {
throw new IllegalArgumentException(
"Failed to compile expression. Reason: " + e.getMessage(), e);
}
}

Boolean run(CelAbstractSyntaxTree ast, Map<String, Object> values) {
CelRuntime runtime = CelRuntimeFactory.standardCelRuntimeBuilder().build();

Boolean result = null;
try {
Program program = runtime.createProgram(ast);
result = (Boolean) program.eval(values);
} catch (CelEvaluationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}

}
127 changes: 127 additions & 0 deletions src/test/java/com/yetanalytics/CelExecutorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.yetanalytics;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yetanalytics.model.Container;
import com.yetanalytics.model.ListElement;
import com.yetanalytics.util.ReadStatement;
import com.yetanalytics.xapi.model.Statement;

import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.types.CelType;
import dev.cel.common.types.SimpleType;

public class CelExecutorTest {

private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);

@Test
public void testEasyCommand() {
CelExecutor ce = new CelExecutor();
ce.runExpression("Blargh");
}

@Test
public void testMediumCommand() {
CelExecutor ce = new CelExecutor();
ce.runExpression("Bleeegh");
}


private final String EXP_VERB_AND_ACT_ID = "statement.verb.id == '%s' && statement.object.id == '%s'";
private final String VAR_VERB_ID_CORRECT = "https://www.yetanalytics.com/profiles/thing/1.0/concepts/verbs/set";
private final String VAR_OBJ_ID_CORRECT = "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1";


@Test
public void evaluateStatementConditionCorrect() throws URISyntaxException {
Statement statement = ReadStatement.getJsonTestFile("basic");

Map<String,CelType> typeMap = new HashMap<String,CelType>();
typeMap.put("statement", SimpleType.DYN);

Map<String,Object> dyn = new ObjectMapper().convertValue(statement, new TypeReference<>() {});
Map<String, Object> varMap = new HashMap<String, Object>();
varMap.put("statement", dyn);

CelExecutor ce = new CelExecutor();
String expression = String.format(EXP_VERB_AND_ACT_ID, VAR_VERB_ID_CORRECT, VAR_OBJ_ID_CORRECT);

CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);

Boolean result = ce.run(ast, varMap);

assertTrue(result);
}

@Test
public void evaluateStatementConditionIncorrect() throws URISyntaxException {
Statement statement = ReadStatement.getJsonTestFile("basic");

Map<String,CelType> typeMap = new HashMap<String,CelType>();
typeMap.put("statement", SimpleType.DYN);

Map<String,Object> dyn = new ObjectMapper().convertValue(statement, new TypeReference<>() {});
Map<String, Object> varMap = new HashMap<String, Object>();
varMap.put("statement", dyn);

CelExecutor ce = new CelExecutor();
String expression = String.format(EXP_VERB_AND_ACT_ID, "verbo", VAR_OBJ_ID_CORRECT);

CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);

Boolean result = ce.run(ast, varMap);

assertFalse(result);
}

private final String EXP_ITERATION = "data.elements.filter(x, x.value == '%s').size() > 0";

@Test
public void testIteration() throws URISyntaxException {
// make a stupid object with a list inside
Container container = new Container();
container.setElements(new ArrayList<ListElement>());
container.getElements().add(new ListElement("Thing1"));
container.getElements().add(new ListElement("Thing2"));
container.getElements().add(new ListElement("Thing3"));

//generic object datatype
Map<String,CelType> typeMap = new HashMap<String,CelType>();
typeMap.put("data", SimpleType.DYN);

//convert to generic maps and lists and put as data map
Map<String,Object> containerData = new ObjectMapper().convertValue(container, new TypeReference<>() {});
Map<String, Object> varMap = new HashMap<String, Object>();
varMap.put("data", containerData);

CelExecutor ce = new CelExecutor();
String expression = String.format(EXP_ITERATION, "Thing2");

CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);

Boolean result = ce.run(ast, varMap);

assertTrue(result);

String badExpression = String.format(EXP_ITERATION, "Woooo");
CelAbstractSyntaxTree badAst = ce.compile(badExpression, typeMap);

Boolean badResult = ce.run(badAst, varMap);

assertFalse(badResult);
}

}
17 changes: 17 additions & 0 deletions src/test/java/com/yetanalytics/model/Container.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.yetanalytics.model;

import java.util.List;

public class Container {

private List<ListElement> elements;

public List<ListElement> getElements() {
return elements;
}

public void setElements(List<ListElement> elements) {
this.elements = elements;
}

}
19 changes: 19 additions & 0 deletions src/test/java/com/yetanalytics/model/ListElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yetanalytics.model;

public class ListElement {

public ListElement(String val){
this.value = val;
}

private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

}
28 changes: 28 additions & 0 deletions src/test/java/com/yetanalytics/util/ReadStatement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.yetanalytics.util;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yetanalytics.CelExecutor;
import com.yetanalytics.xapi.model.Statement;
import com.yetanalytics.xapi.util.Mapper;

public class ReadStatement {

private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);

public static Statement getJsonTestFile(String filename){
String path = String.format("src/test/resources/statements/%s.json", filename);
File file = new File(path);
Statement stmt = null;
try {
stmt = Mapper.getMapper().readValue(file, Statement.class);
} catch (IOException e) {
log.error("failed to read statement at location: {}", path, e);
}
return stmt;
}
}
32 changes: 32 additions & 0 deletions src/test/resources/statements/basic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8",
"actor": {
"name": "Cliff Casey",
"account": {
"homePage": "https://users.training.com",
"name": "23897525"
}
},
"verb": {
"id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/verbs/set",
"display": { "en-us": "Set" }
},
"object": {
"id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1",
"definition": {
"name": {
"en-us": "Activity 1"
},
"description": {
"en-us": "The First Activity"
}
}
},
"authority": {
"name": "Yet Analytics Inc",
"mbox": "mailto:authority@yetanalytics.com"
},
"version": "1.0.3",
"stored": "2023-10-27T09:03:21.722Z",
"timestamp": "2023-10-27T09:03:21.723Z"
}