-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecode.go
More file actions
137 lines (113 loc) · 3.4 KB
/
Copy pathdecode.go
File metadata and controls
137 lines (113 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (c) 2022, Roel Schut. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package env
import (
"bytes"
"io"
"reflect"
"github.com/go-pogo/env/envtag"
"github.com/go-pogo/errors"
)
const ErrStructPointerExpected errors.Msg = "expected a non-nil pointer to a struct"
// Unmarshaler is the interface implemented by types that can unmarshal a
// textual representation of themselves.
// It is similar to [encoding.TextUnmarshaler].
type Unmarshaler interface {
UnmarshalEnv([]byte) error
}
// Unmarshal parses the env formatted data and stores the result in the value
// pointed to by v. If v is nil or not a pointer, Unmarshal returns an
// [ErrStructPointerExpected] error.
func Unmarshal(data []byte, v any) error {
return NewReaderDecoder(bytes.NewReader(data)).Decode(v)
}
type DecodeOptions struct {
// ReplaceVars
ReplaceVars bool
}
// A Decoder looks up environment variables while decoding them into a struct.
type Decoder struct {
DecodeOptions
TagOptions
lookupper Lookupper
}
const panicNilLookupper = "env.Decoder: Lookupper must not be nil"
// NewDecoder returns a new [Decoder] which looks up environment variables from
// the provided [Lookupper](s). When a [Chain] is provided it must not be empty.
func NewDecoder(src ...Lookupper) *Decoder {
l, chained := chain(src...)
if !chained && l == nil {
panic(panicNilLookupper)
} else if c, ok := l.(chainLookupper); ok && len(c) == 0 {
panic(panicNilLookupper)
}
return (&Decoder{lookupper: l}).
WithOptions(DecodeOptions{ReplaceVars: true}).
WithTagOptions(envtag.DefaultOptions())
}
// NewReaderDecoder returns a new [Decoder] similar to calling [NewDecoder] with
// [NewReader] as argument.
func NewReaderDecoder(r io.Reader) *Decoder {
return (&Decoder{lookupper: NewReader(r)}).
WithOptions(DecodeOptions{ReplaceVars: true}).
WithTagOptions(envtag.DefaultOptions())
}
// Strict sets the StrictTags option to true.
func (d *Decoder) Strict() *Decoder {
d.StrictTags = true
return d
}
// WithLookupper sets the internal [Lookupper] to l.
func (d *Decoder) WithLookupper(l Lookupper) *Decoder {
if l == nil {
panic(panicNilLookupper)
}
d.lookupper = l
return d
}
// WithOptions sets DecodeOptions to the provided [DecodeOptions] opts.
func (d *Decoder) WithOptions(opts DecodeOptions) *Decoder {
d.DecodeOptions = opts
return d
}
// WithTagOptions sets TagOptions to the provided [TagOptions] opts.
func (d *Decoder) WithTagOptions(opts TagOptions) *Decoder {
d.TagOptions = opts
return d
}
func (d *Decoder) Decode(v any) error {
if d.lookupper == nil {
panic(panicNilLookupper)
}
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New(ErrStructPointerExpected)
}
if underlyingKind(rv.Elem().Type()) != reflect.Struct {
return errors.New(ErrStructPointerExpected)
}
if d.ReplaceVars {
if _, ok := d.lookupper.(*Replacer); !ok {
d.lookupper = NewReplacer(d.lookupper)
}
}
return (&traverser{
TagOptions: d.TagOptions,
isKnownType: typeKnownByUnmarshaler,
handleField: d.decodeField,
}).start(rv)
}
func (d *Decoder) decodeField(rv reflect.Value, tag envtag.Tag) error {
val, err := d.lookupper.Lookup(tag.Name)
if err != nil && !IsNotFound(err) {
return err
}
if val.String() == "" {
if tag.Default == "" {
return nil
}
val = Value(tag.Default)
}
return unmarshaler.Unmarshal(val, rv)
}