-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from fauna/BT-4346-fauna-templating
BT-4346 Fauna templating
- Loading branch information
Showing
4 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
faunaJava/src/main/java/com/fauna/query/template/FaunaTemplate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
faunaJava/src/main/java/com/fauna/query/template/TemplatePartType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.fauna.query.template; | ||
|
||
public enum TemplatePartType { | ||
|
||
LITERAL, | ||
VARIABLE | ||
|
||
} |
78 changes: 78 additions & 0 deletions
78
faunaJava/src/test/java/com/fauna/query/template/FaunaTemplateTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
|
||
} |