Repository: sacloud/usacloud
Pull request #587 (2020-11-17 13:28:04)

use a slice for definitions instead of map
follow up #584 

コード生成時にmapを参照していると出力されるコード(中の値)の並び順が不定となる。
このためvdefでの値定義時にmapを利用しないようにする。


Patch:
diff --git a/pkg/cmd/commands/disk/zz_create_gen.go b/pkg/cmd/commands/disk/zz_create_gen.go
index a1ab1550..e715432b 100644
--- a/pkg/cmd/commands/disk/zz_create_gen.go
+++ b/pkg/cmd/commands/disk/zz_create_gen.go
@@ -40,7 +40,7 @@ func (p *createParameter) buildFlags(fs *pflag.FlagSet) {
 	fs.VarP(core.NewIDFlag(&p.ServerID, &p.ServerID), "server-id", "", "")
 	fs.IntVarP(&p.SizeGB, "size", "", p.SizeGB, "")
 	fs.VarP(core.NewIDSliceFlag(&p.DistantFrom, &p.DistantFrom), "distant-from", "", "")
-	fs.StringVarP(&p.OSType, "os-type", "", p.OSType, "options: [ubuntu1804/coreos/windows2016-rds-office/windows2016-sql2017-enterprise/windows2019-rds/windows2016-sql2017-standard/ubuntu2004/windows2016-sql-standard-all/windows2019/windows2019-sql2019-web/windows2019-sql2019-standard/windows2016-sql2017-standard-all/windows2019-rds-office2019/windows2019-sql2017-web/windows2019-sql2017-standard/windows2019-sql2019-standard-all/rancheros/kusanagi/freebsd/windows2016-sql-standard/windows2019-sql2019-enterprise/windows2019-sql2017-standard-all/ubuntu/debian10/k3os/windows2016/windows2016-rds/windows2016-sql-web/centos/debian/windows2019-sql2017-enterprise/centos8/centos7/centos6/ubuntu1604/debian9]")
+	fs.StringVarP(&p.OSType, "os-type", "", p.OSType, "options: [centos/centos8/centos7/centos6/ubuntu/ubuntu2004/ubuntu1804/ubuntu1604/debian/debian10/debian9/coreos/rancheros/k3os/kusanagi/freebsd/windows2016/windows2016-rds/windows2016-rds-office/windows2016-sql-web/windows2016-sql-standard/windows2016-sql-standard-all/windows2016-sql2017-standard/windows2016-sql2017-enterprise/windows2016-sql2017-standard-all/windows2019/windows2019-rds/windows2019-rds-office2019/windows2019-sql2017-web/windows2019-sql2019-web/windows2019-sql2017-standard/windows2019-sql2019-standard/windows2019-sql2017-enterprise/windows2019-sql2019-enterprise/windows2019-sql2017-standard-all/windows2019-sql2019-standard-all]")
 	fs.BoolVarP(&p.AssumeYes, "assumeyes", "y", p.AssumeYes, "Assume that the answer to any question which would be asked is yes")
 	fs.StringVarP(&p.OutputType, "output-type", "o", p.OutputType, "Output format: one of the following [table/json/yaml] (aliases: --out)")
 	fs.BoolVarP(&p.Quiet, "quiet", "q", p.Quiet, "Output IDs only")
diff --git a/pkg/vdef/definitions.go b/pkg/vdef/definitions.go
index 3f7a31e9..4f62ec61 100644
--- a/pkg/vdef/definitions.go
+++ b/pkg/vdef/definitions.go
@@ -24,23 +24,28 @@ import (
 	"github.com/sacloud/libsacloud/v2/sacloud/types"
 )
 
+type definition struct {
+	key   interface{}
+	value interface{}
+}
+
 // definitions usacloudで使う名称(key)/値(value)のペア
-var definitions = map[string]map[interface{}]interface{}{
+var definitions = map[string][]*definition{
 	"disk_plan": {
-		"ssd": types.DiskPlans.SSD,
-		"hdd": types.DiskPlans.HDD,
+		{key: "ssd", value: types.DiskPlans.SSD},
+		{key: "hdd", value: types.DiskPlans.HDD},
 	},
 	"disk_connection": {
-		types.DiskConnections.VirtIO.String(): types.DiskConnections.VirtIO.String(),
-		types.DiskConnections.IDE.String():    types.DiskConnections.IDE.String(),
+		{key: types.DiskConnections.VirtIO.String(), value: types.DiskConnections.VirtIO.String()},
+		{key: types.DiskConnections.IDE.String(), value: types.DiskConnections.IDE.String()},
 	},
 	"os_type": ostypeDefinition(),
 }
 
-func ostypeDefinition() map[interface{}]interface{} {
-	def := map[interface{}]interface{}{}
+func ostypeDefinition() []*definition {
+	var def []*definition
 	for _, name := range ostype.OSTypeShortNames {
-		def[name] = ostype.StrToOSType(name)
+		def = append(def, &definition{key: name, value: ostype.StrToOSType(name)})
 	}
 	return def
 }
@@ -75,8 +80,8 @@ func registerValidators() {
 	// definitionsの各値からキーを取り出し、"oneof=keyのスペース区切り"というルールを登録する
 	for name, defs := range definitions {
 		var allows []string
-		for key := range defs {
-			switch s := key.(type) {
+		for _, def := range defs {
+			switch s := def.key.(type) {
 			case string:
 				allows = append(allows, s)
 			case fmt.Stringer:
@@ -91,8 +96,8 @@ func registerCLITagOptions() {
 	// definitionsの各値からキーを取り出し、FlagOptionsMapに登録する
 	for name, defs := range definitions {
 		var allows []string
-		for key := range defs {
-			switch s := key.(type) {
+		for _, def := range defs {
+			switch s := def.key.(type) {
 			case string:
 				allows = append(allows, s)
 			case fmt.Stringer:
@@ -103,12 +108,12 @@ func registerCLITagOptions() {
 	}
 }
 
-func convertFuncToValue(defName string, def map[interface{}]interface{}) mapconv.FilterFunc {
+func convertFuncToValue(defName string, defs []*definition) mapconv.FilterFunc {
 	return func(v interface{}) (interface{}, error) {
 		var result interface{}
-		for key, value := range def {
-			if reflect.DeepEqual(v, key) {
-				result = value
+		for _, def := range defs {
+			if reflect.DeepEqual(v, def.key) {
+				result = def.value
 				break
 			}
 		}
@@ -119,12 +124,12 @@ func convertFuncToValue(defName string, def map[interface{}]interface{}) mapconv
 	}
 }
 
-func convertFuncToKey(defName string, def map[interface{}]interface{}) mapconv.FilterFunc {
+func convertFuncToKey(defName string, defs []*definition) mapconv.FilterFunc {
 	return func(v interface{}) (interface{}, error) {
 		var result interface{}
-		for key, value := range def {
-			if reflect.DeepEqual(v, value) {
-				result = key
+		for _, def := range defs {
+			if reflect.DeepEqual(v, def.value) {
+				result = def.key
 				break
 			}
 		}
@@ -135,23 +140,23 @@ func convertFuncToKey(defName string, def map[interface{}]interface{}) mapconv.F
 	}
 }
 
-func templateFuncToValue(def map[interface{}]interface{}) func(interface{}) interface{} {
+func templateFuncToValue(defs []*definition) func(interface{}) interface{} {
 	return func(raw interface{}) interface{} {
 		in := ""
 		if v, ok := raw.(json.Number); ok {
 			in = v.String()
 		}
 		var result interface{}
-		for key, value := range def {
-			switch ky := key.(type) {
+		for _, def := range defs {
+			switch ky := def.key.(type) {
 			case fmt.Stringer:
 				if reflect.DeepEqual(in, ky.String()) {
-					result = value
+					result = def.value
 					break
 				}
 			default:
-				if reflect.DeepEqual(in, key) {
-					result = value
+				if reflect.DeepEqual(in, def.key) {
+					result = def.value
 					break
 				}
 			}
@@ -160,23 +165,23 @@ func templateFuncToValue(def map[interface{}]interface{}) func(interface{}) inte
 	}
 }
 
-func templateFuncToKey(def map[interface{}]interface{}) func(interface{}) interface{} {
+func templateFuncToKey(defs []*definition) func(interface{}) interface{} {
 	return func(raw interface{}) interface{} {
 		in := ""
 		if v, ok := raw.(json.Number); ok {
 			in = v.String()
 		}
 		var result interface{}
-		for key, value := range def {
-			switch val := value.(type) {
+		for _, def := range defs {
+			switch val := def.value.(type) {
 			case fmt.Stringer:
 				if reflect.DeepEqual(in, val.String()) {
-					result = key
+					result = def.key
 					break
 				}
 			default:
-				if reflect.DeepEqual(in, value) {
-					result = key
+				if reflect.DeepEqual(in, def.value) {
+					result = def.key
 					break
 				}
 			}


Test patch:
diff --git a/pkg/vdef/definitions_test.go b/pkg/vdef/definitions_test.go
index f3a56d5f..3db0c760 100644
--- a/pkg/vdef/definitions_test.go
+++ b/pkg/vdef/definitions_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestDefinition_initialize(t *testing.T) {
 	// definitionsのinitで各所にfunc登録されるか
-	definitions["test"] = map[interface{}]interface{}{}
+	definitions["test"] = []*definition{}
 	registerFunctions()
 
 	testNames := []string{
@@ -44,10 +44,10 @@ func TestDefinition_initialize(t *testing.T) {
 }
 
 func TestConverterFuncGenerator(t *testing.T) {
-	def := map[interface{}]interface{}{
-		"string": "stringValue",
-		1:        "intValue",
-		"intKey": 1,
+	def := []*definition{
+		{key: "string", value: "stringValue"},
+		{key: 1, value: "intValue"},
+		{key: "intKey", value: 1},
 	}
 
 	t.Run("convertFuncToValue", func(t *testing.T) {