package workitem import ( "context" "encoding/json" "errors" "strings" ) var ErrInvalidRef = errors.New("invalid workitem ref") type ProviderID string type Capability string const ( CapabilityLookup Capability = "lookup" CapabilityComment Capability = "comment" CapabilityStatusProjection Capability = "status_projection" CapabilityLabelProjection Capability = "label_projection" ) type Capabilities map[Capability]bool func (c Capabilities) Supports(capability Capability) bool { return c[capability] } type Provider interface { ProviderID() ProviderID Capabilities() Capabilities } type Reader interface { FetchWorkItem(ctx context.Context, ref Ref) (WorkItem, error) } type Commenter interface { AppendComment(ctx context.Context, input CommentInput) error } type StatusProjector interface { ProjectStatus(ctx context.Context, input StatusProjection) error } type LabelProjector interface { ProjectLabels(ctx context.Context, input LabelProjection) error } type Ref struct { Provider ProviderID `json:"provider"` Tenant string `json:"tenant"` Project string `json:"project"` ID string `json:"id"` URL string `json:"url,omitempty"` Metadata json.RawMessage `json:"metadata,omitempty"` } type WorkItem struct { Ref Ref `json:"ref"` Title string `json:"title"` DescriptionText string `json:"description_text,omitempty"` DescriptionHTML string `json:"description_html,omitempty"` State string `json:"state,omitempty"` AssigneeIDs []string `json:"assignee_ids,omitempty"` Labels []string `json:"labels,omitempty"` Metadata json.RawMessage `json:"metadata,omitempty"` } type CommentFormat string const ( CommentFormatText CommentFormat = "text" CommentFormatMarkdown CommentFormat = "markdown" CommentFormatHTML CommentFormat = "html" ) type CommentInput struct { Ref Ref `json:"ref"` Body string `json:"body"` Format CommentFormat `json:"format"` ExternalSource string `json:"external_source,omitempty"` ExternalID string `json:"external_id,omitempty"` Visibility string `json:"visibility,omitempty"` } type StatusProjection struct { Ref Ref `json:"ref"` CanonicalState string `json:"canonical_state"` ProviderState string `json:"provider_state"` } type LabelTarget struct { Canonical string `json:"canonical"` Provider string `json:"provider"` } type LabelProjection struct { Ref Ref `json:"ref"` Add []LabelTarget `json:"add,omitempty"` Remove []LabelTarget `json:"remove,omitempty"` } type Mapping struct { States map[string]string `json:"states,omitempty"` Labels map[string]string `json:"labels,omitempty"` } func (m Mapping) StateTarget(canonical string) (string, bool) { target := strings.TrimSpace(m.States[strings.TrimSpace(canonical)]) return target, target != "" } func (m Mapping) LabelTarget(canonical string) (string, bool) { target := strings.TrimSpace(m.Labels[strings.TrimSpace(canonical)]) return target, target != "" } func NormalizeRef(ref Ref) (Ref, error) { provider := ProviderID(strings.TrimSpace(string(ref.Provider))) id := strings.TrimSpace(ref.ID) if provider == "" || id == "" { return Ref{}, ErrInvalidRef } meta := ref.Metadata if len(meta) == 0 { meta = json.RawMessage("{}") } else if !json.Valid(meta) { return Ref{}, ErrInvalidRef } return Ref{ Provider: provider, Tenant: strings.TrimSpace(ref.Tenant), Project: strings.TrimSpace(ref.Project), ID: id, URL: strings.TrimSpace(ref.URL), Metadata: meta, }, nil }