Move config to internal

This commit is contained in:
9seconds
2021-04-02 21:41:02 +03:00
parent ea7033d569
commit 7bffdcdf4a
31 changed files with 15 additions and 15 deletions
+163
View File
@@ -0,0 +1,163 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/pelletier/go-toml"
)
type Config struct {
Debug bool `json:"debug"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bind-to"`
TCPBuffer TypeBytes `json:"tcp-buffer"`
PreferIP TypePreferIP `json:"prefer-ip"`
DomainFrontingPort TypePort `json:"domain-fronting-port"`
Concurrency uint `json:"concurrency"`
Defense struct {
Time struct {
Enabled bool `json:"enabled"`
AllowSkewness TypeDuration `json:"allow-skewness"`
} `json:"time"`
AntiReplay struct {
Enabled bool `json:"enabled"`
MaxSize TypeBytes `json:"max-size"`
ErrorRate TypeErrorRate `json:"error-rate"`
} `json:"anti-replay"`
Blocklist struct {
Enabled bool `json:"enabled"`
DownloadConcurrency uint `json:"download-concurrency"`
URLs []TypeBlocklistURI `json:"urls"`
UpdateEach TypeDuration `json:"update-each"`
} `json:"blocklist"`
} `json:"defense"`
Network struct {
Timeout struct {
TCP TypeDuration `json:"tcp"`
HTTP TypeDuration `json:"http"`
Idle TypeDuration `json:"idle"`
} `json:"timeout"`
DOHIP TypeIP `json:"doh-ip"`
Proxies []TypeURL `json:"proxies"`
} `json:"network"`
Stats struct {
StatsD struct {
Enabled bool `json:"enabled"`
Address TypeHostPort `json:"address"`
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
TagFormat TypeStatsdTagFormat `json:"tag-format"`
} `json:"statsd"`
Prometheus struct {
Enabled bool `json:"enabled"`
BindTo TypeHostPort `json:"bind-to"`
HTTPPath TypeHTTPPath `json:"http-path"`
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
} `json:"prometheus"`
} `json:"stats"`
}
func (c *Config) Validate() error {
if !c.Secret.Valid() {
return fmt.Errorf("invalid secret %s", c.Secret.String())
}
if len(c.BindTo.HostValue(nil)) == 0 || c.BindTo.PortValue(0) == 0 {
return fmt.Errorf("incorrect bind-to parameter %s", c.BindTo.String())
}
return nil
}
func (c *Config) String() string {
buf := &bytes.Buffer{}
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
if err := encoder.Encode(c); err != nil {
panic(err)
}
return buf.String()
}
type configRaw struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bind-to"`
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
Defense struct {
Time struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
AllowSkewness string `toml:"allow-skewness" json:"allow-skewness,omitempty"`
} `toml:"time" json:"time,omitempty"`
AntiReplay struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"max-size,omitempty"`
ErrorRate float64 `toml:"error-rate" json:"error-rate,omitempty"`
} `toml:"anti-replay" json:"anti-replay,omitempty"`
Blocklist struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
DownloadConcurrency uint `toml:"download-concurrency" json:"download-concurrency,omitempty"`
URLs []string `toml:"urls" json:"urls,omitempty"`
UpdateEach string `toml:"update-each" json:"update-each,omitempty"`
} `toml:"blocklist" json:"blocklist,omitempty"`
} `toml:"defense" json:"defense,omitempty"`
Network struct {
Timeout struct {
TCP string `toml:"tcp" json:"tcp,omitempty"`
HTTP string `toml:"http" json:"http,omitempty"`
Idle string `toml:"idle" json:"idle,omitempty"`
} `toml:"timeout" json:"timeout,omitempty"`
DOHIP string `toml:"doh-ip" json:"doh-ip,omitempty"`
Proxies []string `toml:"proxies" json:"proxies,omitempty"`
} `toml:"network" json:"network,omitempty"`
Stats struct {
StatsD struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
Address string `toml:"address" json:"address,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
TagFormat string `toml:"tag-format" json:"tag-format,omitempty"`
} `toml:"statsd" json:"statsd,omitempty"`
Prometheus struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
BindTo string `toml:"bind-to" json:"bind-to,omitempty"`
HTTPPath string `toml:"http-path" json:"http-path,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
} `toml:"prometheus" json:"prometheus,omitempty"`
} `toml:"stats" json:"stats,omitempty"`
}
func Parse(rawData []byte) (*Config, error) {
rawConf := &configRaw{}
jsonBuf := &bytes.Buffer{}
conf := &Config{}
jsonEncoder := json.NewEncoder(jsonBuf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", "")
if err := toml.Unmarshal(rawData, rawConf); err != nil {
return nil, fmt.Errorf("cannot parse toml config: %w", err)
}
if err := jsonEncoder.Encode(rawConf); err != nil {
panic(err)
}
if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
return nil, fmt.Errorf("cannot parse a config: %w", err)
}
if err := conf.Validate(); err != nil {
return nil, fmt.Errorf("cannot validate config: %w", err)
}
return conf, nil
}
+54
View File
@@ -0,0 +1,54 @@
package config_test
import (
"os"
"path/filepath"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/suite"
)
type ConfigTestSuite struct {
suite.Suite
}
func (suite *ConfigTestSuite) ReadConfig(filename string) []byte {
data, err := os.ReadFile(filepath.Join("testdata", filename))
suite.NoError(err)
return data
}
func (suite *ConfigTestSuite) TestParseEmpty() {
_, err := config.Parse([]byte{})
suite.Error(err)
}
func (suite *ConfigTestSuite) TestParseBrokenToml() {
_, err := config.Parse(suite.ReadConfig("broken.toml"))
suite.Error(err)
}
func (suite *ConfigTestSuite) TestParseOnlySecret() {
_, err := config.Parse(suite.ReadConfig("only_secret.toml"))
suite.Error(err)
}
func (suite *ConfigTestSuite) TestParseMinimalConfig() {
conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
suite.NoError(err)
suite.Equal("7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t", conf.Secret.Base64())
suite.Equal("0.0.0.0:3128", conf.BindTo.String())
}
func (suite *ConfigTestSuite) TestString() {
conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
suite.NoError(err)
suite.NotEmpty(conf.String())
}
func TestConfig(t *testing.T) {
t.Parallel()
suite.Run(t, &ConfigTestSuite{})
}
+1
View File
@@ -0,0 +1 @@
s = sdfsdfds
+2
View File
@@ -0,0 +1,2 @@
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
bind-to = "0.0.0.0:3128"
+1
View File
@@ -0,0 +1 @@
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
+68
View File
@@ -0,0 +1,68 @@
package config
import (
"fmt"
"net/url"
"os"
"path/filepath"
)
type TypeBlocklistURI struct {
value string
}
func (c *TypeBlocklistURI) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := string(data)
if filepath.IsAbs(text) {
if _, err := os.Stat(text); os.IsNotExist(err) {
return fmt.Errorf("filepath %s does not exist", text)
}
c.value = text
return nil
}
parsedURL, err := url.Parse(text)
if err != nil {
return fmt.Errorf("incorrect url: %w", err)
}
switch parsedURL.Scheme {
case "http", "https": // nolint: goconst
default:
return fmt.Errorf("unknown schema %s", parsedURL.Scheme)
}
if parsedURL.Host == "" {
return fmt.Errorf("incorrect url %s", text)
}
c.value = parsedURL.String()
return nil
}
func (c TypeBlocklistURI) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c TypeBlocklistURI) String() string {
return c.value
}
func (c TypeBlocklistURI) IsRemote() bool {
return !filepath.IsAbs(c.value)
}
func (c TypeBlocklistURI) Value(defaultValue string) string {
if c.value == "" {
return defaultValue
}
return c.value
}
+177
View File
@@ -0,0 +1,177 @@
package config_test
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeBlocklistURITestStruct struct {
Value config.TypeBlocklistURI `json:"value"`
}
type TypeBlocklistURITestSuite struct {
suite.Suite
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalNil() {
typ := &config.TypeBlocklistURI{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeBlocklistURITestSuite) TestUnknownSchema() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("gopher://lalala")))
}
func (suite *TypeBlocklistURITestSuite) TestEmptyHost() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("https:///path")))
}
func (suite *TypeBlocklistURITestSuite) TestIncorrectURL() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("h:/--")))
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
rnd := make([]byte, 48)
rand.Read(rnd) // nolint: errcheck
unknownPath := base64.StdEncoding.EncodeToString(rnd)
testData := []string{
"1",
unknownPath,
"/" + unknownPath,
"http:/",
"gopher://lalalal",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeBlocklistURITestStruct{}))
})
}
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := []string{
"http://lalala",
filepath.Join(dir, "config.go"),
"https://lalala",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(""))
})
}
}
func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := []string{
"http://lalalal",
filepath.Join(dir, "config.go"),
}
for _, v := range testData {
name := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
}
func (suite *TypeBlocklistURITestSuite) TestValue() {
testStruct := &typeBlocklistURITestStruct{}
suite.Equal("http://lalala", testStruct.Value.Value("http://lalala"))
data, err := json.Marshal(map[string]string{
"value": "http://blablabla",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("http://blablabla", testStruct.Value.Value(""))
}
func (suite *TypeBlocklistURITestSuite) TestIsRemote() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := map[bool]string{
true: "http://lalalal",
false: filepath.Join(dir, "config.go"),
}
for k, v := range testData {
ok := k
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatBool(ok), func(t *testing.T) {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
if ok {
assert.True(t, testStruct.Value.IsRemote())
} else {
assert.False(t, testStruct.Value.IsRemote())
}
})
}
}
func TestTypeBlocklistURI(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeBlocklistURITestSuite{})
}
+54
View File
@@ -0,0 +1,54 @@
package config
import (
"fmt"
"strings"
"github.com/alecthomas/units"
)
type TypeBytes struct {
value units.Base2Bytes
}
func (c *TypeBytes) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
normalizedData := strings.ToUpper(string(data))
normalizedData = strings.ReplaceAll(normalizedData, "IB", "iB")
value, err := units.ParseBase2Bytes(normalizedData)
if err != nil {
return fmt.Errorf("incorrect bytes value: %w", err)
}
if value < 0 {
return fmt.Errorf("%d should be positive number", value)
}
c.value = value
return nil
}
func (c TypeBytes) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeBytes) String() string {
if c.value == 0 {
return ""
}
return strings.ToLower(c.value.String())
}
func (c TypeBytes) Value(defaultValue uint) uint {
if c.value == 0 {
return defaultValue
}
return uint(c.value)
}
+120
View File
@@ -0,0 +1,120 @@
package config_test
import (
"encoding/json"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeBytesTestStruct struct {
Value config.TypeBytes `json:"value"`
}
type TypeBytesTestSuite struct {
suite.Suite
}
func (suite *TypeBytesTestSuite) TestUnmarshalNil() {
typ := &config.TypeBytes{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeBytesTestSuite) TestUnmarshalFail() {
testData := []string{
"1m",
"1",
"-1kb",
"-1kib",
"-1QB",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeBytesTestStruct{}))
})
}
}
func (suite *TypeBytesTestSuite) TestUnmarshalOk() {
testData := map[string]uint{
"1b": 1,
"1kb": 1024,
"1kib": 1024,
"2mb": 2 * 1024 * 1024,
"2mib": 2 * 1024 * 1024,
}
for k, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": k,
})
suite.NoError(err)
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeBytesTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(0))
})
}
}
func (suite *TypeBytesTestSuite) TestMarshalOk() {
testData := []string{
"1b",
"1kib",
"2mib",
}
for _, v := range testData {
name := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typeBytesTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
}
func (suite *TypeBytesTestSuite) TestValue() {
testStruct := &typeBytesTestStruct{}
suite.EqualValues(0, testStruct.Value.Value(0))
suite.EqualValues(1, testStruct.Value.Value(1))
data, err := json.Marshal(map[string]string{
"value": "1kb",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(1024, testStruct.Value.Value(0))
suite.EqualValues(1024, testStruct.Value.Value(1))
}
func TestTypeBytes(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeBytesTestSuite{})
}
+46
View File
@@ -0,0 +1,46 @@
package config
import (
"fmt"
"strings"
"time"
)
type TypeDuration struct {
value time.Duration
}
func (c *TypeDuration) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
dur, err := time.ParseDuration(strings.ToLower(string(data)))
if err != nil {
return fmt.Errorf("incorrect duration: %w", err)
}
if dur < 0 {
return fmt.Errorf("%s should be positive duration", dur)
}
c.value = dur
return nil
}
func (c TypeDuration) MarshalText() ([]byte, error) {
return []byte(c.value.String()), nil
}
func (c TypeDuration) String() string {
return c.value.String()
}
func (c TypeDuration) Value(defaultValue time.Duration) time.Duration {
if c.value == 0 {
return defaultValue
}
return c.value
}
+118
View File
@@ -0,0 +1,118 @@
package config_test
import (
"encoding/json"
"testing"
"time"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeDurationTestStruct struct {
Value config.TypeDuration `json:"value"`
}
type TypeDurationTestSuite struct {
suite.Suite
}
func (suite *TypeDurationTestSuite) TestUnmarshalNil() {
typ := &config.TypeDuration{}
suite.NoError(typ.UnmarshalText(nil))
suite.EqualValues(0, typ.Value(0))
}
func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
testData := []string{
"1t",
"1",
"-1s",
"-1h",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeDurationTestStruct{}))
})
}
}
func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
testData := map[string]time.Duration{
"1s": time.Second,
"1m": time.Minute,
"2h1s": 2*time.Hour + time.Second,
}
for k, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": k,
})
suite.NoError(err)
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeDurationTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.Value(0))
})
}
}
func (suite *TypeDurationTestSuite) TestMarshalOk() {
testData := []string{
"1s",
"1m0s",
"2h0m1s",
}
for _, v := range testData {
name := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typeDurationTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
}
func (suite *TypeDurationTestSuite) TestValue() {
testStruct := &typeDurationTestStruct{}
suite.EqualValues(0, testStruct.Value.Value(0))
suite.Equal(time.Second, testStruct.Value.Value(time.Second))
data, err := json.Marshal(map[string]string{
"value": "1s",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal(time.Second, testStruct.Value.Value(0))
suite.Equal(time.Second, testStruct.Value.Value(time.Minute))
}
func TestTypeDuration(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeDurationTestSuite{})
}
+43
View File
@@ -0,0 +1,43 @@
package config
import (
"fmt"
"strconv"
)
const typeErrorRateIgnoreLess = 1e-8
type TypeErrorRate struct {
value float64
}
func (c *TypeErrorRate) UnmarshalJSON(data []byte) error {
value, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return fmt.Errorf("incorrect float value: %w", err)
}
if value <= 0 || value >= 100 {
return fmt.Errorf("%f should be 0 < x < 100", value)
}
c.value = value
return nil
}
func (c *TypeErrorRate) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeErrorRate) String() string {
return strconv.FormatFloat(c.value, 'f', -1, 64)
}
func (c TypeErrorRate) Value(defaultValue float64) float64 {
if c.value < typeErrorRateIgnoreLess {
return defaultValue
}
return c.value
}
+125
View File
@@ -0,0 +1,125 @@
package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeErrorRateTestStruct struct {
Value config.TypeErrorRate `json:"value"`
}
type TypeErrorRateTestSuite struct {
suite.Suite
}
func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
testData := []float64{
1000,
-100,
-0.0001,
}
for _, v := range testData {
data, err := json.Marshal(map[string]float64{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeErrorRateTestStruct{}))
})
}
data, err := json.Marshal(map[string]string{
"value": "hello",
})
suite.NoError(err)
suite.Error(json.Unmarshal(data, &typeErrorRateTestStruct{}))
}
func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
testData := []float64{
1,
55.5,
0.0001,
1e-6,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]float64{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
testStruct := &typeErrorRateTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.InEpsilon(t, value, testStruct.Value.Value(0), 1e-10)
})
}
}
func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
testData := []float64{
1,
55.5,
0.0001,
1e-6,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]float64{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
testStruct := &typeErrorRateTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
parsed, err := strconv.ParseFloat(testStruct.Value.String(), 64)
assert.NoError(t, err)
assert.InEpsilon(t, value, parsed, 1e-10)
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
parsed, err = strconv.ParseFloat(string(marshalled), 64)
assert.NoError(t, err)
assert.InEpsilon(t, value, parsed, 1e-10)
})
}
}
func (suite *TypeErrorRateTestSuite) TestValue() {
testStruct := &typeErrorRateTestStruct{}
suite.InEpsilon(1, testStruct.Value.Value(1), 1e-10)
suite.InEpsilon(2, testStruct.Value.Value(2), 1e-10)
data, err := json.Marshal(map[string]float64{
"value": 1,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.InEpsilon(1, testStruct.Value.Value(2), 1e-10)
suite.InEpsilon(1, testStruct.Value.Value(3), 1e-10)
}
func TestTypeErrorRate(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeErrorRateTestSuite{})
}
+67
View File
@@ -0,0 +1,67 @@
package config
import (
"fmt"
"net"
"strconv"
)
type TypeHostPort struct {
host TypeIP
port TypePort
}
func (c *TypeHostPort) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := string(data)
host, port, err := net.SplitHostPort(text)
if err != nil {
return fmt.Errorf("incorrect host:port syntax: %w", err)
}
if port == "" {
return fmt.Errorf("port in %s host:port pair cannot be empty", text)
}
if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
return fmt.Errorf("incorrect port in host:port: %w", err)
}
if err := c.host.UnmarshalText([]byte(host)); err != nil {
return fmt.Errorf("incorrect host: %w", err)
}
return nil
}
func (c TypeHostPort) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeHostPort) String() string {
return c.Value(net.IP{}, 0)
}
func (c TypeHostPort) HostValue(defaultValue net.IP) net.IP {
return c.host.Value(defaultValue)
}
func (c TypeHostPort) PortValue(defaultValue uint) uint {
return c.port.Value(defaultValue)
}
func (c TypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
host := c.HostValue(defaultHostValue)
port := c.PortValue(defaultPortValue)
hostStr := ""
if len(host) > 0 {
hostStr = host.String()
}
return net.JoinHostPort(hostStr, strconv.Itoa(int(port)))
}
+115
View File
@@ -0,0 +1,115 @@
package config_test
import (
"encoding/json"
"net"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeHostPortTestStruct struct {
Value config.TypeHostPort `json:"value"`
}
type TypeHostPortTestSuite struct {
suite.Suite
}
func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
testData := []string{
"10.0.0.10:aaa",
"10.0.0.10:",
":",
"xxx",
"xxx:80",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeHostPortTestStruct{}))
})
}
}
func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
testData := []string{
"10.0.0.10:80",
"0.0.0.0:80",
":8000",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeHostPortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(nil, 0))
})
}
}
func (suite *TypeHostPortTestSuite) TestMarshalOk() {
testData := []string{
"10.0.0.10:80",
"0.0.0.0:80",
":8000",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeHostPortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
})
}
}
func (suite *TypeHostPortTestSuite) TestValue() {
testStruct := &typeHostPortTestStruct{}
suite.EqualValues("127.0.0.1:80",
testStruct.Value.Value(net.ParseIP("127.0.0.1"), 80))
suite.EqualValues("127.1.0.1:80",
testStruct.Value.Value(net.ParseIP("127.1.0.1"), 80))
data, err := json.Marshal(map[string]string{
"value": "127.0.0.1:80",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(nil, 0))
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(net.ParseIP("10.0.0.10"), 3000))
}
func TestTypeHostPort(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeHostPortTestSuite{})
}
+35
View File
@@ -0,0 +1,35 @@
package config
import "strings"
type TypeHTTPPath struct {
value string
}
func (c *TypeHTTPPath) UnmarshalText(data []byte) error {
if len(data) > 0 {
c.value = "/" + strings.Trim(string(data), "/")
}
return nil
}
func (c TypeHTTPPath) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeHTTPPath) String() string {
if c.value == "" {
return "/"
}
return c.value
}
func (c TypeHTTPPath) Value(defaultValue string) string {
if c.value == "" {
return defaultValue
}
return c.value
}
+91
View File
@@ -0,0 +1,91 @@
package config_test
import (
"encoding/json"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeHTTPPathTestStruct struct {
Value config.TypeHTTPPath `json:"value"`
}
type TypeHTTPPathTestSuite struct {
suite.Suite
}
func (suite *TypeHTTPPathTestSuite) TestUnmarshal() {
testData := []string{
"/hello",
"hello",
"hello/",
"/hello/",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeHTTPPathTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, "/hello", testStruct.Value.Value(""))
})
}
}
func (suite *TypeHTTPPathTestSuite) TestMarshalOk() {
testData := map[string]string{
"": "/",
"/hello": "/hello",
"/hello/": "/hello",
"hello/": "/hello",
"hello": "/hello",
}
for k, v := range testData {
toPass := k
compareWith := v
data, err := json.Marshal(map[string]string{
"value": toPass,
})
suite.NoError(err)
suite.T().Run(toPass, func(t *testing.T) {
testStruct := &typeHTTPPathTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, compareWith, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, compareWith, string(marshalled))
})
}
}
func (suite *TypeHTTPPathTestSuite) TestValue() {
testStruct := &typeHTTPPathTestStruct{}
suite.Equal("/hello", testStruct.Value.Value("/hello"))
data, err := json.Marshal(map[string]string{
"value": "/map",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("/map", testStruct.Value.Value("/hello"))
}
func TestTypeHTTPPath(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeHTTPPathTestSuite{})
}
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"fmt"
"net"
)
type TypeIP struct {
value net.IP
}
func (c *TypeIP) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
ip := net.ParseIP(string(data))
if ip == nil {
return fmt.Errorf("incorrect ip address: %s", string(data))
}
c.value = ip
return nil
}
func (c *TypeIP) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeIP) String() string {
if len(c.value) > 0 {
return c.value.String()
}
return ""
}
func (c TypeIP) Value(defaultValue net.IP) net.IP {
if c.value == nil {
return defaultValue
}
return c.value
}
+115
View File
@@ -0,0 +1,115 @@
package config_test
import (
"encoding/json"
"net"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeIPTestStruct struct {
Value config.TypeIP `json:"value"`
}
type TypeIPTestSuite struct {
suite.Suite
}
func (suite *TypeIPTestSuite) TestUnmarshalFail() {
testData := []string{
"0.0.10",
"10.0.0.10:",
"xxx:80",
"2001:0db8:85a3:0000:0000:8a2e:4",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeIPTestStruct{}))
})
}
}
func (suite *TypeIPTestSuite) TestUnmarshalOk() {
testData := []string{
"0.0.0.0",
"10.0.0.10",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t,
net.ParseIP(value).String(),
testStruct.Value.Value(nil).String())
})
}
}
func (suite *TypeIPTestSuite) TestMarshalOk() {
testData := []string{
"0.0.0.0",
"10.0.0.10",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
}
for _, v := range testData {
value := net.ParseIP(v).String()
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
})
}
}
func (suite *TypeIPTestSuite) TestValue() {
testStruct := &typeIPTestStruct{}
suite.Empty(testStruct.Value.String())
suite.Nil(testStruct.Value.Value(nil))
suite.Equal("127.1.0.1", testStruct.Value.Value(net.ParseIP("127.1.0.1")).String())
data, err := json.Marshal(map[string]string{
"value": "127.0.0.1",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("127.0.0.1", testStruct.Value.Value(nil).String())
suite.Equal("127.0.0.1", testStruct.Value.Value(net.ParseIP("10.0.0.10")).String())
}
func TestTypeIP(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeIPTestSuite{})
}
+41
View File
@@ -0,0 +1,41 @@
package config
import (
"fmt"
"regexp"
)
type TypeMetricPrefix struct {
value string
}
func (c *TypeMetricPrefix) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
prefix := string(data)
if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
return fmt.Errorf("incorrect metric prefix: %s", prefix)
}
c.value = prefix
return nil
}
func (c TypeMetricPrefix) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeMetricPrefix) String() string {
return c.value
}
func (c TypeMetricPrefix) Value(defaultValue string) string {
if c.value == "" {
return defaultValue
}
return c.value
}
+115
View File
@@ -0,0 +1,115 @@
package config_test
import (
"encoding/json"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeMetricPrefixTestStruct struct {
Value config.TypeMetricPrefix `json:"value"`
}
type TypeMetricPrefixTestSuite struct {
suite.Suite
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalNil() {
typ := &config.TypeMetricPrefix{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
testData := []string{
"aaa.aaa",
"aaa-bbb",
"aaa:ccc",
"metric prefix",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeMetricPrefixTestStruct{}))
})
}
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalOk() {
testData := []string{
"mtg",
"mtg111",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeMetricPrefixTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.Value(""))
})
}
}
func (suite *TypeMetricPrefixTestSuite) TestMarshalOk() {
testData := []string{
"mtg",
"mtg111",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeMetricPrefixTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
})
}
}
func (suite *TypeMetricPrefixTestSuite) TestValue() {
testStruct := &typeMetricPrefixTestStruct{}
suite.Equal("mtg", testStruct.Value.Value("mtg"))
suite.Equal("vvv", testStruct.Value.Value("vvv"))
data, err := json.Marshal(map[string]string{
"value": "aaa",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("aaa", testStruct.Value.Value("mtg"))
suite.Equal("aaa", testStruct.Value.Value("vvv"))
}
func TestTypeMetricPrefix(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeMetricPrefixTestSuite{})
}
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"fmt"
"strconv"
)
type TypePort struct {
value uint
}
func (c *TypePort) UnmarshalJSON(data []byte) error {
if len(data) == 0 {
return nil
}
intValue, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("port number is not a number: %w", err)
}
if intValue == 0 || intValue >= 65536 {
return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
}
c.value = uint(intValue)
return nil
}
func (c *TypePort) MarshalJSON() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypePort) String() string {
return strconv.Itoa(int(c.value))
}
func (c TypePort) Value(defaultValue uint) uint {
if c.value == 0 {
return defaultValue
}
return c.value
}
+117
View File
@@ -0,0 +1,117 @@
package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typePortTestStruct struct {
Value config.TypePort `json:"value"`
}
type TypePortTestSuite struct {
suite.Suite
}
func (suite *TypePortTestSuite) TestUnmarshalNil() {
typ := &config.TypePort{}
suite.NoError(typ.UnmarshalJSON(nil))
suite.Equal("0", typ.String())
}
func (suite *TypePortTestSuite) TestUnmarshalFail() {
testData := []int{
-1,
1_000_000,
}
for _, v := range testData {
data, err := json.Marshal(map[string]int{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typePortTestStruct{}))
})
}
}
func (suite *TypePortTestSuite) TestUnmarshalOk() {
testData := []int{
1,
1_000,
65535,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]int{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
testStruct := &typePortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(0))
})
}
}
func (suite *TypePortTestSuite) TestMarshalOk() {
testData := map[string]int{
"1": 1,
"1000": 1000,
"65535": 65535,
}
for k, v := range testData {
name := k
value := v
data, err := json.Marshal(map[string]int{
"value": value,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typePortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
}
func (suite *TypePortTestSuite) TestValue() {
testStruct := &typePortTestStruct{}
suite.EqualValues(0, testStruct.Value.Value(0))
suite.EqualValues(1, testStruct.Value.Value(1))
data, err := json.Marshal(map[string]int{
"value": 5,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(5, testStruct.Value.Value(0))
suite.EqualValues(5, testStruct.Value.Value(1))
}
func TestTypePort(t *testing.T) {
t.Parallel()
suite.Run(t, &TypePortTestSuite{})
}
+50
View File
@@ -0,0 +1,50 @@
package config
import (
"fmt"
"strings"
)
const (
TypePreferIPPreferIPv4 = "prefer-ipv4"
TypePreferIPPreferIPv6 = "prefer-ipv6"
TypePreferOnlyIPv4 = "only-ipv4"
TypePreferOnlyIPv6 = "only-ipv6"
)
type TypePreferIP struct {
value string
}
func (c *TypePreferIP) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := strings.ToLower(string(data))
switch text {
case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6, TypePreferOnlyIPv4, TypePreferOnlyIPv6:
c.value = text
default:
return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
}
return nil
}
func (c TypePreferIP) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c *TypePreferIP) String() string {
return c.value
}
func (c *TypePreferIP) Value(defaultValue string) string {
if c.value == "" {
return defaultValue
}
return c.value
}
+142
View File
@@ -0,0 +1,142 @@
package config_test
import (
"encoding/json"
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typePreferIPTestStruct struct {
Value config.TypePreferIP `json:"value"`
}
type TypePreferIPTestSuite struct {
suite.Suite
}
func (suite *TypePreferIPTestSuite) TestUnmarshalNil() {
typ := &config.TypePreferIP{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypePreferIPTestSuite) TestUnmarshalFail() {
testData := []string{
"p",
"ipv4",
"onlyipv4",
"ipv6prefer",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typePreferIPTestStruct{}))
})
}
}
func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
testData := []string{
config.TypePreferIPPreferIPv4,
config.TypePreferIPPreferIPv6,
config.TypePreferOnlyIPv4,
config.TypePreferOnlyIPv6,
strings.ToUpper(config.TypePreferIPPreferIPv4),
strings.ToUpper(config.TypePreferIPPreferIPv6),
strings.ToUpper(config.TypePreferOnlyIPv4),
strings.ToUpper(config.TypePreferOnlyIPv6),
strings.ToLower(config.TypePreferIPPreferIPv4),
strings.ToLower(config.TypePreferIPPreferIPv6),
strings.ToLower(config.TypePreferOnlyIPv4),
strings.ToLower(config.TypePreferOnlyIPv6),
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typePreferIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t,
strings.ToLower(value),
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
})
}
}
func (suite *TypePreferIPTestSuite) TestMarshalOk() {
testData := []string{
config.TypePreferIPPreferIPv4,
config.TypePreferIPPreferIPv6,
config.TypePreferOnlyIPv4,
config.TypePreferOnlyIPv6,
strings.ToUpper(config.TypePreferIPPreferIPv4),
strings.ToUpper(config.TypePreferIPPreferIPv6),
strings.ToUpper(config.TypePreferOnlyIPv4),
strings.ToUpper(config.TypePreferOnlyIPv6),
strings.ToLower(config.TypePreferIPPreferIPv4),
strings.ToLower(config.TypePreferIPPreferIPv6),
strings.ToLower(config.TypePreferOnlyIPv4),
strings.ToLower(config.TypePreferOnlyIPv6),
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typePreferIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, strings.ToLower(value), string(marshalled))
})
}
}
func (suite *TypePreferIPTestSuite) TestValue() {
testStruct := &typePreferIPTestStruct{}
suite.EqualValues(config.TypePreferIPPreferIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
suite.EqualValues(config.TypePreferIPPreferIPv6,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
data, err := json.Marshal(map[string]string{
"value": config.TypePreferOnlyIPv4,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferOnlyIPv6))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
}
func TestTypePreferIP(t *testing.T) {
t.Parallel()
suite.Run(t, &TypePreferIPTestSuite{})
}
+49
View File
@@ -0,0 +1,49 @@
package config
import (
"fmt"
"strings"
)
const (
TypeStatsdTagFormatInfluxdb = "influxdb"
TypeStatsdTagFormatDatadog = "datadog"
TypeStatsdTagFormatGraphite = "graphite"
)
type TypeStatsdTagFormat struct {
value string
}
func (c *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := strings.ToLower(string(data))
switch text {
case TypeStatsdTagFormatInfluxdb, TypeStatsdTagFormatDatadog, TypeStatsdTagFormatGraphite:
c.value = text
default:
return fmt.Errorf("incorrect tag format value: %s", string(data))
}
return nil
}
func (c TypeStatsdTagFormat) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c *TypeStatsdTagFormat) String() string {
return c.value
}
func (c *TypeStatsdTagFormat) Value(defaultValue string) string {
if c.value == "" {
return defaultValue
}
return c.value
}
@@ -0,0 +1,136 @@
package config_test
import (
"encoding/json"
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeStatsdTagFormatTestStruct struct {
Value config.TypeStatsdTagFormat `json:"value"`
}
type TypeStatsdTagFormatTestSuite struct {
suite.Suite
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalNil() {
typ := &config.TypeStatsdTagFormat{}
suite.NoError(typ.UnmarshalText(nil))
suite.Equal("lalala", typ.Value("lalala"))
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalFail() {
testData := []string{
"p",
"ipv4",
"onlyipv4",
"ipv6prefer",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeStatsdTagFormatTestStruct{}))
})
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalOk() {
testData := []string{
config.TypeStatsdTagFormatDatadog,
config.TypeStatsdTagFormatInfluxdb,
config.TypeStatsdTagFormatGraphite,
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
strings.ToLower(config.TypeStatsdTagFormatDatadog),
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
strings.ToLower(config.TypeStatsdTagFormatGraphite),
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeStatsdTagFormatTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t,
strings.ToLower(value),
testStruct.Value.Value(config.TypeStatsdTagFormatDatadog))
})
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestMarshalOk() {
testData := []string{
config.TypeStatsdTagFormatDatadog,
config.TypeStatsdTagFormatInfluxdb,
config.TypeStatsdTagFormatGraphite,
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
strings.ToLower(config.TypeStatsdTagFormatDatadog),
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
strings.ToLower(config.TypeStatsdTagFormatGraphite),
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeStatsdTagFormatTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, strings.ToLower(value), string(marshalled))
})
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestValue() {
testStruct := &typePreferIPTestStruct{}
suite.EqualValues(config.TypePreferIPPreferIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
suite.EqualValues(config.TypePreferIPPreferIPv6,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
data, err := json.Marshal(map[string]string{
"value": config.TypePreferOnlyIPv4,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferOnlyIPv6))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
}
func TestTypeStatsdTagFormat(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeStatsdTagFormatTestSuite{})
}
+71
View File
@@ -0,0 +1,71 @@
package config
import (
"fmt"
"net"
"net/url"
)
type TypeURL struct {
value *url.URL
}
func (c *TypeURL) UnmarshalText(data []byte) error { // nolint: cyclop
if len(data) == 0 {
return nil
}
value, err := url.Parse(string(data))
if err != nil {
return fmt.Errorf("incorrect URL: %w", err)
}
switch value.Scheme {
case "http", "https", "socks5":
case "":
return fmt.Errorf("url %s has to have a schema", value)
default:
return fmt.Errorf("unsupported schema %s", value.Scheme)
}
if value.Host == "" {
return fmt.Errorf("url %s has to have a host", value)
}
if _, _, err := net.SplitHostPort(value.Host); err != nil {
switch value.Scheme {
case "http":
value.Host = net.JoinHostPort(value.Host, "80")
case "https":
value.Host = net.JoinHostPort(value.Host, "443")
case "socks5":
value.Host = net.JoinHostPort(value.Host, "1080")
default:
return fmt.Errorf("cannot set a default port for %s", value)
}
}
c.value = value
return nil
}
func (c *TypeURL) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeURL) String() string {
if c.value == nil {
return ""
}
return c.value.String()
}
func (c TypeURL) Value(defaultValue *url.URL) *url.URL {
if c.value == nil {
return defaultValue
}
return c.value
}
+107
View File
@@ -0,0 +1,107 @@
package config_test
import (
"encoding/json"
"net/url"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeURLTestStruct struct {
Value config.TypeURL `json:"value"`
}
type TypeURLTestSuite struct {
suite.Suite
}
func (suite *TypeURLTestSuite) TestUnmarshalNil() {
u, _ := url.Parse("https://google.com")
typ := &config.TypeURL{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
suite.Equal("https://google.com", typ.Value(u).String())
}
func (suite *TypeURLTestSuite) TestUnmarshalFail() {
testData := []string{
"http:/aaa.com",
"ipv4",
"111",
"://111",
"http://aaa.com:xxx",
"gopher://aaa.com:888",
"gopher://aaa.com",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeURLTestStruct{}))
})
}
}
func (suite *TypeURLTestSuite) TestUnmarshalOk() {
testData := map[string]string{
"https://10.0.0.10:80": "https://10.0.0.10:80",
"https://10.0.0.10:443": "https://10.0.0.10",
"http://10.0.0.10:8": "http://10.0.0.10:8",
"http://10.0.0.10:80": "http://10.0.0.10",
"socks5://10.0.0.10:1080": "socks5://10.0.0.10",
"socks5://10.0.0.10:888": "socks5://10.0.0.10:888",
}
for k, v := range testData {
expected := k
actual := v
data, err := json.Marshal(map[string]string{
"value": actual,
})
suite.NoError(err)
suite.T().Run(actual, func(t *testing.T) {
testStruct := &typeURLTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, expected, testStruct.Value.Value(nil).String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, expected, string(marshalled))
})
}
}
func (suite *TypeURLTestSuite) TestValue() {
testStruct := &typeURLTestStruct{}
u1, _ := url.Parse("https://10.0.0.10:80")
u2, _ := url.Parse("https://10.1.0.10:80")
suite.Equal("https://10.0.0.10:80", testStruct.Value.Value(u1).String())
suite.Equal("https://10.1.0.10:80", testStruct.Value.Value(u2).String())
data, err := json.Marshal(map[string]string{
"value": "http://127.0.0.1:80",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u1).String())
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u2).String())
}
func TestTypeURL(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeURLTestSuite{})
}