225 lines
6.1 KiB
Go
225 lines
6.1 KiB
Go
package routing
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ActivityExecutor defines how to execute an activity
|
||
|
|
type ActivityExecutor interface {
|
||
|
|
// Execute runs the activity with given parameters
|
||
|
|
Execute(ctx context.Context, activityName string, params map[string]interface{}) (interface{}, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TemporalActivityExecutor executes activities via Temporal
|
||
|
|
type TemporalActivityExecutor struct {
|
||
|
|
// This would be implemented by workflow context
|
||
|
|
executor func(context.Context, string, interface{}) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// StateTransitioner defines state machine transitions
|
||
|
|
type StateTransitioner interface {
|
||
|
|
// CanTransition checks if transition is allowed
|
||
|
|
CanTransition(from, to *State) bool
|
||
|
|
// Transit performs the transition
|
||
|
|
Transit(from, to *State) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultStateTransitioner implements basic transitions
|
||
|
|
type DefaultStateTransitioner struct {
|
||
|
|
validators []TransitionValidator
|
||
|
|
}
|
||
|
|
|
||
|
|
// TransitionValidator validates a specific transition
|
||
|
|
type TransitionValidator interface {
|
||
|
|
Validate(from, to *State) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewDefaultStateTransitioner creates a new transitioner
|
||
|
|
func NewDefaultStateTransitioner() *DefaultStateTransitioner {
|
||
|
|
return &DefaultStateTransitioner{
|
||
|
|
validators: []TransitionValidator{
|
||
|
|
&StateTypeValidator{},
|
||
|
|
&OutputMatchValidator{},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CanTransition checks if transition is valid
|
||
|
|
func (dst *DefaultStateTransitioner) CanTransition(from, to *State) bool {
|
||
|
|
return dst.Transit(from, to) == nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Transit validates and performs transition
|
||
|
|
func (dst *DefaultStateTransitioner) Transit(from, to *State) error {
|
||
|
|
for _, v := range dst.validators {
|
||
|
|
if err := v.Validate(from, to); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// StateTypeValidator checks state type compatibility
|
||
|
|
type StateTypeValidator struct{}
|
||
|
|
|
||
|
|
func (stv *StateTypeValidator) Validate(from, to *State) error {
|
||
|
|
if from == nil {
|
||
|
|
return nil // Initial transition
|
||
|
|
}
|
||
|
|
|
||
|
|
// Can't transition from terminal states
|
||
|
|
if from.Type == StateTypeFail {
|
||
|
|
return fmt.Errorf("cannot transition from Fail state")
|
||
|
|
}
|
||
|
|
if from.End && from.Type != StateTypePass {
|
||
|
|
return fmt.Errorf("cannot transition from end state")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// OutputMatchValidator checks output-input binding
|
||
|
|
type OutputMatchValidator struct{}
|
||
|
|
|
||
|
|
func (omv *OutputMatchValidator) Validate(from, to *State) error {
|
||
|
|
// Could validate that outputs from previous state match inputs needed
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParameterBinder resolves parameters from context
|
||
|
|
type ParameterBinder interface {
|
||
|
|
// Bind resolves all parameters for a state
|
||
|
|
Bind(state *State, context *ExecutionContext) (map[string]interface{}, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultParameterBinder implements parameter resolution
|
||
|
|
type DefaultParameterBinder struct {
|
||
|
|
resolver *JSONPathResolver
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewDefaultParameterBinder creates a new binder
|
||
|
|
func NewDefaultParameterBinder() *DefaultParameterBinder {
|
||
|
|
return &DefaultParameterBinder{
|
||
|
|
resolver: NewJSONPathResolver(nil, nil),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bind resolves all parameters
|
||
|
|
func (dpb *DefaultParameterBinder) Bind(state *State, context *ExecutionContext) (map[string]interface{}, error) {
|
||
|
|
dpb.resolver.input = context.Input
|
||
|
|
dpb.resolver.stepResults = context.StepResults
|
||
|
|
|
||
|
|
resolved, err := dpb.resolver.ResolvePaths(state.Parameters)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("parameter binding failed: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return resolved, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// WorkflowValidator validates workflow specs
|
||
|
|
type WorkflowValidator interface {
|
||
|
|
// Validate checks if workflow is valid
|
||
|
|
Validate(spec *WorkflowSpec) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// CompositeValidator combines multiple validators
|
||
|
|
type CompositeValidator struct {
|
||
|
|
validators []WorkflowValidator
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewCompositeValidator creates a composite validator
|
||
|
|
func NewCompositeValidator(validators ...WorkflowValidator) *CompositeValidator {
|
||
|
|
return &CompositeValidator{validators: validators}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate runs all validators
|
||
|
|
func (cv *CompositeValidator) Validate(spec *WorkflowSpec) error {
|
||
|
|
for _, v := range cv.validators {
|
||
|
|
if err := v.Validate(spec); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// StateGraphValidator validates state graph structure
|
||
|
|
type StateGraphValidator struct{}
|
||
|
|
|
||
|
|
func (sgv *StateGraphValidator) Validate(spec *WorkflowSpec) error {
|
||
|
|
if spec == nil {
|
||
|
|
return fmt.Errorf("workflow spec is nil")
|
||
|
|
}
|
||
|
|
if len(spec.States) == 0 {
|
||
|
|
return fmt.Errorf("workflow has no states")
|
||
|
|
}
|
||
|
|
|
||
|
|
stateMap := make(map[string]*State)
|
||
|
|
for i := range spec.States {
|
||
|
|
stateMap[spec.States[i].Name] = &spec.States[i]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check all transitions point to valid states
|
||
|
|
for _, state := range spec.States {
|
||
|
|
if state.Type == StateTypeTask && !state.End {
|
||
|
|
if state.Next == "" {
|
||
|
|
return fmt.Errorf("state %s has no next state and is not end", state.Name)
|
||
|
|
}
|
||
|
|
if _, exists := stateMap[state.Next]; !exists {
|
||
|
|
return fmt.Errorf("state %s references non-existent next state %s", state.Name, state.Next)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate catch clauses
|
||
|
|
for _, catch := range state.Catch {
|
||
|
|
if _, exists := stateMap[catch.Next]; !exists {
|
||
|
|
return fmt.Errorf("catch handler in %s references non-existent state %s", state.Name, catch.Next)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ActivityAvailabilityValidator validates activities exist
|
||
|
|
type ActivityAvailabilityValidator struct {
|
||
|
|
kb *KnowledgeBase
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewActivityAvailabilityValidator creates a new validator
|
||
|
|
func NewActivityAvailabilityValidator(kb *KnowledgeBase) *ActivityAvailabilityValidator {
|
||
|
|
return &ActivityAvailabilityValidator{kb: kb}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate checks all activities are available
|
||
|
|
func (aav *ActivityAvailabilityValidator) Validate(spec *WorkflowSpec) error {
|
||
|
|
for _, state := range spec.States {
|
||
|
|
if state.Type == StateTypeTask {
|
||
|
|
if !aav.kb.HasActivity(state.Resource) {
|
||
|
|
return fmt.Errorf("activity %s not found in knowledge base", state.Resource)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// TimeoutValidator validates timeouts
|
||
|
|
type TimeoutValidator struct{}
|
||
|
|
|
||
|
|
func (tv *TimeoutValidator) Validate(spec *WorkflowSpec) error {
|
||
|
|
for _, state := range spec.States {
|
||
|
|
if state.Timeout != "" {
|
||
|
|
if _, err := parseDuration(state.Timeout); err != nil {
|
||
|
|
return fmt.Errorf("invalid timeout in state %s: %w", state.Name, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseDuration(d string) (interface{}, error) {
|
||
|
|
// Placeholder for duration parsing
|
||
|
|
return nil, nil
|
||
|
|
}
|