diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
index dcd550617ad..a6d2a82ce61 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
@@ -109,11 +109,16 @@ abstract Schema createDocumentSchema(
MessageType messageType
);
+ @Deprecated
+ Node transformSmithyValueToProtocolValue(Node value) {
+ return value;
+ }
+
/**
* Converts Smithy values in Node form to a data exchange format used by a protocol (e.g., XML).
* Then returns the converted value as a long string (escaping where necessary).
* If data exchange format is JSON (e.g., as in restJson1 protocol),
- * method should return values without any modification.
+ * method should respect the jsonName trait, but otherwise not modify the node.
*
*
Used for the value property of OpenAPI example objects.
* For protocols that do not use JSON as data-exchange format,
@@ -122,10 +127,14 @@ abstract Schema createDocumentSchema(
* E.g., for restXML protocol, values would be converted to a large String of XML value / object,
* escaping where necessary.
*
- * @param value value to be converted.
+ * @param context Conversion context.
+ * @param shape The shape that the value represents.
+ * @param value value to be converted.
* @return the long string (escaped where necessary) of values in a data exchange format used by a protocol.
*/
- abstract Node transformSmithyValueToProtocolValue(Node value);
+ Node transformSmithyValueToProtocolValue(Context context, Shape shape, Node value) {
+ return value;
+ }
@Override
public Set getProtocolRequestHeaders(Context context, OperationShape operationShape) {
@@ -204,6 +213,7 @@ private List createPathParameters(Context context, Operation
.in("path")
.schema(schema)
.examples(createExamplesForMembersWithHttpTraits(
+ context,
operation,
binding,
MessageType.REQUEST,
@@ -219,6 +229,7 @@ private List createPathParameters(Context context, Operation
* path parameters, query parameters, header parameters, and payload.
*/
private Map createExamplesForMembersWithHttpTraits(
+ Context context,
Shape operationOrError,
HttpBinding binding,
MessageType type,
@@ -229,15 +240,17 @@ private Map createExamplesForMembersWithHttpTraits(
}
if (type == MessageType.ERROR) {
- return createErrorExamplesForMembersWithHttpTraits(operationOrError, binding, operation);
+ return createErrorExamplesForMembersWithHttpTraits(context, operationOrError, binding, operation);
} else {
Map examples = new TreeMap<>();
// unique numbering for unique example names in OpenAPI.
int uniqueNum = 1;
- Optional examplesTrait = operationOrError.getTrait(ExamplesTrait.class);
- for (ExamplesTrait.Example example : examplesTrait.map(ExamplesTrait::getExamples)
- .orElse(Collections.emptyList())) {
+ List modeledExamples = operationOrError.getTrait(ExamplesTrait.class)
+ .map(ExamplesTrait::getExamples)
+ .orElse(Collections.emptyList());
+
+ for (ExamplesTrait.Example example : modeledExamples) {
ObjectNode inputOrOutput = type == MessageType.REQUEST ? example.getInput()
: example.getOutput().orElse(Node.objectNode());
String name = operationOrError.getId().getName() + "_example" + uniqueNum++;
@@ -251,7 +264,7 @@ private Map createExamplesForMembersWithHttpTraits(
ExampleObject.builder()
.summary(example.getTitle())
.description(example.getDocumentation().orElse(""))
- .value(transformSmithyValueToProtocolValue(values))
+ .value(transformSmithyValueToProtocolValue(context, binding.getMember(), values))
.build()
.toNode());
}
@@ -264,6 +277,7 @@ private Map createExamplesForMembersWithHttpTraits(
* Helper method for createExamples() method.
*/
private Map createErrorExamplesForMembersWithHttpTraits(
+ Context context,
Shape error,
HttpBinding binding,
OperationShape operation
@@ -290,7 +304,7 @@ private Map createErrorExamplesForMembersWithHttpTraits(
ExampleObject.builder()
.summary(example.getTitle())
.description(example.getDocumentation().orElse(""))
- .value(transformSmithyValueToProtocolValue(values))
+ .value(transformSmithyValueToProtocolValue(context, binding.getMember(), values))
.build()
.toNode());
}
@@ -302,6 +316,7 @@ private Map createErrorExamplesForMembersWithHttpTraits(
* This method is used for converting the Smithy examples to OpenAPI examples for non-payload HTTP message body.
*/
private Map createBodyExamples(
+ Context context,
Shape operationOrError,
List bindings,
MessageType type,
@@ -312,7 +327,7 @@ private Map createBodyExamples(
}
if (type == MessageType.ERROR) {
- return createErrorBodyExamples(operationOrError, bindings, operation);
+ return createErrorBodyExamples(context, operationOrError, bindings, operation);
} else {
Map examples = new TreeMap<>();
// unique numbering for unique example names in OpenAPI.
@@ -321,18 +336,30 @@ private Map createBodyExamples(
Optional examplesTrait = operationOrError.getTrait(ExamplesTrait.class);
for (ExamplesTrait.Example example : examplesTrait.map(ExamplesTrait::getExamples)
.orElse(Collections.emptyList())) {
+
+ Shape structure = operationOrError;
+ if (operationOrError.isOperationShape()) {
+ OperationShape op = operationOrError.asOperationShape().get();
+ if (type == MessageType.REQUEST) {
+ structure = context.getModel().expectShape(op.getInputShape());
+ } else {
+ structure = context.getModel().expectShape(op.getOutputShape());
+ }
+ }
+
// get members included in bindings
ObjectNode values = getMembersWithHttpBindingTrait(bindings,
type == MessageType.REQUEST ? example.getInput()
: example.getOutput().orElse(Node.objectNode()));
String name = operationOrError.getId().getName() + "_example" + uniqueNum++;
+
// this if condition is needed to avoid errors when converting examples of response.
if (!example.getError().isPresent() || type == MessageType.REQUEST) {
examples.put(name,
ExampleObject.builder()
.summary(example.getTitle())
.description(example.getDocumentation().orElse(""))
- .value(transformSmithyValueToProtocolValue(values))
+ .value(transformSmithyValueToProtocolValue(context, structure, values))
.build()
.toNode());
}
@@ -342,6 +369,7 @@ private Map createBodyExamples(
}
private Map createErrorBodyExamples(
+ Context context,
Shape error,
List bindings,
OperationShape operation
@@ -362,7 +390,7 @@ private Map createErrorBodyExamples(
ExampleObject.builder()
.summary(example.getTitle())
.description(example.getDocumentation().orElse(""))
- .value(transformSmithyValueToProtocolValue(values))
+ .value(transformSmithyValueToProtocolValue(context, error, values))
.build()
.toNode());
}
@@ -456,7 +484,8 @@ private List createQueryParameters(Context context, Operatio
}
param.schema(createQuerySchema(context, member, target));
- param.examples(createExamplesForMembersWithHttpTraits(operation, binding, MessageType.REQUEST, null));
+ param.examples(
+ createExamplesForMembersWithHttpTraits(context, operation, binding, MessageType.REQUEST, null));
result.add(param.build());
}
@@ -496,7 +525,8 @@ private Map createHeaderParameters(
param.in(null).name(null);
}
- param.examples(createExamplesForMembersWithHttpTraits(operationOrError, binding, messageType, operation));
+ param.examples(
+ createExamplesForMembersWithHttpTraits(context, operationOrError, binding, messageType, operation));
// Create the appropriate schema based on the shape type.
Shape target = context.getModel().expectShape(member.getTarget());
@@ -566,6 +596,7 @@ private Optional createRequestPayload(
return shapeName + "InputPayload";
}).toBuilder()
.examples(createExamplesForMembersWithHttpTraits(
+ context,
operation,
binding,
MessageType.REQUEST,
@@ -598,7 +629,7 @@ private Optional createRequestDocument(
String pointer = context.putSynthesizedSchema(synthesizedName, schema);
MediaTypeObject mediaTypeObject = MediaTypeObject.builder()
.schema(Schema.builder().ref(pointer).build())
- .examples(createBodyExamples(operation, bindings, MessageType.REQUEST, null))
+ .examples(createBodyExamples(context, operation, bindings, MessageType.REQUEST, null))
.build();
// If any of the top level bindings are required, then the body itself must be required.
@@ -757,6 +788,7 @@ private void createResponsePayload(
: shapeName + "ErrorPayload";
}).toBuilder()
.examples(createExamplesForMembersWithHttpTraits(
+ context,
operationOrError,
binding,
type,
@@ -840,7 +872,7 @@ private void createResponseDocumentIfNeeded(
String pointer = context.putSynthesizedSchema(synthesizedName, schema);
MediaTypeObject mediaTypeObject = MediaTypeObject.builder()
.schema(Schema.builder().ref(pointer).build())
- .examples(createBodyExamples(operationOrError, bindings, messageType, operation))
+ .examples(createBodyExamples(context, operationOrError, bindings, messageType, operation))
.build();
responseBuilder.putContent(mediaType, mediaTypeObject);
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1Protocol.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1Protocol.java
index bbde5e40444..3f84277acac 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1Protocol.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1Protocol.java
@@ -166,7 +166,7 @@ private boolean hasSingleUnionMember(StructureShape shape, Model model) {
}
@Override
- Node transformSmithyValueToProtocolValue(Node value) {
- return value;
+ Node transformSmithyValueToProtocolValue(Context context, Shape shape, Node value) {
+ return value.accept(new JsonValueNodeTransformer(context, shape));
}
}
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/JsonValueNodeTransformer.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/JsonValueNodeTransformer.java
new file mode 100644
index 00000000000..62f4df56ed2
--- /dev/null
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/JsonValueNodeTransformer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package software.amazon.smithy.openapi.fromsmithy.protocols;
+
+import java.util.Map;
+import software.amazon.smithy.model.node.ArrayNode;
+import software.amazon.smithy.model.node.BooleanNode;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.NodeVisitor;
+import software.amazon.smithy.model.node.NullNode;
+import software.amazon.smithy.model.node.NumberNode;
+import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.node.StringNode;
+import software.amazon.smithy.model.shapes.MapShape;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.traits.JsonNameTrait;
+import software.amazon.smithy.openapi.fromsmithy.Context;
+
+/**
+ * Applies the jsonName trait to a node value if applicable.
+ */
+public class JsonValueNodeTransformer implements NodeVisitor {
+ private final Context> context;
+ private final Shape shape;
+
+ /**
+ * Construct a JsonValueNodeTransformer.
+ *
+ * @param context Conversion context. Used to determine if jsonName should be used.
+ * @param shape The shape of the node being converted.
+ */
+ public JsonValueNodeTransformer(Context> context, Shape shape) {
+ this.context = context;
+ this.shape = shape;
+ }
+
+ @Override
+ public Node booleanNode(BooleanNode node) {
+ return node;
+ }
+
+ @Override
+ public Node nullNode(NullNode node) {
+ return node;
+ }
+
+ @Override
+ public Node numberNode(NumberNode node) {
+ return node;
+ }
+
+ @Override
+ public Node stringNode(StringNode node) {
+ return node;
+ }
+
+ @Override
+ public Node arrayNode(ArrayNode node) {
+ ArrayNode.Builder resultBuilder = ArrayNode.builder();
+ Shape listShape = shape.asMemberShape()
+ .map(m -> context.getModel().expectShape(m.getTarget()))
+ .orElse(shape);
+
+ Shape target = context.getModel().expectShape(listShape.asListShape().get().getMember().getTarget());
+ JsonValueNodeTransformer elementTransformer = new JsonValueNodeTransformer(context, target);
+ for (Node element : node.getElements()) {
+ resultBuilder.withValue(element.accept(elementTransformer));
+ }
+ return resultBuilder.build();
+ }
+
+ @Override
+ public Node objectNode(ObjectNode node) {
+ Shape actual = shape.asMemberShape()
+ .map(m -> context.getModel().expectShape(m.getTarget()))
+ .orElse(shape);
+
+ if (shape.isMapShape()) {
+ return mapNode(actual.asMapShape().get(), node);
+ }
+ return structuredNode(actual, node);
+ }
+
+ private Node structuredNode(Shape structure, ObjectNode node) {
+ ObjectNode.Builder resultBuilder = ObjectNode.builder();
+ for (Map.Entry entry : node.getMembers().entrySet()) {
+ String key = entry.getKey().getValue();
+ if (structure.getMember(key).isPresent()) {
+ MemberShape member = structure.getMember(key).get();
+ Shape target = context.getModel().expectShape(member.getTarget());
+ JsonValueNodeTransformer entryTransformer = new JsonValueNodeTransformer(context, target);
+ resultBuilder.withMember(getKey(member), entry.getValue().accept(entryTransformer));
+ } else {
+ resultBuilder.withMember(key, entry.getValue());
+ }
+ }
+ return resultBuilder.build();
+ }
+
+ private String getKey(MemberShape member) {
+ if (!context.getJsonSchemaConverter().getConfig().getUseJsonName()) {
+ return member.getMemberName();
+ }
+ return member.getTrait(JsonNameTrait.class)
+ .map(JsonNameTrait::getValue)
+ .orElse(member.getMemberName());
+ }
+
+ private Node mapNode(MapShape map, ObjectNode node) {
+ ObjectNode.Builder resultBuilder = ObjectNode.builder();
+ Shape target = context.getModel().expectShape(map.getValue().getTarget());
+ JsonValueNodeTransformer entryTransformer = new JsonValueNodeTransformer(context, target);
+ for (Map.Entry entry : node.getMembers().entrySet()) {
+ resultBuilder.withMember(entry.getKey(), entry.getValue().accept(entryTransformer));
+ }
+ return resultBuilder.build();
+ }
+}
diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.openapi.json
index 4e2350e584e..9d4d7413fac 100644
--- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.openapi.json
+++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.openapi.json
@@ -149,7 +149,7 @@
"description": "withdrawTestDoc",
"value": {
"location": "Denver",
- "bankName": "Chase",
+ "bank": "Chase",
"atmRecording": "dGVzdHZpZGVv"
}
}
@@ -470,7 +470,7 @@
"location": {
"type": "string"
},
- "bankName": {
+ "bank": {
"type": "string"
},
"atmRecording": {
diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.smithy b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.smithy
index b32459f4b5c..5bd61b88a56 100644
--- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.smithy
+++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/examples-test.smithy
@@ -1,59 +1,92 @@
+$version: "2"
+
namespace smithy.examplestrait
use aws.protocols#restJson1
@restJson1
service Banking {
- version: "2022-06-26",
- operations: [Deposit, Withdraw]
+ version: "2022-06-26"
+ operations: [
+ Deposit
+ Withdraw
+ ]
}
@idempotent
@http(method: "PUT", uri: "/account/{username}", code: 200)
operation Deposit {
- input: DepositInput,
- output: DepositOutput,
- errors: [InvalidUsername, InvalidAmount]
+ input := {
+ @httpHeader("accountNumber")
+ accountNumber: String
+
+ @required
+ @httpLabel
+ username: String
+
+ @httpQuery("accountHistory")
+ accountHistory: ExampleList
+
+ @httpPayload
+ depositAmount: String
+ }
+
+ output := {
+ @httpHeader("username")
+ username: String
+
+ @httpHeader("authenticationResult")
+ authenticationResult: ExampleList
+
+ textMessage: String
+
+ emailMessage: String
+ }
+
+ errors: [
+ InvalidUsername
+ InvalidAmount
+ ]
}
@idempotent
@http(method: "PATCH", uri: "/account/withdraw", code: 200)
operation Withdraw {
- input: WithdrawInput,
- output: WithdrawOutput,
- errors: [InvalidUsername]
-}
+ input := {
+ @httpHeader("accountNumber")
+ accountNumber: String
-@input
-structure DepositInput {
- @httpHeader("accountNumber")
- accountNumber: String,
+ @httpHeader("username")
+ username: String
- @required
- @httpLabel
- username: String,
+ @httpQueryParams
+ withdrawParams: ExampleMap
- @httpQuery("accountHistory")
- accountHistory: ExampleList,
+ time: date
- @httpPayload
- depositAmount: String
-}
+ withdrawAmount: String
-@input
-structure WithdrawInput {
- @httpHeader("accountNumber")
- accountNumber: String,
+ withdrawOption: String
+ }
- @httpHeader("username")
- username: String,
+ output := {
+ @httpHeader("branch")
+ branch: String
- @httpQueryParams()
- withdrawParams: ExampleMap,
+ @httpHeader("result")
+ accountHistory: ExampleList
- time: date,
- withdrawAmount: String,
- withdrawOption: String
+ location: String
+
+ @jsonName("bank")
+ bankName: String
+
+ atmRecording: exampleVideo
+ }
+
+ errors: [
+ InvalidUsername
+ ]
}
list ExampleList {
@@ -61,7 +94,7 @@ list ExampleList {
}
map ExampleMap {
- key: String,
+ key: String
value: String
}
@@ -71,35 +104,10 @@ blob exampleVideo
@timestampFormat("http-date")
timestamp date
-@output
-structure DepositOutput {
- @httpHeader("username")
- username: String,
-
- @httpHeader("authenticationResult")
- authenticationResult: ExampleList,
-
- textMessage: String,
- emailMessage: String
-}
-
-@output
-structure WithdrawOutput {
- @httpHeader("branch")
- branch: String,
-
- @httpHeader("result")
- accountHistory: ExampleList,
-
- location: String,
- bankName: String,
- atmRecording: exampleVideo
-}
-
@error("client")
structure InvalidUsername {
@httpHeader("internalErrorCode")
- internalErrorCode: String,
+ internalErrorCode: String
@httpPayload
errorMessage: String
@@ -107,107 +115,90 @@ structure InvalidUsername {
@error("server")
structure InvalidAmount {
- errorMessage1: String,
- errorMessage2: String,
+ errorMessage1: String
+ errorMessage2: String
errorMessage3: String
}
-apply Deposit @examples(
- [
- {
- title: "Deposit valid example",
- documentation: "depositTestDoc",
- input: {
- accountNumber: "102935",
- username: "sichanyoo",
- accountHistory: ["10", "-25", "50"],
- depositAmount: "200"
- },
- output: {
- username: "sichanyoo",
- authenticationResult: ["pass1", "pass2", "pass3"],
- textMessage: "You deposited 200-text",
- emailMessage: "You deposited 200-email"
- },
- },
-
- {
- title: "Deposit invalid username example",
- documentation: "depositTestDoc2",
- input: {
- username: "sichanyoo",
- accountHistory: ["-200", "200", "10"],
- depositAmount: "-200"
- },
- error: {
- shapeId: InvalidUsername,
- content: {
- internalErrorCode: "4gsw2-34",
- errorMessage: "ERROR: Invalid username."
- }
- },
- },
-
- {
- title: "Deposit invalid amount example",
- documentation: "depositTestDoc3",
- input: {
- accountNumber: "203952",
- username: "obidos",
- accountHistory: ["2000", "50000", "100"],
- depositAmount: "-100"
- },
- error: {
- shapeId: InvalidAmount,
- content: {
- errorMessage1: "ERROR: Invalid amount.",
- errorMessage2: "2gdx4-34",
- errorMessage3: "2gcbe-98"
- }
- },
+apply Deposit @examples([
+ {
+ title: "Deposit valid example"
+ documentation: "depositTestDoc"
+ input: {
+ accountNumber: "102935"
+ username: "sichanyoo"
+ accountHistory: ["10", "-25", "50"]
+ depositAmount: "200"
}
- ]
-)
-
-apply Withdraw @examples(
- [
- {
- title: "Withdraw valid example",
- documentation: "withdrawTestDoc",
- input: {
- accountNumber: "124634",
- username: "amazon",
- withdrawParams: {"location" : "Denver", "bankName" : "Chase"},
- time: "Tue, 29 Apr 2014 18:30:38 GMT",
- withdrawAmount: "-35",
- withdrawOption: "ATM"
- },
- output: {
- branch: "Denver-203",
- accountHistory: ["34", "5", "-250"],
- location: "Denver",
- bankName: "Chase",
- atmRecording: "dGVzdHZpZGVv"
- },
- },
-
- {
- title: "Withdraw invalid username example",
- documentation: "withdrawTestDoc2",
- input: {
- accountNumber: "231565",
- username: "peccy",
- withdrawParams: {"location" : "Seoul", "bankName" : "Chase"},
- withdrawAmount: "-450",
- withdrawOption: "Venmo"
- },
- error: {
- shapeId: InvalidUsername,
- content: {
- internalErrorCode: "8dfws-21",
- errorMessage: "ERROR: Invalid username."
- }
- },
+ output: {
+ username: "sichanyoo"
+ authenticationResult: ["pass1", "pass2", "pass3"]
+ textMessage: "You deposited 200-text"
+ emailMessage: "You deposited 200-email"
}
- ]
-)
+ }
+ {
+ title: "Deposit invalid username example"
+ documentation: "depositTestDoc2"
+ input: {
+ username: "sichanyoo"
+ accountHistory: ["-200", "200", "10"]
+ depositAmount: "-200"
+ }
+ error: {
+ shapeId: InvalidUsername
+ content: { internalErrorCode: "4gsw2-34", errorMessage: "ERROR: Invalid username." }
+ }
+ }
+ {
+ title: "Deposit invalid amount example"
+ documentation: "depositTestDoc3"
+ input: {
+ accountNumber: "203952"
+ username: "obidos"
+ accountHistory: ["2000", "50000", "100"]
+ depositAmount: "-100"
+ }
+ error: {
+ shapeId: InvalidAmount
+ content: { errorMessage1: "ERROR: Invalid amount.", errorMessage2: "2gdx4-34", errorMessage3: "2gcbe-98" }
+ }
+ }
+])
+
+apply Withdraw @examples([
+ {
+ title: "Withdraw valid example"
+ documentation: "withdrawTestDoc"
+ input: {
+ accountNumber: "124634"
+ username: "amazon"
+ withdrawParams: { location: "Denver", bankName: "Chase" }
+ time: "Tue, 29 Apr 2014 18:30:38 GMT"
+ withdrawAmount: "-35"
+ withdrawOption: "ATM"
+ }
+ output: {
+ branch: "Denver-203"
+ accountHistory: ["34", "5", "-250"]
+ location: "Denver"
+ bankName: "Chase"
+ atmRecording: "dGVzdHZpZGVv"
+ }
+ }
+ {
+ title: "Withdraw invalid username example"
+ documentation: "withdrawTestDoc2"
+ input: {
+ accountNumber: "231565"
+ username: "peccy"
+ withdrawParams: { location: "Seoul", bankName: "Chase" }
+ withdrawAmount: "-450"
+ withdrawOption: "Venmo"
+ }
+ error: {
+ shapeId: InvalidUsername
+ content: { internalErrorCode: "8dfws-21", errorMessage: "ERROR: Invalid username." }
+ }
+ }
+])