errors.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5package shared
 6
 7import (
 8	"errors"
 9	"fmt"
10	"slices"
11	"strings"
12
13	"git.secluded.site/go-lunatask"
14	"github.com/modelcontextprotocol/go-sdk/mcp"
15)
16
17// ErrorResult creates an MCP CallToolResult indicating an error.
18// Use this for user-facing errors (validation failures, API errors, etc.).
19// The Go error return should remain nil per MCP SDK conventions.
20func ErrorResult(msg string) *mcp.CallToolResult {
21	return &mcp.CallToolResult{
22		IsError: true,
23		Content: []mcp.Content{
24			&mcp.TextContent{Text: msg},
25		},
26	}
27}
28
29// Estimate constraints.
30const (
31	MinEstimate = 0
32	MaxEstimate = 720
33)
34
35// ErrInvalidEstimate indicates the estimate is out of range.
36var ErrInvalidEstimate = errors.New("estimate must be between 0 and 720 minutes")
37
38// ErrInvalidStatusForWorkflow indicates the status is not valid for the area's workflow.
39var ErrInvalidStatusForWorkflow = errors.New("invalid status for workflow")
40
41// ValidateEstimate checks that an estimate is within the valid range (0-720 minutes).
42func ValidateEstimate(estimate int) error {
43	if estimate < MinEstimate || estimate > MaxEstimate {
44		return fmt.Errorf("%w: got %d", ErrInvalidEstimate, estimate)
45	}
46
47	return nil
48}
49
50// ValidateStatusForWorkflow parses a status string and validates it's allowed for
51// the given workflow. Returns the parsed status and nil on success, or an error
52// with valid options on failure.
53func ValidateStatusForWorkflow(
54	input string,
55	workflow lunatask.Workflow,
56) (lunatask.TaskStatus, error) {
57	status, err := lunatask.ParseTaskStatus(input)
58	if err != nil {
59		return "", fmt.Errorf(
60			"%w: '%s' for %s; valid: %s",
61			ErrInvalidStatusForWorkflow, input, workflow.Description(), formatValidStatuses(workflow),
62		)
63	}
64
65	if !slices.Contains(workflow.ValidStatuses(), status) {
66		return "", fmt.Errorf(
67			"%w: '%s' not valid for %s; valid: %s",
68			ErrInvalidStatusForWorkflow, input, workflow.Description(), formatValidStatuses(workflow),
69		)
70	}
71
72	return status, nil
73}
74
75func formatValidStatuses(workflow lunatask.Workflow) string {
76	statuses := workflow.ValidStatuses()
77	strs := make([]string, len(statuses))
78
79	for i, s := range statuses {
80		strs[i] = string(s)
81	}
82
83	return strings.Join(strs, ", ")
84}
85
86// FormatAllStatuses returns a comma-separated list of all valid task statuses.
87func FormatAllStatuses() string {
88	statuses := lunatask.AllTaskStatuses()
89	strs := make([]string, len(statuses))
90
91	for i, s := range statuses {
92		strs[i] = string(s)
93	}
94
95	return strings.Join(strs, ", ")
96}