Class: Fractor::Workflow::JobDependencyValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/fractor/workflow/job_dependency_validator.rb

Overview

Validates job dependencies in workflows. Ensures dependencies exist and are acyclic.

This validator prevents runtime errors by catching configuration issues before workflow execution begins.

Defined Under Namespace

Classes: DependencyError

Instance Method Summary collapse

Constructor Details

#initialize(jobs) ⇒ JobDependencyValidator

Returns a new instance of JobDependencyValidator.



14
15
16
17
# File 'lib/fractor/workflow/job_dependency_validator.rb', line 14

def initialize(jobs)
  @jobs = jobs
  @jobs_by_name = build_jobs_index
end

Instance Method Details

#check_circular_dependenciestrue

Check for circular dependencies using depth-first search.

Returns:

  • (true)

    if no circular dependencies

Raises:



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/fractor/workflow/job_dependency_validator.rb', line 34

def check_circular_dependencies
  visited = Set.new
  path = [] # Track current path as an array

  @jobs.each do |job|
    next if visited.include?(job.name)

    cycle = dfs_cycle_check(job, visited, path)
    if cycle
      cycle_path = cycle.join(" -> ")
      raise DependencyError, "Circular dependency detected: #{cycle_path}"
    end
  end

  true
end

#check_missing_dependenciestrue

Check that all job dependencies exist.

Returns:

  • (true)

    if all dependencies exist

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/fractor/workflow/job_dependency_validator.rb', line 93

def check_missing_dependencies
  missing = []

  @jobs.each do |job|
    job.dependencies.each do |dep_name|
      unless @jobs_by_name.key?(dep_name)
        missing << "#{job.name} depends on non-existent job '#{dep_name}'"
      end
    end
  end

  return true if missing.empty?

  raise DependencyError,
        "Missing dependencies:\n  - #{missing.join("\n  - ")}"
end

#dfs_cycle_check(job, visited, path) ⇒ Array<String>?

DFS cycle detection that returns the cycle path if found.

Parameters:

  • job (Job)

    The job to check

  • visited (Set)

    Jobs already visited

  • path (Array)

    Current path being explored

Returns:

  • (Array<String>, nil)

    Cycle path if found, nil otherwise



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/fractor/workflow/job_dependency_validator.rb', line 57

def dfs_cycle_check(job, visited, path)
  return nil unless job

  # If this job is in the current path, we found a cycle
  if path.include?(job.name)
    # Extract the cycle portion
    cycle_start_index = path.index(job.name)
    return path[cycle_start_index..] + [job.name]
  end

  # If we've already fully explored this job, no cycle from here
  return nil if visited.include?(job.name)

  # Add to current path
  path << job.name

  # Check all dependencies
  job.dependencies.each do |dep_name|
    dep_job = @jobs_by_name[dep_name]
    next unless dep_job

    cycle = dfs_cycle_check(dep_job, visited, path)
    return cycle if cycle
  end

  # Remove from path and mark as visited
  path.pop
  visited.add(job.name)

  nil
end

#validate!true

Validate all job dependencies. Raises DependencyError if any validation fails.

Returns:

  • (true)

    if validation passes

Raises:



24
25
26
27
28
# File 'lib/fractor/workflow/job_dependency_validator.rb', line 24

def validate!
  check_missing_dependencies
  check_circular_dependencies
  true
end