281 lines
6.8 KiB
Go
281 lines
6.8 KiB
Go
package routing
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
)
|
||
|
|
|
||
|
|
// SpecBuilder defines interface for building workflow specs
|
||
|
|
type SpecBuilder interface {
|
||
|
|
// FromIntent builds spec from an analyzed intent
|
||
|
|
FromIntent(intent *Intent, metadata *BuildMetadata) (*WorkflowSpec, error)
|
||
|
|
|
||
|
|
// Validate checks if builder can build this intent
|
||
|
|
Validate(intent *Intent) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildMetadata contains metadata for spec building
|
||
|
|
type BuildMetadata struct {
|
||
|
|
KnowledgeBase *KnowledgeBase
|
||
|
|
Context map[string]interface{}
|
||
|
|
Validators []WorkflowValidator
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultSpecBuilder implements basic spec building
|
||
|
|
type DefaultSpecBuilder struct {
|
||
|
|
kb *KnowledgeBase
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewDefaultSpecBuilder creates a builder
|
||
|
|
func NewDefaultSpecBuilder(kb *KnowledgeBase) *DefaultSpecBuilder {
|
||
|
|
return &DefaultSpecBuilder{kb: kb}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate checks if intent is buildable
|
||
|
|
func (dsb *DefaultSpecBuilder) Validate(intent *Intent) error {
|
||
|
|
if len(intent.Activities) == 0 {
|
||
|
|
return fmt.Errorf("intent has no activities")
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, actName := range intent.Activities {
|
||
|
|
if !dsb.kb.HasActivity(actName) {
|
||
|
|
return fmt.Errorf("activity %s not found", actName)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// FromIntent builds spec from intent
|
||
|
|
func (dsb *DefaultSpecBuilder) FromIntent(intent *Intent, metadata *BuildMetadata) (*WorkflowSpec, error) {
|
||
|
|
if err := dsb.Validate(intent); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
states := make([]State, 0, len(intent.Activities)+1)
|
||
|
|
|
||
|
|
// Build states for each activity
|
||
|
|
for i, actName := range intent.Activities {
|
||
|
|
act := dsb.kb.GetActivity(actName)
|
||
|
|
|
||
|
|
state := State{
|
||
|
|
Name: actName,
|
||
|
|
Type: StateTypeTask,
|
||
|
|
Resource: actName,
|
||
|
|
Parameters: dsb.buildParameters(act, intent, i),
|
||
|
|
Timeout: act.Constraints.DefaultTimeout,
|
||
|
|
Retry: dsb.buildRetryPolicy(act, intent),
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set next state
|
||
|
|
if i < len(intent.Activities)-1 {
|
||
|
|
state.Next = intent.Activities[i+1]
|
||
|
|
} else {
|
||
|
|
state.End = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add catch for flaky activities
|
||
|
|
if act.Constraints.IsFlaky && intent.ErrorHandling != "fail-fast" {
|
||
|
|
state.Catch = []CatchClause{
|
||
|
|
{
|
||
|
|
ErrorEquals: []string{"ActivityError", "TimeoutError"},
|
||
|
|
Next: "HandleError",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
states = append(states, state)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add error handler if needed
|
||
|
|
if dsb.hasFlaky(intent) && intent.ErrorHandling != "fail-fast" {
|
||
|
|
states = append(states, State{
|
||
|
|
Name: "HandleError",
|
||
|
|
Type: StateTypeFail,
|
||
|
|
Error: "ActivityFailed",
|
||
|
|
Cause: "One or more activities failed",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build input map
|
||
|
|
inputMap := make(map[string]interface{})
|
||
|
|
for k, v := range intent.Parameters {
|
||
|
|
inputMap[k] = v
|
||
|
|
}
|
||
|
|
if metadata != nil && metadata.Context != nil {
|
||
|
|
for k, v := range metadata.Context {
|
||
|
|
if _, exists := inputMap[k]; !exists {
|
||
|
|
inputMap[k] = v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spec := &WorkflowSpec{
|
||
|
|
Name: intent.WorkflowName,
|
||
|
|
Input: inputMap,
|
||
|
|
States: states,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate if validators provided
|
||
|
|
if metadata != nil && len(metadata.Validators) > 0 {
|
||
|
|
for _, v := range metadata.Validators {
|
||
|
|
if err := v.Validate(spec); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return spec, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// buildParameters creates parameters for activity
|
||
|
|
func (dsb *DefaultSpecBuilder) buildParameters(act *ActivityMetadata, intent *Intent, stateIndex int) map[string]interface{} {
|
||
|
|
params := make(map[string]interface{})
|
||
|
|
resolver := ¶mResolver{
|
||
|
|
intent: intent,
|
||
|
|
kb: dsb.kb,
|
||
|
|
}
|
||
|
|
|
||
|
|
if stateIndex > 0 {
|
||
|
|
resolver.prevState = intent.Activities[stateIndex-1]
|
||
|
|
}
|
||
|
|
|
||
|
|
for name, def := range act.Inputs {
|
||
|
|
if val := resolver.resolve(name, def); val != nil {
|
||
|
|
params[name] = val
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return params
|
||
|
|
}
|
||
|
|
|
||
|
|
// buildRetryPolicy creates retry policy
|
||
|
|
func (dsb *DefaultSpecBuilder) buildRetryPolicy(act *ActivityMetadata, intent *Intent) *RetryPolicy {
|
||
|
|
if intent.ErrorHandling == "fail-fast" {
|
||
|
|
return &RetryPolicy{
|
||
|
|
MaxAttempts: 1,
|
||
|
|
BackoffRate: 1.0,
|
||
|
|
InitialInterval: "1s",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return &RetryPolicy{
|
||
|
|
MaxAttempts: int32(act.Constraints.RecommendedRetries),
|
||
|
|
BackoffRate: act.Constraints.RetryBackoff,
|
||
|
|
InitialInterval: "1s",
|
||
|
|
MaxInterval: "30s",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// hasFlaky checks if any activity is flaky
|
||
|
|
func (dsb *DefaultSpecBuilder) hasFlaky(intent *Intent) bool {
|
||
|
|
for _, actName := range intent.Activities {
|
||
|
|
act := dsb.kb.GetActivity(actName)
|
||
|
|
if act != nil && act.Constraints.IsFlaky {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// CronSpecBuilder builds cron workflow specs
|
||
|
|
type CronSpecBuilder struct {
|
||
|
|
regularBuilder SpecBuilder
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewCronSpecBuilder creates a cron builder
|
||
|
|
func NewCronSpecBuilder(regularBuilder SpecBuilder) *CronSpecBuilder {
|
||
|
|
return &CronSpecBuilder{
|
||
|
|
regularBuilder: regularBuilder,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate checks if intent is valid for cron
|
||
|
|
func (csb *CronSpecBuilder) Validate(intent *Intent) error {
|
||
|
|
if !intent.IsCron {
|
||
|
|
return fmt.Errorf("intent is not marked as cron")
|
||
|
|
}
|
||
|
|
|
||
|
|
if intent.CronSchedule == "" {
|
||
|
|
return fmt.Errorf("cron schedule is empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
return csb.regularBuilder.Validate(intent)
|
||
|
|
}
|
||
|
|
|
||
|
|
// FromIntent builds cron spec
|
||
|
|
func (csb *CronSpecBuilder) FromIntent(intent *Intent, metadata *BuildMetadata) (*WorkflowSpec, error) {
|
||
|
|
spec, err := csb.regularBuilder.FromIntent(intent, metadata)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// In production, wrap with cron metadata
|
||
|
|
// For now, just return the regular spec
|
||
|
|
return spec, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// SpecBuilderFactory creates appropriate spec builders
|
||
|
|
type SpecBuilderFactory struct {
|
||
|
|
kb *KnowledgeBase
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewSpecBuilderFactory creates a factory
|
||
|
|
func NewSpecBuilderFactory(kb *KnowledgeBase) *SpecBuilderFactory {
|
||
|
|
return &SpecBuilderFactory{kb: kb}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CreateBuilder creates appropriate builder for intent
|
||
|
|
func (sbf *SpecBuilderFactory) CreateBuilder(intent *Intent) (SpecBuilder, error) {
|
||
|
|
if intent.IsCron {
|
||
|
|
return NewCronSpecBuilder(NewDefaultSpecBuilder(sbf.kb)), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
return NewDefaultSpecBuilder(sbf.kb), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// CompositeSpecBuilder combines multiple builders with fallback
|
||
|
|
type CompositeSpecBuilder struct {
|
||
|
|
builders []SpecBuilder
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewCompositeSpecBuilder creates a composite builder
|
||
|
|
func NewCompositeSpecBuilder(builders ...SpecBuilder) *CompositeSpecBuilder {
|
||
|
|
return &CompositeSpecBuilder{builders: builders}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate tries each builder
|
||
|
|
func (csb *CompositeSpecBuilder) Validate(intent *Intent) error {
|
||
|
|
var lastErr error
|
||
|
|
|
||
|
|
for _, builder := range csb.builders {
|
||
|
|
if err := builder.Validate(intent); err == nil {
|
||
|
|
return nil
|
||
|
|
} else {
|
||
|
|
lastErr = err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if lastErr != nil {
|
||
|
|
return lastErr
|
||
|
|
}
|
||
|
|
return fmt.Errorf("no builder could validate intent")
|
||
|
|
}
|
||
|
|
|
||
|
|
// FromIntent tries each builder
|
||
|
|
func (csb *CompositeSpecBuilder) FromIntent(intent *Intent, metadata *BuildMetadata) (*WorkflowSpec, error) {
|
||
|
|
var lastErr error
|
||
|
|
|
||
|
|
for _, builder := range csb.builders {
|
||
|
|
if spec, err := builder.FromIntent(intent, metadata); err == nil {
|
||
|
|
return spec, nil
|
||
|
|
} else {
|
||
|
|
lastErr = err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if lastErr != nil {
|
||
|
|
return nil, lastErr
|
||
|
|
}
|
||
|
|
return nil, fmt.Errorf("no builder could create spec")
|
||
|
|
}
|