dynamo-browse/internal/common/ui/commandctrl/cmdpacks/proxy.go

281 lines
7.5 KiB
Go
Raw Normal View History

2025-05-17 01:11:04 +00:00
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"
)
2025-05-17 01:11:04 +00:00
2025-05-19 12:14:22 +00:00
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]
}
2025-05-19 12:14:22 +00:00
func (tp SimpleProxy[T]) ProxyValue() T {
return tp.value
}
2025-05-19 12:14:22 +00:00
func (tp SimpleProxy[T]) String() string {
if tp.proxyInfo.strFunc != nil {
return tp.proxyInfo.strFunc(tp.value)
}
return fmt.Sprint(tp.value)
}
2025-05-19 12:14:22 +00:00
func (tp SimpleProxy[T]) Truthy() bool {
var zeroT T
return tp.value != zeroT
}
2025-05-19 12:14:22 +00:00
func (tp SimpleProxy[T]) Len() int {
if tp.proxyInfo.lenFunc != nil {
return tp.proxyInfo.lenFunc(tp.value)
}
return len(tp.proxyInfo.fields)
}
2025-05-19 12:14:22 +00:00
func (tp SimpleProxy[T]) Value(k string) ucl.Object {
f, ok := tp.proxyInfo.fields[k]
if !ok {
return nil
}
return f(tp.value)
}
2025-05-19 12:14:22 +00:00
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 {
2025-05-19 12:14:22 +00:00
return SimpleProxy[*models.ResultSet]{value: rs, proxyInfo: resultSetProxyFields}
}
2025-05-19 12:14:22 +00:00
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 {
2025-05-19 12:14:22 +00:00
return SimpleProxy[*models.TableInfo]{value: table, proxyInfo: tableProxyFields}
}
2025-05-19 12:14:22 +00:00
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 {
2025-05-19 12:14:22 +00:00
return SimpleProxy[models.KeyAttribute]{value: keyAttrs, proxyInfo: keyAttributeProxyFields}
}
2025-05-19 12:14:22 +00:00
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) },
2025-05-19 12:14:22 +00:00
},
}
func newGSIProxy(gsi models.TableGSI) ucl.Object {
2025-05-19 12:14:22 +00:00
return SimpleProxy[models.TableGSI]{value: gsi, proxyInfo: gsiProxyFields}
}
2025-05-19 12:14:22 +00:00
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 {
2025-05-19 12:14:22 +00:00
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 {
2025-05-19 12:14:22 +00:00
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")
2025-05-17 01:11:04 +00:00
}