Fail deliveries on archive errors; validate expiry at creation (#43)
Some checks failed
check / check (push) Failing after 57s
Some checks failed
check / check (push) Failing after 57s
Two review findings on the database archiving target: - An archive error now records the attempt as failed with the error string and marks the delivery failed, instead of logging the error and reporting success. A target that could not do its one job must not claim it did. - The archive expiry is now actually configurable: the add-target form gains an expiry field for database targets, and the value is validated at creation time via the new delivery.ValidateArchiveExpiry (empty, "never", or a positive Go duration), rejecting bad values with a 400 at the only place a human can fix them, mirroring how Slack target URLs are validated at creation. Test updates: a forced archive failure asserts a failed delivery with a recorded error and no archive file; config builder tests cover empty/never/duration and rejection paths; the two engine tests that exercise the database target now build engines with a real webhook DB manager since archiving is no longer a no-op; the reopen-debounce test uses a wider window so parallel test load cannot make two rapid writes straddle it.
This commit is contained in:
@@ -37,6 +37,13 @@ var (
|
||||
errArchiveNoDataDir = errors.New(
|
||||
"database target has no data directory",
|
||||
)
|
||||
|
||||
// errArchiveExpiryNotPositive is returned when a
|
||||
// user-supplied archive expiry parses as a duration but is
|
||||
// zero or negative; "never" is the way to disable pruning.
|
||||
errArchiveExpiryNotPositive = errors.New(
|
||||
"expiry must be a positive duration or \"never\"",
|
||||
)
|
||||
)
|
||||
|
||||
// databaseTargetConfig is the optional per-target JSON config
|
||||
@@ -105,6 +112,35 @@ func parseArchiveExpiry(
|
||||
return dur, nil
|
||||
}
|
||||
|
||||
// ValidateArchiveExpiry checks a user-supplied archive expiry
|
||||
// for a database target at configuration time. Valid values are
|
||||
// empty, "never" (both meaning keep forever), or a positive Go
|
||||
// duration such as "720h". Anything else is an error, so a bad
|
||||
// expiry is rejected when the target is created rather than
|
||||
// failing every subsequent delivery.
|
||||
func ValidateArchiveExpiry(expiry string) error {
|
||||
if expiry == "" || expiry == archiveExpiryNever {
|
||||
return nil
|
||||
}
|
||||
|
||||
dur, err := time.ParseDuration(expiry)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"expiry must be %q or a Go duration "+
|
||||
"such as \"720h\": %w",
|
||||
archiveExpiryNever, err,
|
||||
)
|
||||
}
|
||||
|
||||
if dur <= 0 {
|
||||
return fmt.Errorf(
|
||||
"%w: %q", errArchiveExpiryNotPositive, expiry,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// archiveWriter owns one per-webhook archive SQLite file. It
|
||||
// serialises writes, and after each write closes and reopens
|
||||
// the file (debounced to at most once per debounce window) so
|
||||
|
||||
Reference in New Issue
Block a user