package checker import ( "context" "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) func NewSectionRule(section, label string, defaultMin float64) sdk.CheckRule { return §ionRule{section: section, label: label, defaultMin: defaultMin} } type sectionRule struct { section string label string defaultMin float64 } func (r *sectionRule) Name() string { return "happydeliver.score." + r.section } func (r *sectionRule) Description() string { return fmt.Sprintf("Verify happyDeliver's %s score is above the configured minimum.", r.label) } func (r *sectionRule) optionID() string { return "min_score_" + r.section } func (r *sectionRule) Options() sdk.CheckerOptionsDocumentation { return sdk.CheckerOptionsDocumentation{ UserOpts: []sdk.CheckerOptionDocumentation{ { Id: r.optionID(), Type: "number", Label: fmt.Sprintf("Minimum %s score", r.label), Description: fmt.Sprintf("Minimum acceptable score (0-100) for the %s section.", r.label), Default: r.defaultMin, }, }, } } func (r *sectionRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { var data HappyDeliverData if err := obs.Get(ctx, ObservationKeyHappyDeliver, &data); err != nil { return []sdk.CheckState{{ Status: sdk.StatusError, Message: fmt.Sprintf("Failed to read happyDeliver observation: %v", err), Code: "happydeliver.observation.error", }} } if data.Phase != "ok" || data.Scores == nil { return []sdk.CheckState{{ Status: sdk.StatusInfo, Message: "No happyDeliver report available yet", Code: "happydeliver.score.unavailable", Subject: r.section, }} } score, ok := data.Scores[r.section] if !ok { return []sdk.CheckState{{ Status: sdk.StatusInfo, Message: fmt.Sprintf("happyDeliver report has no %s score", r.label), Code: "happydeliver.score.missing", Subject: r.section, }} } threshold := sdk.GetFloatOption(opts, r.optionID(), r.defaultMin) status := sdk.StatusOK if float64(score) < threshold { status = sdk.StatusCrit } return []sdk.CheckState{{ Status: status, Message: fmt.Sprintf("%s score: %d (min %.0f, grade %s)", r.label, score, threshold, data.Grades[r.section]), Code: "happydeliver.score." + r.section, Subject: r.section, RuleName: r.Name(), Meta: map[string]any{ "score": score, "grade": data.Grades[r.section], "min": threshold, }, }} } // Surfaces lifecycle failures in the UI when no per-section scores exist yet. func NewLifecycleRule() sdk.CheckRule { return &lifecycleRule{} } type lifecycleRule struct{} func (r *lifecycleRule) Name() string { return "happydeliver.lifecycle" } func (r *lifecycleRule) Description() string { return "Reports happyDeliver lifecycle errors (allocation, send, timeout, fetch)." } func (r *lifecycleRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { var data HappyDeliverData if err := obs.Get(ctx, ObservationKeyHappyDeliver, &data); err != nil { return []sdk.CheckState{{ Status: sdk.StatusError, Message: fmt.Sprintf("Failed to read happyDeliver observation: %v", err), Code: "happydeliver.observation.error", }} } switch data.Phase { case "ok": return []sdk.CheckState{{ Status: sdk.StatusOK, Message: fmt.Sprintf("Message analysed in %.1fs", data.LatencySeconds), Code: "happydeliver.lifecycle.ok", }} case "allocate": return []sdk.CheckState{{ Status: sdk.StatusCrit, Message: "Failed to allocate a happyDeliver test address: " + data.Error, Code: "happydeliver.api.unavailable", }} case "send": return []sdk.CheckState{{ Status: sdk.StatusCrit, Message: "Failed to send the test email: " + data.Error, Code: "happydeliver.send.failed", }} case "timeout": return []sdk.CheckState{{ Status: sdk.StatusWarn, Message: "happyDeliver did not analyse the message before the timeout", Code: "happydeliver.no_message_received", }} case "wait", "fetch", "parse": return []sdk.CheckState{{ Status: sdk.StatusCrit, Message: "happyDeliver lifecycle error: " + data.Error, Code: "happydeliver." + data.Phase + ".failed", }} default: return []sdk.CheckState{{ Status: sdk.StatusInfo, Message: "happyDeliver run in unknown phase: " + data.Phase, Code: "happydeliver.lifecycle.unknown", }} } }