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 }