order sagas
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/flow"
|
||||
)
|
||||
|
||||
// flowStore holds the saga/flow definitions the service can run. Definitions
|
||||
// are JSON files in a directory (editable by the backoffice saga editor),
|
||||
// overlaid on a set of built-in seeds so a fresh deployment always has the
|
||||
// defaults. Saving validates against the engine before writing.
|
||||
type flowStore struct {
|
||||
mu sync.RWMutex
|
||||
dir string
|
||||
engine *flow.Engine
|
||||
defs map[string]*flow.Definition
|
||||
}
|
||||
|
||||
var validFlowName = func(s string) bool {
|
||||
if s == "" || len(s) > 64 {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if !(r == '-' || r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// newFlowStore seeds with the built-in definitions, then overlays any JSON files
|
||||
// found in dir (on-disk wins, so edits persist and take effect).
|
||||
func newFlowStore(dir string, engine *flow.Engine, seed map[string]*flow.Definition) (*flowStore, error) {
|
||||
s := &flowStore{dir: dir, engine: engine, defs: map[string]*flow.Definition{}}
|
||||
for name, def := range seed {
|
||||
s.defs[name] = def
|
||||
}
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, "*.json"))
|
||||
for _, m := range matches {
|
||||
data, err := os.ReadFile(m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read flow %s: %w", m, err)
|
||||
}
|
||||
def, err := flow.Parse(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse flow %s: %w", m, err)
|
||||
}
|
||||
if def.Name == "" {
|
||||
def.Name = strings.TrimSuffix(filepath.Base(m), ".json")
|
||||
}
|
||||
s.defs[def.Name] = def
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *flowStore) get(name string) (*flow.Definition, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
d, ok := s.defs[name]
|
||||
return d, ok
|
||||
}
|
||||
|
||||
func (s *flowStore) list() []*flow.Definition {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
names := make([]string, 0, len(s.defs))
|
||||
for n := range s.defs {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
out := make([]*flow.Definition, 0, len(names))
|
||||
for _, n := range names {
|
||||
out = append(out, s.defs[n])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// save validates the definition, persists it to <dir>/<name>.json and updates
|
||||
// the in-memory map so the next checkout uses it.
|
||||
func (s *flowStore) save(name string, def *flow.Definition) error {
|
||||
if !validFlowName(name) {
|
||||
return fmt.Errorf("invalid flow name %q", name)
|
||||
}
|
||||
if def.Name == "" {
|
||||
def.Name = name
|
||||
}
|
||||
if err := s.engine.Validate(def); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(def, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(s.dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(s.dir, name+".json"), append(data, '\n'), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.defs[name] = def
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user