Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-eade5c9.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "bugfix",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Improve support for operationContextParams with chained index and multi-select expressions and improve support for StringArray endpoint parametes."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in parameters*

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
Expand Down Expand Up @@ -160,21 +159,16 @@ private boolean shouldGenerateJmesPathRuntime() {
return true;
}

Map<String, ParameterModel> endpointParameters = model.getCustomizationConfig().getEndpointParameters();
Map<String, ParameterModel> endpointParameters = model.getEndpointRuleSetModel().getParameters();
if (endpointParameters == null) {
return false;
}

return endpointParameters.values().stream().anyMatch(this::paramRequiresPathParserRuntime);
}

private boolean paramRequiresPathParserRuntime(ParameterModel parameterModel) {
return paramIsOperationalContextParam(parameterModel) &&
"stringarray".equals(parameterModel.getType().toLowerCase(Locale.US));
}

//TODO (string-array-params): resolve this logical test before finalizing coding
private boolean paramIsOperationalContextParam(ParameterModel parameterModel) {
return true;
// if any operation has operationContextParams then we must include jmesPathRuntime
return model.getOperations().values().stream()
.anyMatch(op -> {
Map<String, ?> opContextParams = op.getOperationContextParams();
return opContextParams != null && !opContextParams.isEmpty();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public static BracketSpecifier withWildcardExpressionContents(WildcardExpression
return withContents(BracketSpecifierWithContents.wildcardExpression(wildcardExpression));
}

public static BracketSpecifier withMultiSelectListContents(MultiSelectList multiSelectList) {
return withContents(BracketSpecifierWithContents.multiSelectList(multiSelectList));
}

public static BracketSpecifier withoutContents() {
BracketSpecifier result = new BracketSpecifier();
result.bracketSpecifierWithoutContents = new BracketSpecifierWithoutContents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
* <li>A number, as in [1]</li>
* <li>A star expression, as in [*]</li>
* <li>A slice expression, as in [1:2:3]</li>
* <li>A multi-select-list, as in [string, object.key]</li>
* </ul>
*/
public class BracketSpecifierWithContents {
private Integer number;
private WildcardExpression wildcardExpression;
private SliceExpression sliceExpression;
private MultiSelectList multiSelectList;

private BracketSpecifierWithContents() {
}
Expand All @@ -55,6 +57,13 @@ public static BracketSpecifierWithContents sliceExpression(SliceExpression slice
return result;
}

public static BracketSpecifierWithContents multiSelectList(MultiSelectList multiSelectList) {
Validate.notNull(multiSelectList, "multiSelectList");
BracketSpecifierWithContents result = new BracketSpecifierWithContents();
result.multiSelectList = multiSelectList;
return result;
}

public boolean isNumber() {
return number != null;
}
Expand All @@ -67,6 +76,10 @@ public boolean isSliceExpression() {
return sliceExpression != null;
}

public boolean isMultiSelectList() {
return multiSelectList != null;
}

public int asNumber() {
Validate.validState(isNumber(), "Not a Number");
return number;
Expand All @@ -82,13 +95,20 @@ public SliceExpression asSliceExpression() {
return sliceExpression;
}

public MultiSelectList asMultiSelectList() {
Validate.validState(isMultiSelectList(), "Not a MultiSelectList");
return multiSelectList;
}

public void visit(JmesPathVisitor visitor) {
if (isNumber()) {
visitor.visitNumber(asNumber());
} else if (isWildcardExpression()) {
visitor.visitWildcardExpression(asWildcardExpression());
} else if (isSliceExpression()) {
visitor.visitSliceExpression(asSliceExpression());
} else if (isMultiSelectList()) {
visitor.visitMultiSelectList(asMultiSelectList());
} else {
throw new IllegalStateException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static Expression parse(String jmesPathString) {
private Expression parse() {
ParseResult<Expression> expression = parseExpression(0, input.length());
if (!expression.hasResult()) {
throw new IllegalArgumentException("Failed to parse expression.");
throw new IllegalArgumentException("Failed to parse expression: " + input);
}

return expression.result();
Expand Down Expand Up @@ -246,13 +246,21 @@ private ParseResult<IndexExpression> parseIndexExpressionWithLhsExpression(int s
endPosition = trimRightWhitespace(startPosition, endPosition);

List<Integer> bracketPositions = findCharacters(startPosition + 1, endPosition - 1, "[");
for (Integer bracketPosition : bracketPositions) {

// Try bracket positions from right to left to support chaining multiple bracket specifiers
// e.g., listOfUnions[*][string, object.key][] should parse as:
// - left: listOfUnions[*][string, object.key]
// - right: []
// This allows both the left and right side to be recursively parsed
for (int i = bracketPositions.size() - 1; i >= 0; i--) {
Integer bracketPosition = bracketPositions.get(i);
ParseResult<Expression> leftSide = parseExpression(startPosition, bracketPosition);
if (!leftSide.hasResult()) {
continue;
}

ParseResult<BracketSpecifier> rightSide = parseBracketSpecifier(bracketPosition, endPosition);
// we know there is a left hand expression and that the Rhs is a bracketSpecifier
ParseResult<BracketSpecifier> rightSide = parseBracketSpecifierWithLhsExpression(bracketPosition, endPosition);
if (!rightSide.hasResult()) {
continue;
}
Expand All @@ -272,6 +280,74 @@ private ParseResult<MultiSelectList> parseMultiSelectList(int startPosition, int
.mapResult(MultiSelectList::new);
}

/**
* Parse multi-select-list content without the surrounding brackets.
* Used when parsing bracket specifier contents like [string, object.key] or [string]
* This parses: expression *( "," expression )
*/
private ParseResult<MultiSelectList> parseMultiSelectListContent(int startPosition, int endPosition) {
startPosition = trimLeftWhitespace(startPosition, endPosition);
endPosition = trimRightWhitespace(startPosition, endPosition);

List<Integer> commaPositions = findCharacters(startPosition, endPosition, ",");

// Single expression case (no commas)
if (commaPositions.isEmpty()) {
ParseResult<Expression> singleExpr = parseExpression(startPosition, endPosition);
if (!singleExpr.hasResult()) {
return ParseResult.error();
}
return ParseResult.success(new MultiSelectList(Collections.singletonList(singleExpr.result())));
}

// Multiple expressions separated by commas
List<Expression> expressions = new ArrayList<>();

// Find first valid entry before a comma
int startOfSecondEntry = -1;
for (Integer comma : commaPositions) {
ParseResult<Expression> result = parseExpression(startPosition, comma);
if (!result.hasResult()) {
continue;
}

expressions.add(result.result());
startOfSecondEntry = comma + 1;
break;
}

if (expressions.size() == 0) {
logError("multi-select-list-content", "Invalid value", startPosition);
return ParseResult.error();
}

// Find any subsequent entries
int startPositionAfterComma = startOfSecondEntry;
for (Integer commaPosition : commaPositions) {
if (startPositionAfterComma > commaPosition) {
continue;
}

ParseResult<Expression> entry = parseExpression(startPositionAfterComma, commaPosition);
if (!entry.hasResult()) {
continue;
}

expressions.add(entry.result());
startPositionAfterComma = commaPosition + 1;
}

// Parse the last entry after the final comma
ParseResult<Expression> entry = parseExpression(startPositionAfterComma, endPosition);
if (!entry.hasResult()) {
logError("multi-select-list-content", "Invalid final entry", startPositionAfterComma);
return ParseResult.error();
}
expressions.add(entry.result());

return ParseResult.success(new MultiSelectList(expressions));
}

/**
* multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}"
*/
Expand Down Expand Up @@ -410,6 +486,38 @@ private ParseResult<BracketSpecifier> parseBracketSpecifier(int startPosition, i
.parse(startPosition + 1, endPosition - 1);
}

/**
* This is a special case for bracket specifiers with a left-hand expression and which can contain multi-select-lists.
* bracket-specifier-with-multiselect = "[" multi-select-list-content "]"
*/
private ParseResult<BracketSpecifier> parseBracketSpecifierWithLhsExpression(int startPosition, int endPosition) {
startPosition = trimLeftWhitespace(startPosition, endPosition);
endPosition = trimRightWhitespace(startPosition, endPosition);

if (!startsAndEndsWith(startPosition, endPosition, '[', ']')) {
logError("bracket-specifier-with-multiselect", "Expecting '[' and ']'", startPosition);
return ParseResult.error();
}

// "[]"
if (charsInRange(startPosition, endPosition) == 2) {
return ParseResult.success(BracketSpecifier.withoutContents());
}

// "[?" expression "]"
if (input.charAt(startPosition + 1) == '?') {
return parseExpression(startPosition + 2, endPosition - 1)
.mapResult(e -> BracketSpecifier.withQuestionMark(new BracketSpecifierWithQuestionMark(e)));
}

// "[" (number / "*" / slice-expression / multi-select-list-content) "]"
return CompositeParser.firstTry(this::parseNumber, BracketSpecifier::withNumberContents)
.thenTry(this::parseWildcardExpression, BracketSpecifier::withWildcardExpressionContents)
.thenTry(this::parseSliceExpression, BracketSpecifier::withSliceExpressionContents)
.thenTry(this::parseMultiSelectListContent, BracketSpecifier::withMultiSelectListContents)
.parse(startPosition + 1, endPosition - 1);
}

/**
* comparator-expression = expression comparator expression
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.jr.stree.JrsArray;
import com.fasterxml.jackson.jr.stree.JrsBoolean;
import com.fasterxml.jackson.jr.stree.JrsString;
import com.squareup.javapoet.ClassName;
Expand Down Expand Up @@ -364,6 +365,11 @@ private MethodSpec addStaticContextParamsMethod(OperationModel opModel) {
case VALUE_FALSE:
b.addStatement("params.$N($L)", setterName, ((JrsBoolean) value).booleanValue());
break;
case START_ARRAY:
JrsArray arrayValue = (JrsArray) value;
CodeBlock arrayCode = endpointRulesSpecUtils.treeNodeToLiteral(arrayValue);
b.addStatement("params.$N($L)", setterName, arrayCode);
break;
default:
throw new RuntimeException("Don't know how to set parameter of type " + value.asToken());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ public void visitBracketSpecifierWithContents(BracketSpecifierWithContents input
codeBlock.add(".index(" + input.asNumber() + ")");
} else if (input.isWildcardExpression()) {
codeBlock.add(".wildcard()");
} else if (input.isMultiSelectList()) {
visitMultiSelectList(input.asMultiSelectList());
} else {
throw new UnsupportedOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.codegen.jmespath.component.Comparator;
import software.amazon.awssdk.codegen.jmespath.component.Expression;
import software.amazon.awssdk.codegen.jmespath.component.MultiSelectList;
import software.amazon.awssdk.codegen.jmespath.parser.JmesPathParser;

public class JmesPathParserTest {
Expand All @@ -38,6 +39,38 @@ public void testSubExpressionWithMultiSelectList() {
assertThat(expression.asSubExpression().rightSubExpression().asMultiSelectList().expressions().get(0).asIdentifier()).isEqualTo("bar");
}

@Test
public void testSubExpressionWithMultiSelectListAndFlatten() {
Expression expression = JmesPathParser.parse("listOfUnions[*][string, object.key][]");

// The expression should be parsed as:
// IndexExpression(IndexExpression(IndexExpression(listOfUnions, [*]), [string, object.key]), [])
assertThat(expression.isIndexExpression()).isTrue();

// the right most flatten
assertThat(expression.asIndexExpression().bracketSpecifier().isBracketSpecifierWithoutContents()).isTrue();

// Middle: [string, object.key]
Expression middleBracketsExpr = expression.asIndexExpression().expression().get();
assertThat(middleBracketsExpr.isIndexExpression()).isTrue();
assertThat(middleBracketsExpr.asIndexExpression().bracketSpecifier().isBracketSpecifierWithContents()).isTrue();
assertThat(middleBracketsExpr.asIndexExpression().bracketSpecifier().asBracketSpecifierWithContents().isMultiSelectList()).isTrue();

MultiSelectList multiSelectList = middleBracketsExpr.asIndexExpression().bracketSpecifier()
.asBracketSpecifierWithContents().asMultiSelectList();
assertThat(multiSelectList.expressions()).hasSize(2);
assertThat(multiSelectList.expressions().get(0).asIdentifier()).isEqualTo("string");
assertThat(multiSelectList.expressions().get(1).asSubExpression().leftExpression().asIdentifier()).isEqualTo("object");
assertThat(multiSelectList.expressions().get(1).asSubExpression().rightSubExpression().asIdentifier()).isEqualTo("key");

// left most wildcard: listOfUnions[*]
Expression wildCardExpr = middleBracketsExpr.asIndexExpression().expression().get();
assertThat(wildCardExpr.isIndexExpression()).isTrue();
assertThat(wildCardExpr.asIndexExpression().expression().get().asIdentifier()).isEqualTo("listOfUnions");
assertThat(wildCardExpr.asIndexExpression().bracketSpecifier().isBracketSpecifierWithContents()).isTrue();
assertThat(wildCardExpr.asIndexExpression().bracketSpecifier().asBracketSpecifierWithContents().isWildcardExpression()).isTrue();
}

@Test
public void testSubExpressionWithMultiSelectHash() {
Expression expression = JmesPathParser.parse("foo.{bar : baz}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,10 @@ void endpointResolverInterceptorClassWithEndpointBasedAuth() {
ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(ClientTestModels.queryServiceModelsEndpointAuthParamsWithoutAllowList());
assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor-with-endpointsbasedauth.java"));
}

@Test
public void endpointProviderTestClassWithStringArray() {
ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(ClientTestModels.stringArrayServiceModels());
assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor-with-stringarray.java"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ void testNestedMultiSelectListsLeft() {
+ "x3 -> x3.field(\"baz\"))");
}

@Test
void testMultiSelectListAndFlatten() {
testConversion("listOfUnions[*][string, object.key][]",
"input.field(\"listOfUnions\").wildcard().multiSelectList(x0 -> x0.field(\"string\"), "
+ "x1 -> x1.field(\"object\").field(\"key\")).flatten()");
}

@Test
void testMultiSelectHash2() {
assertThatThrownBy(() -> testConversion("{fooK : fooV, barK : barV}", ""))
Expand Down
Loading
Loading