Module: RubyReactor::Dsl::Reactor::ClassMethods

Includes:
AsyncMacros, TemplateHelpers, ValidationHelpers
Defined in:
lib/ruby_reactor/dsl/reactor.rb

Constant Summary

Constants included from StepSignals

StepSignals::TAG

Instance Method Summary collapse

Methods included from AsyncMacros

#async?, #async_reactor, #async_step, #background, #background_handoff

Methods included from ValidationHelpers

#build_args_validator, #build_inline_validator, #build_macro_validator, #build_scalar_validator, #build_validation_schema, #create_input_validator

Methods included from TemplateHelpers

#Failure, #Halt, #Skipped, #Success, #element, #result, #value

Methods included from StepSignals

#fail!, #halt!, #skip!, #success!

Instance Method Details

#asyncObject

Whole-reactor async true is gone: it named the same idea as background's cut point with a different word, right next to the new async_step/async_reactor macros whose names mean something else entirely. async? (the reader) lives in AsyncMacros, driven off background_handoff.



50
51
52
53
54
55
56
# File 'lib/ruby_reactor/dsl/reactor.rb', line 50

def async(*)
  raise RubyReactor::Error::DeprecatedDslError,
        "`async true` on a reactor has been removed: it named the same idea as `background`'s cut " \
        "point with a different word, and read confusingly next to the `async_step`/`async_reactor` " \
        "step macros. Use `background all: true` instead — identical behavior, including validating " \
        "inputs inside the worker."
end

#call(inputs = {}) ⇒ Object



213
214
215
# File 'lib/ruby_reactor/dsl/reactor.rb', line 213

def call(inputs = {})
  run(inputs)
end

#compose(name, composed_reactor_class = nil, &block) ⇒ Object



127
128
129
130
131
132
133
134
135
# File 'lib/ruby_reactor/dsl/reactor.rb', line 127

def compose(name, composed_reactor_class = nil, &block)
  builder = RubyReactor::Dsl::ComposeBuilder.new(name, composed_reactor_class, self, &block)

  builder.instance_eval(&block) if block_given?

  step_config = builder.build
  steps[name] = step_config
  step_config
end

#input(name, type = nil, transform: nil, description: nil, validate: nil, optional: false, redact: false, **predicates, &block) ⇒ Object

rubocop:disable Metrics/ParameterLists



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ruby_reactor/dsl/reactor.rb', line 71

def input(name, type = nil, transform: nil, description: nil, validate: nil, optional: false, redact: false,
          **predicates, &block)
  # rubocop:enable Metrics/ParameterLists
  inputs[name] = {
    transform: transform,
    description: description,
    optional: optional,
    redact: redact
  }

  validator = build_input_validator_for(name, type, optional, validate, predicates, &block)
  input_validations[name] = validator if validator
end

#input_validationsObject



41
42
43
# File 'lib/ruby_reactor/dsl/reactor.rb', line 41

def input_validations
  @input_validations ||= {}
end

#inputsObject



25
26
27
# File 'lib/ruby_reactor/dsl/reactor.rb', line 25

def inputs
  @inputs ||= {}
end

#interrupt(name, resume: :inline, &block) ⇒ Object

resume: :background — after continue validates and stores the payload, the remaining work is enqueued to a worker instead of running inline in the delivering process (webhook, admin UI).



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ruby_reactor/dsl/reactor.rb', line 150

def interrupt(name, resume: :inline, &block)
  unless i[inline background].include?(resume)
    raise RubyReactor::Error::ValidationError,
          "interrupt :#{name} has invalid `resume: #{resume.inspect}` — " \
          "use `:inline` (default, resume runs in the calling process) or " \
          "`:background` (resume is enqueued to a worker)."
  end

  builder = RubyReactor::Dsl::InterruptBuilder.new(name, self, resume: resume)
  builder.instance_eval(&block) if block_given?

  step_config = builder.build
  steps[name] = step_config
  step_config
end

#map(name, reactor_class = nil, &block) ⇒ Object



137
138
139
140
141
142
143
144
145
# File 'lib/ruby_reactor/dsl/reactor.rb', line 137

def map(name, reactor_class = nil, &block)
  builder = RubyReactor::Dsl::MapBuilder.new(name, reactor_class, self, &block)

  builder.instance_eval(&block) if block_given?

  step_config = builder.build
  steps[name] = step_config
  step_config
end

#middleware(middleware_class, **options) ⇒ Object



174
175
176
177
178
179
180
# File 'lib/ruby_reactor/dsl/reactor.rb', line 174

def middleware(middleware_class, **options)
  middlewares << if options.empty?
                   middleware_class
                 else
                   [middleware_class, options]
                 end
end

#middlewaresObject



37
38
39
# File 'lib/ruby_reactor/dsl/reactor.rb', line 37

def middlewares
  @middlewares ||= []
end

#retry_defaults(**kwargs) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ruby_reactor/dsl/reactor.rb', line 58

def retry_defaults(**kwargs)
  if kwargs.empty?
    @retry_defaults ||= { max_attempts: 1, backoff: :exponential, base_delay: 1 }
  else
    @retry_defaults = {
      max_attempts: kwargs[:max_attempts] || 1,
      backoff: kwargs[:backoff] || :exponential,
      base_delay: kwargs[:base_delay] || 1
    }
  end
end

#return_stepObject



33
34
35
# File 'lib/ruby_reactor/dsl/reactor.rb', line 33

def return_step
  @return_step
end

#returns(step_name = nil) ⇒ Object



166
167
168
169
170
171
172
# File 'lib/ruby_reactor/dsl/reactor.rb', line 166

def returns(step_name = nil)
  if step_name
    reject_async_return_step!(step_name)
    @return_step = step_name
  end
  @return_step
end

#run(inputs = {}) ⇒ Object

Entry point for running the reactor



207
208
209
210
211
# File 'lib/ruby_reactor/dsl/reactor.rb', line 207

def run(inputs = {})
  reactor = new
  result = reactor.run(inputs)
  attach_execution_id!(result, reactor.context.context_id)
end

#step(name, impl = nil, &block) ⇒ Object



117
118
119
120
121
122
123
124
125
# File 'lib/ruby_reactor/dsl/reactor.rb', line 117

def step(name, impl = nil, &block)
  builder = RubyReactor::Dsl::StepBuilder.new(name, impl, self)

  builder.instance_eval(&block) if block_given?

  step_config = builder.build
  steps[name] = step_config
  step_config
end

#stepsObject



29
30
31
# File 'lib/ruby_reactor/dsl/reactor.rb', line 29

def steps
  @steps ||= {}
end

#validate_inputs(inputs_hash) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ruby_reactor/dsl/reactor.rb', line 182

def validate_inputs(inputs_hash)
  errors = {}

  input_validations.each do |input_name, validator|
    # Skip validation if input is optional and not provided
    next if inputs[input_name][:optional] && !inputs_hash.key?(input_name)

    input_data = inputs_hash[input_name]
    # Validate by wrapping the individual input in a hash with its name
    result = validator.call({ input_name => input_data })

    errors.merge!(result.error.field_errors) if result.failure? && result.error.respond_to?(:field_errors)
  end

  if errors.empty?
    RubyReactor.Success(inputs_hash)
  else
    error = RubyReactor::Error::InputValidationError.new(errors)
    # Same shape as executor-built validation failures: expose the
    # structured field errors on the Failure itself.
    RubyReactor.Failure(error, validation_errors: errors, reactor_name: name)
  end
end