Skip to content

Commit df72a6a

Browse files
Merge pull request #9 from alexandreh2ag/fix_cron_tag_validator
Create custom cron expr validator to fix expr like '0 */2 * * *'
2 parents a8e019e + 5b0d560 commit df72a6a

8 files changed

+118
-13
lines changed

cli/root.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
appCtx "github.com/alexandreh2ag/go-task/context"
7+
gtaskValidator "github.com/alexandreh2ag/go-task/validator"
78
"github.com/go-playground/validator/v10"
89
"log/slog"
910

@@ -44,7 +45,7 @@ func GetRootPreRunEFn(ctx *appCtx.Context, validateCfg bool) func(*cobra.Command
4445
initConfig(ctx, cmd)
4546

4647
if validateCfg {
47-
validate := validator.New()
48+
validate := gtaskValidator.New()
4849
err = validate.Struct(ctx.Config)
4950
if err != nil {
5051

cli/validate.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"github.com/alexandreh2ag/go-task/context"
7+
gtaskValidator "github.com/alexandreh2ag/go-task/validator"
78
"github.com/go-playground/validator/v10"
89
"github.com/spf13/cobra"
910
)
@@ -23,7 +24,7 @@ func GetValidateCmd(ctx *context.Context) *cobra.Command {
2324

2425
func GetValidateRunFn(ctx *context.Context) func(*cobra.Command, []string) error {
2526
return func(cmd *cobra.Command, args []string) error {
26-
validate := validator.New()
27+
validate := gtaskValidator.New()
2728
err := validate.Struct(ctx.Config)
2829
if err != nil {
2930

config/config_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"github.com/alexandreh2ag/go-task/types"
5+
gtaskValidator "github.com/alexandreh2ag/go-task/validator"
56
"github.com/go-playground/validator/v10"
67
"github.com/stretchr/testify/assert"
78
"testing"
@@ -27,7 +28,7 @@ func Test_ConfigWorkers_SuccessValidateEmpty(t *testing.T) {
2728

2829
func Test_ConfigWorkers_SuccessValidate(t *testing.T) {
2930
cfg := DefaultConfig()
30-
validate := validator.New()
31+
validate := gtaskValidator.New()
3132
cfg.Workers = types.WorkerTasks{
3233
{Id: "test", Command: "fake"},
3334
{Id: "test2", Command: "fake"},
@@ -43,7 +44,7 @@ func Test_ConfigWorkers_SuccessValidate(t *testing.T) {
4344

4445
func Test_ConfigWorkers_ErrorValidateDivet(t *testing.T) {
4546
cfg := DefaultConfig()
46-
validate := validator.New()
47+
validate := gtaskValidator.New()
4748
cfg.Workers = types.WorkerTasks{
4849
{Id: "test", Command: "fake"},
4950
{Id: "test2"},
@@ -56,12 +57,12 @@ func Test_ConfigWorkers_ErrorValidateDivet(t *testing.T) {
5657

5758
assert.Error(t, err)
5859
assert.Contains(t, err.Error(), "Config.Workers[1].Command' Error:Field validation for 'Command' failed on the 'required' tag")
59-
assert.Contains(t, err.Error(), "Config.Scheduled[1].CronExpr' Error:Field validation for 'CronExpr' failed on the 'cron' tag")
60+
assert.Contains(t, err.Error(), "Config.Scheduled[1].CronExpr' Error:Field validation for 'CronExpr' failed on the 'cron-expr' tag")
6061
}
6162

6263
func Test_ConfigWorkers_ErrorValidateDuplicateId(t *testing.T) {
6364
cfg := DefaultConfig()
64-
validate := validator.New()
65+
validate := gtaskValidator.New()
6566
cfg.Workers = types.WorkerTasks{
6667
{Id: "test", Command: "fake"},
6768
{Id: "test", Command: "fake"},

types/scheduled_task.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type ScheduledTasks = []*ScheduledTask
2121

2222
type ScheduledTask struct {
2323
Id string `mapstructure:"id" validate:"required,excludesall=!@#$ "`
24-
CronExpr string `mapstructure:"expr" validate:"required,cron"`
24+
CronExpr string `mapstructure:"expr" validate:"required,cron-expr"`
2525
Command string `mapstructure:"command" validate:"required"`
2626
Directory string `mapstructure:"directory" validate:"omitempty,required,dirpath"`
2727
TaskResult *TaskResult

types/scheduled_task_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"github.com/alexandreh2ag/go-task/log"
7-
"github.com/go-playground/validator/v10"
7+
gtaskValidator "github.com/alexandreh2ag/go-task/validator"
88
"github.com/stretchr/testify/assert"
99
"io"
1010
"log/slog"
@@ -15,7 +15,7 @@ import (
1515
)
1616

1717
func Test_ScheduledTask_SuccessValidate(t *testing.T) {
18-
validate := validator.New()
18+
validate := gtaskValidator.New()
1919
scheduled := ScheduledTask{
2020
Id: "test",
2121
CronExpr: "* * * * *",
@@ -27,7 +27,7 @@ func Test_ScheduledTask_SuccessValidate(t *testing.T) {
2727
}
2828

2929
func Test_ScheduledTask_SuccessValidateWithOptionalData(t *testing.T) {
30-
validate := validator.New()
30+
validate := gtaskValidator.New()
3131
scheduled := ScheduledTask{
3232
Id: "test",
3333
CronExpr: "* * * * *",
@@ -40,7 +40,7 @@ func Test_ScheduledTask_SuccessValidateWithOptionalData(t *testing.T) {
4040
}
4141

4242
func Test_ScheduledTask_ErrorValidate(t *testing.T) {
43-
validate := validator.New()
43+
validate := gtaskValidator.New()
4444
scheduled := ScheduledTask{
4545
Id: "test",
4646
CronExpr: "* * * * *",
@@ -52,7 +52,7 @@ func Test_ScheduledTask_ErrorValidate(t *testing.T) {
5252
}
5353

5454
func Test_ScheduledTask_ErrorValidateComplex(t *testing.T) {
55-
validate := validator.New()
55+
validate := gtaskValidator.New()
5656
scheduled := ScheduledTask{
5757
Id: "test",
5858
CronExpr: "wrong",
@@ -62,7 +62,7 @@ func Test_ScheduledTask_ErrorValidateComplex(t *testing.T) {
6262
err := validate.Struct(scheduled)
6363

6464
assert.Error(t, err)
65-
assert.Contains(t, err.Error(), "Field validation for 'CronExpr' failed on the 'cron' tag")
65+
assert.Contains(t, err.Error(), "Field validation for 'CronExpr' failed on the 'cron-expr' tag")
6666
assert.Contains(t, err.Error(), "Field validation for 'Directory' failed on the 'dirpath' tag")
6767
}
6868

validator/cron.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package validator
2+
3+
import (
4+
"github.com/go-playground/validator/v10"
5+
"regexp"
6+
)
7+
8+
const (
9+
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+-\d+)|\d+|\*|\*\/\d+) ?){5,7})`
10+
CronExprKey = "cron-expr"
11+
)
12+
13+
var (
14+
cronRegex = regexp.MustCompile(cronRegexString)
15+
)
16+
17+
func ValidateCronExpr(fl validator.FieldLevel) bool {
18+
cronString := fl.Field().String()
19+
return cronRegex.MatchString(cronString)
20+
}

validator/cron_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package validator
2+
3+
import (
4+
"github.com/go-playground/validator/v10"
5+
"github.com/stretchr/testify/assert"
6+
"testing"
7+
)
8+
9+
func TestValidateCronExpr(t *testing.T) {
10+
type args struct {
11+
Cron string `validate:"cron-expr"`
12+
}
13+
validate := validator.New()
14+
_ = validate.RegisterValidation(CronExprKey, ValidateCronExpr)
15+
16+
tests := []struct {
17+
name string
18+
args args
19+
wantErr assert.ErrorAssertionFunc
20+
}{
21+
{
22+
name: "successFullWildcard",
23+
args: args{Cron: "* * * * *"},
24+
wantErr: assert.NoError,
25+
},
26+
{
27+
name: "successRangeMinutes",
28+
args: args{Cron: "5-20 * * * *"},
29+
wantErr: assert.NoError,
30+
},
31+
{
32+
name: "successRangeHour",
33+
args: args{Cron: "0 5-20 * * *"},
34+
wantErr: assert.NoError,
35+
},
36+
{
37+
name: "successStepHour",
38+
args: args{Cron: "0 */2 * * *"},
39+
wantErr: assert.NoError,
40+
},
41+
{
42+
name: "successStepMinute",
43+
args: args{Cron: "*/2 * * * *"},
44+
wantErr: assert.NoError,
45+
},
46+
{
47+
name: "successOneMinuteHourDay",
48+
args: args{Cron: "1 1 1 * *"},
49+
wantErr: assert.NoError,
50+
},
51+
{
52+
name: "failStepDigitMissing",
53+
args: args{Cron: "* */ * * *"},
54+
wantErr: assert.Error,
55+
},
56+
{
57+
name: "failRangeDigitMissing",
58+
args: args{Cron: "* 0- * * *"},
59+
wantErr: assert.Error,
60+
},
61+
{
62+
name: "failEmpty",
63+
args: args{Cron: ""},
64+
wantErr: assert.Error,
65+
},
66+
}
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
err := validate.Struct(tt.args)
70+
tt.wantErr(t, err, "ValidateCronExpr is not valid")
71+
})
72+
}
73+
}

validator/validator.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package validator
2+
3+
import "github.com/go-playground/validator/v10"
4+
5+
func New(options ...validator.Option) *validator.Validate {
6+
validate := validator.New()
7+
_ = validate.RegisterValidation(CronExprKey, ValidateCronExpr)
8+
return validate
9+
}

0 commit comments

Comments
 (0)