package cmdpacks import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/pkg/errors" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" "maps" "strconv" "ucl.lmika.dev/ucl" ) type proxyInfo[T comparable] struct { fields map[string]func(t T) ucl.Object lenFunc func(t T) int strFunc func(t T) string } type SimpleProxy[T comparable] struct { value T proxyInfo *proxyInfo[T] } func (tp SimpleProxy[T]) ProxyValue() T { return tp.value } func (tp SimpleProxy[T]) String() string { if tp.proxyInfo.strFunc != nil { return tp.proxyInfo.strFunc(tp.value) } return fmt.Sprint(tp.value) } func (tp SimpleProxy[T]) Truthy() bool { var zeroT T return tp.value != zeroT } func (tp SimpleProxy[T]) Len() int { if tp.proxyInfo.lenFunc != nil { return tp.proxyInfo.lenFunc(tp.value) } return len(tp.proxyInfo.fields) } func (tp SimpleProxy[T]) Value(k string) ucl.Object { f, ok := tp.proxyInfo.fields[k] if !ok { return nil } return f(tp.value) } func (tp SimpleProxy[T]) Each(fn func(k string, v ucl.Object) error) error { for key := range maps.Keys(tp.proxyInfo.fields) { if err := fn(key, tp.Value(key)); err != nil { return err } } return nil } type simpleProxyList[T comparable] struct { values []T converter func(T) ucl.Object } func newSimpleProxyList[T comparable](values []T, converter func(T) ucl.Object) simpleProxyList[T] { return simpleProxyList[T]{values: values, converter: converter} } func (tp simpleProxyList[T]) String() string { return fmt.Sprint(tp.values) } func (tp simpleProxyList[T]) Truthy() bool { return len(tp.values) > 0 } func (tp simpleProxyList[T]) Len() int { return len(tp.values) } func (tp simpleProxyList[T]) Index(k int) ucl.Object { return tp.converter(tp.values[k]) } func newResultSetProxy(rs *models.ResultSet) ucl.Object { return SimpleProxy[*models.ResultSet]{value: rs, proxyInfo: resultSetProxyFields} } var resultSetProxyFields = &proxyInfo[*models.ResultSet]{ lenFunc: func(t *models.ResultSet) int { return len(t.Items()) }, strFunc: func(t *models.ResultSet) string { return fmt.Sprintf("ResultSet(%v:%d)", t.TableInfo.Name, len(t.Items())) }, fields: map[string]func(t *models.ResultSet) ucl.Object{ "Table": func(t *models.ResultSet) ucl.Object { return newTableProxy(t.TableInfo) }, "Items": func(t *models.ResultSet) ucl.Object { return resultSetItemsProxy{t} }, "HasNextPage": func(t *models.ResultSet) ucl.Object { return ucl.BoolObject(t.HasNextPage()) }, }, } func newTableProxy(table *models.TableInfo) ucl.Object { return SimpleProxy[*models.TableInfo]{value: table, proxyInfo: tableProxyFields} } var tableProxyFields = &proxyInfo[*models.TableInfo]{ strFunc: func(t *models.TableInfo) string { return fmt.Sprintf("Table(%v)", t.Name) }, fields: map[string]func(t *models.TableInfo) ucl.Object{ "Name": func(t *models.TableInfo) ucl.Object { return ucl.StringObject(t.Name) }, "Keys": func(t *models.TableInfo) ucl.Object { return newKeyAttributeProxy(t.Keys) }, "DefinedAttributes": func(t *models.TableInfo) ucl.Object { return ucl.StringListObject(t.DefinedAttributes) }, "GSIs": func(t *models.TableInfo) ucl.Object { return newSimpleProxyList(t.GSIs, newGSIProxy) }, }, } func newKeyAttributeProxy(keyAttrs models.KeyAttribute) ucl.Object { return SimpleProxy[models.KeyAttribute]{value: keyAttrs, proxyInfo: keyAttributeProxyFields} } var keyAttributeProxyFields = &proxyInfo[models.KeyAttribute]{ strFunc: func(t models.KeyAttribute) string { return fmt.Sprintf("KeyAttribute(%v,%v)", t.PartitionKey, t.SortKey) }, fields: map[string]func(t models.KeyAttribute) ucl.Object{ "PK": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.PartitionKey) }, "SK": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.SortKey) }, }, } func newGSIProxy(gsi models.TableGSI) ucl.Object { return SimpleProxy[models.TableGSI]{value: gsi, proxyInfo: gsiProxyFields} } var gsiProxyFields = &proxyInfo[models.TableGSI]{ strFunc: func(t models.TableGSI) string { return fmt.Sprintf("TableGSI(%v,(%v,%v))", t.Name, t.Keys.PartitionKey, t.Keys.SortKey) }, fields: map[string]func(t models.TableGSI) ucl.Object{ "Name": func(t models.TableGSI) ucl.Object { return ucl.StringObject(t.Name) }, "Keys": func(t models.TableGSI) ucl.Object { return newKeyAttributeProxy(t.Keys) }, }, } type resultSetItemsProxy struct { resultSet *models.ResultSet } func (ip resultSetItemsProxy) String() string { return "RSItem()" } func (ip resultSetItemsProxy) Truthy() bool { return len(ip.resultSet.Items()) > 0 } func (tp resultSetItemsProxy) Len() int { return len(tp.resultSet.Items()) } func (tp resultSetItemsProxy) Index(k int) ucl.Object { return itemProxy{resultSet: tp.resultSet, idx: k, item: tp.resultSet.Items()[k]} } type itemProxy struct { resultSet *models.ResultSet idx int item models.Item } func (ip itemProxy) String() string { return fmt.Sprintf("RSItems(%v)", len(ip.item)) } func (ip itemProxy) Truthy() bool { return len(ip.item) > 0 } func (tp itemProxy) Len() int { return len(tp.item) } func (tp itemProxy) Value(k string) ucl.Object { f, ok := tp.item[k] if !ok { return nil } return convertAttributeValueToUCLObject(f) } func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error { for key := range maps.Keys(tp.item) { if err := fn(key, tp.Value(key)); err != nil { return err } } return nil } type attributeValueProxy struct { value types.AttributeValue } func (ip attributeValueProxy) String() string { return "attributeValueProxy()" } func (ip attributeValueProxy) Truthy() bool { return ip.value != nil } func convertAttributeValueToUCLObject(attrValue types.AttributeValue) ucl.Object { switch t := attrValue.(type) { case *types.AttributeValueMemberS: return ucl.StringObject(t.Value) case *types.AttributeValueMemberN: i, err := strconv.ParseInt(t.Value, 10, 64) if err != nil { return nil } return ucl.IntObject(i) case *types.AttributeValueMemberBOOL: return ucl.BoolObject(t.Value) case *types.AttributeValueMemberL: vs := make(ucl.ListObject, len(t.Value)) for i, v := range t.Value { vs[i] = convertAttributeValueToUCLObject(v) } return &vs case *types.AttributeValueMemberM: hs := make(ucl.HashObject) for k, v := range t.Value { hs[k] = convertAttributeValueToUCLObject(v) } return hs } return attributeValueProxy{value: attrValue} } func mapUCLObjectToAttributeType(obj ucl.Object) (types.AttributeValue, error) { switch t := obj.(type) { case ucl.StringObject: return &types.AttributeValueMemberS{Value: t.String()}, nil case ucl.IntObject: return &types.AttributeValueMemberN{Value: t.String()}, nil case ucl.BoolObject: return &types.AttributeValueMemberBOOL{Value: t.Truthy()}, nil case ucl.Listable: vals := make([]types.AttributeValue, t.Len()) for i := 0; i < t.Len(); i++ { v, err := mapUCLObjectToAttributeType(t.Index(i)) if err != nil { return nil, err } vals[i] = v } return &types.AttributeValueMemberL{Value: vals}, nil case ucl.Hashable: vals := make(map[string]types.AttributeValue) if err := t.Each(func(k string, v ucl.Object) error { vv, err := mapUCLObjectToAttributeType(v) if err != nil { return err } vals[k] = vv return nil }); err != nil { return nil, err } return &types.AttributeValueMemberM{Value: vals}, nil case attributeValueProxy: return t.value, nil } return nil, errors.New("unsupported attribute type") }