aboutsummaryrefslogtreecommitdiff
path: root/pkg/config
diff options
context:
space:
mode:
authorJulien Dessaux2021-04-05 17:52:31 +0200
committerJulien Dessaux2021-04-05 17:52:31 +0200
commit1ffc9c42054e208a01d3e70e6b6f3e1781e798f8 (patch)
tree721f202dcf46fb5e9c181013237e4da9d27f796a /pkg/config
parentReworked error handling for better and simpler tests (diff)
downloadtrains-1ffc9c42054e208a01d3e70e6b6f3e1781e798f8.tar.gz
trains-1ffc9c42054e208a01d3e70e6b6f3e1781e798f8.tar.bz2
trains-1ffc9c42054e208a01d3e70e6b6f3e1781e798f8.zip
Moved code around to conform best practices
Diffstat (limited to 'pkg/config')
-rw-r--r--pkg/config/config.go55
-rw-r--r--pkg/config/config_test.go54
-rw-r--r--pkg/config/error.go92
-rw-r--r--pkg/config/error_test.go20
-rw-r--r--pkg/config/test_data/invalid.yaml1
-rw-r--r--pkg/config/test_data/invalid_address.yaml3
-rw-r--r--pkg/config/test_data/invalid_address_unresolvable.yaml3
-rw-r--r--pkg/config/test_data/invalid_port.yaml3
-rw-r--r--pkg/config/test_data/invalid_token.yaml3
-rw-r--r--pkg/config/test_data/minimal.yaml3
-rw-r--r--pkg/config/test_data/minimal_with_hostname.yaml3
11 files changed, 240 insertions, 0 deletions
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