Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# JetBrains IDEs
.idea/

# Other files to ignore
.DS_Store
97 changes: 96 additions & 1 deletion getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,104 @@ func GetFloat64(key string) float64 {

// GetFloat64OrDefault returns the value in float64 format or the default value if the environment variable is not set.
func GetFloat64OrDefault(key string, defaultValue float64) float64 {
result, err := strconv.ParseFloat(os.Getenv(key), 64)
value, exists := os.LookupEnv(key)
if !exists || value == "" {
return defaultValue
}

result, err := strconv.ParseFloat(value, 64)
if err != nil {
return defaultValue
}
return result
}

// GetFloat32 returns the value in float32 format of the environment variable named by the key.
func GetFloat32(key string) float32 {
value := os.Getenv(key)
if value == "" {
return 0
}

result, err := strconv.ParseFloat(value, 32)
if err != nil {
return 0
}

return float32(result)
}

// GetFloat32OrDefault returns the value in float32 format or the default value.
func GetFloat32OrDefault(key string, defaultValue float32) float32 {
value, exists := os.LookupEnv(key)
if !exists || value == "" {
return defaultValue
}

result, err := strconv.ParseFloat(value, 32)
if err != nil {
return defaultValue
}

return float32(result)
}

// GetUint returns the value in uint format of the environment variable named by the key.
func GetUint(key string) uint {
value := os.Getenv(key)
if value == "" {
return 0
}

result, err := strconv.ParseUint(value, 10, 0)
if err != nil {
return 0
}

return uint(result)
}

// GetUintOrDefault returns the value in uint format or the default value.
func GetUintOrDefault(key string, defaultValue uint) uint {
value, exists := os.LookupEnv(key)
if !exists || value == "" {
return defaultValue
}

result, err := strconv.ParseUint(value, 10, 0)
if err != nil {
return defaultValue
}

return uint(result)
}

// GetInt64 returns the value in int64 format of the environment variable named by the key.
func GetInt64(key string) int64 {
value := os.Getenv(key)
if value == "" {
return 0
}

result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0
}

return result
}

// GetInt64OrDefault returns the value in int64 format or the default value.
func GetInt64OrDefault(key string, defaultValue int64) int64 {
value, exists := os.LookupEnv(key)
if !exists || value == "" {
return defaultValue
}

result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return defaultValue
}

return result
}
41 changes: 38 additions & 3 deletions load_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import (
"strconv"
)

// Validator defines the interface for format validators
type Validator func(value reflect.Value) error

// LoadOptions provides configuration options for LoadStructWithOptions
type LoadOptions struct {
// Validators is a map of validator name to validator implementation
// When validator tag is present, the corresponding validator will be used
Validators map[string]Validator
}

func LoadStruct(data interface{}) error {
dataType := reflect.TypeOf(data)
dataValue := reflect.ValueOf(data)
Expand All @@ -18,16 +28,31 @@ func LoadStruct(data interface{}) error {
dataType = dataType.Elem()
dataValue = dataValue.Elem()

return parseFields(dataType, dataValue)
return parseFields(dataType, dataValue, LoadOptions{})
}

// LoadStructWithOptions loads environment variables into a struct with additional options
func LoadStructWithOptions(data interface{}, opts LoadOptions) error {
dataType := reflect.TypeOf(data)
dataValue := reflect.ValueOf(data)

if dataType.Kind() != reflect.Ptr || dataValue.IsNil() {
return fmt.Errorf("data must be a pointer to a struct")
}

dataType = dataType.Elem()
dataValue = dataValue.Elem()

return parseFields(dataType, dataValue, opts)
}

func parseFields(dataType reflect.Type, dataValue reflect.Value) error {
func parseFields(dataType reflect.Type, dataValue reflect.Value, opts LoadOptions) error {
for i := 0; i < dataType.NumField(); i++ {
field := dataType.Field(i)
value := dataValue.Field(i)

if value.Kind() == reflect.Struct {
if err := parseFields(field.Type, value); err != nil {
if err := parseFields(field.Type, value, opts); err != nil {
return err
}
continue
Expand Down Expand Up @@ -73,6 +98,16 @@ func parseFields(dataType reflect.Type, dataValue reflect.Value) error {
default:
return fmt.Errorf("unsupported type for field %s", field.Name)
}

// Validate format if validator is provided
validatorTag := field.Tag.Get("validator")
if validatorTag != "" && opts.Validators != nil {
if validator, exists := opts.Validators[validatorTag]; exists {
if err := validator(value); err != nil {
return fmt.Errorf("format validation failed for field %s: %s", field.Name, err)
}
}
}
}

return nil
Expand Down
64 changes: 64 additions & 0 deletions load_struct_options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dotenv

import (
"fmt"
"os"
"reflect"
"strings"
"testing"
)

func TestLoadStructWithOptions_FormatValidation(t *testing.T) {
// Set up test environment variables
_ = os.Setenv("TEST_EMAIL_A", "user@example.com")
_ = os.Setenv("TEST_EMAIL_B", "Arnold@example.com")

defer func() {
_ = os.Unsetenv("TEST_EMAIL_A")
_ = os.Unsetenv("TEST_EMAIL_B")
}()

configA := &struct {
Email string `env:"TEST_EMAIL_A" validator:"startWithA"`
}{}

configB := &struct {
Email string `env:"TEST_EMAIL_B" validator:"startWithA"`
}{}

opts := LoadOptions{
Validators: map[string]Validator{
"startWithA": func(value reflect.Value) error {
if value.Kind() != reflect.String {
return fmt.Errorf("startWithA validator can only be used with string fields")
}

if !strings.HasPrefix(value.String(), "A") {
return fmt.Errorf("value must start with 'A'")
}

return nil
},
},
}

err := LoadStructWithOptions(configA, opts)
if err == nil {
t.Fatalf("Expected error for invalid email, got nil")
}

// Verify values are loaded correctly
if configA.Email != "user@example.com" {
t.Errorf("Expected email user@example.com, got %s", configA.Email)
}

err = LoadStructWithOptions(configB, opts)
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

// Verify values are loaded correctly
if configB.Email != "Arnold@example.com" {
t.Errorf("Expected email Arnold@example.com, got %s", configB.Email)
}
}
Loading
Loading