- llm_router.go: Extract getStringFromMap, firstNonEmpty, paramResolver - buildCronSpec: 12 → 4 complexity - buildParameters: 9 → 5 complexity - routing_workflow.go: Extract stateMachine, stateResult types - RoutingWorkflow: 11 → 6 complexity - Separate executeTask/executePass/executeFail - notification.go: Extract checker interface pattern - DeploymentPreCheckActivity: 10 → 5 complexity - goCheckers() returns language-specific checkers - Added 7 new test cases for helper functions - Coverage: internal/routing 63.6% → 66.0%
This commit is contained in:
@@ -23,98 +23,137 @@ type RoutingWorkflowOutput struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// stateResult holds the result of executing a state
|
||||
type stateResult struct {
|
||||
result interface{}
|
||||
nextState string
|
||||
isDone bool
|
||||
err error
|
||||
}
|
||||
|
||||
// stateMachine manages workflow state execution
|
||||
type stateMachine struct {
|
||||
spec *routing.WorkflowSpec
|
||||
stateIndex map[string]*routing.State
|
||||
execCtx *routing.ExecutionContext
|
||||
output *RoutingWorkflowOutput
|
||||
current string
|
||||
}
|
||||
|
||||
// newStateMachine creates a state machine from spec
|
||||
func newStateMachine(spec *routing.WorkflowSpec) *stateMachine {
|
||||
idx := make(map[string]*routing.State)
|
||||
for i := range spec.States {
|
||||
idx[spec.States[i].Name] = &spec.States[i]
|
||||
}
|
||||
return &stateMachine{
|
||||
spec: spec,
|
||||
stateIndex: idx,
|
||||
execCtx: &routing.ExecutionContext{
|
||||
Input: spec.Input,
|
||||
StepResults: make(map[string]interface{}),
|
||||
},
|
||||
output: &RoutingWorkflowOutput{
|
||||
Status: "FAILED",
|
||||
StepResults: make(map[string]interface{}),
|
||||
},
|
||||
current: spec.States[0].Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *stateMachine) currentState() *routing.State {
|
||||
return m.stateIndex[m.current]
|
||||
}
|
||||
|
||||
func (m *stateMachine) recordResult(name string, result interface{}) {
|
||||
wrapped := map[string]interface{}{"output": result}
|
||||
m.execCtx.StepResults[name] = wrapped
|
||||
m.output.StepResults[name] = result
|
||||
}
|
||||
|
||||
func (m *stateMachine) complete(result interface{}) RoutingWorkflowOutput {
|
||||
m.output.Status = "COMPLETED"
|
||||
m.output.FinalOutput = result
|
||||
return *m.output
|
||||
}
|
||||
|
||||
func (m *stateMachine) fail(errMsg string) RoutingWorkflowOutput {
|
||||
m.output.Error = errMsg
|
||||
return *m.output
|
||||
}
|
||||
|
||||
// executeTask runs a Task state
|
||||
func executeTask(ctx workflow.Context, state *routing.State, execCtx *routing.ExecutionContext, logger log.Logger) stateResult {
|
||||
result, nextState, err := executeTaskState(ctx, state, execCtx, logger)
|
||||
if err != nil {
|
||||
if nextState != "" {
|
||||
return stateResult{nextState: nextState} // Caught, continue to error handler
|
||||
}
|
||||
return stateResult{err: err}
|
||||
}
|
||||
return stateResult{result: result, nextState: state.Next, isDone: state.End}
|
||||
}
|
||||
|
||||
// executePass runs a Pass state
|
||||
func executePass(state *routing.State) stateResult {
|
||||
return stateResult{result: state.Result, nextState: state.Next, isDone: state.End}
|
||||
}
|
||||
|
||||
// executeFail runs a Fail state
|
||||
func executeFail(state *routing.State) stateResult {
|
||||
return stateResult{err: fmt.Errorf("%s: %s", state.Error, state.Cause)}
|
||||
}
|
||||
|
||||
// RoutingWorkflow executes any WorkflowSpec generated by llm-router
|
||||
func RoutingWorkflow(ctx workflow.Context, input RoutingWorkflowInput) (RoutingWorkflowOutput, error) {
|
||||
logger := workflow.GetLogger(ctx)
|
||||
|
||||
output := RoutingWorkflowOutput{
|
||||
Status: "FAILED",
|
||||
StepResults: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if input.Spec == nil || len(input.Spec.States) == 0 {
|
||||
output.Error = "empty workflow spec"
|
||||
return output, nil
|
||||
return RoutingWorkflowOutput{Status: "FAILED", Error: "empty workflow spec", StepResults: map[string]interface{}{}}, nil
|
||||
}
|
||||
|
||||
logger.Info("RoutingWorkflow started", "name", input.Spec.Name, "stateCount", len(input.Spec.States))
|
||||
|
||||
// Build execution context
|
||||
execCtx := &routing.ExecutionContext{
|
||||
Input: input.Spec.Input,
|
||||
StepResults: make(map[string]interface{}),
|
||||
}
|
||||
m := newStateMachine(input.Spec)
|
||||
|
||||
// Build state index for fast lookup
|
||||
stateIndex := make(map[string]*routing.State)
|
||||
for i := range input.Spec.States {
|
||||
stateIndex[input.Spec.States[i].Name] = &input.Spec.States[i]
|
||||
}
|
||||
|
||||
// Find first state (first in array)
|
||||
currentStateName := input.Spec.States[0].Name
|
||||
|
||||
// State machine loop
|
||||
for {
|
||||
state, ok := stateIndex[currentStateName]
|
||||
if !ok {
|
||||
output.Error = fmt.Sprintf("state not found: %s", currentStateName)
|
||||
return output, nil
|
||||
state := m.currentState()
|
||||
if state == nil {
|
||||
return m.fail(fmt.Sprintf("state not found: %s", m.current)), nil
|
||||
}
|
||||
|
||||
logger.Info("executing state", "state", currentStateName, "type", state.Type)
|
||||
logger.Info("executing state", "state", m.current, "type", state.Type)
|
||||
|
||||
var res stateResult
|
||||
switch state.Type {
|
||||
case routing.StateTypeTask:
|
||||
result, nextState, err := executeTaskState(ctx, state, execCtx, logger)
|
||||
if err != nil {
|
||||
// Check for catch clause
|
||||
if nextState != "" {
|
||||
currentStateName = nextState
|
||||
continue
|
||||
}
|
||||
output.Error = fmt.Sprintf("state %s failed: %v", currentStateName, err)
|
||||
return output, nil
|
||||
}
|
||||
// Wrap result in output key for JSONPath compatibility (e.g., ${Clone.output.path})
|
||||
wrappedResult := map[string]interface{}{"output": result}
|
||||
execCtx.StepResults[state.Name] = wrappedResult
|
||||
output.StepResults[state.Name] = result // Keep original for output
|
||||
|
||||
if state.End {
|
||||
output.Status = "COMPLETED"
|
||||
output.FinalOutput = result
|
||||
logger.Info("RoutingWorkflow completed", "name", input.Spec.Name)
|
||||
return output, nil
|
||||
}
|
||||
currentStateName = state.Next
|
||||
|
||||
res = executeTask(ctx, state, m.execCtx, logger)
|
||||
case routing.StateTypePass:
|
||||
execCtx.StepResults[state.Name] = state.Result
|
||||
output.StepResults[state.Name] = state.Result
|
||||
|
||||
if state.End {
|
||||
output.Status = "COMPLETED"
|
||||
output.FinalOutput = state.Result
|
||||
return output, nil
|
||||
}
|
||||
currentStateName = state.Next
|
||||
|
||||
res = executePass(state)
|
||||
case routing.StateTypeFail:
|
||||
output.Error = fmt.Sprintf("%s: %s", state.Error, state.Cause)
|
||||
logger.Error("RoutingWorkflow failed at Fail state", "state", currentStateName, "error", state.Error)
|
||||
return output, nil
|
||||
|
||||
res = executeFail(state)
|
||||
default:
|
||||
output.Error = fmt.Sprintf("unknown state type: %s", state.Type)
|
||||
return output, nil
|
||||
return m.fail(fmt.Sprintf("unknown state type: %s", state.Type)), nil
|
||||
}
|
||||
|
||||
// Safety check
|
||||
if currentStateName == "" {
|
||||
output.Error = "no next state and not end"
|
||||
return output, nil
|
||||
if res.err != nil {
|
||||
logger.Error("state failed", "state", m.current, "error", res.err)
|
||||
return m.fail(fmt.Sprintf("state %s failed: %v", m.current, res.err)), nil
|
||||
}
|
||||
|
||||
if res.result != nil {
|
||||
m.recordResult(state.Name, res.result)
|
||||
}
|
||||
|
||||
if res.isDone {
|
||||
logger.Info("RoutingWorkflow completed", "name", input.Spec.Name)
|
||||
return m.complete(res.result), nil
|
||||
}
|
||||
|
||||
if res.nextState == "" {
|
||||
return m.fail("no next state and not end"), nil
|
||||
}
|
||||
m.current = res.nextState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user