From 1ffc9c42054e208a01d3e70e6b6f3e1781e798f8 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Mon, 5 Apr 2021 17:52:31 +0200 Subject: Moved code around to conform best practices --- pkg/config/config.go | 55 +++++++++++++ pkg/config/config_test.go | 54 +++++++++++++ pkg/config/error.go | 92 ++++++++++++++++++++++ pkg/config/error_test.go | 20 +++++ pkg/config/test_data/invalid.yaml | 1 + pkg/config/test_data/invalid_address.yaml | 3 + .../test_data/invalid_address_unresolvable.yaml | 3 + pkg/config/test_data/invalid_port.yaml | 3 + pkg/config/test_data/invalid_token.yaml | 3 + pkg/config/test_data/minimal.yaml | 3 + pkg/config/test_data/minimal_with_hostname.yaml | 3 + 11 files changed, 240 insertions(+) create mode 100644 pkg/config/config.go create mode 100644 pkg/config/config_test.go create mode 100644 pkg/config/error.go create mode 100644 pkg/config/error_test.go create mode 100644 pkg/config/test_data/invalid.yaml create mode 100644 pkg/config/test_data/invalid_address.yaml create mode 100644 pkg/config/test_data/invalid_address_unresolvable.yaml create mode 100644 pkg/config/test_data/invalid_port.yaml create mode 100644 pkg/config/test_data/invalid_token.yaml create mode 100644 pkg/config/test_data/minimal.yaml create mode 100644 pkg/config/test_data/minimal_with_hostname.yaml (limited to 'pkg/config') diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..f97467a --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,55 @@ +package config + +import ( + "net" + "os" + "regexp" + + "gopkg.in/yaml.v3" +) + +var validToken = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) + +type Config struct { + // Address is the hostname or ip the web server will listen to + Address string `yaml:"address",default:"127.0.0.1"` + Port string `yaml:"port",default:"8080"` + // Token is the sncf api token + Token string `yaml:"token"` +} + +func (c *Config) validate() error { + // address + if ip := net.ParseIP(c.Address); ip == nil { + if _, err := net.LookupIP(c.Address); err != nil { + return newInvalidAddressError(c.Address, err) + } + } + // port + if _, err := net.LookupPort("tcp", c.Port); err != nil { + return newInvalidPortError(c.Port, err) + } + // token + if ok := validToken.MatchString(c.Token); !ok { + return newInvalidTokenError(c.Token) + } + return nil +} + +// LoadFile loads the c from a given file +func LoadFile(path string) (*Config, error) { + var c *Config + f, errOpen := os.Open(path) + if errOpen != nil { + return nil, newOpenError(path, errOpen) + } + defer f.Close() + decoder := yaml.NewDecoder(f) + if err := decoder.Decode(&c); err != nil { + return nil, newDecodeError(path, err) + } + if err := c.validate(); err != nil { + return nil, err + } + return c, nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..3904b5d --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,54 @@ +package config + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadFile(t *testing.T) { + // Minimal yaml file + minimalConfig := Config{ + Address: "127.0.0.2", + Port: "8082", + Token: "12345678-9abc-def0-1234-56789abcdef0", + } + + // Minimal yaml file with hostname resolving + minimalConfigWithResolving := Config{ + Address: "localhost", + Port: "8082", + Token: "12345678-9abc-def0-1234-56789abcdef0", + } + + // Test cases + testCases := []struct { + name string + input string + expected *Config + expectedError interface{} + }{ + {"Non existant file", "test_data/non-existant", nil, &OpenError{}}, + {"Invalid file content", "test_data/invalid.yaml", nil, &DecodeError{}}, + {"Invalid address should fail to load", "test_data/invalid_address.yaml", nil, &InvalidAddressError{}}, + {"Unresolvable address should fail to load", "test_data/invalid_address_unresolvable.yaml", nil, &InvalidAddressError{}}, + {"Invalid port should fail to load", "test_data/invalid_port.yaml", nil, &InvalidPortError{}}, + {"Invalid token should fail to load", "test_data/invalid_token.yaml", nil, &InvalidTokenError{}}, + {"Minimal config", "test_data/minimal.yaml", &minimalConfig, nil}, + {"Minimal config with resolving", "test_data/minimal_with_hostname.yaml", &minimalConfigWithResolving, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + valid, err := LoadFile(tc.input) + if tc.expectedError != nil { + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(tc.expectedError), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(tc.expectedError)) + } else { + require.NoError(t, err) + } + assert.Equal(t, tc.expected, valid, "Invalid value") + }) + } +} diff --git a/pkg/config/error.go b/pkg/config/error.go new file mode 100644 index 0000000..c49b6a9 --- /dev/null +++ b/pkg/config/error.go @@ -0,0 +1,92 @@ +package config + +import "fmt" + +type ErrorType int + +// file open configuration file error +type OpenError struct { + path string + err error +} + +func (e *OpenError) Error() string { + return fmt.Sprintf("Failed to open configuration file : %s", e.path) +} +func (e *OpenError) Unwrap() error { return e.err } + +func newOpenError(path string, err error) error { + return &OpenError{ + path: path, + err: err, + } +} + +// Yaml configuration file decoding error +type DecodeError struct { + path string + err error +} + +func (e *DecodeError) Error() string { + return fmt.Sprintf("Failed to decode configuration file : %s", e.path) +} +func (e *DecodeError) Unwrap() error { return e.err } + +func newDecodeError(path string, err error) error { + return &DecodeError{ + path: path, + err: err, + } +} + +// Invalid address field error +type InvalidAddressError struct { + address string + err error +} + +func (e *InvalidAddressError) Error() string { + return fmt.Sprintf("Invalid address %s : it must be a valid ipv4 address, ipv6 address, or resolvable name", e.address) +} +func (e *InvalidAddressError) Unwrap() error { return e.err } + +func newInvalidAddressError(address string, err error) error { + return &InvalidAddressError{ + address: address, + err: err, + } +} + +// Invalid port field error +type InvalidPortError struct { + port string + err error +} + +func (e *InvalidPortError) Error() string { + return fmt.Sprintf("Invalid port %s : it must be a valid port number or tcp service name", e.port) +} +func (e *InvalidPortError) Unwrap() error { return e.err } + +func newInvalidPortError(port string, err error) error { + return &InvalidPortError{ + port: port, + err: err, + } +} + +// Invalid token field error +type InvalidTokenError struct { + token string +} + +func (e *InvalidTokenError) Error() string { + return fmt.Sprintf("Invalid token %s : it must be an hexadecimal string that lookslike 12345678-9abc-def0-1234-56789abcdef0", e.token) +} + +func newInvalidTokenError(token string) error { + return &InvalidTokenError{ + token: token, + } +} diff --git a/pkg/config/error_test.go b/pkg/config/error_test.go new file mode 100644 index 0000000..f9807c1 --- /dev/null +++ b/pkg/config/error_test.go @@ -0,0 +1,20 @@ +package config + +import "testing" + +func TestErrorsCoverage(t *testing.T) { + openErr := OpenError{} + _ = openErr.Error() + _ = openErr.Unwrap() + decodeErr := DecodeError{} + _ = decodeErr.Error() + _ = decodeErr.Unwrap() + invalidAddressErr := InvalidAddressError{} + _ = invalidAddressErr.Error() + _ = invalidAddressErr.Unwrap() + invalidPortErr := InvalidPortError{} + _ = invalidPortErr.Error() + _ = invalidPortErr.Unwrap() + invalidTokenErr := InvalidTokenError{} + _ = invalidTokenErr.Error() +} diff --git a/pkg/config/test_data/invalid.yaml b/pkg/config/test_data/invalid.yaml new file mode 100644 index 0000000..db1ddad --- /dev/null +++ b/pkg/config/test_data/invalid.yaml @@ -0,0 +1 @@ +blargh(ads) diff --git a/pkg/config/test_data/invalid_address.yaml b/pkg/config/test_data/invalid_address.yaml new file mode 100644 index 0000000..e3682b2 --- /dev/null +++ b/pkg/config/test_data/invalid_address.yaml @@ -0,0 +1,3 @@ +address: "0.0.0.0.0" +port: 8082 +token: 12345678-9abc-def0-1234-56789abcdef0 diff --git a/pkg/config/test_data/invalid_address_unresolvable.yaml b/pkg/config/test_data/invalid_address_unresolvable.yaml new file mode 100644 index 0000000..d476093 --- /dev/null +++ b/pkg/config/test_data/invalid_address_unresolvable.yaml @@ -0,0 +1,3 @@ +address: "invalid" +port: 8082 +token: 12345678-9abc-def0-1234-56789abcdef0 diff --git a/pkg/config/test_data/invalid_port.yaml b/pkg/config/test_data/invalid_port.yaml new file mode 100644 index 0000000..2790b3f --- /dev/null +++ b/pkg/config/test_data/invalid_port.yaml @@ -0,0 +1,3 @@ +address: localhost +port: invalid +token: 12345678-9abc-def0-1234-56789abcdef0 diff --git a/pkg/config/test_data/invalid_token.yaml b/pkg/config/test_data/invalid_token.yaml new file mode 100644 index 0000000..6f9e297 --- /dev/null +++ b/pkg/config/test_data/invalid_token.yaml @@ -0,0 +1,3 @@ +address: 127.0.0.1 +port: 8080 +token: invalid diff --git a/pkg/config/test_data/minimal.yaml b/pkg/config/test_data/minimal.yaml new file mode 100644 index 0000000..2944645 --- /dev/null +++ b/pkg/config/test_data/minimal.yaml @@ -0,0 +1,3 @@ +address: 127.0.0.2 +port: 8082 +token: 12345678-9abc-def0-1234-56789abcdef0 diff --git a/pkg/config/test_data/minimal_with_hostname.yaml b/pkg/config/test_data/minimal_with_hostname.yaml new file mode 100644 index 0000000..716b2d5 --- /dev/null +++ b/pkg/config/test_data/minimal_with_hostname.yaml @@ -0,0 +1,3 @@ +address: localhost +port: 8082 +token: 12345678-9abc-def0-1234-56789abcdef0 -- cgit v1.2.3