Skip to content

Commit

Permalink
Add regexp.Regexp support
Browse files Browse the repository at this point in the history
It's useful to support validated regular expressions in configuration
files. Previously, we could represent these fields as raw pattern
strings and compile them in application code. By supporting Regexp as a
first-class field type, we get a smaller application code and more
immediate error reporting without complicating the API.
  • Loading branch information
jparise committed May 17, 2021
1 parent f1d6c5f commit 2eec4b5
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 5 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fig is a tiny library for loading an application's config file and its environme
- Define your **configuration**, **validations** and **defaults** in a single location
- Optionally **load from the environment** as well
- Only **3** external dependencies
- Full support for`time.Time` & `time.Duration`
- Full support for`time.Time`, `time.Duration` & `regexp.Regexp`
- Tiny API
- Decoders for `.yaml`, `.json` and `.toml` files

Expand Down Expand Up @@ -64,8 +64,9 @@ type Config struct {
Cleanup time.Duration `fig:"cleanup" default:"30m"`
}
Logger struct {
Level string `fig:"level" default:"info"`
Trace bool `fig:"trace"`
Level string `fig:"level" default:"info"`
Pattern *regexp.Regexp `fig:"pattern" default:".*"`
Trace bool `fig:"trace"`
}
}

Expand Down
7 changes: 7 additions & 0 deletions fig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -289,6 +290,12 @@ func (f *fig) setValue(fv reflect.Value, val string) error {
return err
}
fv.Set(reflect.ValueOf(t))
} else if _, ok := fv.Interface().(regexp.Regexp); ok {
re, err := regexp.Compile(val)
if err != nil {
return err
}
fv.Set(reflect.ValueOf(*re))
} else {
return fmt.Errorf("unsupported type %s", fv.Kind())
}
Expand Down
45 changes: 43 additions & 2 deletions fig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -221,8 +222,9 @@ func Test_fig_Load_Defaults(t *testing.T) {
Host string `fig:"host" default:"127.0.0.1"`
Ports []int `fig:"ports" default:"[80,443]"`
Logger struct {
LogLevel string `fig:"log_level" default:"info"`
Production bool `fig:"production"`
LogLevel string `fig:"log_level" default:"info"`
Pattern *regexp.Regexp `fig:"pattern" default:".*"`
Production bool `fig:"production"`
Metadata struct {
Keys []string `fig:"keys" default:"[ts]"`
}
Expand All @@ -236,6 +238,7 @@ func Test_fig_Load_Defaults(t *testing.T) {
want.Host = "0.0.0.0"
want.Ports = []int{80, 443}
want.Logger.LogLevel = "debug"
want.Logger.Pattern = regexp.MustCompile(".*")
want.Logger.Production = false
want.Logger.Metadata.Keys = []string{"ts"}
want.Application.BuildDate = time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)
Expand Down Expand Up @@ -1014,6 +1017,35 @@ func Test_fig_setValue(t *testing.T) {
}
})

t.Run("regexp", func(t *testing.T) {
var re regexp.Regexp
fv := reflect.ValueOf(&re).Elem()

err := fig.setValue(fv, "[a-z]+")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

want, err := regexp.Compile("[a-z]+")
if err != nil {
t.Fatalf("error parsing time: %v", err)
}

if re.String() != want.String() {
t.Fatalf("want %v, got %v", want, re)
}
})

t.Run("bad regexp", func(t *testing.T) {
var re regexp.Regexp
fv := reflect.ValueOf(&re).Elem()

err := fig.setValue(fv, "[a-")
if err == nil {
t.Fatalf("expected err")
}
})

t.Run("interface returns error", func(t *testing.T) {
var i interface{}
fv := reflect.ValueOf(i)
Expand Down Expand Up @@ -1089,6 +1121,15 @@ func Test_fig_setSlice(t *testing.T) {
},
Val: "[2019-12-25T10:30:30Z,2020-01-01T00:00:00Z]",
},
{
Name: "regexps",
InSlice: &[]*regexp.Regexp{},
WantSlice: &[]*regexp.Regexp{
regexp.MustCompile("[a-z]+"),
regexp.MustCompile(".*"),
},
Val: "[[a-z]+,.*]",
},
} {
t.Run(tc.Val, func(t *testing.T) {
in := reflect.ValueOf(tc.InSlice).Elem()
Expand Down

0 comments on commit 2eec4b5

Please sign in to comment.