Files
2026-10-24 02:44:51 +00:00

295 lines
6.6 KiB
Go

package eventstream
import (
"fmt"
"math/big"
"time"
"github.com/aws/smithy-go"
"github.com/aws/smithy-go/document"
"github.com/aws/smithy-go/traits"
)
// ShapeDeserializer wraps a [smithy.ShapeDeserializer] to handle event stream
// message binding traits.
type ShapeDeserializer struct {
Message *Message
inner smithy.ShapeDeserializer
depth int
schema *smithy.Schema
bindings []*smithy.Schema
bindIdx int
inBindings bool
inBody bool
hasPayload bool
hasBody bool
}
var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil)
// NewShapeDeserializer returns a deserializer for a Message.
func NewShapeDeserializer(msg *Message, inner smithy.ShapeDeserializer) *ShapeDeserializer {
return &ShapeDeserializer{
Message: msg,
inner: inner,
}
}
func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error {
d.depth++
if d.depth > 1 {
return d.inner.ReadStruct(s)
}
d.schema = s
for _, m := range s.Members() {
if _, ok := smithy.SchemaTrait[*traits.EventPayload](m); ok {
d.hasPayload = true
}
if isEventBound(m) {
d.bindings = append(d.bindings, m)
} else {
d.hasBody = true
}
}
return nil
}
func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) {
if d.depth > 1 {
ms, err := d.inner.ReadStructMember()
if ms == nil {
d.depth--
}
return ms, err
}
// like httpbinding, throw back the bound stuff first before we drop into
// the body
for d.bindIdx < len(d.bindings) {
m := d.bindings[d.bindIdx]
d.bindIdx++
if isEventHeader(m) && d.Message.Headers.Get(m.MemberName()) == nil {
continue
}
d.inBindings = true
return m, nil
}
d.inBindings = false
if d.hasPayload {
d.depth--
return nil, nil
}
if !d.hasBody {
d.depth--
return nil, nil
}
if !d.inBody {
d.inBody = true
if err := d.inner.ReadStruct(d.schema); err != nil {
return nil, err
}
}
ms, err := d.inner.ReadStructMember()
if ms == nil {
d.depth--
}
return ms, err
}
func (d *ShapeDeserializer) ReadString(s *smithy.Schema, v *string) error {
if d.inBindings {
if isEventHeader(s) {
hv := d.Message.Headers.Get(s.MemberName())
if hv == nil {
return nil
}
sv, ok := hv.(StringValue)
if !ok {
return fmt.Errorf("event header %q: expected string, got %T", s.MemberName(), hv)
}
*v = string(sv)
return nil
}
if isEventPayload(s) {
*v = string(d.Message.Payload)
return nil
}
}
return d.inner.ReadString(s, v)
}
func (d *ShapeDeserializer) ReadBool(s *smithy.Schema, v *bool) error {
if d.inBindings && isEventHeader(s) {
hv := d.Message.Headers.Get(s.MemberName())
if hv == nil {
return nil
}
bv, ok := hv.(BoolValue)
if !ok {
return fmt.Errorf("event header %q: expected bool, got %T", s.MemberName(), hv)
}
*v = bool(bv)
return nil
}
return d.inner.ReadBool(s, v)
}
func (d *ShapeDeserializer) readHeaderInt64(name string) (int64, bool, error) {
hv := d.Message.Headers.Get(name)
if hv == nil {
return 0, false, nil
}
switch v := hv.(type) {
case Int8Value:
return int64(v), true, nil
case Int16Value:
return int64(v), true, nil
case Int32Value:
return int64(v), true, nil
case Int64Value:
return int64(v), true, nil
default:
return 0, false, fmt.Errorf("event header %q: expected integer, got %T", name, hv)
}
}
type intn interface {
int8 | int16 | int32 | int64
}
func readEventHeaderInt[T intn](d *ShapeDeserializer, s *smithy.Schema, v *T) error {
n, ok, err := d.readHeaderInt64(s.MemberName())
if err != nil || !ok {
return err
}
*v = T(n)
return nil
}
func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error {
if d.inBindings && isEventHeader(s) {
return readEventHeaderInt(d, s, v)
}
return d.inner.ReadInt8(s, v)
}
func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error {
if d.inBindings && isEventHeader(s) {
return readEventHeaderInt(d, s, v)
}
return d.inner.ReadInt16(s, v)
}
func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error {
if d.inBindings && isEventHeader(s) {
return readEventHeaderInt(d, s, v)
}
return d.inner.ReadInt32(s, v)
}
func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error {
if d.inBindings && isEventHeader(s) {
return readEventHeaderInt(d, s, v)
}
return d.inner.ReadInt64(s, v)
}
func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error {
return d.inner.ReadFloat32(s, v)
}
func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error {
return d.inner.ReadFloat64(s, v)
}
func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error {
if d.inBindings {
if isEventHeader(s) {
hv := d.Message.Headers.Get(s.MemberName())
if hv == nil {
return nil
}
bv, ok := hv.(BytesValue)
if !ok {
return fmt.Errorf("event header %q: expected bytes, got %T", s.MemberName(), hv)
}
*v = []byte(bv)
return nil
}
if isEventPayload(s) {
*v = d.Message.Payload
return nil
}
}
return d.inner.ReadBlob(s, v)
}
func (d *ShapeDeserializer) ReadTime(s *smithy.Schema, v *time.Time) error {
if d.inBindings && isEventHeader(s) {
hv := d.Message.Headers.Get(s.MemberName())
if hv == nil {
return nil
}
tv, ok := hv.(TimestampValue)
if !ok {
return fmt.Errorf("event header %q: expected timestamp, got %T", s.MemberName(), hv)
}
*v = time.Time(tv)
return nil
}
return d.inner.ReadTime(s, v)
}
func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error {
return d.inner.ReadList(s)
}
func (d *ShapeDeserializer) ReadListItem(s *smithy.Schema) (bool, error) {
return d.inner.ReadListItem(s)
}
func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error {
return d.inner.ReadMap(s)
}
func (d *ShapeDeserializer) ReadMapKey(s *smithy.Schema) (string, bool, error) {
return d.inner.ReadMapKey(s)
}
func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) {
return d.inner.ReadUnion(s)
}
func (d *ShapeDeserializer) ReadNil(s *smithy.Schema) (bool, error) {
return d.inner.ReadNil(s)
}
func (d *ShapeDeserializer) ReadDocument(s *smithy.Schema, v *document.Value) error {
return d.inner.ReadDocument(s, v)
}
func isEventBound(schema *smithy.Schema) bool {
_, h := smithy.SchemaTrait[*traits.EventHeader](schema)
_, p := smithy.SchemaTrait[*traits.EventPayload](schema)
return h || p
}
// ReadBigInt is unimplemented and will return an error.
func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error {
return fmt.Errorf("unimplemented")
}
// ReadBigFloat is unimplemented and will return an error.
func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error {
return fmt.Errorf("unimplemented")
}