internal/kernel/conversions.go (269 lines of code) (raw):
package kernel
import (
"fmt"
"reflect"
"time"
"github.com/aws/jsii-runtime-go/internal/api"
)
var (
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
)
// CastAndSetToPtr accepts a pointer to any type and attempts to cast the value
// argument to be the same type. Then it sets the value of the pointer element
// to be the newly cast data. This is used to cast payloads from JSII to
// expected return types for Get and Invoke functions.
func (c *Client) CastAndSetToPtr(ptr interface{}, data interface{}) {
ptrVal := reflect.ValueOf(ptr).Elem()
dataVal := reflect.ValueOf(data)
c.castAndSetToPtr(ptrVal, dataVal)
}
// castAndSetToPtr is the same as CastAndSetToPtr except it operates on the
// reflect.Value representation of the pointer and data.
func (c *Client) castAndSetToPtr(ptr reflect.Value, data reflect.Value) {
if !data.IsValid() {
// data will not be valid if was made from a nil value, as there would
// not have been enough type information available to build a valid
// reflect.Value. In such cases, we must craft the correctly-typed zero
// value ourselves.
data = reflect.Zero(ptr.Type())
} else if ptr.Kind() == reflect.Ptr && ptr.IsNil() {
// if ptr is a Pointer type and data is valid, initialize a non-nil pointer
// type. Otherwise inner value is not-settable upon recursion. See third
// law of reflection.
// https://blog.golang.org/laws-of-reflection
ptr.Set(reflect.New(ptr.Type().Elem()))
c.castAndSetToPtr(ptr.Elem(), data)
return
} else if data.Kind() == reflect.Interface && !data.IsNil() {
// If data is a non-nil interface, unwrap it to get it's dynamic value
// type sorted out, so that further calls in this method don't have to
// worry about this edge-case when reasoning on kinds.
data = reflect.ValueOf(data.Interface())
}
if ref, isRef := castValToRef(data); isRef {
// If return data is a jsii struct passed by reference, de-reference it all.
if fields, _, isStruct := c.Types().StructFields(ptr.Type()); isStruct {
for _, field := range fields {
got, err := c.Get(GetProps{
Property: field.Tag.Get("json"),
ObjRef: ref,
})
if err != nil {
panic(err)
}
fieldVal := ptr.FieldByIndex(field.Index)
c.castAndSetToPtr(fieldVal, reflect.ValueOf(got.Value))
}
return
}
targetType := ptr.Type()
if typ, ok := c.Types().FindType(ref.TypeFQN()); ok && typ.AssignableTo(ptr.Type()) {
// Specialize the return type to be the dynamic value type
targetType = typ
}
// If it's currently tracked, return the current instance
if object, ok := c.objects.GetObjectAs(ref.InstanceID, targetType); ok {
ptr.Set(object)
return
}
// If return data is jsii object references, add to objects table.
if err := c.Types().InitJsiiProxy(ptr, targetType); err == nil {
if err = c.RegisterInstance(ptr, ref); err != nil {
panic(err)
}
} else {
panic(err)
}
return
}
if enumref, isEnum := castValToEnumRef(data); isEnum {
member, err := c.Types().EnumMemberForEnumRef(enumref)
if err != nil {
panic(err)
}
ptr.Set(reflect.ValueOf(member))
return
}
if date, isDate := castValToDate(data); isDate {
ptr.Set(reflect.ValueOf(date))
return
}
// maps
if m, isMap := c.castValToMap(data, ptr.Type()); isMap {
ptr.Set(m)
return
}
// arrays
if data.Kind() == reflect.Slice {
len := data.Len()
var slice reflect.Value
if ptr.Kind() == reflect.Slice {
slice = reflect.MakeSlice(ptr.Type(), len, len)
} else {
slice = reflect.MakeSlice(reflect.SliceOf(anyType), len, len)
}
// If return type is a slice, recursively cast elements
for i := 0; i < len; i++ {
c.castAndSetToPtr(slice.Index(i), data.Index(i))
}
ptr.Set(slice)
return
}
ptr.Set(data)
}
// Accepts pointers to structs that implement interfaces and searches for an
// existing object reference in the kernel. If it exists, it casts it to an
// objref for the runtime. Recursively casts types that may contain nested
// object references.
func (c *Client) CastPtrToRef(dataVal reflect.Value) interface{} {
if !dataVal.IsValid() {
// dataVal is a 0-value, meaning we have no value available... We return
// this to JavaScript as a "null" value.
return nil
}
if (dataVal.Kind() == reflect.Interface || dataVal.Kind() == reflect.Ptr) && dataVal.IsNil() {
return nil
}
// In case we got a time.Time value (or pointer to one).
if wireDate, isDate := castPtrToDate(dataVal); isDate {
return wireDate
}
switch dataVal.Kind() {
case reflect.Map:
result := api.WireMap{MapData: make(map[string]interface{})}
iter := dataVal.MapRange()
for iter.Next() {
key := iter.Key().String()
val := iter.Value()
result.MapData[key] = c.CastPtrToRef(val)
}
return result
case reflect.Interface, reflect.Ptr:
if valref, valHasRef := c.FindObjectRef(dataVal); valHasRef {
return valref
}
// In case we got a pointer to a map, slice, enum, ...
if elem := reflect.Indirect(dataVal.Elem()); elem.Kind() != reflect.Struct {
return c.CastPtrToRef(elem)
}
if dataVal.Elem().Kind() == reflect.Struct {
elemVal := dataVal.Elem()
if fields, fqn, isStruct := c.Types().StructFields(elemVal.Type()); isStruct {
data := make(map[string]interface{})
for _, field := range fields {
fieldVal := elemVal.FieldByIndex(field.Index)
if (fieldVal.Kind() == reflect.Ptr || fieldVal.Kind() == reflect.Interface) && fieldVal.IsNil() {
// If there is the "field" tag, and it's "required", then panic since the value is nil.
if requiredOrOptional, found := field.Tag.Lookup("field"); found && requiredOrOptional == "required" {
panic(fmt.Sprintf("Field %v.%v is required, but has nil value", field.Type, field.Name))
}
continue
}
key := field.Tag.Get("json")
data[key] = c.CastPtrToRef(fieldVal)
}
return api.WireStruct{
StructDescriptor: api.StructDescriptor{
FQN: fqn,
Fields: data,
},
}
}
} else if dataVal.Elem().Kind() == reflect.Ptr {
// Typically happens when a struct pointer is passed into an interface{}
// typed API (such as a place where a union is accepted).
elemVal := dataVal.Elem()
return c.CastPtrToRef(elemVal)
}
if ref, err := c.ManageObject(dataVal); err != nil {
panic(err)
} else {
return ref
}
case reflect.Slice:
refs := make([]interface{}, dataVal.Len())
for i := 0; i < dataVal.Len(); i++ {
refs[i] = c.CastPtrToRef(dataVal.Index(i))
}
return refs
case reflect.String:
if enumRef, isEnumRef := c.Types().TryRenderEnumRef(dataVal); isEnumRef {
return enumRef
}
}
return dataVal.Interface()
}
// castPtrToDate obtains an api.WireDate from the provided reflect.Value if it
// represents a time.Time or *time.Time value. It accepts both a pointer and
// direct value as a convenience (when passing time.Time through an interface{}
// parameter, having to unwrap it as a pointer is annoying and unneeded).
func castPtrToDate(data reflect.Value) (wireDate api.WireDate, ok bool) {
var timestamp *time.Time
if timestamp, ok = data.Interface().(*time.Time); !ok {
var val time.Time
if val, ok = data.Interface().(time.Time); ok {
timestamp = &val
}
}
if ok {
wireDate.Timestamp = timestamp.Format(time.RFC3339Nano)
}
return
}
func castValToRef(data reflect.Value) (ref api.ObjectRef, ok bool) {
if data.Kind() == reflect.Map {
for _, k := range data.MapKeys() {
// Finding values type requires extracting from reflect.Value
// otherwise .Kind() returns `interface{}`
v := reflect.ValueOf(data.MapIndex(k).Interface())
if k.Kind() != reflect.String {
continue
}
switch k.String() {
case "$jsii.byref":
if v.Kind() != reflect.String {
ok = false
return
}
ref.InstanceID = v.String()
ok = true
case "$jsii.interfaces":
if v.Kind() != reflect.Slice {
continue
}
ifaces := make([]api.FQN, v.Len())
for i := 0; i < v.Len(); i++ {
e := reflect.ValueOf(v.Index(i).Interface())
if e.Kind() != reflect.String {
ok = false
return
}
ifaces[i] = api.FQN(e.String())
}
ref.Interfaces = ifaces
}
}
}
return ref, ok
}
// TODO: This should return a time.Time instead
func castValToDate(data reflect.Value) (date time.Time, ok bool) {
if data.Kind() == reflect.Map {
for _, k := range data.MapKeys() {
v := reflect.ValueOf(data.MapIndex(k).Interface())
if k.Kind() == reflect.String && k.String() == "$jsii.date" && v.Kind() == reflect.String {
var err error
date, err = time.Parse(time.RFC3339Nano, v.String())
ok = (err == nil)
break
}
}
}
return
}
func castValToEnumRef(data reflect.Value) (enum api.EnumRef, ok bool) {
ok = false
if data.Kind() == reflect.Map {
for _, k := range data.MapKeys() {
// Finding values type requires extracting from reflect.Value
// otherwise .Kind() returns `interface{}`
v := reflect.ValueOf(data.MapIndex(k).Interface())
if k.Kind() == reflect.String && k.String() == "$jsii.enum" && v.Kind() == reflect.String {
enum.MemberFQN = v.String()
ok = true
break
}
}
}
return
}
// castValToMap attempts converting the provided jsii wire value to a
// go map. This recognizes the "$jsii.map" object and does the necessary
// recursive value conversion.
func (c *Client) castValToMap(data reflect.Value, mapType reflect.Type) (m reflect.Value, ok bool) {
ok = false
if data.Kind() != reflect.Map || data.Type().Key().Kind() != reflect.String {
return
}
if mapType.Kind() == reflect.Map && mapType.Key().Kind() != reflect.String {
return
}
if mapType == anyType {
mapType = reflect.TypeOf((map[string]interface{})(nil))
}
dataIter := data.MapRange()
for dataIter.Next() {
key := dataIter.Key().String()
if key != "$jsii.map" {
continue
}
// Finding value type requries extracting from reflect.Value
// otherwise .Kind() returns `interface{}`
val := reflect.ValueOf(dataIter.Value().Interface())
if val.Kind() != reflect.Map {
return
}
ok = true
m = reflect.MakeMap(mapType)
iter := val.MapRange()
for iter.Next() {
val := iter.Value()
// Note: reflect.New(t) returns a pointer to a newly allocated t
convertedVal := reflect.New(mapType.Elem()).Elem()
c.castAndSetToPtr(convertedVal, val)
m.SetMapIndex(iter.Key(), convertedVal)
}
return
}
return
}