Skip to content
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
63 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
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