T2.1: Add domain.Schedule value object
Introduce src/domain/schedule.go with a Schedule value object that centralizes schedule parsing and validation: Parse, Validate, and Next(time.Time). It owns the cron parser and @every handling, moved out of the scheduler. The scheduler's nextRunTime is kept as a thin wrapper delegating to domain.Parse for now (T2.2 will parse once on load/edit). Add unit tests covering invalid specs, @every intervals, five-field cron, cron descriptors, whitespace trimming, the zero-value Next, and String. Mark T2.1 complete in REFACTORING.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,11 +10,8 @@ import (
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/storage"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/runner"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
|
||||
// Scheduler owns the timing loop for jobs that are currently loaded in the GUI.
|
||||
// It receives a pointer to the jobs slice because the GUI edits the same slice;
|
||||
// this keeps the early architecture simple while storage and scheduling are
|
||||
@@ -230,24 +227,11 @@ func (s *Scheduler) prepareNextRun(job *domain.Job, from time.Time) {
|
||||
job.NextRun = job.NextDue.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// nextRunTime is a thin wrapper over domain.Schedule kept for the scheduler's
|
||||
// existing call sites. It parses the schedule on every call for now; T2.2
|
||||
// replaces this with a Schedule parsed once on load/edit.
|
||||
func nextRunTime(schedule string, from time.Time) (time.Time, bool) {
|
||||
schedule = strings.TrimSpace(schedule)
|
||||
if schedule == "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
if strings.HasPrefix(schedule, "@every ") {
|
||||
// @every is kept alongside cron because it is convenient for quick tests
|
||||
// and for simple intervals that are awkward to express as five fields.
|
||||
interval, err := time.ParseDuration(strings.TrimSpace(strings.TrimPrefix(schedule, "@every ")))
|
||||
if err != nil || interval <= 0 {
|
||||
return time.Time{}, false
|
||||
}
|
||||
return from.Add(interval), true
|
||||
}
|
||||
// Standard five-field cron keeps GoSentry compatible with the mental model
|
||||
// users already know from Unix cron, while robfig/cron handles edge cases
|
||||
// such as ranges, steps, and day-of-week names.
|
||||
parsed, err := cronParser.Parse(schedule)
|
||||
parsed, err := domain.Parse(schedule)
|
||||
if err != nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user