Repository: snowflakedb/gosnowflake
Pull request #939 (2023-10-17 15:31:32)

SNOW-856228 easy logging finder
### Description
Please explain the changes you made here.

### Checklist
- [x] Code compiles correctly
- [x] Run ``make fmt`` to fix inconsistent formats
- [x] Run ``make lint`` to get lint errors and fix all of them
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [x] Extended the README / documentation, if necessary



Patch:
diff --git a/client_configuration.go b/client_configuration.go
index 381ed8d..52d55de 100644
--- a/client_configuration.go
+++ b/client_configuration.go
@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path"
 	"strings"
 )
 
@@ -20,6 +21,68 @@ const (
 	levelTrace string = "TRACE" // trace log level
 )
 
+const (
+	defaultConfigName = "sf_client_config.json"
+	clientConfEnvName = "SF_CLIENT_CONFIG_FILE"
+)
+
+func getClientConfig(filePathFromConnectionString string) (*ClientConfig, error) {
+	configPredefinedFilePaths := clientConfigPredefinedDirs()
+	filePath, err := findClientConfigFilePath(filePathFromConnectionString, configPredefinedFilePaths)
+	if err != nil {
+		return nil, err
+	}
+	if filePath == "" { // we did not find a config file
+		return nil, nil
+	}
+	return parseClientConfiguration(filePath)
+}
+
+func findClientConfigFilePath(filePathFromConnectionString string, configPredefinedDirs []string) (string, error) {
+	if filePathFromConnectionString != "" {
+		return filePathFromConnectionString, nil
+	}
+	envConfigFilePath := os.Getenv(clientConfEnvName)
+	if envConfigFilePath != "" {
+		return envConfigFilePath, nil
+	}
+	return searchForConfigFile(configPredefinedDirs)
+}
+
+func searchForConfigFile(directories []string) (string, error) {
+	for _, dir := range directories {
+		filePath := path.Join(dir, defaultConfigName)
+		exists, err := existsFile(filePath)
+		if err != nil {
+			return "", err
+		}
+		if exists {
+			return filePath, nil
+		}
+	}
+	return "", nil
+}
+
+func existsFile(filePath string) (bool, error) {
+	_, err := os.Stat(filePath)
+	if err == nil {
+		return true, nil
+	}
+	if errors.Is(err, os.ErrNotExist) {
+		return false, nil
+	}
+	return false, err
+}
+
+func clientConfigPredefinedDirs() []string {
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		logger.Warnf("Home dir could not be determined: %w", err)
+		return []string{".", os.TempDir()}
+	}
+	return []string{".", homeDir, os.TempDir()}
+}
+
 // ClientConfig config root
 type ClientConfig struct {
 	Common *ClientConfigCommonProps `json:"common"`


Test patch:
diff --git a/assert_test.go b/assert_test.go
index 0394b4b..58d4cc4 100644
--- a/assert_test.go
+++ b/assert_test.go
@@ -21,6 +21,10 @@ func assertEqualE(t *testing.T, actual any, expected any, descriptions ...string
 	errorOnNonEmpty(t, validateEqual(actual, expected, descriptions...))
 }
 
+func assertEqualF(t *testing.T, actual any, expected any, descriptions ...string) {
+	fatalOnNonEmpty(t, validateEqual(actual, expected, descriptions...))
+}
+
 func assertStringContainsE(t *testing.T, actual string, expectedToContain string, descriptions ...string) {
 	errorOnNonEmpty(t, validateStringContains(actual, expectedToContain, descriptions...))
 }
diff --git a/client_configuration_test.go b/client_configuration_test.go
index 8da3be3..a63eb67 100644
--- a/client_configuration_test.go
+++ b/client_configuration_test.go
@@ -9,6 +9,105 @@ import (
 	"testing"
 )
 
+func TestFindConfigFileFromConnectionParameters(t *testing.T) {
+	dirs := createTestDirectories(t)
+	connParameterConfigPath := createFile(t, "conn_parameters_config.json", "random content", dirs.dir)
+	envConfigPath := createFile(t, "env_var_config.json", "random content", dirs.dir)
+	t.Setenv(clientConfEnvName, envConfigPath)
+	createFile(t, defaultConfigName, "random content", dirs.predefinedDir1)
+	createFile(t, defaultConfigName, "random content", dirs.predefinedDir2)
+
+	clientConfigFilePath, err := findClientConfigFilePath(connParameterConfigPath, predefinedTestDirs(dirs))
+
+	assertNilF(t, err, "get client config error")
+	assertEqualE(t, clientConfigFilePath, connParameterConfigPath, "config file path")
+}
+
+func TestFindConfigFileFromEnvVariable(t *testing.T) {
+	dirs := createTestDirectories(t)
+	envConfigPath := createFile(t, "env_var_config.json", "random content", dirs.dir)
+	t.Setenv(clientConfEnvName, envConfigPath)
+	createFile(t, defaultConfigName, "random content", dirs.predefinedDir1)
+	createFile(t, defaultConfigName, "random content", dirs.predefinedDir2)
+
+	clientConfigFilePath, err := findClientConfigFilePath("", predefinedTestDirs(dirs))
+
+	assertNilF(t, err, "get client config error")
+	assertEqualE(t, clientConfigFilePath, envConfigPath, "config file path")
+}
+
+func TestFindConfigFileFromFirstPredefinedDir(t *testing.T) {
+	dirs := createTestDirectories(t)
+	configPath := createFile(t, defaultConfigName, "random content", dirs.predefinedDir1)
+	createFile(t, defaultConfigName, "random content", dirs.predefinedDir2)
+
+	clientConfigFilePath, err := findClientConfigFilePath("", predefinedTestDirs(dirs))
+
+	assertNilF(t, err, "get client config error")
+	assertEqualE(t, clientConfigFilePath, configPath, "config file path")
+}
+
+func TestFindConfigFileFromSubsequentDirectoryIfNotFoundInPreviousOne(t *testing.T) {
+	dirs := createTestDirectories(t)
+	createFile(t, "wrong_file_name.json", "random content", dirs.predefinedDir1)
+	configPath := createFile(t, defaultConfigName, "random content", dirs.predefinedDir2)
+
+	clientConfigFilePath, err := findClientConfigFilePath("", predefinedTestDirs(dirs))
+
+	assertNilF(t, err, "get client config error")
+	assertEqualE(t, clientConfigFilePath, configPath, "config file path")
+}
+
+func TestNotFindConfigFileWhenNotDefined(t *testing.T) {
+	dirs := createTestDirectories(t)
+	createFile(t, "wrong_file_name.json", "random content", dirs.predefinedDir1)
+	createFile(t, "wrong_file_name.json", "random content", dirs.predefinedDir2)
+
+	clientConfigFilePath, err := findClientConfigFilePath("", predefinedTestDirs(dirs))
+
+	assertNilF(t, err, "get client config error")
+	assertEqualE(t, clientConfigFilePath, "", "config file path")
+}
+
+func TestCreatePredefinedDirs(t *testing.T) {
+	homeDir, err := os.UserHomeDir()
+	assertNilF(t, err, "get home dir error")
+
+	locations := clientConfigPredefinedDirs()
+
+	assertEqualF(t, len(locations), 3, "size")
+	assertEqualE(t, locations[0], ".", "driver directory")
+	assertEqualE(t, locations[1], homeDir, "home directory")
+	assertEqualE(t, locations[2], os.TempDir(), "temp directory")
+}
+
+func TestGetClientConfig(t *testing.T) {
+	dir := t.TempDir()
+	fileName := "config.json"
+	configContents := `{
+			"common": {
+				"log_level" : "INFO",
+				"log_path" : "/some-path/some-directory"
+			}
+		}`
+	createFile(t, fileName, configContents, dir)
+	filePath := path.Join(dir, fileName)
+
+	clientConfigFilePath, err := getClientConfig(filePath)
+
+	assertNilF(t, err)
+	assertNotNilF(t, clientConfigFilePath)
+	assertEqualE(t, clientConfigFilePath.Common.LogLevel, "INFO", "log level")
+	assertEqualE(t, clientConfigFilePath.Common.LogPath, "/some-path/some-directory", "log path")
+}
+
+func TestNoResultForGetClientConfigWhenNoFileFound(t *testing.T) {
+	clientConfigFilePath, err := getClientConfig("")
+
+	assertNilF(t, err)
+	assertNilF(t, clientConfigFilePath)
+}
+
 func TestParseConfiguration(t *testing.T) {
 	dir := t.TempDir()
 	testCases := []struct {
@@ -154,3 +253,34 @@ func createFile(t *testing.T, fileName string, fileContents string, directory st
 	assertNilF(t, err, "create file error")
 	return fullFileName
 }
+
+func createTestDirectories(t *testing.T) struct {
+	dir            string
+	predefinedDir1 string
+	predefinedDir2 string
+} {
+	dir := t.TempDir()
+	predefinedDir1 := path.Join(dir, "dir1")
+	err := os.Mkdir(predefinedDir1, 0755)
+	assertNilF(t, err, "predefined dir1 error")
+	predefinedDir2 := path.Join(dir, "dir2")
+	err = os.Mkdir(predefinedDir2, 0755)
+	assertNilF(t, err, "predefined dir2 error")
+	return struct {
+		dir            string
+		predefinedDir1 string
+		predefinedDir2 string
+	}{
+		dir:            dir,
+		predefinedDir1: predefinedDir1,
+		predefinedDir2: predefinedDir2,
+	}
+}
+
+func predefinedTestDirs(dirs struct {
+	dir            string
+	predefinedDir1 string
+	predefinedDir2 string
+}) []string {
+	return []string{dirs.predefinedDir1, dirs.predefinedDir2}
+}