ariadne/internal/domain/failure/failure.go
2026-07-24 05:45:04 +09:00

118 lines
3.1 KiB
Go

package failure
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
)
const SchemaVersionV1 = "ariadne.execution-failure.v1"
var codePattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]{2,99}$`)
type Stage string
const (
StageIngestion Stage = "ingestion"
StageCorrelation Stage = "correlation"
StageTargetSelection Stage = "target_selection"
StageRepositoryAnalysis Stage = "repository_analysis"
StageRuntimeAnalysis Stage = "runtime_analysis"
StageTranslation Stage = "translation"
StageIOPSubmission Stage = "iop_submission"
StageIOPExecution Stage = "iop_execution"
StageChange Stage = "change"
StageValidation Stage = "validation"
StageMergeRequest Stage = "merge_request"
)
type Failure struct {
SchemaVersion string
Code string
Stage Stage
Message string
UserMessage string
Retryable bool
OccurredAt time.Time
DetailsReference string
Causes []Cause
}
type Cause struct {
Code string
Stage Stage
Message string
Retryable bool
}
func (failure Failure) Validate() error {
var validationErrors []error
if failure.SchemaVersion != SchemaVersionV1 {
validationErrors = append(
validationErrors,
fmt.Errorf("unsupported failure schema %q", failure.SchemaVersion),
)
}
if !codePattern.MatchString(failure.Code) {
validationErrors = append(validationErrors, errors.New("failure code is invalid"))
}
if !failure.Stage.Valid() {
validationErrors = append(validationErrors, errors.New("failure stage is invalid"))
}
if strings.TrimSpace(failure.Message) == "" {
validationErrors = append(validationErrors, errors.New("failure message is required"))
}
if strings.TrimSpace(failure.UserMessage) == "" {
validationErrors = append(validationErrors, errors.New("failure user message is required"))
}
if failure.OccurredAt.IsZero() {
validationErrors = append(validationErrors, errors.New("failure occurrence time is required"))
}
if len(failure.Causes) > 32 {
validationErrors = append(validationErrors, errors.New("failure cause stack exceeds 32 entries"))
}
for index, cause := range failure.Causes {
if err := cause.Validate(); err != nil {
validationErrors = append(
validationErrors,
fmt.Errorf("failure cause %d: %w", index, err),
)
}
}
return errors.Join(validationErrors...)
}
func (cause Cause) Validate() error {
var validationErrors []error
if !codePattern.MatchString(cause.Code) {
validationErrors = append(validationErrors, errors.New("cause code is invalid"))
}
if !cause.Stage.Valid() {
validationErrors = append(validationErrors, errors.New("cause stage is invalid"))
}
if strings.TrimSpace(cause.Message) == "" {
validationErrors = append(validationErrors, errors.New("cause message is required"))
}
return errors.Join(validationErrors...)
}
func (stage Stage) Valid() bool {
switch stage {
case StageIngestion,
StageCorrelation,
StageTargetSelection,
StageRepositoryAnalysis,
StageRuntimeAnalysis,
StageTranslation,
StageIOPSubmission,
StageIOPExecution,
StageChange,
StageValidation,
StageMergeRequest:
return true
default:
return false
}
}