Skip to content

Commit

Permalink
Adds RestJson deserializer and middleware generation support (#568)
Browse files Browse the repository at this point in the history
* adds support for restjson deserializer middleware, json deserializers for output, error shapes

* adds error check for discard unknown field deserializer util

* suggested feedback

* minor feedback updates
  • Loading branch information
skotambkar authored and skmcgrail committed Sep 24, 2020
1 parent 6abf244 commit 69e8bf8
Show file tree
Hide file tree
Showing 2 changed files with 924 additions and 7 deletions.
132 changes: 132 additions & 0 deletions aws/protocol/json/decoder_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package json

import (
"encoding/json"
"fmt"
"github.com/awslabs/smithy-go"
"io"
"strings"
)

// GetErrorInfo util looks for code, __type, and message members in the
// json body. These members are optionally available, and the function
// returns the value of member if it is available. This function is useful to
// identify the error code, msg in a REST JSON error response.
func GetErrorInfo(decoder *json.Decoder) (errorType string, message string, err error) {
startToken, err := decoder.Token()
if err == io.EOF {
return "", "", nil
}
if err != nil {
return "", "", err
}

if t, ok := startToken.(json.Delim); !ok || t.String() != "{" {
return "", "", fmt.Errorf("expected start token to be {")
}

for decoder.More() {
var target *string
t, err := decoder.Token()
if err != nil {
return "", "", err
}

switch st := t.(string); {
case strings.EqualFold(st, "code"):
fallthrough
case strings.EqualFold(st, "__type"):
target = &errorType
case strings.EqualFold(st, "message"):
target = &message
default:
DiscardUnknownField(decoder)
continue
}

v, err := decoder.Token()
if err != nil {
return errorType, message, err
}
*target = v.(string)
}

endToken, err := decoder.Token()
if err != nil {
return "", "", err
}

if t, ok := endToken.(json.Delim); !ok || t.String() != "}" {
return "", "", fmt.Errorf("expected end token to be }")
}

// sanitize error
errorType = SanitizeErrorCode(errorType)
return errorType, message, nil
}

// SanitizeErrorCode sanitizes the errorCode string .
// The rule for sanitizing is if a `:` character is present, then take only the
// contents before the first : character in the value.
// If a # character is present, then take only the contents after the
// first # character in the value.
func SanitizeErrorCode(errorCode string) string {
if strings.ContainsAny(errorCode, ":") {
errorCode = strings.SplitN(errorCode, ":", 2)[0]
}

if strings.ContainsAny(errorCode, "#") {
errorCode = strings.SplitN(errorCode, "#", 2)[1]
}

return errorCode
}

// DiscardUnknownField discards unknown fields from decoder body.
// This function is useful while deserializing json body with additional
// unknown information that should be discarded.
func DiscardUnknownField(decoder *json.Decoder) error {
v, err := decoder.Token()
if err == io.EOF {
return nil
}
if err != nil {
return err
}

if _, ok := v.(json.Delim); ok {
for decoder.More() {
err = DiscardUnknownField(decoder)
}
endToken, err := decoder.Token()
if err != nil {
return err
}
if _, ok := endToken.(json.Delim); !ok {
return fmt.Errorf("invalid JSON : expected json delimiter, found %T %v",
endToken, endToken)
}
}

return nil
}

// GetSmithyGenericAPIError returns smithy generic api error and an error interface.
// Takes in json decoder, and error Code string as args. The function retrieves error message
// and error code from the decoder body. If errorCode of length greater than 0 is passed in as
// an argument, it is used instead.
func GetSmithyGenericAPIError(decoder *json.Decoder, errorCode string) (*smithy.GenericAPIError, error) {
errorType, message, err := GetErrorInfo(decoder)
if err != nil {
return nil, err
}

if len(errorCode) == 0 {
errorCode = errorType
}

return &smithy.GenericAPIError{
Code: errorCode,
Message: message,
}, nil
}
Loading

0 comments on commit 69e8bf8

Please sign in to comment.