complete item 1 task substrate
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrConflict = errors.New("task version conflict")
|
||||
var ErrNotFound = errors.New("task not found")
|
||||
var ErrInvalid = errors.New("invalid event")
|
||||
|
||||
type TaskState string
|
||||
|
||||
const (
|
||||
StateQueued TaskState = "queued"
|
||||
StateLeased TaskState = "leased"
|
||||
StateCompleted TaskState = "completed"
|
||||
StateFailed TaskState = "failed"
|
||||
StateBlocked TaskState = "blocked"
|
||||
)
|
||||
|
||||
type Estimate struct {
|
||||
Value float64 `json:"value"`
|
||||
Who string `json:"who"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
type Lease struct {
|
||||
HarnessID string `json:"harness_id"`
|
||||
Until time.Time `json:"until"`
|
||||
}
|
||||
type Task struct {
|
||||
ID string `json:"id"`
|
||||
Source string `json:"source"`
|
||||
ExternalID string `json:"external_id"`
|
||||
Project string `json:"project"`
|
||||
Capability []string `json:"capability"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
InherentPriority int `json:"inherent_priority"`
|
||||
Due *time.Time `json:"due,omitempty"`
|
||||
Estimate *Estimate `json:"estimate,omitempty"`
|
||||
State TaskState `json:"state"`
|
||||
Lease *Lease `json:"lease,omitempty"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Seq uint64 `json:"seq"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
TaskID string `json:"task_id"`
|
||||
Version int `json:"version"`
|
||||
At time.Time `json:"at"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
func Hash(v []byte) string { h := sha256.Sum256(v); return hex.EncodeToString(h[:]) }
|
||||
|
||||
// NewID returns a sortable, 128-bit ULID-like identifier using the canonical
|
||||
// 48-bit millisecond timestamp plus 80 bits of cryptographic randomness.
|
||||
|
||||
var ulidEncoding = base32.NewEncoding("0123456789ABCDEFGHJKMNPQRSTVWXYZ").WithPadding(base32.NoPadding)
|
||||
|
||||
func NewID() string {
|
||||
b := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(b[:8], uint64(time.Now().UnixMilli())<<16)
|
||||
_, _ = rand.Read(b[6:])
|
||||
return ulidEncoding.EncodeToString(b)
|
||||
}
|
||||
func ValidateEvent(e Event) error {
|
||||
if e.Type == "" || e.TaskID == "" || len(e.Payload) == 0 || len(e.Payload) > 64*1024 {
|
||||
return ErrInvalid
|
||||
}
|
||||
allowed := map[string]bool{"TaskCreated": true, "TaskLeased": true, "TaskReleased": true, "TaskCompleted": true, "TaskFailed": true, "TaskBlocked": true, "ApprovalRequested": true, "ApprovalGranted": true, "ApprovalDenied": true, "TaskAmended": true}
|
||||
if !allowed[e.Type] {
|
||||
return fmt.Errorf("%w: unknown type %q", ErrInvalid, e.Type)
|
||||
}
|
||||
var p map[string]any
|
||||
if err := json.Unmarshal(e.Payload, &p); err != nil {
|
||||
return fmt.Errorf("%w: payload is not JSON", ErrInvalid)
|
||||
}
|
||||
return ValidatePayload(e.Type, p)
|
||||
}
|
||||
func ValidateCreated(p map[string]any) error {
|
||||
for _, k := range []string{"source", "external_id", "project"} {
|
||||
if s, ok := p[k].(string); !ok || strings.TrimSpace(s) == "" {
|
||||
return fmt.Errorf("%w: %s required", ErrInvalid, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidatePayload(typ string, p map[string]any) error {
|
||||
requiredString := func(key string) error {
|
||||
v, ok := p[key].(string)
|
||||
if !ok || strings.TrimSpace(v) == "" {
|
||||
return fmt.Errorf("%w: %s required", ErrInvalid, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch typ {
|
||||
case "TaskCreated":
|
||||
return ValidateCreated(p)
|
||||
case "TaskLeased":
|
||||
if err := requiredString("harness_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := p["until_ns"].(float64); !ok {
|
||||
return fmt.Errorf("%w: until_ns required", ErrInvalid)
|
||||
}
|
||||
case "TaskReleased":
|
||||
if err := requiredString("handoff_ref"); err != nil && p["reason"] == nil {
|
||||
return err
|
||||
}
|
||||
case "TaskCompleted":
|
||||
if err := requiredString("report_ref"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "TaskFailed":
|
||||
if err := requiredString("reason"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "TaskBlocked":
|
||||
if err := requiredString("blocker"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "TaskAmended":
|
||||
if len(p) == 0 {
|
||||
return fmt.Errorf("%w: amendment cannot be empty", ErrInvalid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user