Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃 Add support encoding.TextUnmarshaler #123

Merged
merged 6 commits into from Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -192,7 +192,8 @@ There are following supported types:
- maps (of any other supported type);
- `time.Duration`;
- `time.Time` (layout by default is RFC3339, may be overridden by `env-layout`);
- `*time.Location` (time zone parsing [depends](https://pkg.go.dev/time#LoadLocation) on running machine)
- `*time.Location` (time zone parsing [depends](https://pkg.go.dev/time#LoadLocation) on running machine);
- any type that implements `encoding.TextUnmarshaler`;
- any type implementing `cleanenv.Setter` interface.


Expand Down Expand Up @@ -319,4 +320,4 @@ The logo was made by [alexchoffy](https://www.instagram.com/alexchoffy/).

## Blog Posts

[Clean Configuration Management in Golang](https://dev.to/ilyakaznacheev/clean-configuration-management-in-golang-1c89).
[Clean Configuration Management in Golang](https://dev.to/ilyakaznacheev/clean-configuration-management-in-golang-1c89).
mirecl marked this conversation as resolved.
Show resolved Hide resolved
20 changes: 14 additions & 6 deletions cleanenv.go
@@ -1,6 +1,7 @@
package cleanenv

import (
"encoding"
"encoding/json"
"flag"
"fmt"
Expand Down Expand Up @@ -459,15 +460,27 @@ func readEnvVars(cfg interface{}, update bool) error {
func parseValue(field reflect.Value, value, sep string, layout *string) error {
// TODO: simplify recursion

valueType := field.Type()

// look for supported struct parser
if structParser, found := validStructs[valueType]; found {
return structParser(&field, value, layout)
}

mirecl marked this conversation as resolved.
Show resolved Hide resolved
if field.CanInterface() {
if ctp, ok := field.Addr().Interface().(encoding.TextUnmarshaler); ok {
return ctp.UnmarshalText([]byte(value))
} else if ct, ok := field.Interface().(encoding.TextUnmarshaler); ok {
return ct.UnmarshalText([]byte(value))
}

if cs, ok := field.Interface().(Setter); ok {
return cs.SetValue(value)
} else if csp, ok := field.Addr().Interface().(Setter); ok {
return csp.SetValue(value)
}
mirecl marked this conversation as resolved.
Show resolved Hide resolved
}

valueType := field.Type()
switch valueType.Kind() {

// parse string value
Expand Down Expand Up @@ -542,11 +555,6 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error {
field.Set(*mapValue)

default:
// look for supported struct parser
if structParser, found := validStructs[valueType]; found {
return structParser(&field, value, layout)
}

return fmt.Errorf("unsupported type %s.%s", valueType.PkgPath(), valueType.Name())
}

Expand Down
29 changes: 21 additions & 8 deletions example_test.go
Expand Up @@ -179,36 +179,49 @@ func ExampleReadEnvWithURL() {
//Output: https://images.cdn/
}

// MyField is an example type with a custom setter
type MyField string
// MyField1 is an example type with a custom setter
type MyField1 string

func (f *MyField) SetValue(s string) error {
func (f *MyField1) SetValue(s string) error {
if s == "" {
return fmt.Errorf("field value can't be empty")
}
*f = MyField("my field is: " + s)
*f = MyField1("my field is: " + s)
return nil
}

func (f MyField) String() string {
func (f MyField1) String() string {
return string(f)
}

// MyField2 is an example type with encoding.TextUnmarshaler implementation.
type MyField2 string

func (f *MyField2) UnmarshalText(p []byte) error {
if len(p) == 0 {
return fmt.Errorf("field value can't be empty")
}
*f = MyField2("my field is: " + string(p))
return nil
}

// Example_setter uses type with a custom setter to parse environment variable data
func Example_setter() {
type config struct {
Default string `env:"ONE"`
Custom MyField `env:"TWO"`
Default string `env:"ONE"`
Custom1 MyField1 `env:"TWO"`
Custom2 MyField2 `env:"THREE"`
}

var cfg config

os.Setenv("ONE", "test1")
os.Setenv("TWO", "test2")
os.Setenv("THREE", "test3")

cleanenv.ReadEnv(&cfg)
fmt.Printf("%+v\n", cfg)
//Output: {Default:test1 Custom:my field is: test2}
//Output: {Default:test1 Custom1:my field is: test2 Custom2:my field is: test3}
}

// ConfigUpdate is a type with a custom updater
Expand Down