Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix response payload and incorrectly parsing error response #66

Merged
merged 8 commits into from
Jan 10, 2020

Conversation

AllanZhengYP
Copy link
Contributor

@AllanZhengYP AllanZhengYP commented Dec 27, 2019

reopen #63

Two fixes:

  1. Currently when response body is a payload member, the SDK either
    keeps reponse intact if it has a streaming trait, or parse the body
    to JS object.

    Actually when response payload is not streaming, it is
    NOT always parsable(not a valid JSON or XML string). Specificly,
    when payload is String, we shouldn't
    parse it with XML or JSON parser; We should also treat Blob shape
    differently as we should encode the binaries into string; Only when
    shape is Union or Structure can we assume payload is parsable
    by JSON or XML parser.

  2. For some protocols, error type flag exists in error response body,
    then we need to collect response stream to JS object and parse the
    error type; For other protocols, error type flag doesn't exist in
    error response body, then we don't need to collect the response
    stream in error dispatcher. Instead, we can treat the error like
    normal response. So that error shape supports the same traits as
    normal responses like streaming, payload etc.

    This is done by adding a new class member in Protocol generator--
    isErrorCodeInBody. When it is true, it means error type flag
    exists in error response body, then body is parsed in errors
    dispatcher, and each error deser only need to deal with parsed
    response body in JS object format;

Example:

//Exception dispatcher
async function exceptionDispatcher(
  output: __HttpResponse,
  context: __SerdeContext,
): Promise<CommandOutput> {
    const parsedOutput: any = { // parse output in dispatcher
    ...output,
    body: await parseBody(output.body, context),
  };
  let response: __SmithyException & __MetadataBearer;
  let errorCode: String;
  const errorTypeParts: String = parsedOutput.body["__type"].split('#');
  errorCode = (errorTypeParts[1] === undefined) ? errorTypeParts[0] : errorTypeParts[1];
  switch (errorCode) {
     case "com.amazon.aws.sdk.foo#FooException":
        response = await deserializeFooExceptionResponse(parsedOutput, context);
        break;
  //other cases
  }
}

const deserializeFooExceptionResponse = async (
  output: any,
  context: __SerdeContext
): Promise<FooException> => {
  //Don't parse response again in error deser
  const deserialized: any = deserializeFooException(output.body, context);
  const contents: FooException = {
    __type: "FooException",
    $fault: "client",
    $metadata: deserializeMetadata(output),
    ...deserialized,
  };
  return contents;
};

When it is false, it means
error type can be inferred without touching response body, then
error deser can access the error response intact.

async function exceptionDispatcher (
  output: __HttpResponse,
  context: __SerdeContext,
): Promise<CommandOutput> {
  const errorOutput: any = { // No need to parse response here
    ...output,
    body: output.body,
  };
  let response: __SmithyException & __MetadataBearer;
  let errorCode: String;
  errorCode = output.headers["x-amzn-errortype"].split(':')[0];
  switch (errorCode) {
     case "FooException":
      response = await deserializeFooExceptionResponse(errorOutput, context);
      break;
    //other cases
  }
}

const deserializeFooExceptionResponse = async (
  output: any,
  context: __SerdeContext
): Promise<FooException> => {
  const contents: FooException = {
    __type: "FooException",
    $fault: "client",
    $metadata: deserializeMetadata(output),
    Code: undefined,
    Message: undefined,
  };
  const data: any = await parseBody(output.body, context); //parse response here.
  //parse the error members
  return contents;
};

/cc @kstich

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -594,7 +605,7 @@ private void generateOperationDeserializer(

// Write out the error deserialization dispatcher.
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
context, operation, responseType, this::writeErrorCodeParser);
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody);
Copy link
Contributor

Choose a reason for hiding this comment

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

References to this.isErrorCodeInBody don't need this. if there's not another variable named that in scope. That should be all cases except the constructor.

@@ -254,7 +264,7 @@ private void generateOperationDeserializer(GenerationContext context, OperationS

// Write out the error deserialization dispatcher.
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
context, operation, responseType, this::writeErrorCodeParser);
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody);
Copy link
Contributor

Choose a reason for hiding this comment

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

References to this.isErrorCodeInBody don't need this. if there's not another variable named that in scope. That should be all cases except the constructor.

if (isBodyParsed) {
// Body is already parsed in error dispatcher, simply assign body to data.
writer.write("const data: any = output.body;");
return ListUtils.of();
Copy link
Contributor

Choose a reason for hiding this comment

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

This will short circuit deserializing any modeled content in the document of an error response. There is no call to deserializeOutputDocument and any shapes that may need to be deserialized won't be added to the deserializingDocumentShapes set here.

@@ -621,7 +633,8 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
});

readHeaders(context, error, bindingIndex);
List<HttpBinding> documentBindings = readResponseBody(context, error, bindingIndex);
List<HttpBinding> documentBindings = readErrorResponseBody(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This doesn't need to fall to a new line.

/**
* Creates a Http binding protocol generator.
*
* @param isErrorCodeInBody A boolean indicates whether error code is located in error response body.
Copy link
Contributor

Choose a reason for hiding this comment

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

A boolean that indicates if the error code for the implementing protocol is located in the error response body, meaning this generator will parse the body before attempting to load an error code.

// Prepare error response for parsing error code. If error code needs to be parsed from response body
// then we collect body and parse it to JS object, otherwise leave the response body as is.
writer.openBlock(
"const $L: any = {", "};", shouldParseErrorBody ? "parsedOutput" : "errorOutput",
Copy link
Contributor

Choose a reason for hiding this comment

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

This object name toggle is used in multiple places, can this logic exist once and be stored?

/**
* Creates a Http RPC protocol generator.
*
* @param isErrorCodeInBody A boolean indicates whether error code is located in error response body.
Copy link
Contributor

Choose a reason for hiding this comment

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

See updated text in other file.

if (isErrorCodeInBody) {
// Body is already parsed in error dispatcher, simply assign body to data.
writer.write("const data: any = output.body;");
List<HttpBinding> responseBindings = bindingIndex.getResponseBindings(error).values()
Copy link
Contributor

Choose a reason for hiding this comment

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

This will contain all response bindings for the error shape, use Location.DOCUMENT to refine this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will update this.

For some protocols, error type flag exists in error response body,
then we need to collect response stream to JS object and parse the
error type; For other protocols, error type flag doesn't exist in
error response body, then we don't need to collect the response
stream in error dispatcher. Instead, we can treat the error like
normal response. So that error shape supports the same traits as
normal responses like streaming, payload etc.

This is done by add a new flag in Protocol generator--
isErrorCodeInBody. When it return true, it means error type flag
exists in error response body, then body is parsed in errors
dispatcher, and each error deser only need to deal with parsed
response body in JS object format. When it returns false, it means
error type can be inferred without touching response body, then
error deser can access the error response intact.
@kstich kstich merged commit 07f9273 into smithy-lang:master Jan 10, 2020
AllanZhengYP added a commit to AllanZhengYP/aws-sdk-js-v3 that referenced this pull request Jan 10, 2020
trivikr pushed a commit to aws/aws-sdk-js-v3 that referenced this pull request Jan 10, 2020
AllanZhengYP added a commit to AllanZhengYP/aws-sdk-js-v3 that referenced this pull request Mar 20, 2020
trivikr pushed a commit to trivikr/aws-sdk-js-v3 that referenced this pull request Mar 20, 2020
trivikr pushed a commit to trivikr/aws-sdk-js-v3 that referenced this pull request Mar 24, 2020
trivikr pushed a commit to trivikr/aws-sdk-js-v3 that referenced this pull request Mar 24, 2020
srchase pushed a commit to srchase/smithy-typescript that referenced this pull request Mar 17, 2023
Currently when response body is a payload member, the SDK either
keeps reponse intact if it has a streaming trait, or parse the body
to JS object.

Actually when response payload is not streaming, it is
NOT always parsable(not a valid JSON or XML string). Specificly,
when payload is String, we shouldn't
parse it with XML or JSON parser; We should also treat Blob shape
differently as we should encode the binaries into string; Only when
shape is Union or Structure can we assume payload is parsable
by JSON or XML parser.

For some protocols, error type flag exists in error response body,
then we need to collect response stream to JS object and parse the
error type; For other protocols, error type flag doesn't exist in
error response body, then we don't need to collect the response
stream in error dispatcher. Instead, we can treat the error like
normal response. So that error shape supports the same traits as
normal responses like streaming, payload etc.

This is done by add a new flag in Protocol generator--
isErrorCodeInBody. When it return true, it means error type flag
exists in error response body, then body is parsed in errors
dispatcher, and each error deser only need to deal with parsed
response body in JS object format. When it returns false, it means
error type can be inferred without touching response body, then
error deser can access the error response intact.
srchase pushed a commit that referenced this pull request Mar 23, 2023
* add logger middleware and logger class

* update logger level setting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants