Repository: danvergara/dblab
Pull request #9 (2021-04-22 04:49:56)

Build connection from data


Patch:
diff --git a/pkg/connection/connection.go b/pkg/connection/connection.go
index fb4a51e..13c462d 100644
--- a/pkg/connection/connection.go
+++ b/pkg/connection/connection.go
@@ -4,18 +4,25 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
+	"os"
+	"os/user"
 	"strings"
 
 	"github.com/danvergara/dblab/pkg/command"
 )
 
 var (
+	// ErrCantDetectUSer is the error used to notify that a default username is not found
+	// in the system to be used as database username.
+	ErrCantDetectUSer = errors.New("could not detect default username")
 	// ErrInvalidUPostgresRLFormat is the error used to notify that the postgres given url is not valid.
-	ErrInvalidUPostgresRLFormat = errors.New("Invalid URL. Valid format: postgres://user:password@host:port/db?sslmode=mode")
+	ErrInvalidUPostgresRLFormat = errors.New("invalid URL - Valid format: postgres://user:password@host:port/db?sslmode=mode")
 	// ErrInvalidUMySQLRLFormat is the error used to notify that the given mysql url is not valid.
-	ErrInvalidUMySQLRLFormat = errors.New("Invalid URL. Valid format: mysql://user:password@tcp(host:port)/db")
+	ErrInvalidUMySQLRLFormat = errors.New("invalid URL - valid format: mysql://user:password@tcp(host:port)/db")
 	// ErrInvalidURLFormat is used to notify the url is invalid.
 	ErrInvalidURLFormat = errors.New("invalid url")
+	// ErrInvalidDriver is used to notify that the provided driver is not supported.
+	ErrInvalidDriver = errors.New("invalid driver")
 )
 
 // BuildConnectionFromOpts return the connection uri string given the options passed by the uses.
@@ -31,6 +38,51 @@ func BuildConnectionFromOpts(opts command.Options) (string, error) {
 		}
 	}
 
+	if opts.User == "" {
+		u, err := currentUser()
+		if err == nil {
+			opts.User = u
+		}
+	}
+
+	switch opts.Driver {
+	case "postgres":
+		query := url.Values{}
+		if opts.SSL != "" {
+			query.Add("sslmode", opts.SSL)
+		} else {
+			if opts.Host == "localhost" || opts.Host == "127.0.0.1" {
+				query.Add("sslmode", "disable")
+			}
+		}
+
+		connDB := url.URL{
+			Scheme:   opts.Driver,
+			Host:     fmt.Sprintf("%v:%v", opts.Host, opts.Port),
+			User:     url.UserPassword(opts.User, opts.Pass),
+			Path:     fmt.Sprintf("/%s", opts.DBName),
+			RawQuery: query.Encode(),
+		}
+
+		return connDB.String(), nil
+	case "mysql":
+		return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", opts.User, opts.Pass, opts.Host, opts.Port, opts.DBName), nil
+	default:
+		return "", fmt.Errorf("%s: %w", opts.URL, ErrInvalidDriver)
+	}
+}
+
+func currentUser() (string, error) {
+	u, err := user.Current()
+	if err == nil {
+		return u.Username, nil
+	}
+
+	name := os.Getenv("USER")
+	if name != "" {
+		return name, nil
+	}
+
 	return "", nil
 }
 
@@ -72,12 +124,16 @@ func formatPostgresURL(opts command.Options) (string, error) {
 // formatMySQLURL returns valid uri for mysql connection.
 func formatMySQLURL(opts command.Options) (string, error) {
 	if !hasValidMySQLPrefix(opts.URL) {
-		return "", ErrInvalidUMySQLRLFormat
+		return "", fmt.Errorf("%s, %w", opts.URL, ErrInvalidUMySQLRLFormat)
+	}
+
+	if strings.Contains(opts.URL, "3306") {
+		return opts.URL, nil
 	}
 
 	uri, err := url.Parse(opts.URL)
 	if err != nil {
-		return "", ErrInvalidUMySQLRLFormat
+		return "", fmt.Errorf("%v %w", err, ErrInvalidUMySQLRLFormat)
 	}
 
 	result := map[string]string{}


Test patch:
diff --git a/pkg/connection/connection_test.go b/pkg/connection/connection_test.go
index 0dbe98b..6f84385 100644
--- a/pkg/connection/connection_test.go
+++ b/pkg/connection/connection_test.go
@@ -8,7 +8,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestBuildConnectionFromOpts(t *testing.T) {
+func TestBuildConnectionFromOptsFromURL(t *testing.T) {
 	type given struct {
 		opts command.Options
 	}
@@ -22,6 +22,7 @@ func TestBuildConnectionFromOpts(t *testing.T) {
 		given given
 		want  want
 	}{
+		// postgres
 		{
 			name: "valid postgres localhost",
 			given: given{
@@ -84,6 +85,69 @@ func TestBuildConnectionFromOpts(t *testing.T) {
 				err:      ErrInvalidUPostgresRLFormat,
 			},
 		},
+		// mysql
+		{
+			name: "valid mysql localhost",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(localhost:3306)/db",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(localhost:3306)/db",
+			},
+		},
+		{
+			name: "valid mysql localhost with params",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(localhost:3306)/db?charset=utf8",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(localhost:3306)/db?charset=utf8",
+			},
+		},
+		{
+			name: "valid mysql remote url",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(your-amazonaws-uri.com:3306)/dbname",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(your-amazonaws-uri.com:3306)/dbname",
+			},
+		},
+		{
+			name: "error misspelled mysql",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysq://user:password@tcp(localhost:3306)/db?charset=utf8",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUMySQLRLFormat,
+			},
+		},
+		{
+			name: "error invalid url",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "not-a-url",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUMySQLRLFormat,
+			},
+		},
 	}
 
 	for _, tc := range cases {
@@ -109,6 +173,126 @@ func TestBuildConnectionFromOpts(t *testing.T) {
 	}
 }
 
+func TestBuildConnectionFromOptsUserData(t *testing.T) {
+	type given struct {
+		opts command.Options
+	}
+	type want struct {
+		uri string
+	}
+	var cases = []struct {
+		name  string
+		given given
+		want  want
+	}{
+		// postgres
+		{
+			name: "success - localhost with no explicit ssl mode - postgres",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					User:   "user",
+					Pass:   "password",
+					Host:   "localhost",
+					Port:   "5432",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "postgres://user:password@localhost:5432/db?sslmode=disable",
+			},
+		},
+		{
+			name: "success - 127.0.0.1 with no explicit ssl mode - postgres",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					User:   "user",
+					Pass:   "password",
+					Host:   "127.0.0.1",
+					Port:   "5432",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "postgres://user:password@127.0.0.1:5432/db?sslmode=disable",
+			},
+		},
+		{
+			name: "success - remote host - postgres",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					User:   "user",
+					Pass:   "password",
+					Host:   "your-amazonaws-uri.com",
+					Port:   "5432",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "postgres://user:password@your-amazonaws-uri.com:5432/db",
+			},
+		},
+		// mysql
+		{
+			name: "success - localhost - mysql",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					User:   "user",
+					Pass:   "password",
+					Host:   "localhost",
+					Port:   "3306",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(localhost:3306)/db",
+			},
+		},
+		{
+			name: "success - 127.0.0.1 - mysql",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					User:   "user",
+					Pass:   "password",
+					Host:   "127.0.0.1",
+					Port:   "3306",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(127.0.0.1:3306)/db",
+			},
+		},
+		{
+			name: "success - remote host -mysql",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					User:   "user",
+					Pass:   "password",
+					Host:   "your-amazonaws-uri.com",
+					Port:   "3306",
+					DBName: "db",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(your-amazonaws-uri.com:3306)/db",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+		})
+	}
+}
+
 func TestFormatPostgresURL(t *testing.T) {
 	type given struct {
 		opts command.Options
@@ -122,7 +306,70 @@ func TestFormatPostgresURL(t *testing.T) {
 		name  string
 		given given
 		want  want
-	}{}
+	}{
+		{
+			name: "valid postgres localhost",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					URL:    "postgres://user:password@localhost:5432/db?sslmode=disable",
+				},
+			},
+			want: want{
+				uri: "postgres://user:password@localhost:5432/db?sslmode=disable",
+			},
+		},
+		{
+			name: "valid postgres localhost but add sslmode",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					URL:    "postgres://user:password@localhost:5432/db",
+				},
+			},
+			want: want{
+				uri: "postgres://user:password@localhost:5432/db?sslmode=disable",
+			},
+		},
+		{
+			name: "valid postgres localhost postgresql as protocol",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					URL:    "postgresql://user:password@localhost:5432/db",
+				},
+			},
+			want: want{
+				uri: "postgresql://user:password@localhost:5432/db?sslmode=disable",
+			},
+		},
+		{
+			name: "error misspelled postgres",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					URL:    "potgre://user:password@localhost:5432/db",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUPostgresRLFormat,
+			},
+		},
+		{
+			name: "error invalid url",
+			given: given{
+				opts: command.Options{
+					Driver: "postgres",
+					URL:    "not-a-url",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUPostgresRLFormat,
+			},
+		},
+	}
 
 	for _, tc := range cases {
 		tc := tc
@@ -159,12 +406,76 @@ func TestFormatMySQLURL(t *testing.T) {
 		name  string
 		given given
 		want  want
-	}{}
+	}{
+		{
+			name: "valid mysql localhost",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(localhost:3306)/db",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(localhost:3306)/db",
+			},
+		},
+		{
+			name: "valid mysql localhost with params",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(localhost:3306)/db?charset=utf8",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(localhost:3306)/db?charset=utf8",
+			},
+		},
+		{
+			name: "valid mysql remote url",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysql://user:password@tcp(your-amazonaws-uri.com:3306)/dbname",
+				},
+			},
+			want: want{
+				uri: "mysql://user:password@tcp(your-amazonaws-uri.com:3306)/dbname",
+			},
+		},
+		{
+			name: "error misspelled mysql",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "mysq://user:password@tcp(localhost:3306)/db?charset=utf8",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUMySQLRLFormat,
+			},
+		},
+		{
+			name: "error invalid url",
+			given: given{
+				opts: command.Options{
+					Driver: "mysql",
+					URL:    "not-a-url",
+				},
+			},
+			want: want{
+				hasError: true,
+				err:      ErrInvalidUMySQLRLFormat,
+			},
+		},
+	}
 
 	for _, tc := range cases {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
+
 			uri, err := formatMySQLURL(tc.given.opts)
 
 			if tc.want.hasError {