Skip to content

Commit

Permalink
feat(quartz): introduce new error variables
Browse files Browse the repository at this point in the history
  • Loading branch information
reugn committed Sep 24, 2024
1 parent e4e93b2 commit ac62bd0
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 97 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.18.x, 1.22.x]
go-version: [1.20.x, 1.23.x]
steps:
- name: Setup Go
uses: actions/setup-go@v5
Expand All @@ -27,7 +27,7 @@ jobs:
run: go test -race ./... -coverprofile=coverage.out -covermode=atomic

- name: Upload coverage to Codecov
if: ${{ matrix.go-version == '1.18.x' }}
if: ${{ matrix.go-version == '1.20.x' }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/reugn/go-quartz

go 1.18
go 1.20
30 changes: 15 additions & 15 deletions quartz/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"time"
)

// CronTrigger implements the quartz.Trigger interface.
// CronTrigger implements the [Trigger] interface.
// Used to fire a Job at given moments in time, defined with Unix 'cron-like' schedule definitions.
//
// Examples:
Expand Down Expand Up @@ -37,15 +37,15 @@ type CronTrigger struct {
// Verify CronTrigger satisfies the Trigger interface.
var _ Trigger = (*CronTrigger)(nil)

// NewCronTrigger returns a new CronTrigger using the UTC location.
// NewCronTrigger returns a new [CronTrigger] using the UTC location.
func NewCronTrigger(expression string) (*CronTrigger, error) {
return NewCronTriggerWithLoc(expression, time.UTC)
}

// NewCronTriggerWithLoc returns a new CronTrigger with the given time.Location.
// NewCronTriggerWithLoc returns a new [CronTrigger] with the given [time.Location].
func NewCronTriggerWithLoc(expression string, location *time.Location) (*CronTrigger, error) {
if location == nil {
return nil, illegalArgumentError("location is nil")
return nil, newIllegalArgumentError("location is nil")
}
expression = trimCronExpression(expression)
fields, err := parseCronExpression(expression)
Expand Down Expand Up @@ -141,13 +141,13 @@ func parseCronExpression(expression string) ([]*cronField, error) {
}
length := len(tokens)
if length < 6 || length > 7 {
return nil, cronParseError("invalid expression length")
return nil, newCronParseError("invalid expression length")
}
if length == 6 {
tokens = append(tokens, "*")
}
if (tokens[3] != "?" && tokens[3] != "*") && (tokens[5] != "?" && tokens[5] != "*") {
return nil, cronParseError("day field set twice")
return nil, newCronParseError("day field set twice")
}

return buildCronField(tokens)
Expand Down Expand Up @@ -217,7 +217,7 @@ func parseField(field string, min, max int, translate ...[]string) (*cronField,
if inScope(i, min, max) {
return &cronField{[]int{i}}, nil
}
return nil, invalidCronFieldError("simple", field)
return nil, newInvalidCronFieldError("simple", field)
}
// list values
if strings.ContainsRune(field, listRune) {
Expand All @@ -240,10 +240,10 @@ func parseField(field string, min, max int, translate ...[]string) (*cronField,
if inScope(intVal, min, max) {
return &cronField{[]int{intVal}}, nil
}
return nil, invalidCronFieldError("literal", field)
return nil, newInvalidCronFieldError("literal", field)
}

return nil, cronParseError(fmt.Sprintf("invalid field %s", field))
return nil, newCronParseError(fmt.Sprintf("invalid field %s", field))
}

func parseListField(field string, min, max int, glossary []string) (*cronField, error) {
Expand Down Expand Up @@ -276,7 +276,7 @@ func parseListField(field string, min, max int, glossary []string) (*cronField,
func parseRangeField(field string, min, max int, glossary []string) (*cronField, error) {
t := strings.Split(field, string(rangeRune))
if len(t) != 2 {
return nil, invalidCronFieldError("range", field)
return nil, newInvalidCronFieldError("range", field)
}
from, err := normalize(t[0], glossary)
if err != nil {
Expand All @@ -287,7 +287,7 @@ func parseRangeField(field string, min, max int, glossary []string) (*cronField,
return nil, err
}
if !inScope(from, min, max) || !inScope(to, min, max) {
return nil, invalidCronFieldError("range", field)
return nil, newInvalidCronFieldError("range", field)
}
rangeValues, err := fillRangeValues(from, to)
if err != nil {
Expand All @@ -300,7 +300,7 @@ func parseRangeField(field string, min, max int, glossary []string) (*cronField,
func parseStepField(field string, min, max int, glossary []string) (*cronField, error) {
t := strings.Split(field, string(stepRune))
if len(t) != 2 {
return nil, invalidCronFieldError("step", field)
return nil, newInvalidCronFieldError("step", field)
}
to := max
var (
Expand All @@ -313,7 +313,7 @@ func parseStepField(field string, min, max int, glossary []string) (*cronField,
case strings.ContainsRune(t[0], rangeRune):
trange := strings.Split(t[0], string(rangeRune))
if len(trange) != 2 {
return nil, invalidCronFieldError("step", field)
return nil, newInvalidCronFieldError("step", field)
}
from, err = normalize(trange[0], glossary)
if err != nil {
Expand All @@ -331,10 +331,10 @@ func parseStepField(field string, min, max int, glossary []string) (*cronField,
}
step, err := strconv.Atoi(t[1])
if err != nil {
return nil, invalidCronFieldError("step", field)
return nil, newInvalidCronFieldError("step", field)
}
if !inScope(from, min, max) || !inScope(step, 1, max) || !inScope(to, min, max) {
return nil, invalidCronFieldError("step", field)
return nil, newInvalidCronFieldError("step", field)
}
stepValues, err := fillStepValues(from, step, to)
if err != nil {
Expand Down
31 changes: 14 additions & 17 deletions quartz/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,30 @@ import (
// Errors
var (
ErrIllegalArgument = errors.New("illegal argument")
ErrIllegalState = errors.New("illegal state")
ErrCronParse = errors.New("parse cron expression")
ErrJobNotFound = errors.New("job not found")
ErrQueueEmpty = errors.New("queue is empty")
ErrTriggerExpired = errors.New("trigger has expired")

ErrIllegalState = errors.New("illegal state")
ErrQueueEmpty = errors.New("queue is empty")
ErrJobNotFound = errors.New("job not found")
ErrJobAlreadyExists = errors.New("job already exists")
ErrJobIsSuspended = errors.New("job is suspended")
ErrJobIsActive = errors.New("job is active")
)

// illegalArgumentError returns an illegal argument error with a custom
// newIllegalArgumentError returns an illegal argument error with a custom
// error message, which unwraps to ErrIllegalArgument.
func illegalArgumentError(message string) error {
func newIllegalArgumentError(message string) error {
return fmt.Errorf("%w: %s", ErrIllegalArgument, message)
}

// illegalStateError returns an illegal state error with a custom
// error message, which unwraps to ErrIllegalState.
func illegalStateError(message string) error {
return fmt.Errorf("%w: %s", ErrIllegalState, message)
}

// cronParseError returns a cron parse error with a custom error message,
// newCronParseError returns a cron parse error with a custom error message,
// which unwraps to ErrCronParse.
func cronParseError(message string) error {
func newCronParseError(message string) error {
return fmt.Errorf("%w: %s", ErrCronParse, message)
}

// jobNotFoundError returns a job not found error with a custom error message,
// which unwraps to ErrJobNotFound.
func jobNotFoundError(message string) error {
return fmt.Errorf("%w: %s", ErrJobNotFound, message)
// newIllegalStateError returns an illegal state error specifying it with err.
func newIllegalStateError(err error) error {
return fmt.Errorf("%w: %w", ErrIllegalState, err)
}
41 changes: 14 additions & 27 deletions quartz/error_test.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
package quartz

import (
"errors"
"fmt"
"testing"

"github.com/reugn/go-quartz/internal/assert"
)

func TestIllegalArgumentError(t *testing.T) {
func TestError_IllegalArgument(t *testing.T) {
message := "argument is nil"
err := illegalArgumentError(message)
if !errors.Is(err, ErrIllegalArgument) {
t.Fatal("error must match ErrIllegalArgument")
}
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrIllegalArgument, message))
}
err := newIllegalArgumentError(message)

func TestIllegalStateError(t *testing.T) {
message := "job already exists"
err := illegalStateError(message)
if !errors.Is(err, ErrIllegalState) {
t.Fatal("error must match ErrIllegalState")
}
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrIllegalState, message))
assert.ErrorIs(t, err, ErrIllegalArgument)
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrIllegalArgument, message))
}

func TestCronParseError(t *testing.T) {
func TestError_CronParse(t *testing.T) {
message := "invalid field"
err := cronParseError(message)
if !errors.Is(err, ErrCronParse) {
t.Fatal("error must match ErrCronParse")
}
err := newCronParseError(message)

assert.ErrorIs(t, err, ErrCronParse)
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrCronParse, message))
}

func TestJobNotFoundError(t *testing.T) {
message := "for key"
err := jobNotFoundError(message)
if !errors.Is(err, ErrJobNotFound) {
t.Fatal("error must match ErrJobNotFound")
}
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrJobNotFound, message))
func TestError_IllegalState(t *testing.T) {
err := newIllegalStateError(ErrJobAlreadyExists)

assert.ErrorIs(t, err, ErrIllegalState)
assert.ErrorIs(t, err, ErrJobAlreadyExists)
assert.Equal(t, err.Error(), fmt.Sprintf("%s: %s", ErrIllegalState, ErrJobAlreadyExists))
}
24 changes: 12 additions & 12 deletions quartz/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package quartz

import (
"container/heap"
"fmt"
"sync"
)

Expand All @@ -16,7 +15,7 @@ type scheduledJob struct {

var _ ScheduledJob = (*scheduledJob)(nil)

// Job returns the scheduled job instance.
// JobDetail returns the details of the scheduled job.
func (scheduled *scheduledJob) JobDetail() *JobDetail {
return scheduled.job
}
Expand Down Expand Up @@ -45,11 +44,13 @@ type JobQueue interface {
Push(job ScheduledJob) error

// Pop removes and returns the next to run scheduled job from the queue.
// Implementations should return quartz.ErrQueueEmpty if the queue is empty.
// Implementations should return an error wrapping [ErrQueueEmpty] if the
// queue is empty.
Pop() (ScheduledJob, error)

// Head returns the first scheduled job without removing it from the queue.
// Implementations should return quartz.ErrQueueEmpty if the queue is empty.
// Implementations should return an error wrapping [ErrQueueEmpty] if the
// queue is empty.
Head() (ScheduledJob, error)

// Get returns the scheduled job with the specified key without removing it
Expand All @@ -61,7 +62,7 @@ type JobQueue interface {

// ScheduledJobs returns a slice of scheduled jobs in the queue.
// The matchers parameter acts as a filter to build the resulting list.
// For a job to be returned in the result slice, it must satisfy all of the
// For a job to be returned in the result slice, it must satisfy all the
// specified matchers. Empty matchers return all scheduled jobs in the queue.
//
// Custom queue implementations may consider using pattern matching on the
Expand Down Expand Up @@ -151,8 +152,7 @@ func (jq *jobQueue) Push(job ScheduledJob) error {
heap.Remove(&jq.delegate, i)
break
}
return illegalStateError(fmt.Sprintf("job with the key %s already exists",
job.JobDetail().jobKey))
return newIllegalStateError(ErrJobAlreadyExists)
}
}
heap.Push(&jq.delegate, job)
Expand All @@ -164,7 +164,7 @@ func (jq *jobQueue) Pop() (ScheduledJob, error) {
jq.mtx.Lock()
defer jq.mtx.Unlock()
if len(jq.delegate) == 0 {
return nil, ErrQueueEmpty
return nil, newIllegalStateError(ErrQueueEmpty)
}
return heap.Pop(&jq.delegate).(ScheduledJob), nil
}
Expand All @@ -174,7 +174,7 @@ func (jq *jobQueue) Head() (ScheduledJob, error) {
jq.mtx.Lock()
defer jq.mtx.Unlock()
if len(jq.delegate) == 0 {
return nil, ErrQueueEmpty
return nil, newIllegalStateError(ErrQueueEmpty)
}
return jq.delegate[0], nil
}
Expand All @@ -189,7 +189,7 @@ func (jq *jobQueue) Get(jobKey *JobKey) (ScheduledJob, error) {
return scheduled, nil
}
}
return nil, jobNotFoundError(jobKey.String())
return nil, newIllegalStateError(ErrJobNotFound)
}

// Remove removes and returns the scheduled job with the specified key.
Expand All @@ -202,11 +202,11 @@ func (jq *jobQueue) Remove(jobKey *JobKey) (ScheduledJob, error) {
return heap.Remove(&jq.delegate, i).(ScheduledJob), nil
}
}
return nil, jobNotFoundError(jobKey.String())
return nil, newIllegalStateError(ErrJobNotFound)
}

// ScheduledJobs returns a slice of scheduled jobs in the queue.
// For a job to be returned, it must satisfy all of the specified matchers.
// For a job to be returned, it must satisfy all the specified matchers.
// Given an empty matchers it returns all scheduled jobs.
func (jq *jobQueue) ScheduledJobs(matchers []Matcher[ScheduledJob]) ([]ScheduledJob, error) {
jq.mtx.Lock()
Expand Down
Loading

0 comments on commit ac62bd0

Please sign in to comment.