Skip to content

Commit

Permalink
chore: go 1.23 upgrade, README fix, docstring additions, & go styling…
Browse files Browse the repository at this point in the history
… updates (#28)

* [chore] go 1.23.2 upgrade, README fix, docstring additions, & go styling updates

Signed-off-by: sheikhrachel <[email protected]>

* fix: remove local test file

Signed-off-by: sheikhrachel <[email protected]>

* fix: 1.23 go version & refactor redundant switch clauses

Signed-off-by: Rachel <[email protected]>

* fix: remove bin dir

Signed-off-by: Rachel <[email protected]>

---------

Signed-off-by: sheikhrachel <[email protected]>
Signed-off-by: Rachel <[email protected]>
  • Loading branch information
sheikhrachel authored Oct 25, 2024
1 parent 131cef9 commit ec74a1d
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 172 deletions.
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"log"
"time"

env "github.com/Netflix/go-env"
"github.com/Netflix/go-env"
)

type Environment struct {
Expand Down Expand Up @@ -50,7 +50,7 @@ func main() {

// ...

es, err = env.Marshal(environment)
es, err = env.Marshal(&environment)
if err != nil {
log.Fatal(err)
}
Expand All @@ -64,27 +64,36 @@ func main() {
es.Apply(cs)

environment = Environment{}
err = env.Unmarshal(es, &environment)
if err != nil {
if err = env.Unmarshal(es, &environment); err != nil {
log.Fatal(err)
}

environment.Extras = es
}
```

This will initially throw an error if `IM_REQUIRED` is not set in the environment as part of the env struct validation.

This error can be resolved by setting the `IM_REQUIRED` environment variable manually in the environment or by setting it in the
code prior to calling `UnmarshalFromEnviron` with:
```go
os.Setenv("IM_REQUIRED", "some_value")
```

## Custom Marshaler/Unmarshaler

There is limited support for dictating how a field should be marshaled or unmarshaled. The following example
shows how you could marshal/unmarshal from JSON

```go
package main

import (
"encoding/json"
"fmt"
"log"

env "github.com/Netflix/go-env"
"github.com/Netflix/go-env"
)

type SomeData struct {
Expand All @@ -93,8 +102,7 @@ type SomeData struct {

func (s *SomeData) UnmarshalEnvironmentValue(data string) error {
var tmp SomeData
err := json.Unmarshal([]byte(data), &tmp)
if err != nil {
if err := json.Unmarshal([]byte(data), &tmp); err != nil {
return err
}
*s = tmp
Expand All @@ -114,9 +122,8 @@ type Config struct {
}

func main() {
var cfg Config
_, err := env.UnmarshalFromEnviron(&cfg)
if err != nil {
var cfg Config
if _, err := env.UnmarshalFromEnviron(&cfg); err != nil {
log.Fatal(err)
}

Expand All @@ -126,7 +133,7 @@ func main() {
fmt.Printf("Got nil or some other value: %v\n", cfg.SomeData)
}

es, err = env.Marshal(cfg)
es, err := env.Marshal(&cfg)
if err != nil {
log.Fatal(err)
}
Expand Down
88 changes: 51 additions & 37 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ import (
"time"
)

const (
// tagKeyDefault is the key used in the struct field tag to specify a default
tagKeyDefault = "default"
// tagKeyRequired is the key used in the struct field tag to specify that the
// field is required
tagKeyRequired = "required"
// tagKeySeparator is the key used in the struct field tag to specify a
// separator for slice fields
tagKeySeparator = "separator"
)

var (
// ErrInvalidValue returned when the value passed to Unmarshal is nil or not a
// pointer to a struct.
Expand All @@ -37,11 +48,14 @@ var (
// ErrUnexportedField returned when a field with tag "env" is not exported.
ErrUnexportedField = errors.New("field must be exported")

// unmarshalType is the reflect.Type element of the Unmarshaler interface
unmarshalType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
)

// ErrMissingRequiredValue returned when a field with required=true contains no value or default
type ErrMissingRequiredValue struct {
// Value is the type of value that is required to provide error context to
// the caller
Value string
}

Expand Down Expand Up @@ -72,17 +86,13 @@ func Unmarshal(es EnvSet, v interface{}) error {
}

t := rv.Type()
for i := 0; i < rv.NumField(); i++ {
for i := range rv.NumField() {
valueField := rv.Field(i)
switch valueField.Kind() {
case reflect.Struct:
if valueField.Kind() == reflect.Struct {
if !valueField.Addr().CanInterface() {
continue
}

iface := valueField.Addr().Interface()
err := Unmarshal(es, iface)
if err != nil {
if err := Unmarshal(es, valueField.Addr().Interface()); err != nil {
return err
}
}
Expand Down Expand Up @@ -120,8 +130,7 @@ func Unmarshal(es EnvSet, v interface{}) error {
}
}

err := set(typeField.Type, valueField, envValue, envTag.Separator)
if err != nil {
if err := set(typeField.Type, valueField, envValue, envTag.Separator); err != nil {
return err
}
delete(es, tag)
Expand Down Expand Up @@ -165,8 +174,7 @@ func set(t reflect.Type, f reflect.Value, value, sliceSeparator string) error {
switch t.Kind() {
case reflect.Ptr:
ptr := reflect.New(t.Elem())
err := set(t.Elem(), ptr.Elem(), value, sliceSeparator)
if err != nil {
if err := set(t.Elem(), ptr.Elem(), value, sliceSeparator); err != nil {
return err
}
f.Set(ptr)
Expand Down Expand Up @@ -224,8 +232,7 @@ func set(t reflect.Type, f reflect.Value, value, sliceSeparator string) error {
default:
dest := reflect.MakeSlice(reflect.SliceOf(t.Elem()), len(values), len(values))
for i, v := range values {
err := set(t.Elem(), dest.Index(i), v, sliceSeparator)
if err != nil {
if err := set(t.Elem(), dest.Index(i), v, sliceSeparator); err != nil {
return err
}
}
Expand Down Expand Up @@ -278,16 +285,14 @@ func Marshal(v interface{}) (EnvSet, error) {

es := make(EnvSet)
t := rv.Type()
for i := 0; i < rv.NumField(); i++ {
for i := range rv.NumField() {
valueField := rv.Field(i)
switch valueField.Kind() {
case reflect.Struct:
if valueField.Kind() == reflect.Struct {
if !valueField.Addr().CanInterface() {
continue
}

iface := valueField.Addr().Interface()
nes, err := Marshal(iface)
nes, err := Marshal(valueField.Addr().Interface())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -315,8 +320,10 @@ func Marshal(v interface{}) (EnvSet, error) {
el = valueField.Interface()
}

var err error
var envValue string
var (
err error
envValue string
)
if m, ok := el.(Marshaler); ok {
envValue, err = m.MarshalEnvironmentValue()
if err != nil {
Expand All @@ -334,32 +341,39 @@ func Marshal(v interface{}) (EnvSet, error) {
return es, nil
}

// tag is a struct used to store the parsed "env" field tag when unmarshalling.
type tag struct {
Keys []string
Default string
Required bool
// Keys is used to store the keys specified in the "env" field tag
Keys []string
// Default is used to specify a default value for the field
Default string
// Required is used to specify that the field is required
Required bool
// Separator is used to split the value of a slice field
Separator string
}

// parseTag is used in the Unmarshal function to parse the "env" field tags
// into a tag struct for use in the set function.
func parseTag(tagString string) tag {
var t tag
envKeys := strings.Split(tagString, ",")
for _, key := range envKeys {
if strings.Contains(key, "=") {
keyData := strings.SplitN(key, "=", 2)
switch strings.ToLower(keyData[0]) {
case "default":
t.Default = keyData[1]
case "required":
t.Required = strings.ToLower(keyData[1]) == "true"
case "separator":
t.Separator = keyData[1]
default:
// just ignoring unsupported keys
continue
}
} else {
if !strings.Contains(key, "=") {
t.Keys = append(t.Keys, key)
continue
}
keyData := strings.SplitN(key, "=", 2)
switch strings.ToLower(keyData[0]) {
case tagKeyDefault:
t.Default = keyData[1]
case tagKeyRequired:
t.Required = strings.ToLower(keyData[1]) == "true"
case tagKeySeparator:
t.Separator = keyData[1]
default:
// just ignoring unsupported keys
continue
}
}
return t
Expand Down
Loading

0 comments on commit ec74a1d

Please sign in to comment.