From 268f02f04237047ea8ebb7c5e43a74042a7a3cf4 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Sat, 4 May 2024 14:18:26 +0200
Subject: [PATCH] feat: remap string values for time, duration, etc #62

---
 persistence.go            |  27 +++-
 persistence_test.go       |   3 +-
 schedule-delay.go         |   2 +-
 schedule-interval.go      |   2 +-
 schedule-interval_test.go |   4 +-
 scheduler.go              | 284 ++++++++++++++++++++++++++++++--------
 scheduler_test.go         |   4 +-
 7 files changed, 259 insertions(+), 67 deletions(-)

diff --git a/persistence.go b/persistence.go
index 1674e8f..f513581 100644
--- a/persistence.go
+++ b/persistence.go
@@ -105,6 +105,25 @@ func (j *JobPersistence) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+func parseDateFormats(value string) (time.Time, error) {
+	return parseMultipleDateFormats(value, []string{
+		time.RFC3339, time.RFC3339Nano,
+		"2006-01-02T15:04:05",
+		"2006-01-02T15:04:05.999999999",
+		"2006-01-02T15:04:05Z07:00",
+		"2006-01-02T15:04:05",
+		"2006-01-02T15:04",
+		"2006-01-02T15",
+		"2006-01-02 15:04:05",
+		"2006-01-02 15:04:05.999999999",
+		"2006-01-02 15:04:05Z07:00",
+		"2006-01-02 15:04:05",
+		"2006-01-02 15:04",
+		"2006-01-02 15",
+		"2006-01-02",
+	})
+}
+
 func parseMultipleDateFormats(value string, formats []string) (time.Time, error) {
 	var t time.Time
 	var err error
@@ -356,11 +375,11 @@ func CreateJobAndSchedulerFromPersistence(jobImport JobPersistence, manager *Man
 	switch sType {
 	case "interval":
 
-		if jobImport.Scheduler.Interval == 0 {
+		if *jobImport.Scheduler.Interval == 0 {
 			return nil, nil, fmt.Errorf("%w: interval is 0", ErrSchedulerMisconfiguration)
 		}
 
-		scheduler = &IntervalScheduler{Interval: jobImport.Scheduler.Interval}
+		scheduler = &IntervalScheduler{Interval: *jobImport.Scheduler.Interval}
 
 	case "cron":
 
@@ -385,11 +404,11 @@ func CreateJobAndSchedulerFromPersistence(jobImport JobPersistence, manager *Man
 
 	case "delay":
 
-		if jobImport.Scheduler.Delay == 0 {
+		if *jobImport.Scheduler.Delay == 0 {
 			return nil, nil, fmt.Errorf("%w: delay is 0", ErrSchedulerMisconfiguration)
 		}
 
-		scheduler = &DelayScheduler{Delay: jobImport.Scheduler.Delay}
+		scheduler = &DelayScheduler{Delay: *jobImport.Scheduler.Delay}
 
 	case "event":
 
diff --git a/persistence_test.go b/persistence_test.go
index 7c7db9f..321425b 100644
--- a/persistence_test.go
+++ b/persistence_test.go
@@ -17,6 +17,7 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) {
 
 	time10s := 10 * time.Second
 	time2s := 5 * time.Minute
+	time1m := 1 * time.Minute
 
 	tests := []struct {
 		name      string
@@ -39,7 +40,7 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) {
 				},
 				Scheduler: SchedulerPersistence{
 					Type:     "Interval",
-					Interval: 1 * time.Minute,
+					Interval: &time1m,
 				},
 			},
 			wantJob:   GenericJob(&Job[ShellResult]{ /* Initialization */ }),
diff --git a/schedule-delay.go b/schedule-delay.go
index fbc6bc1..ce60b3b 100644
--- a/schedule-delay.go
+++ b/schedule-delay.go
@@ -97,6 +97,6 @@ func (s *DelayScheduler) JobExists(id JobID) bool {
 func (s *DelayScheduler) GetPersistence() SchedulerPersistence {
 	return SchedulerPersistence{
 		Type:  s.GetType(),
-		Delay: s.Delay,
+		Delay: &s.Delay,
 	}
 }
diff --git a/schedule-interval.go b/schedule-interval.go
index 0a2c0d2..871d48f 100644
--- a/schedule-interval.go
+++ b/schedule-interval.go
@@ -105,6 +105,6 @@ func (s *IntervalScheduler) JobExists(id JobID) bool {
 func (s *IntervalScheduler) GetPersistence() SchedulerPersistence {
 	return SchedulerPersistence{
 		Type:     s.GetType(),
-		Interval: s.Interval,
+		Interval: &s.Interval,
 	}
 }
diff --git a/schedule-interval_test.go b/schedule-interval_test.go
index ad72822..5b142c7 100644
--- a/schedule-interval_test.go
+++ b/schedule-interval_test.go
@@ -28,7 +28,7 @@ interval: "1h30m"
 	assert.Equal(t, expectedTime, *sp.Time, "the time should be parsed correctly")
 
 	expectedInterval, _ := time.ParseDuration("1h30m")
-	assert.Equal(t, expectedInterval, sp.Interval, "the interval should be parsed correctly")
+	assert.Equal(t, &expectedInterval, sp.Interval, "the interval should be parsed correctly")
 }
 
 // TestUnmarshalJSON tests the UnmarshalJSON method
@@ -56,5 +56,5 @@ func TestUnmarshalJSON(t *testing.T) {
 
 	// Verify that the Delay field is correctly parsed
 	expectedInterval, _ := time.ParseDuration("1h30m")
-	assert.Equal(t, expectedInterval, sp.Interval, "Interval should be correctly parsed")
+	assert.Equal(t, &expectedInterval, sp.Interval, "Interval should be correctly parsed")
 }
diff --git a/scheduler.go b/scheduler.go
index cb1c3fb..f3d2c41 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -23,10 +23,15 @@ type Scheduler interface {
 }
 
 type SchedulerPersistence struct {
-	Time       *time.Time    `yaml:"time,omitempty" json:"time,omitempty" gorm:"column:time"`
-	Interval   time.Duration `yaml:"interval,omitempty" json:"interval,omitempty" gorm:"column:interval"`
-	Delay      time.Duration `yaml:"delay,omitempty" json:"delay,omitempty" gorm:"column:delay"`
-	EventFlags fsnotify.Op   `yaml:"eventFlags,omitempty" json:"eventFlags,omitempty" gorm:"column:eventFlags"`
+	Time       *time.Time     `yaml:"-" json:"-" gorm:"column:time"`
+	Interval   *time.Duration `yaml:"-" json:"-" gorm:"column:interval"`
+	Delay      *time.Duration `yaml:"-" json:"-" gorm:"column:delay"`
+	EventFlags fsnotify.Op    `yaml:"-" json:"-" gorm:"column:eventFlags"`
+
+	TimeString       *string `yaml:"time,omitempty" json:"time,omitempty" gorm:"-"`
+	IntervalString   *string `yaml:"interval,omitempty" json:"interval,omitempty" gorm:"-"`
+	DelayString      *string `yaml:"delay,omitempty" json:"delay,omitempty" gorm:"-"`
+	EventFlagsString *string `yaml:"eventFlags,omitempty" json:"eventFlags,omitempty" gorm:"-"`
 
 	Type     string    `yaml:"type" json:"type" gorm:"column:type"`
 	Spec     string    `yaml:"spec,omitempty" json:"spec,omitempty" gorm:"column:spec"`
@@ -35,61 +40,155 @@ type SchedulerPersistence struct {
 	Path     string    `yaml:"path,omitempty" json:"path,omitempty" gorm:"column:path"`
 }
 
-type scheduleImportStruct struct {
-	Time       *string `yaml:"time,omitempty" json:"time,omitempty"`
-	Interval   *string `yaml:"interval,omitempty" json:"interval,omitempty"`
-	Delay      *string `yaml:"delay,omitempty" json:"delay,omitempty"`
-	EventFlags *string `yaml:"eventFlags,omitempty" json:"eventFlags,omitempty"`
-
-	Type     string `yaml:"type" json:"type"`
-	Spec     string `yaml:"spec,omitempty" json:"spec,omitempty"`
-	Event    string `yaml:"event,omitempty" json:"event,omitempty"`
-	Executed bool   `yaml:"executed,omitempty" json:"executed,omitempty"`
-	Path     string `yaml:"path,omitempty" json:"path,omitempty"`
+func formatEventFlags(flags fsnotify.Op) string {
+	var flagStrings []string
+	if flags&fsnotify.Create != 0 {
+		flagStrings = append(flagStrings, "Create")
+	}
+	if flags&fsnotify.Write != 0 {
+		flagStrings = append(flagStrings, "Write")
+	}
+	if flags&fsnotify.Remove != 0 {
+		flagStrings = append(flagStrings, "Remove")
+	}
+	if flags&fsnotify.Rename != 0 {
+		flagStrings = append(flagStrings, "Rename")
+	}
+	if flags&fsnotify.Chmod != 0 {
+		flagStrings = append(flagStrings, "Chmod")
+	}
+	return strings.Join(flagStrings, "|")
+}
+
+func (j *SchedulerPersistence) MarshalJSON() ([]byte, error) {
+	type Alias SchedulerPersistence
+	return json.Marshal(&struct {
+		*Alias
+		TimeString       string `json:"time,omitempty"`
+		IntervalString   string `json:"interval,omitempty"`
+		DelayString      string `json:"delay,omitempty"`
+		EventFlagsString string `json:"eventFlags,omitempty"`
+	}{
+		Alias:            (*Alias)(j),
+		TimeString:       formatTime(j.Time),
+		IntervalString:   formatDuration(j.Interval),
+		DelayString:      formatDuration(j.Delay),
+		EventFlagsString: formatEventFlags(j.EventFlags),
+	})
 }
 
-func (sp *SchedulerPersistence) parseAndAssignFields(aux scheduleImportStruct) error {
-
-	sp.Type = aux.Type
-	sp.Spec = aux.Spec
-	sp.Event = EventName(aux.Event)
-	sp.Executed = aux.Executed
-	sp.Path = aux.Path
-
-	if aux.Time != nil && *aux.Time != "" {
-		var t time.Time
-		var err error
-		for _, format := range SupportedTimeFormats {
-			t, err = time.Parse(format, *aux.Time)
-			if err == nil {
-				break
+func (j *SchedulerPersistence) UnmarshalJSON(data []byte) error {
+	type Alias SchedulerPersistence
+	aux := &struct {
+		*Alias
+		TimeString       *string `json:"time,omitempty"`
+		IntervalString   *string `json:"interval,omitempty"`
+		DelayString      *string `json:"delay,omitempty"`
+		EventFlagsString *string `json:"eventFlags,omitempty"`
+	}{
+		Alias: (*Alias)(j),
+	}
+	if err := json.Unmarshal(data, aux); err != nil {
+		return err
+	}
+	if aux.TimeString != nil && *aux.TimeString != "" {
+		t, err := parseDateFormats(*aux.TimeString)
+		if err != nil {
+			return err
+		}
+
+		j.Time = &t
+	}
+
+	if aux.DelayString != nil && *aux.DelayString != "" {
+		d, err := time.ParseDuration(*aux.DelayString)
+		if err != nil {
+			return err
+		}
+		j.Delay = &d
+	}
+	if aux.IntervalString != nil && *aux.IntervalString != "" {
+		d, err := time.ParseDuration(*aux.IntervalString)
+		if err != nil {
+			return err
+		}
+		j.Interval = &d
+	}
+	if aux.DelayString != nil && *aux.DelayString != "" {
+		d, err := time.ParseDuration(*aux.DelayString)
+		if err != nil {
+			return err
+		}
+		j.Delay = &d
+	}
+	if aux.EventFlagsString != nil && *aux.EventFlagsString != "" {
+		j.EventFlags = fsnotify.Op(0)
+		for _, flag := range strings.Split(*aux.EventFlagsString, "|") {
+			switch flag {
+			case "Create":
+				j.EventFlags |= fsnotify.Create
+			case "Write":
+				j.EventFlags |= fsnotify.Write
+			case "Remove":
+				j.EventFlags |= fsnotify.Remove
+			case "Rename":
+				j.EventFlags |= fsnotify.Rename
+			case "Chmod":
+				j.EventFlags |= fsnotify.Chmod
 			}
 		}
+	}
+
+	return nil
+}
+
+func (sp *SchedulerPersistence) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type Alias SchedulerPersistence
+	aux := &struct {
+		*Alias `yaml:",inline"`
+		//TimeString       *string `yaml:"time,omitempty"`
+		//IntervalString   *string `yaml:"interval,omitempty"`
+		//DelayString      *string `yaml:"delay,omitempty"`
+		//EventFlagsString *string `yaml:"eventFlags,omitempty"`
+	}{
+		Alias: (*Alias)(sp),
+	}
+	if err := unmarshal(aux); err != nil {
+		return err
+	}
+	if aux.TimeString != nil && *aux.TimeString != "" {
+		t, err := parseDateFormats(*aux.TimeString)
 		if err != nil {
 			return err
 		}
+
 		sp.Time = &t
 	}
 
-	if aux.Interval != nil && *aux.Interval != "" {
-		d, err := time.ParseDuration(*aux.Interval)
+	if aux.DelayString != nil && *aux.DelayString != "" {
+		d, err := time.ParseDuration(*aux.DelayString)
 		if err != nil {
 			return err
 		}
-		sp.Interval = d
+		sp.Delay = &d
 	}
-
-	if aux.Delay != nil && *aux.Delay != "" {
-		d, err := time.ParseDuration(*aux.Delay)
+	if aux.IntervalString != nil && *aux.IntervalString != "" {
+		d, err := time.ParseDuration(*aux.IntervalString)
 		if err != nil {
 			return err
 		}
-		sp.Delay = d
+		sp.Interval = &d
 	}
-
-	if aux.EventFlags != nil && *aux.EventFlags != "" {
+	if aux.DelayString != nil && *aux.DelayString != "" {
+		d, err := time.ParseDuration(*aux.DelayString)
+		if err != nil {
+			return err
+		}
+		sp.Delay = &d
+	}
+	if aux.EventFlagsString != nil && *aux.EventFlagsString != "" {
 		sp.EventFlags = fsnotify.Op(0)
-		for _, flag := range strings.Split(*aux.EventFlags, "|") {
+		for _, flag := range strings.Split(*aux.EventFlagsString, "|") {
 			switch flag {
 			case "Create":
 				sp.EventFlags |= fsnotify.Create
@@ -101,26 +200,99 @@ func (sp *SchedulerPersistence) parseAndAssignFields(aux scheduleImportStruct) e
 				sp.EventFlags |= fsnotify.Rename
 			case "Chmod":
 				sp.EventFlags |= fsnotify.Chmod
+
 			}
 		}
 	}
-
 	return nil
 }
 
-// UnmarshalJSON implements the json.Unmarshaler interface
-func (sp *SchedulerPersistence) UnmarshalJSON(data []byte) error {
-	var aux scheduleImportStruct
-	if err := json.Unmarshal(data, &aux); err != nil {
-		return err
-	}
-	return sp.parseAndAssignFields(aux)
-}
+//type scheduleImportStruct struct {
+//	Time       *string `yaml:"time,omitempty" json:"time,omitempty"`
+//	Interval   *string `yaml:"interval,omitempty" json:"interval,omitempty"`
+//	Delay      *string `yaml:"delay,omitempty" json:"delay,omitempty"`
+//	EventFlags *string `yaml:"eventFlags,omitempty" json:"eventFlags,omitempty"`
+//
+//	Type     string `yaml:"type" json:"type"`
+//	Spec     string `yaml:"spec,omitempty" json:"spec,omitempty"`
+//	Event    string `yaml:"event,omitempty" json:"event,omitempty"`
+//	Executed bool   `yaml:"executed,omitempty" json:"executed,omitempty"`
+//	Path     string `yaml:"path,omitempty" json:"path,omitempty"`
+//}
+//
+//func (sp *SchedulerPersistence) parseAndAssignFields(aux scheduleImportStruct) error {
+//
+//	sp.Type = aux.Type
+//	sp.Spec = aux.Spec
+//	sp.Event = EventName(aux.Event)
+//	sp.Executed = aux.Executed
+//	sp.Path = aux.Path
+//
+//	if aux.Time != nil && *aux.Time != "" {
+//		var t time.Time
+//		var err error
+//		for _, format := range SupportedTimeFormats {
+//			t, err = time.Parse(format, *aux.Time)
+//			if err == nil {
+//				break
+//			}
+//		}
+//		if err != nil {
+//			return err
+//		}
+//		sp.Time = &t
+//	}
+//
+//	if aux.Interval != nil && *aux.Interval != "" {
+//		d, err := time.ParseDuration(*aux.Interval)
+//		if err != nil {
+//			return err
+//		}
+//		sp.Interval = d
+//	}
+//
+//	if aux.Delay != nil && *aux.Delay != "" {
+//		d, err := time.ParseDuration(*aux.Delay)
+//		if err != nil {
+//			return err
+//		}
+//		sp.Delay = d
+//	}
+//
+//	if aux.EventFlags != nil && *aux.EventFlags != "" {
+//		sp.EventFlags = fsnotify.Op(0)
+//		for _, flag := range strings.Split(*aux.EventFlags, "|") {
+//			switch flag {
+//			case "Create":
+//				sp.EventFlags |= fsnotify.Create
+//			case "Write":
+//				sp.EventFlags |= fsnotify.Write
+//			case "Remove":
+//				sp.EventFlags |= fsnotify.Remove
+//			case "Rename":
+//				sp.EventFlags |= fsnotify.Rename
+//			case "Chmod":
+//				sp.EventFlags |= fsnotify.Chmod
+//			}
+//		}
+//	}
+//
+//	return nil
+//}
 
-func (sp *SchedulerPersistence) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	var aux scheduleImportStruct
-	if err := unmarshal(&aux); err != nil {
-		return err
-	}
-	return sp.parseAndAssignFields(aux)
-}
+//// UnmarshalJSON implements the json.Unmarshaler interface
+//func (sp *SchedulerPersistence) UnmarshalJSON(data []byte) error {
+//	var aux scheduleImportStruct
+//	if err := json.Unmarshal(data, &aux); err != nil {
+//		return err
+//	}
+//	return sp.parseAndAssignFields(aux)
+//}
+//
+//func (sp *SchedulerPersistence) UnmarshalYAML(unmarshal func(interface{}) error) error {
+//	var aux scheduleImportStruct
+//	if err := unmarshal(&aux); err != nil {
+//		return err
+//	}
+//	return sp.parseAndAssignFields(aux)
+//}
diff --git a/scheduler_test.go b/scheduler_test.go
index c072260..dbbe9a9 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -429,7 +429,7 @@ time: "2023-12-15T12:00:00Z"
 	expectedTime, _ := time.Parse(time.RFC3339, "2023-12-15T12:00:00Z")
 
 	assert.Equal(t, "interval", sp.Type, "Type should be unmarshalled correctly")
-	assert.Equal(t, expectedInterval, sp.Interval, "Interval should be unmarshalled correctly")
+	assert.Equal(t, &expectedInterval, sp.Interval, "Interval should be unmarshalled correctly")
 	assert.Equal(t, &expectedTime, sp.Time, "Time should be unmarshalled correctly")
 }
 
@@ -446,5 +446,5 @@ interval: "1m1s"
 
 	expectedInterval, _ := time.ParseDuration("1m1s")
 	assert.Equal(t, "Interval", sp.Type, "Type should be unmarshalled correctly")
-	assert.Equal(t, expectedInterval, sp.Interval, "Interval should be unmarshalled correctly")
+	assert.Equal(t, &expectedInterval, sp.Interval, "Interval should be unmarshalled correctly")
 }
-- 
GitLab