From f21267b824ffb9773147943f9e148a294d2fd593 Mon Sep 17 00:00:00 2001 From: mirecl Date: Fri, 7 Jul 2023 16:56:59 +0300 Subject: [PATCH 1/6] Add support type `encoding.TextUnmarshaler` --- README.md | 21 +++++++++++---------- cleanenv.go | 7 +++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eef38fd..c1ce326 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Minimalistic configuration reader -[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![GoDoc](https://godoc.org/github.com/ilyakaznacheev/cleanenv?status.svg)](https://godoc.org/github.com/ilyakaznacheev/cleanenv) [![Go Report Card](https://goreportcard.com/badge/github.com/ilyakaznacheev/cleanenv)](https://goreportcard.com/report/github.com/ilyakaznacheev/cleanenv) [![Coverage Status](https://codecov.io/github/ilyakaznacheev/cleanenv/coverage.svg?branch=master)](https://codecov.io/gh/ilyakaznacheev/cleanenv) @@ -24,18 +24,18 @@ This is a simple configuration reading tool. It just does the following: - [Installation](#installation) - [Usage](#usage) - - [Read Configuration](#read-configuration) - - [Read Environment Variables Only](#read-environment-variables-only) - - [Update Environment Variables](#update-environment-variables) - - [Description](#description) + - [Read Configuration](#read-configuration) + - [Read Environment Variables Only](#read-environment-variables-only) + - [Update Environment Variables](#update-environment-variables) + - [Description](#description) - [Model Format](#model-format) - [Supported types](#supported-types) - [Custom Functions](#custom-functions) - - [Custom Value Setter](#custom-value-setter) - - [Custom Value Update](#custom-value-update) + - [Custom Value Setter](#custom-value-setter) + - [Custom Value Update](#custom-value-update) - [Supported File Formats](#supported-file-formats) - [Integration](#integration) - - [Flag](#flag) + - [Flag](#flag) - [Examples](#examples) - [Contribution](#contribution) - [Thanks](#thanks) @@ -93,7 +93,7 @@ This will do the following: Sometimes you don't want to use configuration files at all, or you may want to use `.env` file format instead. Thus, you can limit yourself with only reading environment variables: -```go +```go import "github.com/ilyakaznacheev/cleanenv" type ConfigDatabase struct { @@ -193,9 +193,9 @@ There are following supported types: - `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) +- any type that implements `encoding.TextUnmarshaler`; - any type implementing `cleanenv.Setter` interface. - ## Custom Functions To enhance package abilities you can use some custom functions. @@ -251,6 +251,7 @@ There are several most popular config file formats supported: - ENV (`.env`) **Note**: + - while using `.env` file the library will set corresponding data to process environment variables. It will override existing variables with the same keys in the process environment. diff --git a/cleanenv.go b/cleanenv.go index dd4e1d4..2c6c8d6 100644 --- a/cleanenv.go +++ b/cleanenv.go @@ -1,6 +1,7 @@ package cleanenv import ( + "encoding" "encoding/json" "flag" "fmt" @@ -460,6 +461,12 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error { // TODO: simplify recursion if field.CanInterface() { + if ct, ok := field.Addr().Interface().(encoding.TextUnmarshaler); ok { + return ct.UnmarshalText([]byte(value)) + } else if ctp, ok := field.Interface().(encoding.TextUnmarshaler); ok { + return ctp.UnmarshalText([]byte(value)) + } + if cs, ok := field.Interface().(Setter); ok { return cs.SetValue(value) } else if csp, ok := field.Addr().Interface().(Setter); ok { From 021dc5323640f18c5a2bff48b1b674a0096dc011 Mon Sep 17 00:00:00 2001 From: mirecl Date: Fri, 7 Jul 2023 17:37:13 +0300 Subject: [PATCH 2/6] Fix parser structs --- cleanenv.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cleanenv.go b/cleanenv.go index 2c6c8d6..3df3ff7 100644 --- a/cleanenv.go +++ b/cleanenv.go @@ -460,11 +460,18 @@ 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) + } + if field.CanInterface() { - if ct, ok := field.Addr().Interface().(encoding.TextUnmarshaler); ok { - return ct.UnmarshalText([]byte(value)) - } else if ctp, ok := field.Interface().(encoding.TextUnmarshaler); ok { + 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 { @@ -474,7 +481,6 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error { } } - valueType := field.Type() switch valueType.Kind() { // parse string value @@ -549,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()) } From 0d1779df1933928e6ec34453783c0e89da2eed55 Mon Sep 17 00:00:00 2001 From: mirecl Date: Thu, 20 Jul 2023 14:13:23 +0300 Subject: [PATCH 3/6] Add test for `encoding.TextUnmarshaler` and upd README.md --- README.md | 24 ++++++++++++------------ example_test.go | 29 +++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c1ce326..9f7269f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Minimalistic configuration reader -[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![GoDoc](https://godoc.org/github.com/ilyakaznacheev/cleanenv?status.svg)](https://godoc.org/github.com/ilyakaznacheev/cleanenv) [![Go Report Card](https://goreportcard.com/badge/github.com/ilyakaznacheev/cleanenv)](https://goreportcard.com/report/github.com/ilyakaznacheev/cleanenv) [![Coverage Status](https://codecov.io/github/ilyakaznacheev/cleanenv/coverage.svg?branch=master)](https://codecov.io/gh/ilyakaznacheev/cleanenv) @@ -24,18 +24,18 @@ This is a simple configuration reading tool. It just does the following: - [Installation](#installation) - [Usage](#usage) - - [Read Configuration](#read-configuration) - - [Read Environment Variables Only](#read-environment-variables-only) - - [Update Environment Variables](#update-environment-variables) - - [Description](#description) + - [Read Configuration](#read-configuration) + - [Read Environment Variables Only](#read-environment-variables-only) + - [Update Environment Variables](#update-environment-variables) + - [Description](#description) - [Model Format](#model-format) - [Supported types](#supported-types) - [Custom Functions](#custom-functions) - - [Custom Value Setter](#custom-value-setter) - - [Custom Value Update](#custom-value-update) + - [Custom Value Setter](#custom-value-setter) + - [Custom Value Update](#custom-value-update) - [Supported File Formats](#supported-file-formats) - [Integration](#integration) - - [Flag](#flag) + - [Flag](#flag) - [Examples](#examples) - [Contribution](#contribution) - [Thanks](#thanks) @@ -93,7 +93,7 @@ This will do the following: Sometimes you don't want to use configuration files at all, or you may want to use `.env` file format instead. Thus, you can limit yourself with only reading environment variables: -```go +```go import "github.com/ilyakaznacheev/cleanenv" type ConfigDatabase struct { @@ -192,10 +192,11 @@ 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. + ## Custom Functions To enhance package abilities you can use some custom functions. @@ -251,7 +252,6 @@ There are several most popular config file formats supported: - ENV (`.env`) **Note**: - - while using `.env` file the library will set corresponding data to process environment variables. It will override existing variables with the same keys in the process environment. @@ -320,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). \ No newline at end of file diff --git a/example_test.go b/example_test.go index 107854b..a17bc25 100644 --- a/example_test.go +++ b/example_test.go @@ -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 From 7c583d18da0a445b3105744be76cb243490f8ab8 Mon Sep 17 00:00:00 2001 From: mirecl Date: Thu, 20 Jul 2023 15:02:06 +0300 Subject: [PATCH 4/6] Fix impl `encoding.TextUnmarshaler` +docs --- README.md | 22 +++++++++++----------- cleanenv.go | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9f7269f..5967cba 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Minimalistic configuration reader -[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![GoDoc](https://godoc.org/github.com/ilyakaznacheev/cleanenv?status.svg)](https://godoc.org/github.com/ilyakaznacheev/cleanenv) [![Go Report Card](https://goreportcard.com/badge/github.com/ilyakaznacheev/cleanenv)](https://goreportcard.com/report/github.com/ilyakaznacheev/cleanenv) [![Coverage Status](https://codecov.io/github/ilyakaznacheev/cleanenv/coverage.svg?branch=master)](https://codecov.io/gh/ilyakaznacheev/cleanenv) @@ -24,18 +24,18 @@ This is a simple configuration reading tool. It just does the following: - [Installation](#installation) - [Usage](#usage) - - [Read Configuration](#read-configuration) - - [Read Environment Variables Only](#read-environment-variables-only) - - [Update Environment Variables](#update-environment-variables) - - [Description](#description) + - [Read Configuration](#read-configuration) + - [Read Environment Variables Only](#read-environment-variables-only) + - [Update Environment Variables](#update-environment-variables) + - [Description](#description) - [Model Format](#model-format) - [Supported types](#supported-types) - [Custom Functions](#custom-functions) - - [Custom Value Setter](#custom-value-setter) - - [Custom Value Update](#custom-value-update) + - [Custom Value Setter](#custom-value-setter) + - [Custom Value Update](#custom-value-update) - [Supported File Formats](#supported-file-formats) - [Integration](#integration) - - [Flag](#flag) + - [Flag](#flag) - [Examples](#examples) - [Contribution](#contribution) - [Thanks](#thanks) @@ -93,7 +93,7 @@ This will do the following: Sometimes you don't want to use configuration files at all, or you may want to use `.env` file format instead. Thus, you can limit yourself with only reading environment variables: -```go +```go import "github.com/ilyakaznacheev/cleanenv" type ConfigDatabase struct { @@ -196,7 +196,6 @@ There are following supported types: - any type that implements `encoding.TextUnmarshaler`; - any type implementing `cleanenv.Setter` interface. - ## Custom Functions To enhance package abilities you can use some custom functions. @@ -252,6 +251,7 @@ There are several most popular config file formats supported: - ENV (`.env`) **Note**: + - while using `.env` file the library will set corresponding data to process environment variables. It will override existing variables with the same keys in the process environment. @@ -320,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). \ No newline at end of file +[Clean Configuration Management in Golang](https://dev.to/ilyakaznacheev/clean-configuration-management-in-golang-1c89). diff --git a/cleanenv.go b/cleanenv.go index 3df3ff7..d483091 100644 --- a/cleanenv.go +++ b/cleanenv.go @@ -468,10 +468,10 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error { } 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 { + if ct, ok := field.Interface().(encoding.TextUnmarshaler); ok { return ct.UnmarshalText([]byte(value)) + } else if ctp, ok := field.Addr().Interface().(encoding.TextUnmarshaler); ok { + return ctp.UnmarshalText([]byte(value)) } if cs, ok := field.Interface().(Setter); ok { From f138599a34711f7b888979ae4d59d52d439ae1c3 Mon Sep 17 00:00:00 2001 From: mirecl Date: Thu, 20 Jul 2023 15:04:49 +0300 Subject: [PATCH 5/6] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5967cba..2c8ea2f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Minimalistic configuration reader -[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![GoDoc](https://godoc.org/github.com/ilyakaznacheev/cleanenv?status.svg)](https://godoc.org/github.com/ilyakaznacheev/cleanenv) [![Go Report Card](https://goreportcard.com/badge/github.com/ilyakaznacheev/cleanenv)](https://goreportcard.com/report/github.com/ilyakaznacheev/cleanenv) [![Coverage Status](https://codecov.io/github/ilyakaznacheev/cleanenv/coverage.svg?branch=master)](https://codecov.io/gh/ilyakaznacheev/cleanenv) @@ -24,18 +24,18 @@ This is a simple configuration reading tool. It just does the following: - [Installation](#installation) - [Usage](#usage) - - [Read Configuration](#read-configuration) - - [Read Environment Variables Only](#read-environment-variables-only) - - [Update Environment Variables](#update-environment-variables) - - [Description](#description) + - [Read Configuration](#read-configuration) + - [Read Environment Variables Only](#read-environment-variables-only) + - [Update Environment Variables](#update-environment-variables) + - [Description](#description) - [Model Format](#model-format) - [Supported types](#supported-types) - [Custom Functions](#custom-functions) - - [Custom Value Setter](#custom-value-setter) - - [Custom Value Update](#custom-value-update) + - [Custom Value Setter](#custom-value-setter) + - [Custom Value Update](#custom-value-update) - [Supported File Formats](#supported-file-formats) - [Integration](#integration) - - [Flag](#flag) + - [Flag](#flag) - [Examples](#examples) - [Contribution](#contribution) - [Thanks](#thanks) @@ -93,7 +93,7 @@ This will do the following: Sometimes you don't want to use configuration files at all, or you may want to use `.env` file format instead. Thus, you can limit yourself with only reading environment variables: -```go +```go import "github.com/ilyakaznacheev/cleanenv" type ConfigDatabase struct { @@ -196,6 +196,7 @@ There are following supported types: - any type that implements `encoding.TextUnmarshaler`; - any type implementing `cleanenv.Setter` interface. + ## Custom Functions To enhance package abilities you can use some custom functions. @@ -251,7 +252,6 @@ There are several most popular config file formats supported: - ENV (`.env`) **Note**: - - while using `.env` file the library will set corresponding data to process environment variables. It will override existing variables with the same keys in the process environment. From a5a43ad94723c3424fb6893823dde5891f69f926 Mon Sep 17 00:00:00 2001 From: mirecl Date: Thu, 20 Jul 2023 15:28:47 +0300 Subject: [PATCH 6/6] Add comment for check validStruct and `encoding.TextUnmarshaler` --- cleanenv.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cleanenv.go b/cleanenv.go index d483091..bb28cb7 100644 --- a/cleanenv.go +++ b/cleanenv.go @@ -463,6 +463,8 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error { valueType := field.Type() // look for supported struct parser + // parsing of struct must be done before checking the implementation `encoding.TextUnmarshaler` + // standard struct types already have the implementation `encoding.TextUnmarshaler` (for example `time.Time`) if structParser, found := validStructs[valueType]; found { return structParser(&field, value, layout) }