Просмотр исходного кода

Add updated version of config

tags/v2.1.0^2
9seconds 4 лет назад
Родитель
Сommit
ec4f0656fb
34 измененных файлов: 2244 добавлений и 0 удалений
  1. 81
    0
      internal/config2/config.go
  2. 54
    0
      internal/config2/config_test.go
  3. 80
    0
      internal/config2/parse.go
  4. 1
    0
      internal/config2/testdata/broken.toml
  5. 2
    0
      internal/config2/testdata/minimal.toml
  6. 1
    0
      internal/config2/testdata/only_secret.toml
  7. 77
    0
      internal/config2/type_blocklist_uri.go
  8. 113
    0
      internal/config2/type_blocklist_uri_test.go
  9. 40
    0
      internal/config2/type_bool.go
  10. 113
    0
      internal/config2/type_bool_test.go
  11. 55
    0
      internal/config2/type_bytes.go
  12. 86
    0
      internal/config2/type_bytes_test.go
  13. 45
    0
      internal/config2/type_concurrency.go
  14. 73
    0
      internal/config2/type_concurrency_test.go
  15. 53
    0
      internal/config2/type_duration.go
  16. 110
    0
      internal/config2/type_duration_test.go
  17. 47
    0
      internal/config2/type_error_rate.go
  18. 94
    0
      internal/config2/type_error_rate_test.go
  19. 59
    0
      internal/config2/type_hostport.go
  20. 88
    0
      internal/config2/type_hostport_test.go
  21. 33
    0
      internal/config2/type_http_path.go
  22. 67
    0
      internal/config2/type_http_path_test.go
  23. 45
    0
      internal/config2/type_ip.go
  24. 104
    0
      internal/config2/type_ip_test.go
  25. 40
    0
      internal/config2/type_metric_prefix.go
  26. 70
    0
      internal/config2/type_metric_prefix_test.go
  27. 45
    0
      internal/config2/type_port.go
  28. 71
    0
      internal/config2/type_port_test.go
  29. 62
    0
      internal/config2/type_prefer_ip.go
  30. 113
    0
      internal/config2/type_prefer_ip_test.go
  31. 61
    0
      internal/config2/type_proxy_url.go
  32. 95
    0
      internal/config2/type_proxy_url_test.go
  33. 58
    0
      internal/config2/type_statsd_tag_format.go
  34. 108
    0
      internal/config2/type_statsd_tag_format_test.go

+ 81
- 0
internal/config2/config.go Просмотреть файл

@@ -0,0 +1,81 @@
1
+package config2
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+)
10
+
11
+type Config struct {
12
+	Debug                TypeBool        `json:"debug"`
13
+	Secret               mtglib.Secret   `json:"secret"`
14
+	BindTo               TypeHostPort    `json:"bindTo"`
15
+	TCPBuffer            TypeBytes       `json:"tcpBuffer"`
16
+	PreferIP             TypePreferIP    `json:"preferIp"`
17
+	DomainFrontingPort   TypePort        `json:"domainFrontingPort"`
18
+	TolerateTimeSkewness TypeDuration    `json:"tolerateTimeSkewness"`
19
+	Concurrency          TypeConcurrency `json:"concurrency"`
20
+	Defense              struct {
21
+		AntiReplay struct {
22
+			Enabled   TypeBool      `json:"enabled"`
23
+			MaxSize   TypeBytes     `json:"maxSize"`
24
+			ErrorRate TypeErrorRate `json:"errorRate"`
25
+		} `json:"antiReplay"`
26
+		Blocklist struct {
27
+			Enabled             TypeBool           `json:"enabled"`
28
+			DownloadConcurrency TypeConcurrency    `json:"downloadConcurrency"`
29
+			URLs                []TypeBlocklistURI `json:"urls"`
30
+			UpdateEach          TypeDuration       `json:"updateEach"`
31
+		} `json:"blocklist"`
32
+	} `json:"defense"`
33
+	Network struct {
34
+		Timeout struct {
35
+			TCP  TypeDuration `json:"tcp"`
36
+			HTTP TypeDuration `json:"http"`
37
+			Idle TypeDuration `json:"idle"`
38
+		} `json:"timeout"`
39
+		DOHIP   TypeIP         `json:"dohIp"`
40
+		Proxies []TypeProxyURL `json:"proxies"`
41
+	} `json:"network"`
42
+	Stats struct {
43
+		StatsD struct {
44
+			Enabled      TypeBool            `json:"enabled"`
45
+			Address      TypeHostPort        `json:"address"`
46
+			MetricPrefix TypeMetricPrefix    `json:"metricPrefix"`
47
+			TagFormat    TypeStatsdTagFormat `json:"tagFormat"`
48
+		} `json:"statsd"`
49
+		Prometheus struct {
50
+			Enabled      TypeBool         `json:"enabled"`
51
+			BindTo       TypeHostPort     `json:"bindTo"`
52
+			HTTPPath     TypeHTTPPath     `json:"httpPath"`
53
+			MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
54
+		} `json:"prometheus"`
55
+	} `json:"stats"`
56
+}
57
+
58
+func (c *Config) Validate() error {
59
+	if !c.Secret.Valid() {
60
+		return fmt.Errorf("invalid secret %s", c.Secret.String())
61
+	}
62
+
63
+    if c.BindTo.Get("") == "" {
64
+		return fmt.Errorf("incorrect bind-to parameter %s", c.BindTo.String())
65
+	}
66
+
67
+	return nil
68
+}
69
+
70
+func (c *Config) String() string {
71
+	buf := &bytes.Buffer{}
72
+	encoder := json.NewEncoder(buf)
73
+
74
+	encoder.SetEscapeHTML(false)
75
+
76
+	if err := encoder.Encode(c); err != nil {
77
+		panic(err)
78
+	}
79
+
80
+	return buf.String()
81
+}

+ 54
- 0
internal/config2/config_test.go Просмотреть файл

@@ -0,0 +1,54 @@
1
+package config2_test
2
+
3
+import (
4
+	"os"
5
+	"path/filepath"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type ConfigTestSuite struct {
13
+    suite.Suite
14
+}
15
+
16
+func (suite *ConfigTestSuite) ReadConfig(filename string) []byte {
17
+	data, err := os.ReadFile(filepath.Join("testdata", filename))
18
+	suite.NoError(err)
19
+
20
+	return data
21
+}
22
+
23
+func (suite *ConfigTestSuite) TestParseEmpty() {
24
+	_, err := config2.Parse([]byte{})
25
+	suite.Error(err)
26
+}
27
+
28
+func (suite *ConfigTestSuite) TestParseBrokenToml() {
29
+	_, err := config2.Parse(suite.ReadConfig("broken.toml"))
30
+	suite.Error(err)
31
+}
32
+
33
+func (suite *ConfigTestSuite) TestParseOnlySecret() {
34
+	_, err := config2.Parse(suite.ReadConfig("only_secret.toml"))
35
+	suite.Error(err)
36
+}
37
+
38
+func (suite *ConfigTestSuite) TestParseMinimalConfig() {
39
+	conf, err := config2.Parse(suite.ReadConfig("minimal.toml"))
40
+	suite.NoError(err)
41
+	suite.Equal("7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t", conf.Secret.Base64())
42
+	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
43
+}
44
+
45
+func (suite *ConfigTestSuite) TestString() {
46
+	conf, err := config2.Parse(suite.ReadConfig("minimal.toml"))
47
+	suite.NoError(err)
48
+	suite.NotEmpty(conf.String())
49
+}
50
+
51
+func TestConfig(t *testing.T) {
52
+	t.Parallel()
53
+	suite.Run(t, &ConfigTestSuite{})
54
+}

+ 80
- 0
internal/config2/parse.go Просмотреть файл

@@ -0,0 +1,80 @@
1
+package config2
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+
8
+	"github.com/pelletier/go-toml"
9
+)
10
+
11
+type tomlConfig struct {
12
+	Debug                bool   `toml:"debug" json:"debug,omitempty"`
13
+	Secret               string `toml:"secret" json:"secret"`
14
+	BindTo               string `toml:"bind-to" json:"bindTo"`
15
+	TCPBuffer            string `toml:"tcp-buffer" json:"tcpBuffer,omitempty"`
16
+	PreferIP             string `toml:"prefer-ip" json:"preferIp,omitempty"`
17
+	DomainFrontingPort   uint   `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
18
+	TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
19
+	Concurrency          uint   `toml:"concurrency" json:"concurrency,omitempty"`
20
+	Defense              struct {
21
+		AntiReplay struct {
22
+			Enabled   bool    `toml:"enabled" json:"enabled,omitempty"`
23
+			MaxSize   string  `toml:"max-size" json:"maxSize,omitempty"`
24
+			ErrorRate float64 `toml:"error-rate" json:"errorRate,omitempty"`
25
+		} `toml:"anti-replay" json:"antiReplay,omitempty"`
26
+		Blocklist struct {
27
+			Enabled             bool     `toml:"enabled" json:"enabled,omitempty"`
28
+			DownloadConcurrency uint     `toml:"download-concurrency" json:"downloadConcurrency,omitempty"`
29
+			URLs                []string `toml:"urls" json:"urls,omitempty"`
30
+			UpdateEach          string   `toml:"update-each" json:"updateEach,omitempty"`
31
+		} `toml:"blocklist" json:"blocklist,omitempty"`
32
+	} `toml:"defense" json:"defense,omitempty"`
33
+	Network struct {
34
+		Timeout struct {
35
+			TCP  string `toml:"tcp" json:"tcp,omitempty"`
36
+			HTTP string `toml:"http" json:"http,omitempty"`
37
+			Idle string `toml:"idle" json:"idle,omitempty"`
38
+		} `toml:"timeout" json:"timeout,omitempty"`
39
+		DOHIP   string   `toml:"doh-ip" json:"dohIp,omitempty"`
40
+		Proxies []string `toml:"proxies" json:"proxies,omitempty"`
41
+	} `toml:"network" json:"network,omitempty"`
42
+	Stats struct {
43
+		StatsD struct {
44
+			Enabled      bool   `toml:"enabled" json:"enabled,omitempty"`
45
+			Address      string `toml:"address" json:"address,omitempty"`
46
+			MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
47
+			TagFormat    string `toml:"tag-format" json:"tagFormat,omitempty"`
48
+		} `toml:"statsd" json:"statsd,omitempty"`
49
+		Prometheus struct {
50
+			Enabled      bool   `toml:"enabled" json:"enabled,omitempty"`
51
+			BindTo       string `toml:"bind-to" json:"bindTo,omitempty"`
52
+			HTTPPath     string `toml:"http-path" json:"httpPath,omitempty"`
53
+			MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
54
+		} `toml:"prometheus" json:"prometheus,omitempty"`
55
+	} `toml:"stats" json:"stats,omitempty"`
56
+}
57
+
58
+func Parse(rawData []byte) (*Config, error) {
59
+	tomlConf := &tomlConfig{}
60
+	jsonBuf := &bytes.Buffer{}
61
+	conf := &Config{}
62
+
63
+	jsonEncoder := json.NewEncoder(jsonBuf)
64
+	jsonEncoder.SetEscapeHTML(false)
65
+	jsonEncoder.SetIndent("", "")
66
+
67
+	if err := toml.Unmarshal(rawData, tomlConf); err != nil {
68
+		return nil, fmt.Errorf("cannot parse toml config: %w", err)
69
+	}
70
+
71
+	if err := jsonEncoder.Encode(tomlConf); err != nil {
72
+		panic(err)
73
+	}
74
+
75
+	if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
76
+		return nil, fmt.Errorf("cannot parse a config: %w", err)
77
+	}
78
+
79
+	return conf, nil
80
+}

+ 1
- 0
internal/config2/testdata/broken.toml Просмотреть файл

@@ -0,0 +1 @@
1
+s = sdfsdfds

+ 2
- 0
internal/config2/testdata/minimal.toml Просмотреть файл

@@ -0,0 +1,2 @@
1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"

+ 1
- 0
internal/config2/testdata/only_secret.toml Просмотреть файл

@@ -0,0 +1 @@
1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"

+ 77
- 0
internal/config2/type_blocklist_uri.go Просмотреть файл

@@ -0,0 +1,77 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"net/url"
6
+	"os"
7
+	"path/filepath"
8
+)
9
+
10
+type TypeBlocklistURI struct {
11
+	Value string
12
+}
13
+
14
+func (t *TypeBlocklistURI) Set(value string) error {
15
+	if stat, err := os.Stat(value); err == nil || os.IsExist(err) {
16
+        switch {
17
+        case stat.IsDir():
18
+            return fmt.Errorf("value is correct filepath but directory")
19
+        case stat.Mode().Perm() & 0o400 == 0:
20
+            return fmt.Errorf("value is correct filepath but not readable")
21
+        }
22
+
23
+		value, err = filepath.Abs(value)
24
+		if err != nil {
25
+			return fmt.Errorf(
26
+				"value is correct filepath but cannot resolve absolute (%s): %w",
27
+				value, err)
28
+		}
29
+
30
+		t.Value = value
31
+
32
+		return nil
33
+	}
34
+
35
+	parsedURL, err := url.Parse(value)
36
+	if err != nil {
37
+		return fmt.Errorf("incorrect url (%s): %w", value, err)
38
+	}
39
+
40
+	switch parsedURL.Scheme {
41
+	case "http", "https":
42
+	default:
43
+		return fmt.Errorf("unknown schema %s (%s)", parsedURL.Scheme, value)
44
+	}
45
+
46
+	if parsedURL.Host == "" {
47
+		return fmt.Errorf("incorrect url %s", value)
48
+	}
49
+
50
+	t.Value = parsedURL.String()
51
+
52
+	return nil
53
+}
54
+
55
+func (t TypeBlocklistURI) Get(defaultValue string) string {
56
+	if t.Value == "" {
57
+		return defaultValue
58
+	}
59
+
60
+	return t.Value
61
+}
62
+
63
+func (t TypeBlocklistURI) IsRemote() bool {
64
+	return !filepath.IsAbs(t.Value)
65
+}
66
+
67
+func (t *TypeBlocklistURI) UnmarshalText(data []byte) error {
68
+	return t.Set(string(data))
69
+}
70
+
71
+func (t TypeBlocklistURI) MarshalText() ([]byte, error) {
72
+	return []byte(t.String()), nil
73
+}
74
+
75
+func (t TypeBlocklistURI) String() string {
76
+	return t.Value
77
+}

+ 113
- 0
internal/config2/type_blocklist_uri_test.go Просмотреть файл

@@ -0,0 +1,113 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"os"
6
+	"path/filepath"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/9seconds/mtg/v2/internal/config2"
11
+	"github.com/stretchr/testify/assert"
12
+	"github.com/stretchr/testify/suite"
13
+)
14
+
15
+type typeBlocklistURITestStruct struct {
16
+	Value config2.TypeBlocklistURI `json:"value"`
17
+}
18
+
19
+type TypeBlocklistURITestSuite struct {
20
+	suite.Suite
21
+
22
+	directory    string
23
+	absDirectory string
24
+}
25
+
26
+func (suite *TypeBlocklistURITestSuite) SetupSuite() {
27
+	dir, _ := os.Getwd()
28
+	absDir, _ := filepath.Abs(dir)
29
+
30
+	suite.directory = dir
31
+	suite.absDirectory = absDir
32
+}
33
+
34
+func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
35
+	testData := []string{
36
+		"gopher://lalala",
37
+		"https:///paths",
38
+		"h:/=",
39
+		filepath.Join(suite.directory, "___"),
40
+		filepath.Join(suite.absDirectory, "___"),
41
+		suite.directory,
42
+		suite.absDirectory,
43
+	}
44
+
45
+	for _, v := range testData {
46
+		data, err := json.Marshal(map[string]string{
47
+			"value": v,
48
+		})
49
+		suite.NoError(err)
50
+
51
+		suite.T().Run(v, func(t *testing.T) {
52
+			assert.Error(t, json.Unmarshal(data, &typeBlocklistURITestStruct{}))
53
+		})
54
+	}
55
+}
56
+
57
+func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
58
+	testData := []string{
59
+		"http://lalala",
60
+		"https://lalala",
61
+		"https://lalala/path",
62
+		filepath.Join(suite.directory, "config.go"),
63
+		filepath.Join(suite.absDirectory, "config.go"),
64
+	}
65
+
66
+	for _, v := range testData {
67
+		value := v
68
+
69
+		data, err := json.Marshal(map[string]string{
70
+			"value": v,
71
+		})
72
+		suite.NoError(err)
73
+
74
+		suite.T().Run(v, func(t *testing.T) {
75
+			testStruct := &typeBlocklistURITestStruct{}
76
+
77
+			assert.NoError(t, json.Unmarshal(data, testStruct))
78
+			assert.EqualValues(t, value, testStruct.Value.Get(""))
79
+
80
+			if strings.HasPrefix(value, "http") {
81
+				assert.True(t, testStruct.Value.IsRemote())
82
+			} else {
83
+				assert.False(t, testStruct.Value.IsRemote())
84
+			}
85
+		})
86
+	}
87
+}
88
+
89
+func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
90
+	testStruct := &typeBlocklistURITestStruct{
91
+		Value: config2.TypeBlocklistURI{
92
+			Value: "http://some.url/with/path",
93
+		},
94
+	}
95
+
96
+	data, err := json.Marshal(testStruct)
97
+	suite.NoError(err)
98
+	suite.JSONEq(`{"value": "http://some.url/with/path"}`, string(data))
99
+}
100
+
101
+func (suite *TypeBlocklistURITestSuite) TestGet() {
102
+	value := config2.TypeBlocklistURI{}
103
+	suite.Equal("/path", value.Get("/path"))
104
+
105
+    suite.NoError(value.Set("http://lalala.ru"))
106
+    suite.Equal("http://lalala.ru", value.Get("/path"))
107
+    suite.Equal("http://lalala.ru", value.Get(""))
108
+}
109
+
110
+func TestTypeBlocklistURI(t *testing.T) {
111
+	t.Parallel()
112
+	suite.Run(t, &TypeBlocklistURITestSuite{})
113
+}

+ 40
- 0
internal/config2/type_bool.go Просмотреть файл

@@ -0,0 +1,40 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+	"strings"
7
+)
8
+
9
+type TypeBool struct {
10
+	Value bool
11
+}
12
+
13
+func (t *TypeBool) Set(data string) error {
14
+	switch strings.ToLower(data) {
15
+	case "1", "y", "yes", "enabled", "true":
16
+		t.Value = true
17
+	case "0", "n", "no", "disabled", "false":
18
+		t.Value = false
19
+	default:
20
+		return fmt.Errorf("incorrect bool value %s", data)
21
+	}
22
+
23
+	return nil
24
+}
25
+
26
+func (t TypeBool) Get(defaultValue bool) bool {
27
+	return t.Value || defaultValue
28
+}
29
+
30
+func (t *TypeBool) UnmarshalText(data []byte) error {
31
+	return t.Set(string(data))
32
+}
33
+
34
+func (t TypeBool) MarshalText() ([]byte, error) {
35
+	return []byte(t.String()), nil
36
+}
37
+
38
+func (t TypeBool) String() string {
39
+	return strconv.FormatBool(t.Value)
40
+}

+ 113
- 0
internal/config2/type_bool_test.go Просмотреть файл

@@ -0,0 +1,113 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"fmt"
6
+	"strconv"
7
+	"testing"
8
+
9
+	"github.com/9seconds/mtg/v2/internal/config2"
10
+	"github.com/stretchr/testify/assert"
11
+	"github.com/stretchr/testify/suite"
12
+)
13
+
14
+type typeBoolTestStruct struct {
15
+	Value config2.TypeBool `json:"value"`
16
+}
17
+
18
+type TypeBoolTestSuite struct {
19
+	suite.Suite
20
+}
21
+
22
+func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
23
+	testData := []string{
24
+		"",
25
+		"np",
26
+		"нет",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typeBoolTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *TypeBoolTestSuite) TestUnmarshalOk() {
42
+	testData := map[string]bool{
43
+		"0":        false,
44
+		"N":        false,
45
+		"nO":       false,
46
+		"no":       false,
47
+		"dISAbLEd": false,
48
+		"False":    false,
49
+		"false":    false,
50
+
51
+		"1":       true,
52
+		"y":       true,
53
+		"Yes":     true,
54
+		"yes":     true,
55
+		"enABLED": true,
56
+		"True":    true,
57
+		"TRUE":    true,
58
+		"true":    true,
59
+	}
60
+
61
+	for k, v := range testData {
62
+		value := v
63
+
64
+		data, err := json.Marshal(map[string]string{
65
+			"value": k,
66
+		})
67
+		suite.NoError(err)
68
+
69
+		suite.T().Run(k, func(t *testing.T) {
70
+			testStruct := &typeBoolTestStruct{}
71
+			assert.NoError(t, json.Unmarshal(data, testStruct))
72
+
73
+			if value {
74
+				assert.True(t, testStruct.Value.Value)
75
+			} else {
76
+				assert.False(t, testStruct.Value.Value)
77
+			}
78
+		})
79
+	}
80
+}
81
+
82
+func (suite *TypeBoolTestSuite) TestMarshalOk() {
83
+	for _, v := range []bool{true, false} {
84
+		name := strconv.FormatBool(v)
85
+
86
+		suite.T().Run(name, func(t *testing.T) {
87
+			testStruct := typeBoolTestStruct{
88
+				Value: config2.TypeBool{
89
+					Value: v,
90
+				},
91
+			}
92
+
93
+			data, err := json.Marshal(testStruct)
94
+			assert.NoError(t, err)
95
+			assert.JSONEq(t, fmt.Sprintf(`{"value": "%s"}`, name), string(data))
96
+		})
97
+	}
98
+}
99
+
100
+func (suite *TypeBoolTestSuite) TestGet() {
101
+	value := config2.TypeBool{}
102
+	suite.False(value.Get(false))
103
+	suite.True(value.Get(true))
104
+
105
+	value.Value = true
106
+	suite.True(value.Get(false))
107
+	suite.True(value.Get(true))
108
+}
109
+
110
+func TestTypeBool(t *testing.T) {
111
+	t.Parallel()
112
+	suite.Run(t, &TypeBoolTestSuite{})
113
+}

+ 55
- 0
internal/config2/type_bytes.go Просмотреть файл

@@ -0,0 +1,55 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+
7
+	"github.com/alecthomas/units"
8
+)
9
+
10
+var typeBytesStringCleaner = strings.NewReplacer(" ", "", "\t", "", "IB", "iB")
11
+
12
+type TypeBytes struct {
13
+	Value units.Base2Bytes
14
+}
15
+
16
+func (t *TypeBytes) Set(value string) error {
17
+    normalizedValue := typeBytesStringCleaner.Replace(strings.ToUpper(value))
18
+
19
+	parsedValue, err := units.ParseBase2Bytes(normalizedValue)
20
+	if err != nil {
21
+		return fmt.Errorf("incorrect bytes value (%v): %w", value, err)
22
+	}
23
+
24
+	if parsedValue < 0 {
25
+		return fmt.Errorf("bytes should be positive (%s)", value)
26
+	}
27
+
28
+	t.Value = parsedValue
29
+
30
+	return nil
31
+}
32
+
33
+func (t TypeBytes) Get(defaultValue uint) uint {
34
+	if t.Value == 0 {
35
+		return defaultValue
36
+	}
37
+
38
+	return uint(t.Value)
39
+}
40
+
41
+func (t *TypeBytes) UnmarshalText(data []byte) error {
42
+	return t.Set(string(data))
43
+}
44
+
45
+func (t TypeBytes) MarshalText() ([]byte, error) {
46
+	return []byte(t.String()), nil
47
+}
48
+
49
+func (t TypeBytes) String() string {
50
+	if t.Value == 0 {
51
+		return ""
52
+	}
53
+
54
+	return strings.ToLower(t.Value.String())
55
+}

+ 86
- 0
internal/config2/type_bytes_test.go Просмотреть файл

@@ -0,0 +1,86 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeBytesTestStruct struct {
13
+	Value config2.TypeBytes `json:"value"`
14
+}
15
+
16
+type TypeBytesTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeBytesTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"1m",
23
+		"1",
24
+		"-1kb",
25
+		"-1kib",
26
+	}
27
+
28
+	for _, v := range testData {
29
+		data, err := json.Marshal(map[string]string{
30
+			"value": v,
31
+		})
32
+		suite.NoError(err)
33
+
34
+		suite.T().Run(v, func(t *testing.T) {
35
+			assert.Error(t, json.Unmarshal(data, &typeBytesTestStruct{}))
36
+		})
37
+	}
38
+}
39
+
40
+func (suite *TypeBytesTestSuite) TestUnmarshalOk() {
41
+	testData := map[string]uint{
42
+		"1b":   1,
43
+		"1kb":  1024,
44
+		"1kib": 1024,
45
+		"2mb":  2 * 1024 * 1024,
46
+		"2mib": 2 * 1024 * 1024,
47
+	}
48
+
49
+	for k, v := range testData {
50
+		value := v
51
+
52
+		data, err := json.Marshal(map[string]string{
53
+			"value": k,
54
+		})
55
+		suite.NoError(err)
56
+
57
+		suite.T().Run(k, func(t *testing.T) {
58
+			testStruct := &typeBytesTestStruct{}
59
+
60
+			assert.NoError(t, json.Unmarshal(data, testStruct))
61
+			assert.EqualValues(t, value, testStruct.Value.Get(0))
62
+		})
63
+	}
64
+}
65
+
66
+func (suite *TypeBytesTestSuite) TestMarshalOk() {
67
+    value := typeBytesTestStruct{}
68
+    suite.NoError(value.Value.Set("1kib"))
69
+
70
+    data, err := json.Marshal(value)
71
+    suite.NoError(err)
72
+    suite.JSONEq(`{"value": "1kib"}`, string(data))
73
+}
74
+
75
+func (suite *TypeBytesTestSuite) TestGet() {
76
+    value := config2.TypeBytes{}
77
+    suite.EqualValues(1000, value.Get(1000))
78
+
79
+    suite.NoError(value.Set("1mib"))
80
+    suite.EqualValues(1048576, value.Get(1000))
81
+}
82
+
83
+func TestTypeBytes(t *testing.T) {
84
+	t.Parallel()
85
+	suite.Run(t, &TypeBytesTestSuite{})
86
+}

+ 45
- 0
internal/config2/type_concurrency.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+type TypeConcurrency struct {
9
+	Value uint
10
+}
11
+
12
+func (t *TypeConcurrency) Set(value string) error {
13
+	concurrencyValue, err := strconv.ParseUint(value, 10, 64)
14
+	if err != nil {
15
+		return fmt.Errorf("Value is not uint (%s): %w", value, err)
16
+	}
17
+
18
+	if concurrencyValue == 0 {
19
+		return fmt.Errorf("Value should be >0 (%s)", value)
20
+	}
21
+
22
+	t.Value = uint(concurrencyValue)
23
+
24
+	return nil
25
+}
26
+
27
+func (t TypeConcurrency) Get(defaultValue uint) uint {
28
+	if t.Value == 0 {
29
+		return defaultValue
30
+	}
31
+
32
+	return t.Value
33
+}
34
+
35
+func (t *TypeConcurrency) UnmarshalJSON(data []byte) error {
36
+	return t.Set(string(data))
37
+}
38
+
39
+func (t TypeConcurrency) MarshalJSON() ([]byte, error) {
40
+	return []byte(t.String()), nil
41
+}
42
+
43
+func (t TypeConcurrency) String() string {
44
+	return strconv.FormatUint(uint64(t.Value), 10)
45
+}

+ 73
- 0
internal/config2/type_concurrency_test.go Просмотреть файл

@@ -0,0 +1,73 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeConcurrencyTestStruct struct {
13
+	Value config2.TypeConcurrency `json:"value"`
14
+}
15
+
16
+type TypeConcurrencyTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeConcurrencyTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"-1",
23
+		"0",
24
+		"0.0",
25
+		"1.0",
26
+		"1.1",
27
+		".",
28
+		"some_value",
29
+	}
30
+
31
+	for _, v := range testData {
32
+		data, err := json.Marshal(map[string]string{
33
+			"value": v,
34
+		})
35
+		suite.NoError(err)
36
+
37
+		suite.T().Run(v, func(t *testing.T) {
38
+			assert.Error(t, json.Unmarshal(data, &typeConcurrencyTestStruct{}))
39
+		})
40
+	}
41
+}
42
+
43
+func (suite *TypeConcurrencyTestSuite) TestUnmarshalOk() {
44
+	testStruct := &typeConcurrencyTestStruct{}
45
+
46
+	suite.NoError(json.Unmarshal([]byte(`{"value": 1}`), testStruct))
47
+	suite.EqualValues(1, testStruct.Value.Get(2))
48
+}
49
+
50
+func (suite *TypeConcurrencyTestSuite) TestMarshalOk() {
51
+	testStruct := &typeConcurrencyTestStruct{
52
+		Value: config2.TypeConcurrency{
53
+			Value: 2,
54
+		},
55
+	}
56
+
57
+	data, err := json.Marshal(testStruct)
58
+	suite.NoError(err)
59
+	suite.JSONEq(`{"value": 2}`, string(data))
60
+}
61
+
62
+func (suite *TypeConcurrencyTestSuite) TestGet() {
63
+	value := config2.TypeConcurrency{}
64
+	suite.EqualValues(1, value.Get(1))
65
+
66
+	value.Value = 3
67
+	suite.EqualValues(3, value.Get(1))
68
+}
69
+
70
+func TestTypeConcurrency(t *testing.T) {
71
+	t.Parallel()
72
+	suite.Run(t, &TypeConcurrencyTestSuite{})
73
+}

+ 53
- 0
internal/config2/type_duration.go Просмотреть файл

@@ -0,0 +1,53 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+	"time"
7
+)
8
+
9
+var typeDurationStringCleaner = strings.NewReplacer(" ", "", "\t", "")
10
+
11
+type TypeDuration struct {
12
+	Value time.Duration
13
+}
14
+
15
+func (t *TypeDuration) Set(value string) error {
16
+	parsedValue, err := time.ParseDuration(
17
+		typeDurationStringCleaner.Replace(strings.ToLower(value)))
18
+	if err != nil {
19
+		return fmt.Errorf("incorrect duration (%s): %w", value, err)
20
+	}
21
+
22
+	if parsedValue < 0 {
23
+		return fmt.Errorf("duration has to be a positive: %s", value)
24
+	}
25
+
26
+	t.Value = parsedValue
27
+
28
+	return nil
29
+}
30
+
31
+func (t TypeDuration) Get(defaultValue time.Duration) time.Duration {
32
+	if t.Value == 0 {
33
+		return defaultValue
34
+	}
35
+
36
+	return t.Value
37
+}
38
+
39
+func (t *TypeDuration) UnmarshalText(data []byte) error {
40
+	return t.Set(string(data))
41
+}
42
+
43
+func (t TypeDuration) MarshalText() ([]byte, error) {
44
+	return []byte(t.String()), nil
45
+}
46
+
47
+func (t TypeDuration) String() string {
48
+	if t.Value == 0 {
49
+		return ""
50
+	}
51
+
52
+	return t.Value.String()
53
+}

+ 110
- 0
internal/config2/type_duration_test.go Просмотреть файл

@@ -0,0 +1,110 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+	"time"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeDurationTestStruct struct {
14
+	Value config2.TypeDuration `json:"value"`
15
+}
16
+
17
+type TypeDurationTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"-1s",
24
+		"1 seconds ago",
25
+		"1s ago",
26
+		"",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typeDurationTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
42
+	testData := map[string]time.Duration{
43
+		"1s":   time.Second,
44
+		"0":    0 * time.Second,
45
+		"0s":   0 * time.Second,
46
+		"1\tM": time.Minute,
47
+		"1H":   time.Hour,
48
+		"1 h":  time.Hour,
49
+	}
50
+
51
+	for k, v := range testData {
52
+		value := v
53
+
54
+		data, err := json.Marshal(map[string]string{
55
+			"value": k,
56
+		})
57
+		suite.NoError(err)
58
+
59
+		suite.T().Run(k, func(t *testing.T) {
60
+			testStruct := &typeDurationTestStruct{}
61
+
62
+			assert.NoError(t, json.Unmarshal(data, testStruct))
63
+			assert.Equal(t, value, testStruct.Value.Value)
64
+		})
65
+	}
66
+}
67
+
68
+func (suite *TypeDurationTestSuite) TestMarshalOk() {
69
+	testData := map[string]string{
70
+		"1s":  "1s",
71
+		"0":   "",
72
+		"0s":  "",
73
+		"0ms": "",
74
+		"1 H": "1h0m0s",
75
+	}
76
+
77
+	for k, v := range testData {
78
+		value := k
79
+		expected := v
80
+
81
+		suite.T().Run(value, func(t *testing.T) {
82
+			testStruct := &typeDurationTestStruct{}
83
+
84
+			assert.NoError(t, testStruct.Value.Set(value))
85
+
86
+			data, err := json.Marshal(testStruct)
87
+			assert.NoError(t, err)
88
+
89
+			expectedJson, err := json.Marshal(map[string]string{
90
+				"value": expected,
91
+			})
92
+			assert.NoError(t, err)
93
+
94
+			assert.JSONEq(t, string(expectedJson), string(data))
95
+		})
96
+	}
97
+}
98
+
99
+func (suite *TypeDurationTestSuite) TestGet() {
100
+	value := config2.TypeDuration{}
101
+	suite.Equal(time.Second, value.Get(time.Second))
102
+
103
+	value.Value = 3 * time.Second
104
+	suite.Equal(3*time.Second, value.Get(time.Hour))
105
+}
106
+
107
+func TestTypeDuration(t *testing.T) {
108
+	t.Parallel()
109
+	suite.Run(t, &TypeDurationTestSuite{})
110
+}

+ 47
- 0
internal/config2/type_error_rate.go Просмотреть файл

@@ -0,0 +1,47 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+const typeErrorRateIgnoreLess = 1e-8
9
+
10
+type TypeErrorRate struct {
11
+	Value float64
12
+}
13
+
14
+func (t *TypeErrorRate) Set(value string) error {
15
+	parsedValue, err := strconv.ParseFloat(value, 64)
16
+	if err != nil {
17
+		return fmt.Errorf("Value is not a float (%s): %w", value, err)
18
+	}
19
+
20
+	if parsedValue <= 0.0 || parsedValue >= 100.0 {
21
+		return fmt.Errorf("Value should be 0 < x < 100 (%s)", value)
22
+	}
23
+
24
+	t.Value = parsedValue
25
+
26
+	return nil
27
+}
28
+
29
+func (t TypeErrorRate) Get(defaultValue float64) float64 {
30
+	if t.Value < typeErrorRateIgnoreLess {
31
+		return defaultValue
32
+	}
33
+
34
+	return t.Value
35
+}
36
+
37
+func (t *TypeErrorRate) UnmarshalText(data []byte) error {
38
+	return t.Set(string(data))
39
+}
40
+
41
+func (t TypeErrorRate) MarshalText() ([]byte, error) {
42
+	return []byte(t.String()), nil
43
+}
44
+
45
+func (t TypeErrorRate) String() string {
46
+	return strconv.FormatFloat(t.Value, 'f', -1, 64)
47
+}

+ 94
- 0
internal/config2/type_error_rate_test.go Просмотреть файл

@@ -0,0 +1,94 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeErrorRateTestStruct struct {
13
+	Value config2.TypeErrorRate `json:"value"`
14
+}
15
+
16
+type TypeErrorRateTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"",
23
+		"1s",
24
+		"1,",
25
+		"1,2",
26
+		".",
27
+		"3.4.5",
28
+		"3.5.",
29
+		".3.5",
30
+		"some word",
31
+		"1e2",
32
+		"-1.0",
33
+	}
34
+
35
+	for _, v := range testData {
36
+		data, err := json.Marshal(map[string]string{
37
+			"value": v,
38
+		})
39
+		suite.NoError(err)
40
+
41
+		suite.T().Run(v, func(t *testing.T) {
42
+			assert.Error(t, json.Unmarshal(data, &typeErrorRateTestStruct{}))
43
+		})
44
+	}
45
+}
46
+
47
+func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
48
+	testData := map[string]float64{
49
+		"1":   1.0,
50
+		"1.0": 1.0,
51
+		"0.5": 0.5,
52
+		".5":  0.5,
53
+	}
54
+
55
+	for k, v := range testData {
56
+		value := v
57
+
58
+		data, err := json.Marshal(map[string]string{
59
+			"value": k,
60
+		})
61
+		suite.NoError(err)
62
+
63
+		suite.T().Run(k, func(t *testing.T) {
64
+			testStruct := &typeErrorRateTestStruct{}
65
+			assert.NoError(t, json.Unmarshal(data, testStruct))
66
+			assert.InEpsilon(t, value, testStruct.Value.Value, 1e-10)
67
+		})
68
+	}
69
+}
70
+
71
+func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
72
+	testStruct := typeErrorRateTestStruct{
73
+		Value: config2.TypeErrorRate{
74
+			Value: 1.01,
75
+		},
76
+	}
77
+
78
+	encodedJson, err := json.Marshal(testStruct)
79
+	suite.NoError(err)
80
+	suite.JSONEq(`{"value": "1.01"}`, string(encodedJson))
81
+}
82
+
83
+func (suite *TypeErrorRateTestSuite) TestGet() {
84
+	value := config2.TypeErrorRate{}
85
+	suite.InEpsilon(1.0, value.Get(1.0), 1e-10)
86
+
87
+	value.Value = 5.0
88
+	suite.InEpsilon(5.0, value.Get(1.0), 1e-10)
89
+}
90
+
91
+func TestTypeErrorRate(t *testing.T) {
92
+	t.Parallel()
93
+	suite.Run(t, &TypeErrorRateTestSuite{})
94
+}

+ 59
- 0
internal/config2/type_hostport.go Просмотреть файл

@@ -0,0 +1,59 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"strconv"
7
+)
8
+
9
+type TypeHostPort struct {
10
+	Value string
11
+}
12
+
13
+func (t *TypeHostPort) Set(value string) error {
14
+	host, port, err := net.SplitHostPort(value)
15
+	if err != nil {
16
+		return fmt.Errorf("incorrect host:port value (%v): %w", value, err)
17
+	}
18
+
19
+	portValue, err := strconv.ParseUint(port, 10, 16)
20
+	if err != nil {
21
+		return fmt.Errorf("incorrect port number (%v): %w", value, err)
22
+	}
23
+
24
+	if portValue == 0 {
25
+		return fmt.Errorf("incorrect port number (%s)", value)
26
+	}
27
+
28
+	if host == "" {
29
+		return fmt.Errorf("empty host: %s", value)
30
+	}
31
+
32
+	if net.ParseIP(host) == nil {
33
+		return fmt.Errorf("host is not an IP address: %s", value)
34
+	}
35
+
36
+	t.Value = net.JoinHostPort(host, port)
37
+
38
+	return nil
39
+}
40
+
41
+func (t TypeHostPort) Get(defaultValue string) string {
42
+	if t.Value == "" {
43
+		return defaultValue
44
+	}
45
+
46
+	return t.Value
47
+}
48
+
49
+func (t *TypeHostPort) UnmarshalText(data []byte) error {
50
+	return t.Set(string(data))
51
+}
52
+
53
+func (t TypeHostPort) MarshalText() ([]byte, error) {
54
+	return []byte(t.String()), nil
55
+}
56
+
57
+func (t TypeHostPort) String() string {
58
+	return t.Value
59
+}

+ 88
- 0
internal/config2/type_hostport_test.go Просмотреть файл

@@ -0,0 +1,88 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeHostPortTestStruct struct {
13
+	Value config2.TypeHostPort `json:"value"`
14
+}
15
+
16
+type TypeHostPortTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		":",
23
+		":800",
24
+		"127.0.0.1:8000000",
25
+		"12...:80",
26
+		"",
27
+		"localhost",
28
+		"google.com:",
29
+	}
30
+
31
+	for _, v := range testData {
32
+		data, err := json.Marshal(map[string]string{
33
+			"value": v,
34
+		})
35
+		suite.NoError(err)
36
+
37
+		suite.T().Run(v, func(t *testing.T) {
38
+			assert.Error(t, json.Unmarshal(data, &typeHostPortTestStruct{}))
39
+		})
40
+	}
41
+}
42
+
43
+func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
44
+	testData := []string{
45
+		"127.0.0.1:80",
46
+		"10.0.0.10:6553",
47
+	}
48
+
49
+	for _, v := range testData {
50
+		value := v
51
+
52
+		data, err := json.Marshal(map[string]string{
53
+			"value": v,
54
+		})
55
+		suite.NoError(err)
56
+
57
+		suite.T().Run(v, func(t *testing.T) {
58
+			testStruct := &typeHostPortTestStruct{}
59
+			assert.NoError(t, json.Unmarshal(data, testStruct))
60
+			assert.Equal(t, value, testStruct.Value.Value)
61
+		})
62
+	}
63
+}
64
+
65
+func (suite *TypeHostPortTestSuite) TestMarshalOk() {
66
+	testStruct := typeHostPortTestStruct{
67
+		Value: config2.TypeHostPort{
68
+			Value: "127.0.0.1:8000",
69
+		},
70
+	}
71
+
72
+	data, err := json.Marshal(testStruct)
73
+	suite.NoError(err)
74
+	suite.JSONEq(`{"value": "127.0.0.1:8000"}`, string(data))
75
+}
76
+
77
+func (suite *TypeHostPortTestSuite) TestGet() {
78
+	value := config2.TypeHostPort{}
79
+	suite.Equal("127.0.0.1:9000", value.Get("127.0.0.1:9000"))
80
+
81
+	value.Value = "127.0.0.1:80"
82
+	suite.Equal("127.0.0.1:80", value.Get("127.0.0.1:9000"))
83
+}
84
+
85
+func TestTypeHostPort(t *testing.T) {
86
+	t.Parallel()
87
+	suite.Run(t, &TypeHostPortTestSuite{})
88
+}

+ 33
- 0
internal/config2/type_http_path.go Просмотреть файл

@@ -0,0 +1,33 @@
1
+package config2
2
+
3
+import "strings"
4
+
5
+type TypeHTTPPath struct {
6
+	Value string
7
+}
8
+
9
+func (t *TypeHTTPPath) Set(value string) error {
10
+	t.Value = "/" + strings.Trim(value, "/")
11
+
12
+	return nil
13
+}
14
+
15
+func (t TypeHTTPPath) Get(defaultValue string) string {
16
+	if t.Value == "" {
17
+		return defaultValue
18
+	}
19
+
20
+	return t.Value
21
+}
22
+
23
+func (t *TypeHTTPPath) UnmarshalText(data []byte) error {
24
+	return t.Set(string(data))
25
+}
26
+
27
+func (t TypeHTTPPath) MarshalText() ([]byte, error) {
28
+	return []byte(t.String()), nil
29
+}
30
+
31
+func (t TypeHTTPPath) String() string {
32
+	return t.Value
33
+}

+ 67
- 0
internal/config2/type_http_path_test.go Просмотреть файл

@@ -0,0 +1,67 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeHTTPPathTestStruct struct {
13
+	Value config2.TypeHTTPPath `json:"value"`
14
+}
15
+
16
+type TypeHTTPPathTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeHTTPPathTestSuite) TestUnmarshalOk() {
21
+	testData := map[string]string{
22
+		"":      "/",
23
+		"/":     "/",
24
+		"/path": "/path",
25
+		"path":  "/path",
26
+	}
27
+
28
+	for k, v := range testData {
29
+		value := v
30
+
31
+		data, err := json.Marshal(map[string]string{
32
+			"value": k,
33
+		})
34
+		suite.NoError(err)
35
+
36
+		suite.T().Run(k, func(t *testing.T) {
37
+			testStruct := &typeHTTPPathTestStruct{}
38
+			assert.NoError(t, json.Unmarshal(data, testStruct))
39
+			assert.Equal(t, value, testStruct.Value.Get(""))
40
+		})
41
+	}
42
+}
43
+
44
+func (suite *TypeHTTPPathTestSuite) TestMarshalOk() {
45
+	value := typeHTTPPathTestStruct{
46
+		Value: config2.TypeHTTPPath{
47
+			Value: "/path",
48
+		},
49
+	}
50
+
51
+	data, err := json.Marshal(value)
52
+	suite.NoError(err)
53
+	suite.JSONEq(`{"value": "/path"}`, string(data))
54
+}
55
+
56
+func (suite *TypeHTTPPathTestSuite) TestGet() {
57
+	value := config2.TypeHTTPPath{}
58
+	suite.Equal("/hello", value.Get("/hello"))
59
+
60
+	suite.NoError(value.Set("/lalala"))
61
+	suite.Equal("/lalala", value.Get("/hello"))
62
+}
63
+
64
+func TestTypeHTTPPath(t *testing.T) {
65
+	t.Parallel()
66
+	suite.Run(t, &TypeHTTPPathTestSuite{})
67
+}

+ 45
- 0
internal/config2/type_ip.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+)
7
+
8
+type TypeIP struct {
9
+	Value net.IP
10
+}
11
+
12
+func (t *TypeIP) Set(value string) error {
13
+	ip := net.ParseIP(value)
14
+	if ip == nil {
15
+		return fmt.Errorf("incorret ip %s", value)
16
+	}
17
+
18
+    t.Value = ip
19
+
20
+	return nil
21
+}
22
+
23
+func (t *TypeIP) Get(defaultValue net.IP) net.IP {
24
+	if len(t.Value) == 0 {
25
+		return defaultValue
26
+	}
27
+
28
+	return t.Value
29
+}
30
+
31
+func (t *TypeIP) UnmarshalText(data []byte) error {
32
+	return t.Set(string(data))
33
+}
34
+
35
+func (t TypeIP) MarshalText() ([]byte, error) {
36
+	return []byte(t.String()), nil
37
+}
38
+
39
+func (t TypeIP) String() string {
40
+	if len(t.Value) == 0 {
41
+		return ""
42
+	}
43
+
44
+	return t.Value.String()
45
+}

+ 104
- 0
internal/config2/type_ip_test.go Просмотреть файл

@@ -0,0 +1,104 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"net"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeIPTestStruct struct {
14
+	Value config2.TypeIP `json:"value"`
15
+}
16
+
17
+type TypeIPTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeIPTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"",
24
+		"....",
25
+		"0...",
26
+		"300.200.200.800",
27
+		"[]",
28
+	}
29
+
30
+	for _, v := range testData {
31
+		data, err := json.Marshal(map[string]string{
32
+			"value": v,
33
+		})
34
+		suite.NoError(err)
35
+
36
+		suite.T().Run(v, func(t *testing.T) {
37
+			assert.Error(t, json.Unmarshal(data, &typeIPTestStruct{}))
38
+		})
39
+	}
40
+}
41
+
42
+func (suite *TypeIPTestSuite) TestUnmarshalOk() {
43
+	testData := map[string]string{
44
+		"2001:0db8:85a3:0000:0000:8a2e:0370:7334": "2001:db8:85a3::8a2e:370:7334",
45
+		"127.0.0.1": "127.0.0.1",
46
+	}
47
+
48
+	for k, v := range testData {
49
+		expected := v
50
+
51
+		data, err := json.Marshal(map[string]string{
52
+			"value": k,
53
+		})
54
+		suite.NoError(err)
55
+
56
+		suite.T().Run(k, func(t *testing.T) {
57
+			testStruct := &typeIPTestStruct{}
58
+			assert.NoError(t, json.Unmarshal(data, testStruct))
59
+			assert.Equal(t, expected, testStruct.Value.Get(nil).String())
60
+		})
61
+	}
62
+}
63
+
64
+func (suite *TypeIPTestSuite) TestMarshalOk() {
65
+	testData := []string{
66
+		"2001:db8:85a3::8a2e:370:7334",
67
+		"127.0.0.1",
68
+	}
69
+
70
+	for _, v := range testData {
71
+		value := v
72
+
73
+		suite.T().Run(v, func(t *testing.T) {
74
+			testStruct := &typeIPTestStruct{
75
+				Value: config2.TypeIP{
76
+					Value: net.ParseIP(value),
77
+				},
78
+			}
79
+
80
+			encodedJSON, err := json.Marshal(testStruct)
81
+			assert.NoError(t, err)
82
+
83
+			expectedJSON, err := json.Marshal(map[string]string{
84
+				"value": value,
85
+			})
86
+			assert.NoError(t, err)
87
+
88
+			assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
89
+		})
90
+	}
91
+}
92
+
93
+func (suite *TypeIPTestSuite) TestGet() {
94
+	value := config2.TypeIP{}
95
+	suite.Equal("127.0.0.1", value.Get(net.ParseIP("127.0.0.1")).String())
96
+
97
+	suite.NoError(value.Set("127.0.0.2"))
98
+	suite.Equal("127.0.0.2", value.Get(net.ParseIP("127.0.0.1")).String())
99
+}
100
+
101
+func TestTypeIP(t *testing.T) {
102
+	t.Parallel()
103
+	suite.Run(t, &TypeIPTestSuite{})
104
+}

+ 40
- 0
internal/config2/type_metric_prefix.go Просмотреть файл

@@ -0,0 +1,40 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"regexp"
6
+)
7
+
8
+type TypeMetricPrefix struct {
9
+	Value string
10
+}
11
+
12
+func (t *TypeMetricPrefix) Set(value string) error {
13
+	if ok, err := regexp.MatchString("^[a-z0-9]+$", value); !ok || err != nil {
14
+        return fmt.Errorf("incorrect metric prefix %s: %w", value, err)
15
+	}
16
+
17
+    t.Value = value
18
+
19
+	return nil
20
+}
21
+
22
+func (t TypeMetricPrefix) Get(defaultValue string) string {
23
+	if t.Value == "" {
24
+		return defaultValue
25
+	}
26
+
27
+	return t.Value
28
+}
29
+
30
+func (t *TypeMetricPrefix) UnmarshalText(data []byte) error {
31
+	return t.Set(string(data))
32
+}
33
+
34
+func (t TypeMetricPrefix) MarshalText() ([]byte, error) {
35
+	return []byte(t.String()), nil
36
+}
37
+
38
+func (t TypeMetricPrefix) String() string {
39
+	return t.Value
40
+}

+ 70
- 0
internal/config2/type_metric_prefix_test.go Просмотреть файл

@@ -0,0 +1,70 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeMetricPrefixTestStruct struct {
13
+	Value config2.TypeMetricPrefix `json:"value"`
14
+}
15
+
16
+type TypeMetricPrefixTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"",
23
+		"-",
24
+		"hello/world",
25
+		"lala*",
26
+		"++sdf++",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typeMetricPrefixTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *TypeMetricPrefixTestSuite) TestUnmarshalOk() {
42
+	testStruct := &typeMetricPrefixTestStruct{}
43
+	suite.NoError(json.Unmarshal([]byte(`{"value": "mtg"}`), testStruct))
44
+	suite.Equal("mtg", testStruct.Value.Get("lalala"))
45
+}
46
+
47
+func (suite *TypeMetricPrefixTestSuite) TestMarshalOk() {
48
+	testStruct := &typeMetricPrefixTestStruct{
49
+		Value: config2.TypeMetricPrefix{
50
+			Value: "mtg",
51
+		},
52
+	}
53
+
54
+	data, err := json.Marshal(testStruct)
55
+	suite.NoError(err)
56
+	suite.JSONEq(`{"value": "mtg"}`, string(data))
57
+}
58
+
59
+func (suite *TypeMetricPrefixTestSuite) TestGet() {
60
+	value := config2.TypeMetricPrefix{}
61
+	suite.Equal("lalala", value.Get("lalala"))
62
+
63
+	value.Value = "mtg"
64
+	suite.Equal("mtg", value.Get("lalala"))
65
+}
66
+
67
+func TestTypeMetricPrefix(t *testing.T) {
68
+	t.Parallel()
69
+	suite.Run(t, &TypeMetricPrefixTestSuite{})
70
+}

+ 45
- 0
internal/config2/type_port.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+type TypePort struct {
9
+	Value uint16
10
+}
11
+
12
+func (t *TypePort) Set(value string) error {
13
+	portValue, err := strconv.ParseUint(value, 10, 16)
14
+	if err != nil {
15
+		return fmt.Errorf("incorrect port number (%v): %w", value, err)
16
+	}
17
+
18
+	if portValue == 0 {
19
+		return fmt.Errorf("incorrect port number (%s)", value)
20
+	}
21
+
22
+    t.Value = uint16(portValue)
23
+
24
+	return nil
25
+}
26
+
27
+func (t TypePort) Get(defaultValue uint16) uint16 {
28
+	if t.Value == 0 {
29
+		return defaultValue
30
+	}
31
+
32
+	return t.Value
33
+}
34
+
35
+func (t *TypePort) UnmarshalJSON(data []byte) error {
36
+	return t.Set(string(data))
37
+}
38
+
39
+func (t TypePort) MarshalJSON() ([]byte, error) {
40
+	return []byte(t.String()), nil
41
+}
42
+
43
+func (t TypePort) String() string {
44
+	return strconv.Itoa(int(t.Value))
45
+}

+ 71
- 0
internal/config2/type_port_test.go Просмотреть файл

@@ -0,0 +1,71 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typePortTestStruct struct {
13
+	Value config2.TypePort `json:"value"`
14
+}
15
+
16
+type TypePortTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypePortTestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"",
23
+		"port",
24
+		"0",
25
+		"-1",
26
+		"1.5",
27
+		"70000",
28
+	}
29
+
30
+	for _, v := range testData {
31
+		data, err := json.Marshal(map[string]string{
32
+			"value": v,
33
+		})
34
+		suite.NoError(err)
35
+
36
+		suite.T().Run(v, func(t *testing.T) {
37
+			assert.Error(t, json.Unmarshal(data, &typePortTestStruct{}))
38
+		})
39
+	}
40
+}
41
+
42
+func (suite *TypePortTestSuite) TestUnmarshalOk() {
43
+	testStruct := &typePortTestStruct{}
44
+	suite.NoError(json.Unmarshal([]byte(`{"value": 5}`), testStruct))
45
+	suite.EqualValues(5, testStruct.Value.Value)
46
+}
47
+
48
+func (suite *TypePortTestSuite) TestMarshalOk() {
49
+	testStruct := &typePortTestStruct{
50
+		Value: config2.TypePort{
51
+			Value: 10,
52
+		},
53
+	}
54
+
55
+	data, err := json.Marshal(testStruct)
56
+	suite.NoError(err)
57
+	suite.JSONEq(`{"value":10}`, string(data))
58
+}
59
+
60
+func (suite *TypePortTestSuite) TestGet() {
61
+	value := config2.TypePort{}
62
+	suite.EqualValues(10, value.Get(10))
63
+
64
+	value.Value = 100
65
+	suite.EqualValues(100, value.Get(10))
66
+}
67
+
68
+func TestTypePort(t *testing.T) {
69
+	t.Parallel()
70
+	suite.Run(t, &TypePortTestSuite{})
71
+}

+ 62
- 0
internal/config2/type_prefer_ip.go Просмотреть файл

@@ -0,0 +1,62 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+const (
9
+	// TypePreferIPPreferIPv4 states that you prefer to use IPv4 addresses
10
+	// but IPv6 is also possible.
11
+	TypePreferIPPreferIPv4 = "prefer-ipv4"
12
+
13
+	// TypePreferIPPreferIPv6 states that you prefer to use IPv6 addresses
14
+	// but IPv4 is also possible.
15
+	TypePreferIPPreferIPv6 = "prefer-ipv6"
16
+
17
+	// TypePreferOnlyIPv4 states that you prefer to use IPv4 addresses
18
+	// only.
19
+	TypePreferOnlyIPv4 = "only-ipv4"
20
+
21
+	// TypePreferOnlyIPv6 states that you prefer to use IPv6 addresses
22
+	// only.
23
+	TypePreferOnlyIPv6 = "only-ipv6"
24
+)
25
+
26
+type TypePreferIP struct {
27
+	Value string
28
+}
29
+
30
+func (t *TypePreferIP) Set(value string) error {
31
+	value = strings.ToLower(value)
32
+
33
+	switch value {
34
+	case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6,
35
+		TypePreferOnlyIPv4, TypePreferOnlyIPv6:
36
+        t.Value = value
37
+
38
+		return nil
39
+	default:
40
+		return fmt.Errorf("unsupported ip preference: %s", value)
41
+	}
42
+}
43
+
44
+func (t *TypePreferIP) Get(defaultValue string) string {
45
+	if t.Value == "" {
46
+		return defaultValue
47
+	}
48
+
49
+	return t.Value
50
+}
51
+
52
+func (t *TypePreferIP) UnmarshalText(data []byte) error {
53
+	return t.Set(string(data))
54
+}
55
+
56
+func (t TypePreferIP) MarshalText() ([]byte, error) {
57
+	return []byte(t.String()), nil
58
+}
59
+
60
+func (t TypePreferIP) String() string {
61
+	return t.Value
62
+}

+ 113
- 0
internal/config2/type_prefer_ip_test.go Просмотреть файл

@@ -0,0 +1,113 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typePreferIPTestStruct struct {
14
+	Value config2.TypePreferIP `json:"value"`
15
+}
16
+
17
+type TypePreferIPTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypePreferIPTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"",
24
+		"prefer",
25
+		"preferipv4",
26
+		config2.TypePreferIPPreferIPv4 + "_",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typePreferIPTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
42
+	testData := []string{
43
+		config2.TypePreferIPPreferIPv4,
44
+		config2.TypePreferIPPreferIPv6,
45
+		config2.TypePreferOnlyIPv4,
46
+		config2.TypePreferOnlyIPv6,
47
+		strings.ToTitle(config2.TypePreferOnlyIPv4),
48
+		strings.ToTitle(config2.TypePreferOnlyIPv6),
49
+		strings.ToTitle(config2.TypePreferIPPreferIPv4),
50
+		strings.ToTitle(config2.TypePreferIPPreferIPv6),
51
+	}
52
+
53
+	for _, v := range testData {
54
+		value := v
55
+
56
+		data, err := json.Marshal(map[string]string{
57
+			"value": v,
58
+		})
59
+		suite.NoError(err)
60
+
61
+		suite.T().Run(v, func(t *testing.T) {
62
+			testStruct := &typePreferIPTestStruct{}
63
+			assert.NoError(t, json.Unmarshal(data, testStruct))
64
+			assert.Equal(t, strings.ToLower(value), testStruct.Value.Value)
65
+		})
66
+	}
67
+}
68
+
69
+func (suite *TypePreferIPTestSuite) TestMarshalOk() {
70
+	testData := []string{
71
+		config2.TypePreferIPPreferIPv4,
72
+		config2.TypePreferIPPreferIPv6,
73
+		config2.TypePreferOnlyIPv4,
74
+		config2.TypePreferOnlyIPv6,
75
+	}
76
+
77
+	for _, v := range testData {
78
+		value := v
79
+
80
+		suite.T().Run(v, func(t *testing.T) {
81
+			testStruct := &typePreferIPTestStruct{
82
+				Value: config2.TypePreferIP{
83
+					Value: value,
84
+				},
85
+			}
86
+
87
+			encodedJSON, err := json.Marshal(testStruct)
88
+			assert.NoError(t, err)
89
+
90
+			expectedJSON, err := json.Marshal(map[string]string{
91
+				"value": value,
92
+			})
93
+			assert.NoError(t, err)
94
+
95
+			assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
96
+		})
97
+	}
98
+}
99
+
100
+func (suite *TypePreferIPTestSuite) TestGet() {
101
+	value := config2.TypePreferIP{}
102
+	suite.Equal(config2.TypePreferIPPreferIPv4,
103
+		value.Get(config2.TypePreferIPPreferIPv4))
104
+
105
+	suite.NoError(value.Set(config2.TypePreferIPPreferIPv6))
106
+	suite.Equal(config2.TypePreferIPPreferIPv6,
107
+		value.Get(config2.TypePreferIPPreferIPv4))
108
+}
109
+
110
+func TestTypePreferIP(t *testing.T) {
111
+	t.Parallel()
112
+	suite.Run(t, &TypePreferIPTestSuite{})
113
+}

+ 61
- 0
internal/config2/type_proxy_url.go Просмотреть файл

@@ -0,0 +1,61 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+)
8
+
9
+const typeProxyURLDefaultSOCKS5Port = "1080"
10
+
11
+type TypeProxyURL struct {
12
+	Value *url.URL
13
+}
14
+
15
+func (t *TypeProxyURL) Set(value string) error {
16
+	parsedURL, err := url.Parse(value)
17
+	if err != nil {
18
+		return fmt.Errorf("Value is not corect URL (%s): %w", value, err)
19
+	}
20
+
21
+	if parsedURL.Host == "" {
22
+		return fmt.Errorf("url has to have a schema: %s", value)
23
+	}
24
+
25
+	if parsedURL.Scheme != "socks5" {
26
+		return fmt.Errorf("unsupported schema: %s", parsedURL.Scheme)
27
+	}
28
+
29
+	if _, _, err := net.SplitHostPort(parsedURL.Host); err != nil {
30
+		parsedURL.Host = net.JoinHostPort(parsedURL.Host,
31
+			typeProxyURLDefaultSOCKS5Port)
32
+	}
33
+
34
+	t.Value = parsedURL
35
+
36
+	return nil
37
+}
38
+
39
+func (t *TypeProxyURL) Get(defaultValue *url.URL) *url.URL {
40
+	if t.Value == nil {
41
+		return defaultValue
42
+	}
43
+
44
+	return t.Value
45
+}
46
+
47
+func (t *TypeProxyURL) UnmarshalText(data []byte) error {
48
+	return t.Set(string(data))
49
+}
50
+
51
+func (t TypeProxyURL) MarshalText() ([]byte, error) {
52
+	return []byte(t.String()), nil
53
+}
54
+
55
+func (t TypeProxyURL) String() string {
56
+	if t.Value == nil {
57
+		return ""
58
+	}
59
+
60
+	return t.Value.String()
61
+}

+ 95
- 0
internal/config2/type_proxy_url_test.go Просмотреть файл

@@ -0,0 +1,95 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"net/url"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeProxyURLTestStruct struct {
14
+	Value config2.TypeProxyURL `json:"value"`
15
+}
16
+
17
+type ProxyURLTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *ProxyURLTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"",
24
+		"socks5://",
25
+		"://lala",
26
+		"/path",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typeProxyURLTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *ProxyURLTestSuite) TestUnmarshalOk() {
42
+	testData := map[string]string{
43
+		"socks5://127.0.0.1/?open_threshold=1": "socks5://127.0.0.1:1080/?open_threshold=1",
44
+		"socks5://127.0.0.1:80":                "socks5://127.0.0.1:80",
45
+	}
46
+
47
+	for k, v := range testData {
48
+		value := v
49
+
50
+		data, err := json.Marshal(map[string]string{
51
+			"value": k,
52
+		})
53
+		suite.NoError(err)
54
+
55
+		suite.T().Run(k, func(t *testing.T) {
56
+			testStruct := &typeProxyURLTestStruct{}
57
+			assert.NoError(t, json.Unmarshal(data, testStruct))
58
+
59
+			parsed, _ := url.Parse(value)
60
+
61
+			assert.Equal(t, parsed.Scheme, testStruct.Value.Get(nil).Scheme)
62
+			assert.Equal(t, parsed.Host, testStruct.Value.Get(nil).Host)
63
+			assert.Equal(t, parsed.RawQuery, testStruct.Value.Get(nil).RawQuery)
64
+			assert.Equal(t, parsed.Path, testStruct.Value.Get(nil).Path)
65
+		})
66
+	}
67
+}
68
+
69
+func (suite *ProxyURLTestSuite) TestMarshalOk() {
70
+	parsed, _ := url.Parse("socks5://127.0.0.1:1080?open_threshold=1")
71
+	testStruct := &typeProxyURLTestStruct{
72
+		Value: config2.TypeProxyURL{
73
+			Value: parsed,
74
+		},
75
+	}
76
+
77
+	encodedJSON, err := json.Marshal(testStruct)
78
+	suite.NoError(err)
79
+	suite.JSONEq(`{"value": "socks5://127.0.0.1:1080?open_threshold=1"}`, string(encodedJSON))
80
+}
81
+
82
+func (suite *ProxyURLTestSuite) TestGet() {
83
+	emptyURL := &url.URL{}
84
+
85
+	value := config2.TypeProxyURL{}
86
+	suite.Equal(emptyURL, value.Get(emptyURL))
87
+
88
+	value.Value = &url.URL{}
89
+	suite.Equal(value.Value, value.Get(emptyURL))
90
+}
91
+
92
+func TestTypeProxyURL(t *testing.T) {
93
+	t.Parallel()
94
+	suite.Run(t, &ProxyURLTestSuite{})
95
+}

+ 58
- 0
internal/config2/type_statsd_tag_format.go Просмотреть файл

@@ -0,0 +1,58 @@
1
+package config2
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+const (
9
+	// TypeStatsdTagFormatInfluxdb defines a tag format compatible with
10
+	// InfluxDB.
11
+	TypeStatsdTagFormatInfluxdb = "influxdb"
12
+
13
+	// TypeStatsdTagFormatDatadog defines a tag format compatible with
14
+	// DataDog.
15
+	TypeStatsdTagFormatDatadog = "datadog"
16
+
17
+	// TypeStatsdTagFormatGraphite defines a tag format compatible with
18
+	// Graphite.
19
+	TypeStatsdTagFormatGraphite = "graphite"
20
+)
21
+
22
+type TypeStatsdTagFormat struct {
23
+	Value string
24
+}
25
+
26
+func (t *TypeStatsdTagFormat) Set(value string) error {
27
+	lowercasedValue := strings.ToLower(value)
28
+
29
+	switch lowercasedValue {
30
+	case TypeStatsdTagFormatDatadog, TypeStatsdTagFormatInfluxdb,
31
+		TypeStatsdTagFormatGraphite:
32
+        t.Value = lowercasedValue
33
+
34
+		return nil
35
+	default:
36
+		return fmt.Errorf("unknown tag format %s", value)
37
+	}
38
+}
39
+
40
+func (t TypeStatsdTagFormat) Get(defaultValue string) string {
41
+	if t.Value == "" {
42
+		return defaultValue
43
+	}
44
+
45
+	return t.Value
46
+}
47
+
48
+func (t *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
49
+	return t.Set(string(data))
50
+}
51
+
52
+func (t *TypeStatsdTagFormat) MarshalText() ([]byte, error) {
53
+	return []byte(t.String()), nil
54
+}
55
+
56
+func (t *TypeStatsdTagFormat) String() string {
57
+	return t.Value
58
+}

+ 108
- 0
internal/config2/type_statsd_tag_format_test.go Просмотреть файл

@@ -0,0 +1,108 @@
1
+package config2_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config2"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeStatsdTagFormatTestStruct struct {
14
+	Value config2.TypeStatsdTagFormat `json:"value"`
15
+}
16
+
17
+type StatsdTagFormatTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *StatsdTagFormatTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"",
24
+		"dogdog",
25
+	}
26
+
27
+	for _, v := range testData {
28
+		data, err := json.Marshal(map[string]string{
29
+			"value": v,
30
+		})
31
+		suite.NoError(err)
32
+
33
+		suite.T().Run(v, func(t *testing.T) {
34
+			assert.Error(t, json.Unmarshal(data, &typeStatsdTagFormatTestStruct{}))
35
+		})
36
+	}
37
+}
38
+
39
+func (suite *StatsdTagFormatTestSuite) TestUnmarshalOk() {
40
+	testData := []string{
41
+		config2.TypeStatsdTagFormatInfluxdb,
42
+		config2.TypeStatsdTagFormatGraphite,
43
+		config2.TypeStatsdTagFormatDatadog,
44
+		strings.ToUpper(config2.TypeStatsdTagFormatInfluxdb),
45
+		strings.ToUpper(config2.TypeStatsdTagFormatGraphite),
46
+		strings.ToUpper(config2.TypeStatsdTagFormatDatadog),
47
+	}
48
+
49
+	for _, v := range testData {
50
+		value := v
51
+
52
+		data, err := json.Marshal(map[string]string{
53
+			"value": v,
54
+		})
55
+		suite.NoError(err)
56
+
57
+		suite.T().Run(v, func(t *testing.T) {
58
+			testStruct := &typeStatsdTagFormatTestStruct{}
59
+			assert.NoError(t, json.Unmarshal(data, testStruct))
60
+			assert.Equal(t, strings.ToLower(value), testStruct.Value.Value)
61
+		})
62
+	}
63
+}
64
+
65
+func (suite *StatsdTagFormatTestSuite) TestMarshalOk() {
66
+	testData := []string{
67
+		config2.TypeStatsdTagFormatInfluxdb,
68
+		config2.TypeStatsdTagFormatGraphite,
69
+		config2.TypeStatsdTagFormatDatadog,
70
+	}
71
+
72
+	for _, v := range testData {
73
+		value := v
74
+
75
+		suite.T().Run(v, func(t *testing.T) {
76
+			testStruct := &typeStatsdTagFormatTestStruct{
77
+				Value: config2.TypeStatsdTagFormat{
78
+					Value: value,
79
+				},
80
+			}
81
+
82
+			encodedJSON, err := json.Marshal(testStruct)
83
+			assert.NoError(t, err)
84
+
85
+			expectedJSON, err := json.Marshal(map[string]string{
86
+				"value": value,
87
+			})
88
+			assert.NoError(t, err)
89
+
90
+			assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
91
+		})
92
+	}
93
+}
94
+
95
+func (suite *StatsdTagFormatTestSuite) TestGet() {
96
+	value := config2.TypeStatsdTagFormat{}
97
+	suite.Equal(config2.TypeStatsdTagFormatDatadog,
98
+		value.Get(config2.TypeStatsdTagFormatDatadog))
99
+
100
+	suite.NoError(value.Set(config2.TypeStatsdTagFormatInfluxdb))
101
+	suite.Equal(config2.TypeStatsdTagFormatInfluxdb,
102
+		value.Get(config2.TypeStatsdTagFormatDatadog))
103
+}
104
+
105
+func TestTypeStatsdTagFormat(t *testing.T) {
106
+	t.Parallel()
107
+	suite.Run(t, &StatsdTagFormatTestSuite{})
108
+}

Загрузка…
Отмена
Сохранить