github.com/ipld/go-ipld-prime@v0.21.0/fluent/reflect.go (about)

     1  package fluent
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  
     8  	"github.com/ipld/go-ipld-prime/datamodel"
     9  )
    10  
    11  // Reflect creates a new Node by looking at a golang value with reflection
    12  // and converting it into IPLD Data Model.
    13  // This is a quick-and-dirty way to get data into the IPLD Data Model;
    14  // it's useful for rapid prototyping and demos,
    15  // but note that this feature is not intended to be suitable for "production" use
    16  // due to low performance and lack of configurability.
    17  //
    18  // The concrete type of the returned Node is determined by
    19  // the NodePrototype argument provided by the caller.
    20  //
    21  // No type information from the golang value will be observable in the result.
    22  //
    23  // The reflection will walk over any golang value, but is not configurable.
    24  // Golang maps become IPLD maps; golang slices and arrays become IPLD lists;
    25  // and golang structs become IPLD maps too.
    26  // When converting golang structs to IPLD maps, the field names will become the map keys.
    27  // Pointers and interfaces will be traversed transparently and are not visible in the output.
    28  //
    29  // An error will be returned if the process of assembling the Node returns any errors
    30  // (for example, if the NodePrototype is for a schema-constrained Node,
    31  // any validation errors from the schema will cause errors to be returned).
    32  //
    33  // A panic will be raised if there is any difficulty examining the golang value via reflection
    34  // (for example, if the value is a struct with unexported fields,
    35  // or if a non-data type like a channel or function is encountered).
    36  //
    37  // Some configuration (in particular, what to do about map ordering) is available via the Reflector struct.
    38  // That structure has a method of the same name and signiture as this one on it.
    39  // (This function is a shortcut for calling that method on a Reflector struct with default configuration.)
    40  //
    41  // Performance remarks: performance of this function will generally be poor.
    42  // In general, creating data in golang types and then *flipping* it to IPLD form
    43  // involves handling the data at least twice, and so will always be slower
    44  // than just creating the same data in IPLD form programmatically directly.
    45  // In particular, reflection is generally not fast, and this feature has
    46  // not been optimized for either speed nor allocation avoidance.
    47  // Other features in the fluent package will typically out-perform this,
    48  // and using NodeAssemblers directly (without any fluent tools) will be much faster.
    49  // Only use this function if performance is not of consequence.
    50  func Reflect(np datamodel.NodePrototype, i interface{}) (datamodel.Node, error) {
    51  	return defaultReflector.Reflect(np, i)
    52  }
    53  
    54  // MustReflect is a shortcut for Reflect but panics on any error.
    55  // It is useful if you need a single return value for function composition purposes.
    56  func MustReflect(np datamodel.NodePrototype, i interface{}) datamodel.Node {
    57  	n, err := Reflect(np, i)
    58  	if err != nil {
    59  		panic(err)
    60  	}
    61  	return n
    62  }
    63  
    64  // ReflectIntoAssembler is similar to Reflect, but takes a NodeAssembler parameter
    65  // instead of a Node Prototype.
    66  // This may be useful if you need more direct control over allocations,
    67  // or want to fill in only part of a larger node assembly process using the reflect tool.
    68  // Data is accumulated by the NodeAssembler parameter, so no Node is returned.
    69  func ReflectIntoAssembler(na datamodel.NodeAssembler, i interface{}) error {
    70  	return defaultReflector.ReflectIntoAssembler(na, i)
    71  }
    72  
    73  var defaultReflector = Reflector{
    74  	MapOrder: func(x, y string) bool {
    75  		return x < y
    76  	},
    77  }
    78  
    79  // Reflector allows configuration of the Reflect family of functions
    80  // (`Reflect`, `ReflectIntoAssembler`, etc).
    81  type Reflector struct {
    82  	// MapOrder is used to decide a deterministic order for inserting entries to maps.
    83  	// (This is used when converting golang maps, since their iteration order is randomized;
    84  	// it is not used when converting other types such as structs, since those have a stable order.)
    85  	// MapOrder should return x < y in the same way as sort.Interface.Less.
    86  	//
    87  	// If using a default Reflector (e.g. via the package-scope functions),
    88  	// this function is a simple natural golang string sort: it performs `x < y` on the strings.
    89  	MapOrder func(x, y string) bool
    90  }
    91  
    92  // Reflect is as per the package-scope function of the same name and signature,
    93  // but using the configuration in the Reflector struct.
    94  // See the package-scope function for documentation.
    95  func (rcfg Reflector) Reflect(np datamodel.NodePrototype, i interface{}) (datamodel.Node, error) {
    96  	nb := np.NewBuilder()
    97  	if err := rcfg.ReflectIntoAssembler(nb, i); err != nil {
    98  		return nil, err
    99  	}
   100  	return nb.Build(), nil
   101  }
   102  
   103  // ReflectIntoAssembler is as per the package-scope function of the same name and signature,
   104  // but using the configuration in the Reflector struct.
   105  // See the package-scope function for documentation.
   106  func (rcfg Reflector) ReflectIntoAssembler(na datamodel.NodeAssembler, i interface{}) error {
   107  	// Cover the most common values with a type-switch, as it's faster than reflection.
   108  	switch x := i.(type) {
   109  	case map[string]string:
   110  		keys := make([]string, 0, len(x))
   111  		for k := range x {
   112  			keys = append(keys, k)
   113  		}
   114  		sort.Sort(sortableStrings{keys, rcfg.MapOrder})
   115  		ma, err := na.BeginMap(int64(len(x)))
   116  		if err != nil {
   117  			return err
   118  		}
   119  		for _, k := range keys {
   120  			va, err := ma.AssembleEntry(k)
   121  			if err != nil {
   122  				return err
   123  			}
   124  			if err := va.AssignString(x[k]); err != nil {
   125  				return err
   126  			}
   127  		}
   128  		return ma.Finish()
   129  	case map[string]interface{}:
   130  		keys := make([]string, 0, len(x))
   131  		for k := range x {
   132  			keys = append(keys, k)
   133  		}
   134  		sort.Sort(sortableStrings{keys, rcfg.MapOrder})
   135  		ma, err := na.BeginMap(int64(len(x)))
   136  		if err != nil {
   137  			return err
   138  		}
   139  		for _, k := range keys {
   140  			va, err := ma.AssembleEntry(k)
   141  			if err != nil {
   142  				return err
   143  			}
   144  			if err := rcfg.ReflectIntoAssembler(va, x[k]); err != nil {
   145  				return err
   146  			}
   147  		}
   148  		return ma.Finish()
   149  	case []string:
   150  		la, err := na.BeginList(int64(len(x)))
   151  		if err != nil {
   152  			return err
   153  		}
   154  		for _, v := range x {
   155  			if err := la.AssembleValue().AssignString(v); err != nil {
   156  				return err
   157  			}
   158  		}
   159  		return la.Finish()
   160  	case []interface{}:
   161  		la, err := na.BeginList(int64(len(x)))
   162  		if err != nil {
   163  			return err
   164  		}
   165  		for _, v := range x {
   166  			if err := rcfg.ReflectIntoAssembler(la.AssembleValue(), v); err != nil {
   167  				return err
   168  			}
   169  		}
   170  		return la.Finish()
   171  	case string:
   172  		return na.AssignString(x)
   173  	case []byte:
   174  		return na.AssignBytes(x)
   175  	case int64:
   176  		return na.AssignInt(x)
   177  	case nil:
   178  		return na.AssignNull()
   179  	}
   180  	// That didn't fly?  Reflection time.
   181  	rv := reflect.ValueOf(i)
   182  	switch rv.Kind() {
   183  	case reflect.Bool:
   184  		return na.AssignBool(rv.Bool())
   185  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   186  		return na.AssignInt(rv.Int())
   187  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   188  		return na.AssignInt(int64(rv.Uint())) // TODO: check overflow
   189  	case reflect.Float32, reflect.Float64:
   190  		return na.AssignFloat(rv.Float())
   191  	case reflect.String:
   192  		return na.AssignString(rv.String())
   193  	case reflect.Slice, reflect.Array:
   194  		if rv.Type().Elem().Kind() == reflect.Uint8 { // byte slices are a special case
   195  			return na.AssignBytes(rv.Bytes())
   196  		}
   197  		l := rv.Len()
   198  		la, err := na.BeginList(int64(l))
   199  		if err != nil {
   200  			return err
   201  		}
   202  		for i := 0; i < l; i++ {
   203  			if err := rcfg.ReflectIntoAssembler(la.AssembleValue(), rv.Index(i).Interface()); err != nil {
   204  				return err
   205  			}
   206  		}
   207  		return la.Finish()
   208  	case reflect.Map:
   209  		// the keys slice for sorting keeps things in reflect.Value form, because unboxing is cheap,
   210  		//  but re-boxing is not cheap, and the MapIndex method requires reflect.Value again later.
   211  		keys := make([]reflect.Value, 0, rv.Len())
   212  		itr := rv.MapRange()
   213  		for itr.Next() {
   214  			k := itr.Key()
   215  			if k.Kind() != reflect.String {
   216  				return fmt.Errorf("cannot convert a map with non-string keys (%T)", i)
   217  			}
   218  			keys = append(keys, k)
   219  		}
   220  		sort.Sort(sortableReflectStrings{keys, rcfg.MapOrder})
   221  		ma, err := na.BeginMap(int64(rv.Len()))
   222  		if err != nil {
   223  			return err
   224  		}
   225  		for _, k := range keys {
   226  			va, err := ma.AssembleEntry(k.String())
   227  			if err != nil {
   228  				return err
   229  			}
   230  			if err := rcfg.ReflectIntoAssembler(va, rv.MapIndex(k).Interface()); err != nil {
   231  				return err
   232  			}
   233  		}
   234  		return ma.Finish()
   235  	case reflect.Struct:
   236  		l := rv.NumField()
   237  		ma, err := na.BeginMap(int64(l))
   238  		if err != nil {
   239  			return err
   240  		}
   241  		for i := 0; i < l; i++ {
   242  			fn := rv.Type().Field(i).Name
   243  			fv := rv.Field(i)
   244  			va, err := ma.AssembleEntry(fn)
   245  			if err != nil {
   246  				return err
   247  			}
   248  			if err := rcfg.ReflectIntoAssembler(va, fv.Interface()); err != nil {
   249  				return err
   250  			}
   251  		}
   252  		return ma.Finish()
   253  	case reflect.Ptr:
   254  		if rv.IsNil() {
   255  			return na.AssignNull()
   256  		}
   257  		return rcfg.ReflectIntoAssembler(na, rv.Elem())
   258  	case reflect.Interface:
   259  		return rcfg.ReflectIntoAssembler(na, rv.Elem())
   260  	}
   261  	// Some kints of values -- like Uintptr, Complex64/128, Channels, etc -- are not supported by this function.
   262  	return fmt.Errorf("fluent.Reflect: unsure how to handle type %T (kind: %v)", i, rv.Kind())
   263  }
   264  
   265  type sortableStrings struct {
   266  	a    []string
   267  	less func(x, y string) bool
   268  }
   269  
   270  func (a sortableStrings) Len() int           { return len(a.a) }
   271  func (a sortableStrings) Swap(i, j int)      { a.a[i], a.a[j] = a.a[j], a.a[i] }
   272  func (a sortableStrings) Less(i, j int) bool { return a.less(a.a[i], a.a[j]) }
   273  
   274  type sortableReflectStrings struct {
   275  	a    []reflect.Value
   276  	less func(x, y string) bool
   277  }
   278  
   279  func (a sortableReflectStrings) Len() int           { return len(a.a) }
   280  func (a sortableReflectStrings) Swap(i, j int)      { a.a[i], a.a[j] = a.a[j], a.a[i] }
   281  func (a sortableReflectStrings) Less(i, j int) bool { return a.less(a.a[i].String(), a.a[j].String()) }