Skip to content

Commit 9c1d017

Browse files
feat(config): Add nested structs & error handling
1 parent 783ae06 commit 9c1d017

8 files changed

Lines changed: 1958 additions & 451 deletions

File tree

config/config_loader.go

Lines changed: 1072 additions & 362 deletions
Large diffs are not rendered by default.

config/config_test.go

Lines changed: 515 additions & 0 deletions
Large diffs are not rendered by default.

config/constants.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import "regexp"
44

55
const (
66
DefaultTagName = "default"
7+
DotEnvFile = ".env"
8+
DotEnvKVParts = 2
79
)
810

911
var (
@@ -20,30 +22,25 @@ const (
2022
stringUintMap
2123
stringFloatMap
2224
stringBoolMap
23-
stringByteSliceMap
2425
intStringMap
2526
intIntMap
2627
intUintMap
2728
intFloatMap
2829
intBoolMap
29-
intByteSliceMap
3030
uintStringMap
3131
uintIntMap
3232
uintUintMap
3333
uintFloatMap
3434
uintBoolMap
35-
uintByteSliceMap
3635
floatStringMap
3736
floatIntMap
3837
floatUintMap
3938
floatFloatMap
4039
floatBoolMap
41-
floatByteSliceMap
4240
boolStringMap
4341
boolIntMap
4442
boolUintMap
4543
boolFloatMap
4644
boolBoolMap
47-
boolByteSliceMap
4845
invalidMap
4946
)

config/errors.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package config
2+
3+
import "errors"
4+
5+
var (
6+
ErrConfigStructMustBeStruct = errors.New("config struct must be a struct")
7+
ErrValueIsRequired = errors.New("value is required")
8+
ErrInvalidDotEnvFileFormat = errors.New("invalid .env file format")
9+
)

config/getenv.go

Lines changed: 223 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,96 @@
11
package config
22

33
import (
4+
"fmt"
5+
"net/http"
46
"os"
7+
"reflect"
58

69
"github.com/YaCodeDev/GoYaCodeDevUtils/valueparser"
10+
"github.com/YaCodeDev/GoYaCodeDevUtils/yaerrors"
711
"github.com/YaCodeDev/GoYaCodeDevUtils/yalogger"
812
)
913

1014
// GetEnv retrieves the value of an environment variable, parses it to the specified type T,
1115
// and returns it. If the variable is not set, it returns a fallback value.
12-
// If the variable is required and not set, it logs an error and exits the program.
16+
// If the variable is required and not set, it logs and returns an error.
1317
//
1418
// Example usage:
1519
//
16-
// myInt := GetEnv("MY_ENV_VAR", 42, true, log)
17-
//
18-
// PANICS if the environment variable is required and not set.
20+
// myInt, err := GetEnv("MY_ENV_VAR", 42, true, log)
21+
// if err != nil {
22+
// // handle error
23+
// }
1924
func GetEnv[T valueparser.ParsableType](
2025
key string,
2126
fallback T,
2227
required bool,
2328
log yalogger.Logger,
24-
) T {
29+
) (T, yaerrors.Error) {
30+
return GetEnvWithCustomType(
31+
key,
32+
fallback,
33+
required,
34+
reflect.TypeOf(new(T)).Elem(),
35+
log,
36+
)
37+
}
38+
39+
// GetEnvWithCustomType retrieves the value of an environment variable, parses it to the specified type T,
40+
// and returns it. If the variable is not set, it returns a fallback value.
41+
// If the variable is required and not set, it logs an error and returns an error.
42+
// This function is useful when you need to specify a custom type for parsing.
43+
//
44+
// Example usage:
45+
//
46+
// type YourCustomType uint64
47+
//
48+
// func (s *YourCustomType) Unmarshal(data string) error {
49+
// if s == nil {
50+
// return fmt.Errorf("nil pointer to YourCustomType")
51+
// }
52+
//
53+
// switch data {
54+
// case "FIRST":
55+
// *s = 1
56+
// case "SECOND":
57+
// *s = 2
58+
// default:
59+
// return fmt.Errorf("unknown value: %s", data)
60+
// }
61+
//
62+
// return nil
63+
// }
64+
//
65+
// myValue, err := GetEnvWithCustomType(
66+
// "MY_ENV_VAR",
67+
// uint64(0),
68+
// true,
69+
// reflect.TypeOf(YourCustomType(0)),
70+
// log,
71+
// )
72+
func GetEnvWithCustomType[T valueparser.ParsableType](
73+
key string,
74+
fallback T,
75+
required bool,
76+
vType reflect.Type,
77+
log yalogger.Logger,
78+
) (T, yaerrors.Error) {
2579
safetyCheck(&log)
2680

2781
if value, exists := os.LookupEnv(key); exists {
28-
if parsed, err := valueparser.ParseValue[T](value); err == nil {
29-
return parsed
82+
if parsed, err := valueparser.ParseValueWithCustomType[T](value, vType); err == nil {
83+
return parsed, nil
3084
}
3185
}
3286

3387
if required {
34-
log.Fatalf("Environment variable %s is required", key)
88+
return fallback, yaerrors.FromErrorWithLog(
89+
http.StatusInternalServerError,
90+
ErrValueIsRequired,
91+
fmt.Sprintf("get env: environment variable %s is required", key),
92+
log,
93+
)
3594
}
3695

3796
log.Warnf(
@@ -40,81 +99,218 @@ func GetEnv[T valueparser.ParsableType](
4099
fallback,
41100
)
42101

43-
return fallback
102+
return fallback, nil
44103
}
45104

46105
// GetEnvArray retrieves the value of an environment variable, splits it by a specified separator, (default is ","),
47106
// parses each part into the specified type T, and returns a slice of T.
48107
// If the variable is not set, it returns a fallback value.
49-
// If the variable is required and not set, it logs an error and exits the program.
108+
// If the variable is required and not set, it logs and returns an error.
50109
//
51110
// Example usage:
52111
//
53-
// myArray := GetEnvArray("MY_ENV_VAR", []int{1, 2, 3}, nil, true, log)
54-
//
55-
// PANICS if the environment variable is required and not set.
112+
// myArray, err := GetEnvArray("MY_ENV_VAR", []int{1, 2, 3}, nil, true, log)
113+
// if err != nil {
114+
// // handle error
115+
// }
56116
func GetEnvArray[T valueparser.ParsableType](
57117
key string,
58118
fallback []T,
59119
separator *string,
60120
required bool,
61121
log yalogger.Logger,
62-
) []T {
122+
) ([]T, yaerrors.Error) {
123+
return GetEnvArrayWithCustomType(
124+
key,
125+
fallback,
126+
separator,
127+
required,
128+
reflect.TypeOf(new(T)).Elem(),
129+
log,
130+
)
131+
}
132+
133+
// GetEnvArrayWithCustomType retrieves the value of an environment variable, splits it by a specified separator
134+
// (default is ","), parses each part into the specified type T, and returns a slice of T.
135+
// If the variable is not set, it returns a fallback value.
136+
// If the variable is required and not set, it logs and returns an error.
137+
// This function is useful when you need to specify a custom type for parsing.
138+
//
139+
// Example usage:
140+
//
141+
// type YourCustomType uint64
142+
//
143+
// func (s *YourCustomType) Unmarshal(data string) error {
144+
// if s == nil {
145+
// return fmt.Errorf("nil pointer to YourCustomType")
146+
// }
147+
//
148+
// switch data {
149+
// case "FIRST":
150+
// *s = 1
151+
// case "SECOND":
152+
// *s = 2
153+
// default:
154+
// return fmt.Errorf("unknown value: %s", data)
155+
// }
156+
//
157+
// return nil
158+
// }
159+
//
160+
// myArray, err := GetEnvArrayWithCustomType(
161+
// "MY_ENV_VAR",
162+
// []uint64{1, 2, 3},
163+
// nil,
164+
// true,
165+
// reflect.TypeOf(YourCustomType(0)),
166+
// log,
167+
// )
168+
func GetEnvArrayWithCustomType[T valueparser.ParsableType](
169+
key string,
170+
fallback []T,
171+
separator *string,
172+
required bool,
173+
vType reflect.Type,
174+
log yalogger.Logger,
175+
) ([]T, yaerrors.Error) {
63176
safetyCheck(&log)
64177

65178
if value, exists := os.LookupEnv(key); exists {
66-
parsed, err := valueparser.ParseArray[T](value, separator)
179+
parsed, err := valueparser.ParseArrayWithCustomType[T](value, separator, vType)
67180
if err == nil {
68-
return parsed
181+
return parsed, nil
69182
}
70183

71184
log.Errorf("Failed to parse environment variable %s: %v", key, err)
72185
}
73186

74187
if required {
75-
log.Fatalf("Environment variable %s is required", key)
188+
return nil, yaerrors.FromErrorWithLog(
189+
http.StatusInternalServerError,
190+
ErrValueIsRequired,
191+
fmt.Sprintf(
192+
"get env array: environment variable %s is required",
193+
key,
194+
),
195+
log,
196+
)
76197
}
77198

78199
log.Warnf("Environment variable %s is not set, using default value %v", key, fallback)
79200

80-
return fallback
201+
return fallback, nil
81202
}
82203

83204
// GetEnvMap retrieves the value of an environment variable, splits it by a specified entry separator (default is ","),
84205
// and each entry by a specified key-value separator (default is ":").
85206
// It parses the key and value into the specified types K and V, and returns a map of K to V.
86207
// If the variable is not set, it returns a fallback value.
87-
// If the variable is required and not set, it logs an error and exits the program.
208+
// If the variable is required and not set, it logs and returns an error.
88209
//
89210
// Example usage:
90211
//
91-
// myMap := GetEnvMap("MY_ENV_VAR", map[string]int{"key": 1}, true, nil, nil, log)
92-
//
93-
// PANICS if the environment variable is required and not set.
212+
// myMap, err := GetEnvMap("MY_ENV_VAR", map[string]int{"key": 1}, true, nil, nil, log)
213+
// if err != nil {
214+
// // handle error
215+
// }
94216
func GetEnvMap[K valueparser.ParsableComparableType, V valueparser.ParsableType](
95217
key string,
96218
fallback map[K]V,
97219
required bool,
98220
entrySeparator *string,
99221
kvSeparator *string,
100222
log yalogger.Logger,
101-
) map[K]V {
223+
) (map[K]V, yaerrors.Error) {
224+
return GetEnvMapWithCustomType(
225+
key,
226+
fallback,
227+
required,
228+
entrySeparator,
229+
kvSeparator,
230+
reflect.TypeOf(new(K)).Elem(),
231+
reflect.TypeOf(new(V)).Elem(),
232+
log,
233+
)
234+
}
235+
236+
// GetEnvMapWithCustomType retrieves the value of an environment variable, splits it by a specified entry separator
237+
// (default is ","), and each entry by a specified key-value separator (default is ":").
238+
// It parses the key and value into the specified types K and V, and returns a map of K to V.
239+
// If the variable is not set, it returns a fallback value.
240+
// If the variable is required and not set, it logs and returns an error.
241+
// This function is useful when you need to specify custom types for parsing keys and/or values.
242+
//
243+
// Example usage:
244+
//
245+
// type YourCustomType uint64
246+
//
247+
// func (s *YourCustomType) Unmarshal(data string) error {
248+
// if s == nil {
249+
// return fmt.Errorf("nil pointer to YourCustomType")
250+
// }
251+
//
252+
// switch data {
253+
// case "FIRST":
254+
// *s = 1
255+
// case "SECOND":
256+
// *s = 2
257+
// default:
258+
// return fmt.Errorf("unknown value: %s", data)
259+
// }
260+
//
261+
// return nil
262+
// }
263+
//
264+
// myMap, err := GetEnvMapWithCustomType(
265+
// "MY_ENV_VAR",
266+
// map[uint64]int{1: 10, 2: 20},
267+
// true,
268+
// nil,
269+
// nil,
270+
// reflect.TypeOf(YourCustomType(0)),
271+
// reflect.TypeOf(0),
272+
// log,
273+
// )
274+
func GetEnvMapWithCustomType[K valueparser.ParsableComparableType, V valueparser.ParsableType](
275+
key string,
276+
fallback map[K]V,
277+
required bool,
278+
entrySeparator *string,
279+
kvSeparator *string,
280+
kType reflect.Type,
281+
vType reflect.Type,
282+
log yalogger.Logger,
283+
) (map[K]V, yaerrors.Error) {
102284
safetyCheck(&log)
103285

104286
if value, exists := os.LookupEnv(key); exists {
105-
parsed, err := valueparser.ParseMap[K, V](value, entrySeparator, kvSeparator)
287+
parsed, err := valueparser.ParseMapWithCustomType[K, V](
288+
value,
289+
entrySeparator,
290+
kvSeparator,
291+
kType,
292+
vType,
293+
)
106294
if err == nil {
107-
return parsed
295+
return parsed, nil
108296
}
109297

110298
log.Errorf("Failed to parse environment variable %s: %v", key, err)
111299
}
112300

113301
if required {
114-
log.Fatalf("Environment variable %s is required", key)
302+
return nil, yaerrors.FromErrorWithLog(
303+
http.StatusInternalServerError,
304+
ErrValueIsRequired,
305+
fmt.Sprintf(
306+
"get env map: environment variable %s is required",
307+
key,
308+
),
309+
log,
310+
)
115311
}
116312

117313
log.Warnf("Environment variable %s is not set, using default value %v", key, fallback)
118314

119-
return fallback
315+
return fallback, nil
120316
}

0 commit comments

Comments
 (0)