Skip to content

Commit

Permalink
Merge pull request #3 from fauna/BT-4346-fauna-templating
Browse files Browse the repository at this point in the history
BT-4346 Fauna templating
  • Loading branch information
IvanAlfer authored Nov 3, 2023
2 parents 53bde68 + 18d831a commit 88dd198
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 0 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ subprojects {

project(':faunaJava') {
apply plugin: 'java'
compileJava {
options.encoding = 'UTF-8'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand All @@ -37,6 +40,9 @@ project(':faunaJava') {

project(':common') {
apply plugin: 'java'
compileJava {
options.encoding = 'UTF-8'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down
124 changes: 124 additions & 0 deletions faunaJava/src/main/java/com/fauna/query/template/FaunaTemplate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.fauna.query.template;

import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FaunaTemplate implements Iterable<FaunaTemplate.TemplatePart> {

private static final char DELIMITER = '$';
private static final String ID_PATTERN = "[\\p{L}_][\\p{L}\\p{N}_]*";
private static final Pattern PATTERN;

static {
String delim = Pattern.quote(String.valueOf(DELIMITER));
String pattern = String.format(
"%s(?:(?<escaped>%s)|\\{(?<braced>%s)\\}|\\{(?<invalid>[^}]*))?",
delim, delim, ID_PATTERN
);
PATTERN = Pattern.compile(pattern, Pattern.COMMENTS);
}

private final String template;

public FaunaTemplate(String template) {
this.template = template;
}

@Override
public Iterator<TemplatePart> iterator() {
return new Iterator<>() {
private final Matcher matcher = PATTERN.matcher(template);
private int curPos = 0;
private boolean foundMatch = false;

@Override
public boolean hasNext() {
if (curPos < template.length()) {
return true;
}
if (foundMatch) {
return true;
}
return matcher.find(curPos);
}

@Override
public TemplatePart next() {
if (curPos >= template.length()) {
throw new IllegalStateException("No more elements");
}

if (foundMatch || (foundMatch = matcher.find(curPos))) {
int spanStartPos = matcher.start();
int spanEndPos = matcher.end();
String invalid = matcher.group("invalid");
if (invalid != null) {
handleInvalid(matcher.start("invalid"));
}
String escapedPart = matcher.group("escaped");
String variablePart = matcher.group("braced");

TemplatePart part;
if (escapedPart != null) {
String literalPart = template.substring(curPos, spanStartPos) + DELIMITER;
part = new TemplatePart(literalPart, TemplatePartType.LITERAL);
curPos = spanEndPos;
} else if (variablePart != null) {
if (curPos < spanStartPos) {
part = new TemplatePart(template.substring(curPos, spanStartPos), TemplatePartType.LITERAL);
curPos = spanStartPos;
} else {
part = new TemplatePart(variablePart, TemplatePartType.VARIABLE);
curPos = spanEndPos;
}
} else {
part = new TemplatePart(template.substring(curPos, spanStartPos), TemplatePartType.LITERAL);
curPos = spanStartPos;
}
foundMatch = false; // Reset after processing a match
return part;
} else {
TemplatePart part = new TemplatePart(template.substring(curPos), TemplatePartType.LITERAL);
curPos = template.length();
return part;
}
}
};
}

private void handleInvalid(int position) {
String substringUpToPosition = template.substring(0, position);
String[] lines = substringUpToPosition.split("\r?\n");
int colno, lineno;
if (lines.length == 0) {
colno = 1;
lineno = 1;
} else {
String lastLine = lines[lines.length - 1];
// Adjust the column number for invalid placeholder
colno = position - (substringUpToPosition.length() - lastLine.length()) - 1; // -1 to exclude the dollar sign
lineno = lines.length;
}
throw new IllegalArgumentException(String.format("Invalid placeholder in template: line %d, col %d", lineno, colno));
}

public static class TemplatePart {
private final String part;
private final TemplatePartType type;

public TemplatePart(String part, TemplatePartType type) {
this.part = part;
this.type = type;
}

public String getPart() {
return part;
}

public TemplatePartType getType() {
return type;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.fauna.query.template;

public enum TemplatePartType {

LITERAL,
VARIABLE

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.fauna.query.template;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class FaunaTemplateTest {

@Test
void testTemplate_WithSingleVariable() {
FaunaTemplate template = new FaunaTemplate("let x = ${my_var}");
List<FaunaTemplate.TemplatePart> expanded = new ArrayList<>();
template.forEach(expanded::add);
assertEquals(2, expanded.size());
assertEquals("let x = ", expanded.get(0).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(0).getType());
assertEquals("my_var", expanded.get(1).getPart());
assertEquals(TemplatePartType.VARIABLE, expanded.get(1).getType());
}

@Test
void testTemplate_WithDuplicateVariable() {
FaunaTemplate template = new FaunaTemplate("let x = ${my_var}\nlet y = ${my_var}\nx * y");
List<FaunaTemplate.TemplatePart> expanded = new ArrayList<>();
template.forEach(expanded::add);
assertEquals(5, expanded.size());
assertEquals("let x = ", expanded.get(0).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(0).getType());
assertEquals("my_var", expanded.get(1).getPart());
assertEquals(TemplatePartType.VARIABLE, expanded.get(1).getType());
assertEquals("\nlet y = ", expanded.get(2).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(2).getType());
assertEquals("my_var", expanded.get(3).getPart());
assertEquals(TemplatePartType.VARIABLE, expanded.get(3).getType());
assertEquals("\nx * y", expanded.get(4).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(4).getType());
}

@Test
void testTemplate_WithVariableAtStart() {
FaunaTemplate template = new FaunaTemplate("${my_var} { .name }");
List<FaunaTemplate.TemplatePart> expanded = new ArrayList<>();
template.forEach(expanded::add);
assertEquals(2, expanded.size());
assertEquals("my_var", expanded.get(0).getPart());
assertEquals(TemplatePartType.VARIABLE, expanded.get(0).getType());
assertEquals(" { .name }", expanded.get(1).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(1).getType());
}

@Test
void testTemplates_WithEscapes() {
FaunaTemplate template = new FaunaTemplate("let x = '$${not_a_var}'");
List<FaunaTemplate.TemplatePart> expanded = new ArrayList<>();
template.forEach(expanded::add);
assertEquals(2, expanded.size());
assertEquals("let x = '$", expanded.get(0).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(0).getType());
assertEquals("{not_a_var}'", expanded.get(1).getPart());
assertEquals(TemplatePartType.LITERAL, expanded.get(1).getType());
}

@Test
void testTemplates_WithUnsupportedIdentifiers() {
FaunaTemplate template = new FaunaTemplate("let x = ${かわいい}");
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
List<FaunaTemplate.TemplatePart> expanded = new ArrayList<>();
template.forEach(expanded::add);
});
String expectedMessage = "Invalid placeholder in template: line 1, col 9";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}

}

0 comments on commit 88dd198

Please sign in to comment.