Skip to content

fixes:[issue 1000] Refactor parseRange function in cron file #1091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 14, 2024
57 changes: 32 additions & 25 deletions pkg/gofr/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func parseSchedule(s string) (*job, error) {
return nil, err
}

// day/dayOfWeek combination
// day/dayOfWeek combination
mergeDays(j)

return j, nil
Expand All @@ -153,17 +153,17 @@ func mergeDays(j *job) {

// parsePart parse individual schedule part from schedule string.
func parsePart(s string, minValue, maxValue int) (map[int]struct{}, error) {
// wildcard pattern
// wildcard pattern
if s == "*" {
return getDefaultJobField(minValue, maxValue, 1), nil
}

// */2 1-59/5 pattern
// */2 1-59/5 pattern
if matches := matchN.FindStringSubmatch(s); matches != nil {
return parseSteps(s, matches[1], matches[2], minValue, maxValue)
}

// 1,2,4 or 1,2,10-15,20,30-45 pattern
// 1,2,4 or 1,2,10-15,20,30-45 pattern
return parseRange(s, minValue, maxValue)
}

Expand Down Expand Up @@ -194,39 +194,45 @@ func parseRange(s string, minValue, maxValue int) (map[int]struct{}, error) {
r := make(map[int]struct{})
parts := strings.Split(s, ",")

for _, x := range parts {
rng := matchRange.FindStringSubmatch(x)

if rng == nil {
i, err := strconv.Atoi(x)
if err != nil {
return nil, errParsing{x, s}
}

if i < minValue || i > maxValue {
return nil, errOutOfRange{i, s, minValue, maxValue}
}
for _, part := range parts {
if err := parseSingleOrRange(part, minValue, maxValue, r); err != nil {
return nil, err
}
}

r[i] = struct{}{}
if len(r) == 0 {
return nil, errParsing{invalidPart: s}
}

continue
}
return r, nil
}

func parseSingleOrRange(part string, minValue, maxValue int, r map[int]struct{}) error {
if rng := matchRange.FindStringSubmatch(part); rng != nil {
localMin, _ := strconv.Atoi(rng[1])
localMax, _ := strconv.Atoi(rng[2])

if localMin < minValue || localMax > maxValue {
return nil, errOutOfRange{x, s, minValue, maxValue}
return errOutOfRange{part, part, minValue, maxValue}
}

r = getDefaultJobField(localMin, localMax, 1)
}
for i := localMin; i <= localMax; i++ {
r[i] = struct{}{}
}
} else {
i, err := strconv.Atoi(part)
if err != nil {
return errParsing{part, part}
}

if len(r) == 0 {
return nil, errParsing{invalidPart: s}
if i < minValue || i > maxValue {
return errOutOfRange{part, part, minValue, maxValue}
}

r[i] = struct{}{}
}

return r, nil
return nil
}

func getDefaultJobField(minValue, maxValue, incr int) map[int]struct{} {
Expand Down Expand Up @@ -332,6 +338,7 @@ func (c *Crontab) AddJob(schedule, jobName string, fn CronFunc) error {
var errBadScheduleFormat = errors.New("schedule string must have five components like * * * * *")

// errOutOfRange denotes the errors that occur when a range in schedule is out of scope for the particular time unit.

type errOutOfRange struct {
rangeVal interface{}
input string
Expand Down
112 changes: 112 additions & 0 deletions pkg/gofr/cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,115 @@ func Test_noopRequest(t *testing.T) {
require.NoError(t, noop.Bind(nil))
assert.Nil(t, noop.Params("test"))
}
// New tests for parseRange
func TestCron_parseRange(t *testing.T) {
tests := []struct {
name string
input string
expected map[int]struct{}
min int
max int
hasError bool
}{
{
name: "Valid Range",
input: "1-5",
expected: map[int]struct{}{
1: {}, 2: {}, 3: {}, 4: {}, 5: {},
},
min: 1, max: 10,
hasError: false,
},
{
name: "Out of Range",
input: "1-12",
expected: nil,
min: 1, max: 10,
hasError: true,
},
{
name: "Invalid Input",
input: "a-b",
expected: nil,
min: 1, max: 10,
hasError: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output, err := parseRange(test.input, test.min, test.max)

if (err != nil) != test.hasError {
t.Errorf("Expected error: %v, got: %v", test.hasError, err)
}

if len(output) != len(test.expected) {
t.Errorf("Expected: %v, got: %v", test.expected, output)
}
})
}
}

// Additional tests for parsePart can be added here...
func TestCron_parsePart(t *testing.T) {
tests := []struct {
name string
input string
expected map[int]struct{}
min int
max int
hasError bool
}{
{
name: "Single Value",
input: "5",
expected: map[int]struct{}{
5: {},
},
min: 1,
max: 10,
hasError: false,
},
{
name: "Valid Multiple Values",
input: "1,3,5",
expected: map[int]struct{}{
1: {}, 3: {}, 5: {},
},
min: 1,
max: 10,
hasError: false,
},
{
name: "Invalid Value",
input: "15",
expected: nil,
min: 1,
max: 10,
hasError: true,
},
{
name: "Invalid Format",
input: "1,2,a",
expected: nil,
min: 1,
max: 10,
hasError: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output, err := parsePart(test.input, test.min, test.max)

if (err != nil) != test.hasError {
t.Errorf("Expected error: %v, got: %v", test.hasError, err)
}

if len(output) != len(test.expected) {
t.Errorf("Expected: %v, got: %v", test.expected, output)
}
})
}
}
Loading