github.com/blend/go-sdk@v1.20220411.3/vault/decompose_json.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package vault
     9  
    10  import (
    11  	"encoding/json"
    12  	"reflect"
    13  	"strings"
    14  
    15  	"github.com/blend/go-sdk/ex"
    16  	"github.com/blend/go-sdk/reflectutil"
    17  )
    18  
    19  // Constants
    20  const (
    21  	StructTag = "secret"
    22  )
    23  
    24  // IsZeroable is useful to test if we need to set a config field or not.
    25  type IsZeroable interface {
    26  	IsZero() bool
    27  }
    28  
    29  // DecomposeJSON decomposes an object into json fields marked with the `secret` struct tag.
    30  // Top level fields will get their own keys.
    31  // Nested objects are serialized as json.
    32  func DecomposeJSON(obj interface{}) (map[string]string, error) {
    33  	output := make(map[string]string)
    34  
    35  	objType := reflectutil.Type(obj)
    36  	objValue := reflectutil.Value(obj)
    37  
    38  	var field reflect.StructField
    39  	var fieldValue reflect.Value
    40  	var fieldValueValue interface{}
    41  	var tagValue string
    42  	var outputKey string
    43  	var tagPieces []string
    44  	var typed IsZeroable
    45  	var ok bool
    46  	for x := 0; x < objType.NumField(); x++ {
    47  		field = objType.Field(x)
    48  		if !reflectutil.IsExported(field.Name) {
    49  			continue
    50  		}
    51  
    52  		fieldValue = objValue.FieldByName(field.Name)
    53  		tagValue = field.Tag.Get(StructTag)
    54  		if tagValue == "" || tagValue == "-" {
    55  			continue
    56  		}
    57  		tagPieces = strings.Split(tagValue, ",")
    58  		outputKey = tagPieces[0]
    59  
    60  		fieldValueValue = fieldValue.Interface()
    61  		if typed, ok = fieldValueValue.(IsZeroable); ok && typed.IsZero() {
    62  			continue
    63  		}
    64  
    65  		// skip empty values
    66  		if reflectutil.IsEmptyValue(fieldValue) {
    67  			continue
    68  		}
    69  
    70  		contents, err := json.Marshal(fieldValueValue)
    71  		if err != nil {
    72  			return nil, ex.New(err)
    73  		}
    74  		output[outputKey] = string(contents)
    75  	}
    76  
    77  	return output, nil
    78  }
    79  
    80  // RestoreJSON restores an object from a given data bag as JSON.
    81  func RestoreJSON(data map[string]string, obj interface{}) error {
    82  	objType := reflectutil.Type(obj)
    83  	objValue := reflectutil.Value(obj)
    84  
    85  	fieldLookup := make(map[string]string)
    86  
    87  	var field reflect.StructField
    88  	var fieldValue reflect.Value
    89  	var tagValue string
    90  	var outputKey string
    91  	var tagPieces []string
    92  	for x := 0; x < objType.NumField(); x++ {
    93  		field = objType.Field(x)
    94  		if !reflectutil.IsExported(field.Name) {
    95  			continue
    96  		}
    97  
    98  		fieldValue = objValue.FieldByName(field.Name)
    99  		tagValue = field.Tag.Get(StructTag)
   100  		if tagValue == "" || tagValue == "-" {
   101  			continue
   102  		}
   103  		tagPieces = strings.Split(tagValue, ",")
   104  		outputKey = tagPieces[0]
   105  		fieldLookup[outputKey] = field.Name
   106  	}
   107  
   108  	var fieldName string
   109  	var ok bool
   110  	for key, value := range data {
   111  		// figure out which field matches the key ...
   112  		if fieldName, ok = fieldLookup[key]; !ok {
   113  			continue
   114  		}
   115  		fieldValue = objValue.FieldByName(fieldName)
   116  		if err := json.Unmarshal([]byte(value), fieldValue.Addr().Interface()); err != nil {
   117  			return ex.New(err)
   118  		}
   119  	}
   120  	return nil
   121  }