Skip to content
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

feat(quartz): introduce new error variables #139

Merged
merged 1 commit into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading