Add composable issue validators to internal/validation package: - IssueValidator type with Chain() composition function - Exists(), NotTemplate(), NotPinned(), NotClosed(), NotHooked() validators - HasStatus(), HasType() for checking allowed values - ForUpdate(), ForClose(), ForDelete(), ForReopen() convenience chains Update cmd/bd/show_unit_helpers.go to use centralized validators instead of duplicated inline validation logic. This enables consistent validation across all commands with a single source of truth. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/steveyegge/beads/internal/storage"
|
|
"github.com/steveyegge/beads/internal/types"
|
|
"github.com/steveyegge/beads/internal/validation"
|
|
)
|
|
|
|
// validateIssueUpdatable checks if an issue can be updated.
|
|
// Uses the centralized validation package for consistency.
|
|
func validateIssueUpdatable(id string, issue *types.Issue) error {
|
|
// Note: We use NotTemplate() directly instead of ForUpdate() to maintain
|
|
// backward compatibility - the original didn't check for nil issues.
|
|
return validation.NotTemplate()(id, issue)
|
|
}
|
|
|
|
// validateIssueClosable checks if an issue can be closed.
|
|
// Uses the centralized validation package for consistency.
|
|
func validateIssueClosable(id string, issue *types.Issue, force bool) error {
|
|
// Note: We use individual validators instead of ForClose() to maintain
|
|
// backward compatibility - the original didn't check for nil issues.
|
|
return validation.Chain(
|
|
validation.NotTemplate(),
|
|
validation.NotPinned(force),
|
|
)(id, issue)
|
|
}
|
|
|
|
func applyLabelUpdates(ctx context.Context, st storage.Storage, issueID, actor string, setLabels, addLabels, removeLabels []string) error {
|
|
// Set labels (replaces all existing labels)
|
|
if len(setLabels) > 0 {
|
|
currentLabels, err := st.GetLabels(ctx, issueID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, label := range currentLabels {
|
|
if err := st.RemoveLabel(ctx, issueID, label, actor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, label := range setLabels {
|
|
if err := st.AddLabel(ctx, issueID, label, actor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add labels
|
|
for _, label := range addLabels {
|
|
if err := st.AddLabel(ctx, issueID, label, actor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Remove labels
|
|
for _, label := range removeLabels {
|
|
if err := st.RemoveLabel(ctx, issueID, label, actor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|