github.com/s7techlab/cckit@v0.10.5/state/mapping/state_mapping_opt.go (about)

     1  package mapping
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/golang/protobuf/proto"
    10  	"github.com/golang/protobuf/ptypes"
    11  	"github.com/golang/protobuf/ptypes/timestamp"
    12  
    13  	"github.com/s7techlab/cckit/state"
    14  )
    15  
    16  const (
    17  	TimestampKeyLayout = `2006-01-02`
    18  )
    19  
    20  // WithNamespace sets namespace for mapping
    21  func WithNamespace(namespace state.Key) StateMappingOpt {
    22  	return func(sm *StateMapping, smm StateMappings) {
    23  		sm.namespace = namespace
    24  	}
    25  }
    26  
    27  // WithConstPKey set static key for all instances of mapped entry
    28  func WithConstPKey(keys ...state.Key) StateMappingOpt {
    29  	return func(sm *StateMapping, smm StateMappings) {
    30  		key := state.Key{}
    31  		for _, k := range keys {
    32  			key = key.Append(k)
    33  		}
    34  
    35  		sm.primaryKeyer = func(_ interface{}) (state.Key, error) {
    36  			return key, nil
    37  		}
    38  	}
    39  }
    40  
    41  func KeyerFor(schema interface{}) StateMappingOpt {
    42  	return func(sm *StateMapping, smm StateMappings) {
    43  		sm.keyerForSchema = schema
    44  	}
    45  }
    46  
    47  // List defined list container, it must have `Items` attr
    48  func List(list proto.Message) StateMappingOpt {
    49  	return func(sm *StateMapping, smm StateMappings) {
    50  		sm.list = list
    51  	}
    52  }
    53  
    54  // UniqKey defined uniq key in entity
    55  func UniqKey(name string, fields ...[]string) StateMappingOpt {
    56  	var ff []string
    57  	if len(fields) > 0 {
    58  		ff = fields[0]
    59  	}
    60  	return WithIndex(&StateIndexDef{
    61  		Name:     name,
    62  		Fields:   ff,
    63  		Required: true,
    64  		Multi:    false,
    65  	})
    66  }
    67  
    68  func WithIndex(idx *StateIndexDef) StateMappingOpt {
    69  	return func(sm *StateMapping, smm StateMappings) {
    70  		if idx.Name == `` {
    71  			return
    72  		}
    73  
    74  		var keyer InstanceMultiKeyer
    75  		if idx.Keyer != nil {
    76  			keyer = idx.Keyer
    77  		} else {
    78  			aa := []string{idx.Name}
    79  			if len(idx.Fields) > 0 {
    80  				aa = idx.Fields
    81  			}
    82  
    83  			// multiple external ids refers to one entry
    84  			if idx.Multi {
    85  				keyer = attrMultiKeyer(aa[0])
    86  			} else {
    87  				keyer = keyerAsMulti(attrsKeyer(aa))
    88  			}
    89  		}
    90  
    91  		_ = sm.AddIndex(&StateIndex{
    92  			Name:     idx.Name,
    93  			Uniq:     true,
    94  			Required: idx.Required,
    95  			Keyer:    keyer,
    96  		})
    97  	}
    98  }
    99  
   100  // PKeySchema registers all fields from pkeySchema as part of primary key.
   101  // Same fields should exists in mapped entity.
   102  // Also register keyer for pkeySchema with with namespace from current schema.
   103  func PKeySchema(pkeySchema interface{}) StateMappingOpt {
   104  	attrs := attrsFrom(pkeySchema)
   105  
   106  	return func(sm *StateMapping, smm StateMappings) {
   107  		sm.primaryKeyer = attrsKeyer(attrs)
   108  
   109  		// inherit namespace from "parent" mapping
   110  		namespace := sm.namespace
   111  		if len(namespace) == 0 {
   112  			namespace = sm.DefaultNamespace()
   113  		}
   114  
   115  		//add mapping for schema identifier
   116  		smm.Add(
   117  			pkeySchema,
   118  			WithNamespace(namespace),
   119  			PKeyAttr(attrs...),
   120  			KeyerFor(sm.schema))
   121  	}
   122  }
   123  
   124  func PKeyAttr(attrs ...string) StateMappingOpt {
   125  	return func(sm *StateMapping, smm StateMappings) {
   126  		sm.primaryKeyer = attrsKeyer(attrs)
   127  	}
   128  }
   129  
   130  // PKeyId use Id attr as source for mapped state entry key
   131  func PKeyId() StateMappingOpt {
   132  	return PKeyAttr(`Id`)
   133  }
   134  
   135  // PKeyComplexId sets Id as key field, also adds mapping for pkeySchema
   136  // with namespace from mapping schema
   137  func PKeyComplexId(pkeySchema interface{}) StateMappingOpt {
   138  	return func(sm *StateMapping, smm StateMappings) {
   139  		sm.primaryKeyer = attrsKeyer([]string{`Id`})
   140  		smm.Add(pkeySchema,
   141  			WithNamespace(SchemaNamespace(sm.schema)),
   142  			PKeyAttr(attrsFrom(pkeySchema)...),
   143  			KeyerFor(sm.schema))
   144  	}
   145  }
   146  
   147  func PKeyer(pkeyer InstanceKeyer) StateMappingOpt {
   148  	return func(sm *StateMapping, smm StateMappings) {
   149  		sm.primaryKeyer = pkeyer
   150  	}
   151  }
   152  
   153  func skipField(name string, field reflect.Value) bool {
   154  	if strings.HasPrefix(name, `XXX_`) || !field.CanSet() {
   155  		return true
   156  	}
   157  	return false
   158  }
   159  
   160  // attrFrom extracts list of field names from struct
   161  func attrsFrom(schema interface{}) (attrs []string) {
   162  	// fields from schema
   163  	s := reflect.ValueOf(schema).Elem()
   164  	fs := s.Type()
   165  	for i := 0; i < s.NumField(); i++ {
   166  		field := s.Field(i)
   167  		if skipField(fs.Field(i).Name, field) {
   168  			continue
   169  		}
   170  		attrs = append(attrs, fs.Field(i).Name)
   171  	}
   172  	return
   173  }
   174  
   175  // attrsKeyer creates instance keyer
   176  func attrsKeyer(attrs []string) InstanceKeyer {
   177  	return func(instance interface{}) (state.Key, error) {
   178  		var key = state.Key{}
   179  		inst := reflect.Indirect(reflect.ValueOf(instance))
   180  
   181  		for _, attr := range attrs {
   182  
   183  			v := inst.FieldByName(attr)
   184  			if !v.IsValid() {
   185  				return nil, fmt.Errorf(`%s: %s`, ErrFieldNotExists, attr)
   186  			}
   187  
   188  			keyPart, err := keyFromValue(v)
   189  			if err != nil {
   190  				return nil, fmt.Errorf(`key from field %s.%s: %s`, mapKey(instance), attr, err)
   191  			}
   192  			key = key.Append(keyPart)
   193  		}
   194  		return key, nil
   195  	}
   196  }
   197  
   198  // attrMultiKeyer creates keyer based of one field and can return multiple keyss
   199  func attrMultiKeyer(attr string) InstanceMultiKeyer {
   200  	return func(instance interface{}) ([]state.Key, error) {
   201  		inst := reflect.Indirect(reflect.ValueOf(instance))
   202  
   203  		v := inst.FieldByName(attr)
   204  		if !v.IsValid() {
   205  			return nil, fmt.Errorf(`%s: %s`, ErrFieldNotExists, attr)
   206  		}
   207  
   208  		return keysFromValue(v)
   209  	}
   210  }
   211  
   212  // keyerAsMulti adapter keyer to multiKeyer
   213  func keyerAsMulti(keyer InstanceKeyer) InstanceMultiKeyer {
   214  	return func(instance interface{}) (key []state.Key, err error) {
   215  		k, err := keyer(instance)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  
   220  		return []state.Key{k}, nil
   221  	}
   222  }
   223  
   224  // multi - returns multiple key if value type allows it
   225  func keysFromValue(v reflect.Value) ([]state.Key, error) {
   226  	var keys []state.Key
   227  
   228  	switch v.Type().String() {
   229  	case `[]string`:
   230  		for i := 0; i < v.Len(); i++ {
   231  			keys = append(keys, state.Key{v.Index(i).String()})
   232  		}
   233  
   234  	default:
   235  		return nil, ErrFieldTypeNotSupportedForKeyExtraction
   236  	}
   237  
   238  	return keys, nil
   239  }
   240  
   241  // keyFromValue creates string representation of value for state key
   242  func keyFromValue(v reflect.Value) (state.Key, error) {
   243  	switch v.Kind() {
   244  
   245  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   246  		return state.Key{strconv.Itoa(int(v.Uint()))}, nil
   247  
   248  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   249  		// if it is enum in protobuf
   250  		if stringer, ok := v.Interface().(fmt.Stringer); ok {
   251  			return state.Key{stringer.String()}, nil
   252  		}
   253  
   254  		return state.Key{strconv.Itoa(int(v.Int()))}, nil
   255  
   256  	case reflect.Ptr:
   257  		// todo: extract key producer and add custom serializers
   258  		switch val := v.Interface().(type) {
   259  
   260  		case *timestamp.Timestamp:
   261  			t, err := ptypes.Timestamp(val)
   262  			if err != nil {
   263  				return nil, fmt.Errorf(`timestamp key to time: %w`, err)
   264  			}
   265  			return state.Key{t.Format(TimestampKeyLayout)}, nil
   266  
   267  		default:
   268  			key := state.Key{}
   269  			s := reflect.ValueOf(v.Interface()).Elem()
   270  			fs := s.Type()
   271  			// get all field values from struct
   272  			for i := 0; i < s.NumField(); i++ {
   273  				field := s.Field(i)
   274  				if skipField(fs.Field(i).Name, field) {
   275  					continue
   276  				} else {
   277  					subKey, err := keyFromValue(reflect.Indirect(v).Field(i))
   278  					if err != nil {
   279  						return nil, fmt.Errorf(`sub key=%s: %w`, fs.Field(i).Name, err)
   280  					}
   281  					key = key.Append(subKey)
   282  				}
   283  			}
   284  
   285  			return key, nil
   286  		}
   287  	}
   288  
   289  	switch v.Type().String() {
   290  
   291  	case `string`, `int32`, `uint32`, `bool`:
   292  		// multi key possible
   293  		return state.Key{v.String()}, nil
   294  
   295  	case `[]string`:
   296  		key := state.Key{}
   297  		// every slice element is a part of one key
   298  		for i := 0; i < v.Len(); i++ {
   299  			key = append(key, v.Index(i).String())
   300  		}
   301  		return key, nil
   302  
   303  	default:
   304  		return nil, ErrFieldTypeNotSupportedForKeyExtraction
   305  	}
   306  }