mapconv.go raw

   1  // Copyright 2022-2025 The sacloud/iaas-api-go Authors
   2  //
   3  // Licensed under the Apache License, Version 2.0 (the "License");
   4  // you may not use this file except in compliance with the License.
   5  // You may obtain a copy of the License at
   6  //
   7  //      http://www.apache.org/licenses/LICENSE-2.0
   8  //
   9  // Unless required by applicable law or agreed to in writing, software
  10  // distributed under the License is distributed on an "AS IS" BASIS,
  11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12  // See the License for the specific language governing permissions and
  13  // limitations under the License.
  14  
  15  package mapconv
  16  
  17  import (
  18  	"errors"
  19  	"fmt"
  20  	"reflect"
  21  	"strings"
  22  
  23  	"github.com/fatih/structs"
  24  	"github.com/mitchellh/mapstructure"
  25  	"github.com/sacloud/packages-go/objutil"
  26  )
  27  
  28  // DefaultMapConvTag デフォルトのmapconvタグ名
  29  const DefaultMapConvTag = "mapconv"
  30  
  31  // DecoderConfig mapconvでの変換の設定
  32  type DecoderConfig struct {
  33  	TagName     string
  34  	FilterFuncs map[string]FilterFunc
  35  }
  36  
  37  // FilterFunc mapconvでの変換時に適用するフィルタ
  38  type FilterFunc func(v interface{}) (interface{}, error)
  39  
  40  // TagInfo mapconvタグの情報
  41  type TagInfo struct {
  42  	Ignore       bool
  43  	SourceFields []string
  44  	Filters      []string
  45  	DefaultValue interface{}
  46  	OmitEmpty    bool
  47  	Recursive    bool
  48  	Squash       bool
  49  	IsSlice      bool
  50  }
  51  
  52  // Decoder mapconvでの変換
  53  type Decoder struct {
  54  	Config *DecoderConfig
  55  }
  56  
  57  func (d *Decoder) ConvertTo(source interface{}, dest interface{}) error {
  58  	s := structs.New(source)
  59  	mappedValues := Map(make(map[string]interface{}))
  60  
  61  	// recursiveの際に参照するためのdestのmap
  62  	destValues := Map(make(map[string]interface{}))
  63  	if structs.IsStruct(dest) {
  64  		destValues = Map(structs.Map(dest))
  65  	}
  66  
  67  	fields := s.Fields()
  68  	for _, f := range fields {
  69  		if !f.IsExported() {
  70  			continue
  71  		}
  72  
  73  		tags := d.ParseMapConvTag(f.Tag(d.Config.TagName))
  74  		if tags.Ignore {
  75  			continue
  76  		}
  77  		for _, key := range tags.SourceFields {
  78  			destKey := f.Name()
  79  			value := f.Value()
  80  
  81  			if key != "" {
  82  				destKey = key
  83  			}
  84  			if f.IsZero() {
  85  				if tags.OmitEmpty {
  86  					continue
  87  				}
  88  				if tags.DefaultValue != nil {
  89  					value = tags.DefaultValue
  90  				}
  91  			}
  92  
  93  			for _, filter := range tags.Filters {
  94  				filterFunc, ok := d.Config.FilterFuncs[filter]
  95  				if !ok {
  96  					return fmt.Errorf("filter %s not exists", filter)
  97  				}
  98  				filtered, err := filterFunc(value)
  99  				if err != nil {
 100  					return fmt.Errorf("failed to apply the filter: %s", err)
 101  				}
 102  				value = filtered
 103  			}
 104  
 105  			if tags.Squash {
 106  				dest := Map(make(map[string]interface{}))
 107  				err := d.ConvertTo(value, &dest)
 108  				if err != nil {
 109  					return err
 110  				}
 111  				for k, v := range dest {
 112  					mappedValues.Set(k, v)
 113  				}
 114  				continue
 115  			}
 116  
 117  			if tags.Recursive {
 118  				current, err := destValues.Get(destKey)
 119  				if err != nil {
 120  					return err
 121  				}
 122  
 123  				var dest []interface{}
 124  				values := valueToSlice(value)
 125  				currentValues := valueToSlice(current)
 126  				for i, v := range values {
 127  					if structs.IsStruct(v) {
 128  						var currentDest interface{}
 129  						if len(currentValues) > i {
 130  							currentDest = currentValues[i]
 131  						}
 132  						destMap := Map(make(map[string]interface{}))
 133  						if err := d.ConvertTo(v, &destMap); err != nil {
 134  							return err
 135  						}
 136  						// 宛先が存在しstructであれば(map[string]interface{}になっているはずなので)マージする
 137  						if currentDest != nil {
 138  							mv, ok := currentDest.(map[string]interface{})
 139  							// 元の値から空の値を除去する(structs:",omitempty"でも可)
 140  							for k, v := range mv {
 141  								if objutil.IsEmpty(v) {
 142  									delete(mv, k)
 143  								}
 144  							}
 145  							if ok {
 146  								for k, v := range destMap.Map() {
 147  									mv[k] = v
 148  								}
 149  								destMap = Map(mv)
 150  							}
 151  						}
 152  						dest = append(dest, destMap)
 153  					} else {
 154  						dest = append(dest, v)
 155  					}
 156  				}
 157  				if tags.IsSlice || dest == nil || len(dest) > 1 {
 158  					value = dest
 159  				} else {
 160  					value = dest[0]
 161  				}
 162  			}
 163  
 164  			mappedValues.Set(destKey, value)
 165  		}
 166  	}
 167  
 168  	config := &mapstructure.DecoderConfig{
 169  		WeaklyTypedInput: true,
 170  		Result:           dest,
 171  		ZeroFields:       true,
 172  	}
 173  	decoder, err := mapstructure.NewDecoder(config)
 174  	if err != nil {
 175  		return err
 176  	}
 177  	return decoder.Decode(mappedValues.Map())
 178  }
 179  
 180  func (d *Decoder) ConvertFrom(source interface{}, dest interface{}) error {
 181  	var sourceMap Map
 182  	if m, ok := source.(map[string]interface{}); ok {
 183  		sourceMap = Map(m)
 184  	} else {
 185  		sourceMap = Map(structs.New(source).Map())
 186  	}
 187  	destMap := Map(make(map[string]interface{}))
 188  
 189  	s := structs.New(dest)
 190  	fields := s.Fields()
 191  	for _, f := range fields {
 192  		if !f.IsExported() {
 193  			continue
 194  		}
 195  
 196  		tags := d.ParseMapConvTag(f.Tag(d.Config.TagName))
 197  		if tags.Ignore {
 198  			continue
 199  		}
 200  		if tags.Squash {
 201  			return errors.New("ConvertFrom is not allowed squash")
 202  		}
 203  		for _, key := range tags.SourceFields {
 204  			sourceKey := f.Name()
 205  			if key != "" {
 206  				sourceKey = key
 207  			}
 208  
 209  			value, err := sourceMap.Get(sourceKey)
 210  			if err != nil {
 211  				return err
 212  			}
 213  			if value == nil || reflect.ValueOf(value).IsZero() {
 214  				continue
 215  			}
 216  
 217  			for _, filter := range tags.Filters {
 218  				filterFunc, ok := d.Config.FilterFuncs[filter]
 219  				if !ok {
 220  					return fmt.Errorf("filter %s not exists", filter)
 221  				}
 222  				filtered, err := filterFunc(value)
 223  				if err != nil {
 224  					return fmt.Errorf("failed to apply the filter: %s", err)
 225  				}
 226  				value = filtered
 227  			}
 228  
 229  			if tags.Recursive {
 230  				t := reflect.TypeOf(f.Value())
 231  				if t.Kind() == reflect.Slice {
 232  					t = t.Elem().Elem()
 233  				} else {
 234  					t = t.Elem()
 235  				}
 236  
 237  				var dest []interface{}
 238  				values := valueToSlice(value)
 239  				for _, v := range values {
 240  					if v == nil {
 241  						dest = append(dest, v)
 242  						continue
 243  					}
 244  					dt := reflect.New(t).Interface()
 245  					if err := d.ConvertFrom(v, dt); err != nil {
 246  						return err
 247  					}
 248  					dest = append(dest, dt)
 249  				}
 250  
 251  				if dest != nil {
 252  					if tags.IsSlice || len(dest) > 1 {
 253  						value = dest
 254  					} else {
 255  						value = dest[0]
 256  					}
 257  				}
 258  			}
 259  
 260  			destMap.Set(f.Name(), value)
 261  		}
 262  	}
 263  	config := &mapstructure.DecoderConfig{
 264  		WeaklyTypedInput: true,
 265  		Result:           dest,
 266  		ZeroFields:       true,
 267  	}
 268  	decoder, err := mapstructure.NewDecoder(config)
 269  	if err != nil {
 270  		return err
 271  	}
 272  	return decoder.Decode(destMap.Map())
 273  }
 274  
 275  // ConvertTo converts struct which input by mapconv to plain models
 276  func ConvertTo(source interface{}, dest interface{}) error {
 277  	decoder := &Decoder{Config: &DecoderConfig{TagName: DefaultMapConvTag}}
 278  	return decoder.ConvertTo(source, dest)
 279  }
 280  
 281  // ConvertFrom converts struct which input by mapconv from plain models
 282  func ConvertFrom(source interface{}, dest interface{}) error {
 283  	decoder := &Decoder{Config: &DecoderConfig{TagName: DefaultMapConvTag}}
 284  	return decoder.ConvertFrom(source, dest)
 285  }
 286  
 287  // ParseMapConvTag mapconvタグを文字列で受け取りパースしてTagInfoを返す
 288  func (d *Decoder) ParseMapConvTag(tagBody string) TagInfo {
 289  	tokens := strings.Split(tagBody, ",")
 290  	key := strings.TrimSpace(tokens[0])
 291  
 292  	keys := strings.Split(key, "/")
 293  	var defaultValue interface{}
 294  	var filters []string
 295  	var ignore, omitEmpty, recursive, squash, isSlice bool
 296  
 297  	for _, k := range keys {
 298  		if k == "-" {
 299  			ignore = true
 300  			break
 301  		}
 302  		if strings.Contains(k, "[]") {
 303  			isSlice = true
 304  		}
 305  	}
 306  
 307  	for i, token := range tokens {
 308  		if i == 0 {
 309  			continue
 310  		}
 311  
 312  		token = strings.TrimSpace(token)
 313  
 314  		switch {
 315  		case strings.HasPrefix(token, "omitempty"):
 316  			omitEmpty = true
 317  		case strings.HasPrefix(token, "recursive"):
 318  			recursive = true
 319  		case strings.HasPrefix(token, "squash"):
 320  			squash = true
 321  		case strings.HasPrefix(token, "filters"):
 322  			keyValue := strings.Split(token, "=")
 323  			if len(keyValue) > 1 {
 324  				filters = strings.Split(strings.Join(keyValue[1:], ""), " ")
 325  			}
 326  		case strings.HasPrefix(token, "default"):
 327  			keyValue := strings.Split(token, "=")
 328  			if len(keyValue) > 1 {
 329  				defaultValue = strings.Join(keyValue[1:], "")
 330  			}
 331  		}
 332  	}
 333  	return TagInfo{
 334  		Ignore:       ignore,
 335  		SourceFields: keys,
 336  		DefaultValue: defaultValue,
 337  		OmitEmpty:    omitEmpty,
 338  		Recursive:    recursive,
 339  		Squash:       squash,
 340  		IsSlice:      isSlice,
 341  		Filters:      filters,
 342  	}
 343  }
 344