Skip to content

Commit

Permalink
feat: Add lazy loading to regular expressions (#16)
Browse files Browse the repository at this point in the history
## Motivation

We should aim at limiting initial load time when importing the library,
one of the contributors to the initial load time are compilations of
regular expressions. We should avoid loading all of them at once, but
rather lazy load the ones which are used by users once.

## Related Changes

go-playground/validator#1277

## Release Notes

All regular expressions are now lazy loaded, once when the corresponding
rule is used.
  • Loading branch information
nieomylnieja authored Sep 19, 2024
1 parent 69a9aeb commit 73dd5a3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 7 deletions.
34 changes: 34 additions & 0 deletions pkg/rules/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package rules

import (
"regexp"
"sync"
)

// nolint: lll
// Define all regular expressions here:
var (
validUUIDRegexp = lazyRegexCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
asciiRegexp = lazyRegexCompile(`^[\x00-\x7F]*$`)
)

// lazyRegexCompile returns a function that compiles the regular expression
// once, when the function is called for the first time.
// If the function is never called, the regular expression is never compiled,
// thus saving on performance.
//
// All regular expression literals should be compiled using this function.
//
// Credits: https://github.com/go-playground/validator/commit/2e1df48b5ab876bdd461bdccc51d109389e7572f
func lazyRegexCompile(str string) func() *regexp.Regexp {
var (
regex *regexp.Regexp
once sync.Once
)
return func() *regexp.Regexp {
once.Do(func() {
regex = regexp.MustCompile(str)
})
return regex
}
}
18 changes: 18 additions & 0 deletions pkg/rules/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package rules

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestLazyRegexCompile(t *testing.T) {
lazyRegexp := lazyRegexCompile("^test$")

re1 := lazyRegexp()
assert.True(t, re1.MatchString("test"))
re2 := lazyRegexp()
assert.True(t, re2.MatchString("test"))

assert.True(t, re1 == re2, "both regular expression must be the same pointer")
}
9 changes: 2 additions & 7 deletions pkg/rules/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,19 @@ func StringDNSLabel() govy.RuleSet[string] {
).WithErrorCode(ErrorCodeStringDNSLabel)
}

var validUUIDRegexp = regexp.
MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")

// StringUUID ensures property's value is a valid UUID string.
func StringUUID() govy.Rule[string] {
return StringMatchRegexp(validUUIDRegexp,
return StringMatchRegexp(validUUIDRegexp(),
"00000000-0000-0000-0000-000000000000",
"e190c630-8873-11ee-b9d1-0242ac120002",
"79258D24-01A7-47E5-ACBB-7E762DE52298").
WithDetails("expected RFC-4122 compliant UUID string").
WithErrorCode(ErrorCodeStringUUID)
}

var asciiRegexp = regexp.MustCompile("^[\x00-\x7F]*$")

// StringASCII ensures property's value contains only ASCII characters.
func StringASCII() govy.Rule[string] {
return StringMatchRegexp(asciiRegexp).WithErrorCode(ErrorCodeStringASCII)
return StringMatchRegexp(asciiRegexp()).WithErrorCode(ErrorCodeStringASCII)
}

// StringURL ensures property's value is a valid URL as defined by [url.Parse] function.
Expand Down

0 comments on commit 73dd5a3

Please sign in to comment.