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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: go 1.23 upgrade, README fix, docstring additions, & go styling updates #28

Merged
merged 4 commits into from
Oct 25, 2024
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
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
Loading