Skip to content
Draft
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
191 changes: 191 additions & 0 deletions aws-protocols/awsjson10/awsjson10.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package awsjson10

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/aws/smithy-go"
awsjson "github.com/aws/smithy-go/aws-protocols/internal/json"
smithyio "github.com/aws/smithy-go/io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)

// New returns an instance of the awsJson 1.0 protocol.
func New() *Protocol {
return &Protocol{}
}

// Protocol implements aws.protocols#awsJson10.
type Protocol struct {
UseQueryCompatible bool
}

var _ smithyhttp.ClientProtocol = (*Protocol)(nil)

// ID identifies the protocol.
func (*Protocol) ID() string {
return "aws.protocols#awsJson10"
}

// SerializeRequest serializes a request for AWS Json 1.0.
func (p *Protocol) SerializeRequest(
ctx context.Context,
in smithy.Serializable,
req *smithyhttp.Request,
) error {
req.Method = http.MethodPost
req.Header.Set("X-Amz-Target", fmt.Sprintf("%s.%s", middleware.GetServiceName(ctx), middleware.GetOperationName(ctx)))
req.Header.Set("Content-Type", "application/x-amz-json-1.0")
if p.UseQueryCompatible {
req.Header.Set("X-Amzn-Query-Compatible", "true")
}

ss := awsjson.NewShapeSerializer()
in.Serialize(ss)

sreq, err := req.SetStream(bytes.NewReader(ss.Bytes()))
if err != nil {
return fmt.Errorf("set stream: %w", err)
}

*req = *sreq
return nil
}

// DeserializeResponse deserializes a response for AWS Json 1.0.
func (p *Protocol) DeserializeResponse(
ctx context.Context,
types *smithy.TypeRegistry,
resp *smithyhttp.Response,
out smithy.Deserializable,
) error {
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return p.deserializeError(types, resp)
}

payload, err := io.ReadAll(resp.Body)
if err != nil {
return &smithy.DeserializationError{Err: err}
}

if len(payload) == 0 {
return nil
}

sd := awsjson.NewShapeDeserializer(payload)
if err := out.Deserialize(sd); err != nil {
return &smithy.DeserializationError{Err: err}
}

return nil
}

func (p *Protocol) deserializeError(types *smithy.TypeRegistry, response *smithyhttp.Response) error {
var errorBuffer bytes.Buffer
if _, err := io.Copy(&errorBuffer, response.Body); err != nil {
return &smithy.DeserializationError{Err: fmt.Errorf("failed to copy error response body, %w", err)}
}
errorBody := bytes.NewReader(errorBuffer.Bytes())

errorCode := "UnknownError"
errorMessage := errorCode

var headerCode string
headerCode = response.Header.Get("X-Amzn-ErrorType")

var buff [1024]byte
ringBuffer := smithyio.NewRingBuffer(buff[:])

body := io.TeeReader(errorBody, ringBuffer)
decoder := json.NewDecoder(body)
decoder.UseNumber()
bodyInfo, err := getProtocolErrorInfo(decoder)
if err != nil {
var snapshot bytes.Buffer
io.Copy(&snapshot, ringBuffer)
err = &smithy.DeserializationError{
Err: fmt.Errorf("failed to decode response body, %w", err),
Snapshot: snapshot.Bytes(),
}
return err
}

errorBody.Seek(0, io.SeekStart)
if typ, ok := resolveProtocolErrorType(headerCode, bodyInfo); ok {
errorCode = typ
}
if len(bodyInfo.Message) != 0 {
errorMessage = bodyInfo.Message
}

errorCode = sanitizeErrorCode(errorCode)

perr, ok := types.DeserializableError(errorCode)
if !ok {
return &smithy.GenericAPIError{
Code: errorCode,
Message: errorMessage,
}

}

errorBody.Seek(0, io.SeekStart)
errorBytes, _ := io.ReadAll(errorBody)
if len(errorBytes) > 0 {
deser := awsjson.NewShapeDeserializer(errorBytes)
if err := perr.Deserialize(deser); err != nil {
return &smithy.DeserializationError{Err: err}
}
}

return perr
}

type protocolErrorInfo struct {
Type string `json:"__type"`
Message string

// nonstandard, but some AWS services do present the type here
Code any
}

func getProtocolErrorInfo(decoder *json.Decoder) (protocolErrorInfo, error) {
var errInfo protocolErrorInfo
if err := decoder.Decode(&errInfo); err != nil {
if err == io.EOF {
return errInfo, nil
}
return errInfo, err
}

return errInfo, nil
}

func resolveProtocolErrorType(headerType string, bodyInfo protocolErrorInfo) (string, bool) {
if len(headerType) != 0 {
return headerType, true
} else if len(bodyInfo.Type) != 0 {
return bodyInfo.Type, true
} else if code, ok := bodyInfo.Code.(string); ok && len(code) != 0 {
return code, true
}
return "", false
}

// sanitizeErrorCode strips namespace prefixes and URI suffixes from error
// codes received on the wire.
func sanitizeErrorCode(code string) string {
if idx := strings.Index(code, ":"); idx != -1 {
code = code[:idx]
}
if idx := strings.Index(code, "#"); idx != -1 {
code = code[idx+1:]
}
return code
}
7 changes: 7 additions & 0 deletions aws-protocols/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/aws/smithy-go/aws-protocols

go 1.24

require github.com/aws/smithy-go v1.24.0

replace github.com/aws/smithy-go => ../
Empty file added aws-protocols/go.sum
Empty file.
20 changes: 20 additions & 0 deletions aws-protocols/internal/json/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package json

import (
"github.com/aws/smithy-go"
)

// Codec is a JSON codec.
type Codec struct{}

var _ smithy.Codec = (*Codec)(nil)

// Serializer returns a JSON shape serializer.
func (c *Codec) Serializer() smithy.ShapeSerializer {
return NewShapeSerializer()
}

// Deserializer returns a JSON shape deserializer.
func (c *Codec) Deserializer(p []byte) smithy.ShapeDeserializer {
return NewShapeDeserializer(p)
}
Loading