Skip to content

Commit 4eefa52

Browse files
Merge pull request #50 from ilyakaznacheev/required-fields
Required fields
2 parents fec9bfa + 47aa2e3 commit 4eefa52

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Here remote host and port may change in a distributed system architecture. Field
141141

142142
### Description
143143

144-
You can get descriptions of all environment variables to use them in help documentation.
144+
You can get descriptions of all environment variables to use them in the help documentation.
145145

146146
```go
147147
import github.com/ilyakaznacheev/cleanenv
@@ -169,10 +169,11 @@ Environment variables:
169169

170170
## Model Format
171171

172-
Library uses tags to configure model of configuration structure. There are following tags:
172+
Library uses tags to configure the model of configuration structure. There are the following tags:
173173

174174
- `env="<name>"` - environment variable name (e.g. `env="PORT"`);
175175
- `env-upd` - flag to mark a field as updatable. Run `UpdateEnv(&cfg)` to refresh updatable variables from environment;
176+
- `env-required` - flag to mark a field as required. If set will return an error during environment parsing when the flagged as required field is empty (default Go value). Tag `env-default` is ignored in this case;
176177
- `env-default="<value>"` - default value. If the field wasn't filled from the environment variable default value will be used instead;
177178
- `env-separator="<value>"` - custom list and map separator. If not set, the default separator `,` will be used;
178179
- `env-description="<value>"` - environment variable description;
@@ -249,7 +250,7 @@ There are several most popular config file formats supported:
249250

250251
## Integration
251252

252-
Package can be used with many other solutions. To make it more useful, we made some helpers.
253+
The package can be used with many other solutions. To make it more useful, we made some helpers.
253254

254255
### Flag
255256

cleanenv.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ const (
3838
DefaultSeparator = ","
3939
)
4040

41+
// Supported tags
42+
const (
43+
// Name of the environment variable or a list of names
44+
TagEnv = "env"
45+
// Value parsing layout (for types like time.Time)
46+
TagEnvLayout = "env-layout"
47+
// Default value
48+
TagEnvDefault = "env-default"
49+
// Custom list and map separator
50+
TagEnvSeparator = "env-separator"
51+
// Environment variable description
52+
TagEnvDescription = "env-description"
53+
// Flag to mark a field as updatable
54+
TagEnvUpd = "env-upd"
55+
// Flag to mark a field as required
56+
TagEnvRequired = "env-required"
57+
)
58+
4159
// Setter is an interface for a custom value setter.
4260
//
4361
// To implement a custom value setter you need to add a SetValue function to your type that will receive a string raw value:
@@ -179,12 +197,14 @@ func parseENV(r io.Reader, _ interface{}) error {
179197
// structMeta is a structure metadata entity
180198
type structMeta struct {
181199
envList []string
200+
fieldName string
182201
fieldValue reflect.Value
183202
defValue *string
184203
layout *string
185204
separator string
186205
description string
187206
updatable bool
207+
required bool
188208
}
189209

190210
// isFieldValueZero determines if fieldValue empty or not
@@ -230,7 +250,7 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
230250
continue
231251
}
232252
// process time.Time
233-
if l, ok := fType.Tag.Lookup("env-layout"); ok {
253+
if l, ok := fType.Tag.Lookup(TagEnvLayout); ok {
234254
layout = &l
235255
}
236256
}
@@ -240,32 +260,36 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
240260
continue
241261
}
242262

243-
if def, ok := fType.Tag.Lookup("env-default"); ok {
263+
if def, ok := fType.Tag.Lookup(TagEnvDefault); ok {
244264
defValue = &def
245265
}
246266

247-
if sep, ok := fType.Tag.Lookup("env-separator"); ok {
267+
if sep, ok := fType.Tag.Lookup(TagEnvSeparator); ok {
248268
separator = sep
249269
} else {
250270
separator = DefaultSeparator
251271
}
252272

253-
_, upd := fType.Tag.Lookup("env-upd")
273+
_, upd := fType.Tag.Lookup(TagEnvUpd)
274+
275+
_, required := fType.Tag.Lookup(TagEnvRequired)
254276

255277
envList := make([]string, 0)
256278

257-
if envs, ok := fType.Tag.Lookup("env"); ok && len(envs) != 0 {
279+
if envs, ok := fType.Tag.Lookup(TagEnv); ok && len(envs) != 0 {
258280
envList = strings.Split(envs, DefaultSeparator)
259281
}
260282

261283
metas = append(metas, structMeta{
262284
envList: envList,
285+
fieldName: s.Type().Field(idx).Name,
263286
fieldValue: s.Field(idx),
264287
defValue: defValue,
265288
layout: layout,
266289
separator: separator,
267-
description: fType.Tag.Get("env-description"),
290+
description: fType.Tag.Get(TagEnvDescription),
268291
updatable: upd,
292+
required: required,
269293
})
270294
}
271295

@@ -302,6 +326,12 @@ func readEnvVars(cfg interface{}, update bool) error {
302326
}
303327
}
304328

329+
if rawValue == nil && meta.required && meta.isFieldValueZero() {
330+
err := fmt.Errorf("field %q is required but the value is not provided",
331+
meta.fieldName)
332+
return err
333+
}
334+
305335
if rawValue == nil && meta.isFieldValueZero() {
306336
rawValue = meta.defValue
307337
}

cleanenv_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func TestReadEnvVars(t *testing.T) {
7272
Time7 map[string]time.Time `env:"TEST_TIME7" env-separator:"|"`
7373
}
7474

75+
type Required struct {
76+
NotRequired int `env:"NOT_REQUIRED"`
77+
Required int `env:"REQUIRED" env-required:"true"`
78+
}
79+
7580
tests := []struct {
7681
name string
7782
env map[string]string
@@ -285,6 +290,13 @@ func TestReadEnvVars(t *testing.T) {
285290
want: ta,
286291
wantErr: true,
287292
},
293+
294+
{
295+
name: "required error",
296+
cfg: &Required{},
297+
want: &Required{},
298+
wantErr: true,
299+
},
288300
}
289301

290302
for _, tt := range tests {

0 commit comments

Comments
 (0)