package releasekit import ( "errors" "slices" "gitea.maximumdirect.net/eric/ReleaseKit/internal/validation" ) // ValidationCheck identifies a shared validation check that failed. // Current checks include "go-test", "go-vet", and "package-compile". type ValidationCheck string // ValidationFinding describes a failed check observation at an optional // repository-relative source location. type ValidationFinding struct { Path string // repository-relative path when available Line int // one-based source line, or zero when unavailable Column int // one-based source column, or zero when unavailable Message string // useful detail that is not promised to have exact wording Target string // applicable target for a file check Directive string // applicable directive for a file check } // FileSizeMeasurement reports a measured file size and its selected limit. type FileSizeMeasurement struct { Bytes int64 LimitBytes int64 } // ValidationError describes a failed shared validation check returned by // [Check] or [Publish]. Cause preserves the contextual and underlying error chain. type ValidationError struct { Check ValidationCheck // failed shared check Findings []ValidationFinding // actionable observations, when available Measurement *FileSizeMeasurement // measured file size, when applicable Cause error // original contextual and underlying error chain } // Error returns check details alongside the contextual and underlying causes. func (failure *ValidationError) Error() string { if failure == nil { return "" } diagnostic := internalValidationFailure(failure) var original *validation.Failure if errors.As(failure.Cause, &original) && sameValidationDiagnostics(diagnostic, original) { // Converted causes already render these diagnostics and candidate context. return failure.Cause.Error() } return diagnostic.Error() } // Unwrap returns the original validation error chain. func (failure *ValidationError) Unwrap() error { if failure == nil { return nil } return failure.Cause } func convertValidationError(err error) error { if err == nil { return nil } var publicFailure *ValidationError if errors.As(err, &publicFailure) { return err } var failure *validation.Failure if !errors.As(err, &failure) { return err } result := &ValidationError{ Check: ValidationCheck(failure.Check), Cause: err, } if len(failure.Findings) != 0 { result.Findings = make([]ValidationFinding, len(failure.Findings)) for index, finding := range failure.Findings { result.Findings[index] = ValidationFinding{ Path: finding.Path, Line: finding.Line, Column: finding.Column, Message: finding.Message, Target: finding.Target, Directive: finding.Directive, } } } if failure.Measurement != nil { result.Measurement = &FileSizeMeasurement{ Bytes: failure.Measurement.Bytes, LimitBytes: failure.Measurement.LimitBytes, } } return result } func internalValidationFailure(failure *ValidationError) *validation.Failure { result := &validation.Failure{Check: validation.Check(failure.Check), Cause: failure.Cause} for _, finding := range failure.Findings { result.Findings = append(result.Findings, validation.Finding{ Path: finding.Path, Line: finding.Line, Column: finding.Column, Message: finding.Message, Target: finding.Target, Directive: finding.Directive, }) } if failure.Measurement != nil { result.Measurement = &validation.FileSizeMeasurement{ Bytes: failure.Measurement.Bytes, LimitBytes: failure.Measurement.LimitBytes, } } return result } func sameValidationDiagnostics(a, b *validation.Failure) bool { if b == nil || a.Check != b.Check || !slices.Equal(a.Findings, b.Findings) { return false } if a.Measurement == nil || b.Measurement == nil { return a.Measurement == b.Measurement } return *a.Measurement == *b.Measurement } var _ error = (*ValidationError)(nil)