Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
4826331
First cut
robin-aws Oct 31, 2025
18f1b53
m
robin-aws Nov 1, 2025
a1d0486
m
robin-aws Nov 1, 2025
b3fc086
Fixes
robin-aws Nov 2, 2025
6d207bb
Cleanup
robin-aws Nov 3, 2025
92c7e83
m
robin-aws Nov 3, 2025
61effa8
comment
robin-aws Nov 22, 2025
17029b3
Rename package
robin-aws Nov 22, 2025
f58dea7
Unshare class for now
robin-aws Nov 22, 2025
66842b7
Undo
robin-aws Nov 22, 2025
063a9ed
Move into smithy-model
robin-aws Nov 25, 2025
4e2092e
Most of an interpreter
robin-aws Nov 26, 2025
b420453
m
robin-aws Nov 26, 2025
6be8d05
Complete NodeAdaptor
robin-aws Nov 27, 2025
8c36962
Make @contracts a list instead of a map
robin-aws Nov 27, 2025
e3116f5
Improve Adaptor interface
robin-aws Nov 27, 2025
c6214c1
Renames
robin-aws Nov 27, 2025
3aee7e7
First function
robin-aws Nov 28, 2025
e2c2736
Rename to JmespathRuntime, more functions
robin-aws Nov 28, 2025
7c6d93a
length
robin-aws Dec 1, 2025
90209f9
Generic compliance test runner
robin-aws Dec 2, 2025
ca0eb3d
smithy-jmespath-tests
robin-aws Dec 3, 2025
c387d78
Fix slice!
robin-aws Dec 3, 2025
86fcb36
Correct multiselect and parsing
robin-aws Dec 3, 2025
b2388d6
Special case
robin-aws Dec 3, 2025
df528a1
m
robin-aws Dec 3, 2025
0dcef98
Formatting
robin-aws Dec 4, 2025
c7068e7
Cleanup
robin-aws Dec 4, 2025
8bf1256
Fix equality
robin-aws Dec 4, 2025
6f1aa2f
More functions
robin-aws Dec 4, 2025
358ead3
Most of the other functions
robin-aws Dec 4, 2025
47490e0
All functions
robin-aws Dec 4, 2025
9c78e8a
All tests pass woooo
robin-aws Dec 4, 2025
4a089a9
Fix tests/bug
robin-aws Dec 4, 2025
8098176
m
robin-aws Dec 4, 2025
db2c0bc
TODO
robin-aws Dec 4, 2025
5cff87b
Dead copy of test files
robin-aws Dec 4, 2025
0a4eb1d
doc build
robin-aws Dec 4, 2025
39fd5af
Correct place
robin-aws Dec 4, 2025
82f9a95
remove accidental content
robin-aws Dec 5, 2025
01a1022
Remove @contracts on this branch
robin-aws Dec 5, 2025
a362e0d
Cleanup
robin-aws Dec 5, 2025
f7c5882
spotless
robin-aws Dec 5, 2025
e0b0fe7
Javadoc and renames
robin-aws Dec 5, 2025
ac98c5b
spotless
robin-aws Dec 5, 2025
132c37c
HashMap instead of ConcurrentHashMap (need null values)
robin-aws Dec 6, 2025
de27fd4
InheritingClassMap fix
robin-aws Dec 6, 2025
00a8a63
javadoc typo
robin-aws Dec 6, 2025
5cd728f
m
robin-aws Dec 6, 2025
b73466d
feature comment and cleanup
robin-aws Dec 8, 2025
d4b8de9
Dead class
robin-aws Dec 8, 2025
65d0ef3
PR feedback
robin-aws Dec 10, 2025
c7c9a6a
Apply suggestions from code review
robin-aws Dec 10, 2025
69764a7
Fix
robin-aws Dec 10, 2025
a0bdd42
m
robin-aws Dec 10, 2025
6a3ed74
Move functions into evaluation package, reduce visibility of several …
robin-aws Dec 10, 2025
3845f60
Move NodeJmespathRuntime to separate smithy-model-jmespath package
robin-aws Dec 10, 2025
c88e408
ArrayNode.elementAt
robin-aws Dec 10, 2025
9136630
Use int for lengths/indexes
robin-aws Dec 10, 2025
86a3fbe
Tweak ArrayNode.elementAt
robin-aws Dec 11, 2025
d55ea65
Back to first ArrayNode.elementAt version
robin-aws Dec 12, 2025
db2a229
Save smithy-model-jmespath for the next PR
robin-aws Dec 12, 2025
9c0198e
Save Node changes to next PR too
robin-aws Dec 12, 2025
943dfaf
PR feedback
robin-aws Dec 18, 2025
f0ed904
Remove stray feature file
robin-aws Jan 9, 2026
6e32083
Poke CI
robin-aws Jan 9, 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)"
]
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include(":smithy-openapi-traits")
include(":smithy-utils")
include(":smithy-protocol-test-traits")
include(":smithy-jmespath")
include(":smithy-jmespath-tests")
include(":smithy-waiters")
include(":smithy-aws-cloudformation-traits")
include(":smithy-aws-cloudformation")
Expand Down
24 changes: 24 additions & 0 deletions smithy-jmespath-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id("smithy.module-conventions")
}

description = "Compliance tests for JMESPath"

extra["displayName"] = "Smithy :: JMESPath Tests"
extra["moduleName"] = "software.amazon.smithy.jmespathtests"

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
api(libs.junit.jupiter.api)
api(libs.junit.jupiter.params)
api(project(":smithy-jmespath"))
implementation(project(":smithy-utils"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.jmespath.tests;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
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.Evaluator;
import software.amazon.smithy.jmespath.evaluation.JmespathRuntime;
import software.amazon.smithy.utils.IoUtils;

public class ComplianceTestRunner<T> {
private static final String DEFAULT_TEST_CASE_LOCATION = "compliance";
private static final String SUBJECT_MEMBER = "given";
private static final String CASES_MEMBER = "cases";
private static final String COMMENT_MEMBER = "comment";
private static final String EXPRESSION_MEMBER = "expression";
private static final String RESULT_MEMBER = "result";
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 ComplianceTestRunner(JmespathRuntime<T> runtime) {
this.runtime = runtime;
}

public static <T> Stream<Object[]> defaultParameterizedTestSource(JmespathRuntime<T> runtime) {
ComplianceTestRunner<T> runner = new ComplianceTestRunner<>(runtime);
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));
});
} catch (IOException e) {
throw new RuntimeException(e);
}
return runner.parameterizedTestSource();
}

public Stream<Object[]> parameterizedTestSource() {
return testCases.stream().map(testCase -> new Object[] {testCase.name(), testCase});
}

private record TestCase<T>(
JmespathRuntime<T> runtime,
String testSuite,
String comment,
T given,
String expression,
T expectedResult,
JmespathExceptionType expectedError,
String benchmark)
implements Runnable {
public static <T> List<TestCase<T>> from(URL url, JmespathRuntime<T> runtime) {
var path = url.getPath();
var testSuiteName = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'));
var testCases = new ArrayList<TestCase<T>>();
String text = IoUtils.readUtf8Url(url);
T tests = JmespathExpression.parseJson(text, runtime);

for (var test : runtime.asIterable(tests)) {
var given = value(runtime, test, SUBJECT_MEMBER);
for (var testCase : runtime.asIterable(value(runtime, test, CASES_MEMBER))) {
String comment = valueAsString(runtime, testCase, COMMENT_MEMBER);
String expression = valueAsString(runtime, testCase, EXPRESSION_MEMBER);
var result = value(runtime, testCase, RESULT_MEMBER);
var expectedErrorString = valueAsString(runtime, testCase, ERROR_MEMBER);
var expectedError =
expectedErrorString != null ? JmespathExceptionType.fromID(expectedErrorString) : null;

// Special case: The spec says function names cannot be quoted,
// but our parser allows it, and this may be useful in the future.
if ("function names cannot be quoted".equals(comment)) {
expectedError = JmespathExceptionType.UNKNOWN_FUNCTION;
} else if (expression.contains("\"to_string\"")) {
expectedError = null;
result = runtime.createString("1.0");
}

var benchmark = valueAsString(runtime, testCase, BENCH_MEMBER);
testCases.add(new TestCase<>(runtime,
testSuiteName,
comment,
given,
expression,
result,
expectedError,
benchmark));
}
}
return testCases;
}

private static <T> T value(JmespathRuntime<T> runtime, T object, String key) {
return runtime.value(object, runtime.createString(key));
}

private static <T> String valueAsString(JmespathRuntime<T> runtime, T object, String key) {
T result = runtime.value(object, runtime.createString(key));
return runtime.is(result, RuntimeType.NULL) ? null : runtime.asString(result);
}

private String name() {
return testSuite + (comment != null ? " - " + comment : "") + " (" + runtime.toString(given) + ")["
+ expression + "]";
}

@Override
public void run() {
try {
var parsed = JmespathExpression.parse(expression);
var result = new Evaluator<>(given, runtime).visit(parsed);
if (benchmark != null) {
// Benchmarks don't include expected results or errors
return;
}
if (expectedError != null) {
throw new AssertionError("Expected " + expectedError + " error but no error occurred. \n"
+ "Actual: " + runtime.toString(result) + "\n"
+ "For query: " + expression + "\n");
} else {
if (!runtime.equal(expectedResult, result)) {
throw new AssertionError("Expected does not match actual. \n"
+ "Expected: " + runtime.toString(expectedResult) + "\n"
+ "Actual: " + runtime.toString(result) + "\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);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
basic.json
benchmarks.json
boolean.json
current.json
escape.json
filters.json
functions.json
identifiers.json
indices.json
literal.json
multiselect.json
pipe.json
slice.json
syntax.json
unicode.json
wildcard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Compliance tests

This directory is copied from this snapshot of the JMESPath compliance tests repository:

https://github.com/jmespath/jmespath.test/tree/53abcc37901891cf4308fcd910eab287416c4609/tests

The MANIFEST file is added so that these can be retrieved as resources
(which don't support any notion of directories or listing).
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
[{
"given":
{"foo": {"bar": {"baz": "correct"}}},
"cases": [
{
"expression": "foo",
"result": {"bar": {"baz": "correct"}}
},
{
"expression": "foo.bar",
"result": {"baz": "correct"}
},
{
"expression": "foo.bar.baz",
"result": "correct"
},
{
"expression": "foo\n.\nbar\n.baz",
"result": "correct"
},
{
"expression": "foo.bar.baz.bad",
"result": null
},
{
"expression": "foo.bar.bad",
"result": null
},
{
"expression": "foo.bad",
"result": null
},
{
"expression": "bad",
"result": null
},
{
"expression": "bad.morebad.morebad",
"result": null
}
]
},
{
"given":
{"foo": {"bar": ["one", "two", "three"]}},
"cases": [
{
"expression": "foo",
"result": {"bar": ["one", "two", "three"]}
},
{
"expression": "foo.bar",
"result": ["one", "two", "three"]
}
]
},
{
"given": ["one", "two", "three"],
"cases": [
{
"expression": "one",
"result": null
},
{
"expression": "two",
"result": null
},
{
"expression": "three",
"result": null
},
{
"expression": "one.two",
"result": null
}
]
},
{
"given":
{"foo": {"1": ["one", "two", "three"], "-1": "bar"}},
"cases": [
{
"expression": "foo.\"1\"",
"result": ["one", "two", "three"]
},
{
"expression": "foo.\"1\"[0]",
"result": "one"
},
{
"expression": "foo.\"-1\"",
"result": "bar"
}
]
}
]
Loading
Loading