Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
aceec7c
First cut
robin-aws Oct 31, 2025
0e49d7b
m
robin-aws Nov 1, 2025
362776c
m
robin-aws Nov 1, 2025
cb00d7d
Fixes
robin-aws Nov 2, 2025
848ddad
Cleanup
robin-aws Nov 3, 2025
3cf227d
m
robin-aws Nov 3, 2025
2213cb1
comment
robin-aws Nov 22, 2025
4924c96
Rename package
robin-aws Nov 22, 2025
37c59da
Unshare class for now
robin-aws Nov 22, 2025
34c90f4
Undo
robin-aws Nov 22, 2025
9fd957c
Move into smithy-model
robin-aws Nov 25, 2025
7c779c1
Most of an interpreter
robin-aws Nov 26, 2025
e3995fc
m
robin-aws Nov 26, 2025
37d1e4c
Complete NodeAdaptor
robin-aws Nov 27, 2025
b868f25
Make @contracts a list instead of a map
robin-aws Nov 27, 2025
3c2cf03
Improve Adaptor interface
robin-aws Nov 27, 2025
a457760
Renames
robin-aws Nov 27, 2025
77c3436
First function
robin-aws Nov 28, 2025
e09995e
Merge branch 'main' of github.com:awslabs/smithy into smithy-contracts
robin-aws Nov 28, 2025
347c512
Rename to JmespathRuntime, more functions
robin-aws Nov 28, 2025
ead6908
length
robin-aws Dec 1, 2025
59c332b
Merge branch 'main' of github.com:awslabs/smithy into smithy-contracts
robin-aws Dec 1, 2025
b8d56ca
Generic compliance test runner
robin-aws Dec 2, 2025
78c28df
smithy-jmespath-tests
robin-aws Dec 3, 2025
c8ae716
Fix slice!
robin-aws Dec 3, 2025
36cbf9e
Correct multiselect and parsing
robin-aws Dec 3, 2025
db17f41
Special case
robin-aws Dec 3, 2025
ac48d63
m
robin-aws Dec 3, 2025
a635724
Formatting
robin-aws Dec 4, 2025
27f577b
Cleanup
robin-aws Dec 4, 2025
461ebfa
Fix equality
robin-aws Dec 4, 2025
42b762b
More functions
robin-aws Dec 4, 2025
17b67b7
Most of the other functions
robin-aws Dec 4, 2025
7c408fb
All functions
robin-aws Dec 4, 2025
6bca186
All tests pass woooo
robin-aws Dec 4, 2025
cd72363
Fix tests/bug
robin-aws Dec 4, 2025
4f656be
m
robin-aws Dec 4, 2025
8c14746
TODO
robin-aws Dec 4, 2025
c5c7b06
Dead copy of test files
robin-aws Dec 4, 2025
a008e22
doc build
robin-aws Dec 4, 2025
eba96d8
Correct place
robin-aws Dec 4, 2025
e03149d
remove accidental content
robin-aws Dec 5, 2025
97fe1e9
Remove @contracts on this branch
robin-aws Dec 5, 2025
bae6940
Cleanup
robin-aws Dec 5, 2025
c086ce4
spotless
robin-aws Dec 5, 2025
73831ad
Javadoc and renames
robin-aws Dec 5, 2025
9c557e3
spotless
robin-aws Dec 5, 2025
7178421
HashMap instead of ConcurrentHashMap (need null values)
robin-aws Dec 6, 2025
f478763
InheritingClassMap fix
robin-aws Dec 6, 2025
633fe5d
javadoc typo
robin-aws Dec 6, 2025
8648ea3
m
robin-aws Dec 6, 2025
a6d462e
feature comment and cleanup
robin-aws Dec 8, 2025
749103b
Dead class
robin-aws Dec 8, 2025
219954b
Merge branch 'main' of github.com:awslabs/smithy into smithy-jmespath…
robin-aws Dec 8, 2025
6e2b286
PR feedback
robin-aws Dec 10, 2025
cffc270
Apply suggestions from code review
robin-aws Dec 10, 2025
d36352b
Merge branch 'smithy-jmespath-evaluator' of github.com:robin-aws/smit…
robin-aws Dec 10, 2025
5f8101a
Fix
robin-aws Dec 10, 2025
7a61fbf
m
robin-aws Dec 10, 2025
507e8b4
Move functions into evaluation package, reduce visibility of several …
robin-aws Dec 10, 2025
76deec3
Move NodeJmespathRuntime to separate smithy-model-jmespath package
robin-aws Dec 10, 2025
71e28d3
ArrayNode.elementAt
robin-aws Dec 10, 2025
00f1ffc
Use int for lengths/indexes
robin-aws Dec 10, 2025
4e64aae
Tweak ArrayNode.elementAt
robin-aws Dec 11, 2025
ae683d9
Back to first ArrayNode.elementAt version
robin-aws Dec 12, 2025
934c019
Final version
robin-aws Dec 12, 2025
0625736
Save smithy-model-jmespath for the next PR
robin-aws Dec 12, 2025
dbbfb75
Save Node changes to next PR too
robin-aws Dec 12, 2025
91415ab
Merge branch 'smithy-jmespath-evaluator' of github.com:robin-aws/smit…
robin-aws Dec 12, 2025
0be69cf
Starting to add type system
robin-aws Dec 14, 2025
7763869
Merge branch 'main' of github.com:awslabs/smithy into jmespath-custom…
robin-aws Feb 1, 2026
03de1f9
m
robin-aws Feb 2, 2026
475ba81
Merge branch 'main' of github.com:awslabs/smithy into jmespath-custom…
robin-aws Feb 2, 2026
de27d01
m
robin-aws Feb 2, 2026
c7eaf7b
m
robin-aws Feb 3, 2026
39f6df1
m
robin-aws Feb 7, 2026
4d3dda5
m
robin-aws Feb 7, 2026
d8416d1
Progress, broken, need to replace direct foldLeft and ifThenElse with…
robin-aws Feb 11, 2026
1e3e387
Building
robin-aws Feb 11, 2026
eb42ec3
m
robin-aws Feb 11, 2026
21f645f
m
robin-aws Feb 11, 2026
b866796
Fixing tests
robin-aws Feb 12, 2026
10aa16b
Mostly refactored Function interface
robin-aws Feb 12, 2026
0f96f51
m
robin-aws Feb 13, 2026
fd3e663
m
robin-aws Feb 13, 2026
d54ee19
m
robin-aws Feb 13, 2026
db137c3
m
robin-aws Feb 13, 2026
2c7cc7a
m
robin-aws Feb 13, 2026
f5ca5af
m
robin-aws Feb 14, 2026
6ea3d5f
m
robin-aws Feb 14, 2026
eadd0d8
m
robin-aws Feb 14, 2026
e6f291a
m
robin-aws Feb 14, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "feature",
"description": "Added a generic evaluator/interpreter for JMESPath expressions.",
"pull_requests": [
"[#2878](https://github.com/smithy-lang/smithy/pull/2878)"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
import software.amazon.smithy.jmespath.JmespathException;
import software.amazon.smithy.jmespath.JmespathExceptionType;
import software.amazon.smithy.jmespath.JmespathExpression;
import software.amazon.smithy.jmespath.RuntimeType;
import software.amazon.smithy.jmespath.evaluation.EvaluationUtils;
import software.amazon.smithy.jmespath.evaluation.Evaluator;
import software.amazon.smithy.jmespath.evaluation.JmespathAbstractRuntime;
import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;
import software.amazon.smithy.jmespath.type.Type;
import software.amazon.smithy.utils.IoUtils;

public class ComplianceTestRunner<T> {
public class ComplianceTestRunner<T, A extends Type> {
private static final String DEFAULT_TEST_CASE_LOCATION = "compliance";
private static final String SUBJECT_MEMBER = "given";
private static final String CASES_MEMBER = "cases";
Expand All @@ -30,19 +34,25 @@ public class ComplianceTestRunner<T> {
private static final String ERROR_MEMBER = "error";
private static final String BENCH_MEMBER = "bench";
private final JmespathRuntime<T> runtime;
private final List<TestCase<T>> testCases = new ArrayList<>();
private final JmespathAbstractRuntime<A> abstractRuntime;
private final List<TestCase<T, A>> testCases = new ArrayList<>();

private ComplianceTestRunner(JmespathRuntime<T> runtime) {
private ComplianceTestRunner(JmespathRuntime<T> runtime, JmespathAbstractRuntime<A> abstractRuntime) {
this.runtime = runtime;
this.abstractRuntime = abstractRuntime;
}

public static <T> Stream<Object[]> defaultParameterizedTestSource(JmespathRuntime<T> runtime) {
ComplianceTestRunner<T> runner = new ComplianceTestRunner<>(runtime);
return defaultParameterizedTestSource(runtime, null);
}

public static <T, A extends Type> Stream<Object[]> defaultParameterizedTestSource(JmespathRuntime<T> runtime, JmespathAbstractRuntime<A> abstractRuntime) {
ComplianceTestRunner<T, A> runner = new ComplianceTestRunner<>(runtime, abstractRuntime);
URL manifest = ComplianceTestRunner.class.getResource(DEFAULT_TEST_CASE_LOCATION + "/MANIFEST");
try (var reader = new BufferedReader(new InputStreamReader(manifest.openStream(), StandardCharsets.UTF_8))) {
reader.lines().forEach(line -> {
var url = ComplianceTestRunner.class.getResource(DEFAULT_TEST_CASE_LOCATION + "/" + line.trim());
runner.testCases.addAll(TestCase.from(url, runtime));
runner.testCases.addAll(TestCase.from(url, runtime, abstractRuntime));
});
} catch (IOException e) {
throw new RuntimeException(e);
Expand All @@ -54,8 +64,13 @@ public Stream<Object[]> parameterizedTestSource() {
return testCases.stream().map(testCase -> new Object[] {testCase.name(), testCase});
}

private record TestCase<T>(
public <A> Stream<Object[]> parameterizedAbstractTestSource(JmespathRuntime<A> abstractRuntime, BiPredicate<T, A> abstractPredicate) {
return testCases.stream().map(testCase -> new Object[] {testCase.name(), (Runnable)() -> testCase.abstractRun(abstractRuntime, abstractPredicate)});
}

private record TestCase<T, A extends Type>(
JmespathRuntime<T> runtime,
JmespathAbstractRuntime<A> abstractRuntime,
String testSuite,
String comment,
T given,
Expand All @@ -64,10 +79,10 @@ private record TestCase<T>(
JmespathExceptionType expectedError,
String benchmark)
implements Runnable {
public static <T> List<TestCase<T>> from(URL url, JmespathRuntime<T> runtime) {
public static <T, A extends Type> List<TestCase<T, A>> from(URL url, JmespathRuntime<T> runtime, JmespathAbstractRuntime<A> abstractRuntime) {
var path = url.getPath();
var testSuiteName = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'));
var testCases = new ArrayList<TestCase<T>>();
var testCases = new ArrayList<TestCase<T, A>>();
String text = IoUtils.readUtf8Url(url);
T tests = JmespathExpression.parseJson(text, runtime);

Expand All @@ -92,6 +107,7 @@ public static <T> List<TestCase<T>> from(URL url, JmespathRuntime<T> runtime) {

var benchmark = valueAsString(runtime, testCase, BENCH_MEMBER);
testCases.add(new TestCase<>(runtime,
abstractRuntime,
testSuiteName,
comment,
given,
Expand Down Expand Up @@ -122,9 +138,10 @@ private String name() {
public void run() {
try {
var parsed = JmespathExpression.parse(expression);
var result = new Evaluator<>(given, runtime).visit(parsed);
var result = parsed.evaluate(given, runtime);
if (benchmark != null) {
// Benchmarks don't include expected results or errors
// TODO: Could still run these?
return;
}
if (expectedError != null) {
Expand All @@ -138,7 +155,34 @@ public void run() {
+ "Actual: " + runtime.toString(result) + "\n"
+ "For query: " + expression + "\n");
}

if (abstractRuntime != null) {
var abstractedGiven = EvaluationUtils.convert(runtime, given, abstractRuntime);
var abstractResult = parsed.evaluate(abstractedGiven, abstractRuntime);

if (!abstractResult.isInstance(result, runtime)) {
parsed.evaluate(abstractedGiven, abstractRuntime);
abstractResult.isInstance(result, runtime);
throw new AssertionError("Expected " + result + " to be an instance of " + abstractResult + ".\n"
+ "For query: " + expression + "\n");
}
}
}
} catch (JmespathException e) {
if (!e.getType().equals(expectedError)) {
throw new AssertionError("Expected error does not match actual error. \n"
+ "Expected: " + (expectedError != null ? expectedError : "(no error)") + "\n"
+ "Actual: " + e.getType() + " - " + e.getMessage() + "\n"
+ "For query: " + expression + "\n", e);
}
}
}

public <A> void abstractRun(JmespathRuntime<A> abstractRuntime, BiPredicate<T, A> abstractPredicate) {
try {
var parsed = JmespathExpression.parse(expression);
var result = new Evaluator<>(given, runtime).visit(parsed);

} catch (JmespathException e) {
if (!e.getType().equals(expectedError)) {
throw new AssertionError("Expected error does not match actual error. \n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import software.amazon.smithy.jmespath.LiteralExpressionJmespathRuntime;
import software.amazon.smithy.jmespath.type.TypeJmespathRuntime;

public class LiteralExpressionJmespathRuntimeComplianceTests {
@ParameterizedTest(name = "{0}")
Expand All @@ -17,6 +18,8 @@ public void testRunner(String filename, Runnable callable) throws Exception {
}

public static Stream<?> source() {
return ComplianceTestRunner.defaultParameterizedTestSource(LiteralExpressionJmespathRuntime.INSTANCE);
return ComplianceTestRunner.defaultParameterizedTestSource(
LiteralExpressionJmespathRuntime.INSTANCE,
new TypeJmespathRuntime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.jmespath.ast.LiteralExpression;
import software.amazon.smithy.jmespath.evaluation.AbstractEvaluator;
import software.amazon.smithy.jmespath.evaluation.Evaluator;
import software.amazon.smithy.jmespath.evaluation.JmespathAbstractRuntime;
import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;

/**
Expand Down Expand Up @@ -42,7 +44,7 @@ public static JmespathExpression parse(String text) {
* @return Returns the parsed expression.
* @throws JmespathException if the expression is invalid.
*/
public static <T> JmespathExpression parse(String text, JmespathRuntime<T> runtime) {
public static <T> JmespathExpression parse(String text, JmespathAbstractRuntime<T> runtime) {
return Parser.parse(text, runtime);
}

Expand All @@ -54,7 +56,7 @@ public static <T> JmespathExpression parse(String text, JmespathRuntime<T> runti
* @return Returns the parsed JSON value.
* @throws JmespathException if the text is invalid.
*/
public static <T> T parseJson(String text, JmespathRuntime<T> runtime) {
public static <T> T parseJson(String text, JmespathAbstractRuntime<T> runtime) {
Lexer<T> lexer = new Lexer<T>(text, runtime);
return lexer.parseJsonValue();
}
Expand Down Expand Up @@ -129,4 +131,12 @@ public LiteralExpression evaluate(LiteralExpression currentNode) {
public <T> T evaluate(T currentNode, JmespathRuntime<T> runtime) {
return new Evaluator<>(currentNode, runtime).visit(this);
}

public <T> T evaluate(T currentNode, JmespathAbstractRuntime<T> runtime) {
if (runtime instanceof JmespathRuntime) {
return evaluate(currentNode, (JmespathRuntime<T>)runtime);
} else {
return new AbstractEvaluator<>(currentNode, runtime).visit(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package software.amazon.smithy.jmespath;


import software.amazon.smithy.jmespath.evaluation.Function;

import java.util.List;

public interface JmespathExtension {

<T> List<Function<T>> getFunctions();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package software.amazon.smithy.jmespath;


import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;

import java.util.function.Function;

public interface JmespathQuery<T> extends Function<T, T> {

JmespathRuntime<T> runtime();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
import java.util.Objects;
import java.util.function.Predicate;
import software.amazon.smithy.jmespath.ast.LiteralExpression;
import software.amazon.smithy.jmespath.evaluation.JmespathAbstractRuntime;
import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;

final class Lexer<T> {

private static final int MAX_NESTING_LEVEL = 50;

private final JmespathRuntime<T> runtime;
private final JmespathAbstractRuntime<T> runtime;
private final String expression;
private final int length;
private int position = 0;
Expand All @@ -25,7 +26,7 @@ final class Lexer<T> {
private final List<Token> tokens = new ArrayList<>();
private boolean currentlyParsingLiteral;

Lexer(String expression, JmespathRuntime<T> runtime) {
Lexer(String expression, JmespathAbstractRuntime<T> runtime) {
this.runtime = Objects.requireNonNull(runtime, "runtime must not be null");
this.expression = Objects.requireNonNull(expression, "expression must not be null");
this.length = expression.length();
Expand All @@ -35,7 +36,7 @@ static TokenIterator tokenize(String expression) {
return tokenize(expression, LiteralExpressionJmespathRuntime.INSTANCE);
}

static <T> TokenIterator tokenize(String expression, JmespathRuntime<T> runtime) {
static <T> TokenIterator tokenize(String expression, JmespathAbstractRuntime<T> runtime) {
return new Lexer<>(expression, runtime).doTokenize();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public LiteralExpression element(LiteralExpression array, int index) {
return LiteralExpression.from(array.expectArrayValue().get(index));
}

@Override
public LiteralExpression abstractElement(LiteralExpression array, LiteralExpression index) {
return element(array, index.expectNumberValue().intValue());
}

@Override
public Iterable<LiteralExpression> asIterable(LiteralExpression array) {
switch (array.getType()) {
Expand All @@ -109,17 +114,19 @@ private static final class ArrayLiteralExpressionBuilder implements ArrayBuilder
private final List<Object> result = new ArrayList<>();

@Override
public void add(LiteralExpression value) {
public ArrayLiteralExpressionBuilder add(LiteralExpression value) {
result.add(value.getValue());
return this;
}

@Override
public void addAll(LiteralExpression array) {
public ArrayLiteralExpressionBuilder addAll(LiteralExpression array) {
if (array.isArrayValue()) {
result.addAll(array.expectArrayValue());
} else {
result.addAll(array.expectObjectValue().keySet());
}
return this;
}

@Override
Expand All @@ -146,13 +153,15 @@ private static final class ObjectLiteralExpressionBuilder implements ObjectBuild
private final Map<String, Object> result = new HashMap<>();

@Override
public void put(LiteralExpression key, LiteralExpression value) {
public ObjectLiteralExpressionBuilder put(LiteralExpression key, LiteralExpression value) {
result.put(key.expectStringValue(), value.getValue());
return this;
}

@Override
public void putAll(LiteralExpression object) {
public ObjectLiteralExpressionBuilder putAll(LiteralExpression object) {
result.putAll(object.expectObjectValue());
return this;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import software.amazon.smithy.jmespath.ast.ProjectionExpression;
import software.amazon.smithy.jmespath.ast.SliceExpression;
import software.amazon.smithy.jmespath.ast.Subexpression;
import software.amazon.smithy.jmespath.evaluation.JmespathAbstractRuntime;
import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;

/**
Expand Down Expand Up @@ -75,12 +76,12 @@ final class Parser {
private final String expression;
private final TokenIterator iterator;

private Parser(String expression, JmespathRuntime<?> runtime) {
private Parser(String expression, JmespathAbstractRuntime<?> runtime) {
this.expression = expression;
iterator = Lexer.tokenize(expression, runtime);
}

static JmespathExpression parse(String expression, JmespathRuntime<?> runtime) {
static JmespathExpression parse(String expression, JmespathAbstractRuntime<?> runtime) {
Parser parser = new Parser(expression, runtime);
JmespathExpression result = parser.expression(0);
parser.iterator.expect(TokenType.EOF);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package software.amazon.smithy.jmespath;

import java.util.EnumSet;
import java.util.Locale;
import software.amazon.smithy.jmespath.ast.ComparatorType;
import software.amazon.smithy.jmespath.ast.LiteralExpression;
Expand Down Expand Up @@ -150,4 +151,8 @@ public abstract LiteralExpression compare(
LiteralExpression right,
ComparatorType comparator
);

public static EnumSet<RuntimeType> valueTypes() {
return EnumSet.complementOf(EnumSet.of(EXPRESSION, ANY));
}
}
Loading
Loading