112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
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
|
|
}
|