package workflow import ( "encoding/json" "fmt" "os" ) // LoadWorkflowFromFile loads a workflow from a JSON file func LoadWorkflowFromFile(filename string) (*Workflow, error) { data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) } var wf Workflow if err := json.Unmarshal(data, &wf); err != nil { return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } return &wf, nil } // SaveWorkflowToFile saves a workflow to a JSON file func SaveWorkflowToFile(wf *Workflow, filename string) error { data, err := json.MarshalIndent(wf, "", " ") if err != nil { return fmt.Errorf("failed to marshal workflow: %w", err) } if err := os.WriteFile(filename, data, 0644); err != nil { return fmt.Errorf("failed to write file: %w", err) } return nil } // LoadWorkflowFromJSON loads a workflow from a JSON string func LoadWorkflowFromJSON(jsonStr string) (*Workflow, error) { var wf Workflow if err := json.Unmarshal([]byte(jsonStr), &wf); err != nil { return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } return &wf, nil } // ToJSON converts a workflow to a JSON string func (w *Workflow) ToJSON() (string, error) { data, err := json.MarshalIndent(w, "", " ") if err != nil { return "", fmt.Errorf("failed to marshal workflow: %w", err) } return string(data), nil } // GetStepByID finds a step by its ID func (w *Workflow) GetStepByID(stepID string) *Step { for i := range w.Steps { if w.Steps[i].ID == stepID { return &w.Steps[i] } } return nil } // GetStepsByType returns all steps of a specific type func (w *Workflow) GetStepsByType(stepType StepType) []Step { var steps []Step prefix := string(stepType) + "_" for _, step := range w.Steps { if len(step.ID) > len(prefix) && step.ID[:len(prefix)] == prefix { steps = append(steps, step) } } return steps } // ValidateStepReferences validates that all step references (next, children, cases) exist func (w *Workflow) ValidateStepReferences() error { stepIDs := make(map[string]bool) for _, step := range w.Steps { stepIDs[step.ID] = true } for _, step := range w.Steps { // Validate next if step.Next != "" && !stepIDs[step.Next] { return fmt.Errorf("step %s references non-existent next step: %s", step.ID, step.Next) } // Validate children for _, childID := range step.Children { if !stepIDs[childID] { return fmt.Errorf("step %s references non-existent child step: %s", step.ID, childID) } } // Validate cases for _, c := range step.Cases { if len(c) >= 2 && c[1] != "" && !stepIDs[c[1]] { return fmt.Errorf("step %s references non-existent case step: %s", step.ID, c[1]) } } // Validate onError (v3.10+) if step.OnError != "" && !stepIDs[step.OnError] { return fmt.Errorf("step %s references non-existent onError step: %s", step.ID, step.OnError) } } return nil } // Clone creates a deep copy of the workflow func (w *Workflow) Clone() (*Workflow, error) { data, err := json.Marshal(w) if err != nil { return nil, fmt.Errorf("failed to marshal workflow: %w", err) } var clone Workflow if err := json.Unmarshal(data, &clone); err != nil { return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } return &clone, nil } // GetVariableNames returns all declared variable names func (r *Registry) GetVariableNames() ([]string, error) { var names []string for _, varDecl := range r.Vars { parsed, err := ParseVariableDeclaration(varDecl) if err != nil { return nil, err } names = append(names, parsed.Name) } return names, nil } // GetServiceNames returns all declared service names func (r *Registry) GetServiceNames() ([]string, error) { var names []string for _, sig := range r.Services { parsed, err := ParseServiceSignature(sig) if err != nil { return nil, err } names = append(names, parsed.Name) } return names, nil } // BuildWorkflow provides a fluent builder for creating workflows type WorkflowBuilder struct { workflow *Workflow } // NewWorkflowBuilder creates a new workflow builder func NewWorkflowBuilder(name string) *WorkflowBuilder { return &WorkflowBuilder{ workflow: &Workflow{ Version: "3.13", Name: name, Registry: Registry{ Services: []string{}, APIs: []APIDefinition{}, Components: []string{}, Vars: []string{}, Files: FilesRegistry{ Inputs: []string{}, Artifacts: []string{}, }, }, Steps: []Step{}, }, } } // AddService adds a service to the registry func (b *WorkflowBuilder) AddService(signature string) *WorkflowBuilder { b.workflow.Registry.Services = append(b.workflow.Registry.Services, signature) return b } // AddAPI adds an API definition to the registry func (b *WorkflowBuilder) AddAPI(apiDef APIDefinition) *WorkflowBuilder { b.workflow.Registry.APIs = append(b.workflow.Registry.APIs, apiDef) return b } // AddComponent adds a component to the registry func (b *WorkflowBuilder) AddComponent(componentID string) *WorkflowBuilder { b.workflow.Registry.Components = append(b.workflow.Registry.Components, componentID) return b } // AddVar adds a variable to the registry func (b *WorkflowBuilder) AddVar(varDecl string) *WorkflowBuilder { b.workflow.Registry.Vars = append(b.workflow.Registry.Vars, varDecl) return b } // AddInput adds an input file pattern func (b *WorkflowBuilder) AddInput(pattern string) *WorkflowBuilder { b.workflow.Registry.Files.Inputs = append(b.workflow.Registry.Files.Inputs, pattern) return b } // AddArtifact adds an artifact file pattern func (b *WorkflowBuilder) AddArtifact(pattern string) *WorkflowBuilder { b.workflow.Registry.Files.Artifacts = append(b.workflow.Registry.Files.Artifacts, pattern) return b } // AddStep adds a step to the workflow func (b *WorkflowBuilder) AddStep(step Step) *WorkflowBuilder { b.workflow.Steps = append(b.workflow.Steps, step) return b } // Build returns the constructed workflow func (b *WorkflowBuilder) Build() (*Workflow, error) { if err := b.workflow.Validate(); err != nil { return nil, err } return b.workflow, nil } // MustBuild returns the workflow or panics on error func (b *WorkflowBuilder) MustBuild() *Workflow { wf, err := b.Build() if err != nil { panic(err) } return wf }