-
Notifications
You must be signed in to change notification settings - Fork 663
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds RestJson deserializer and middleware generation support (#568)
* 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
1 parent
6abf244
commit 69e8bf8
Showing
2 changed files
with
924 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.