Class: Tasker::Orchestration::ResponseProcessor

Inherits:
Object
  • Object
show all
Includes:
Concerns::EventPublisher
Defined in:
lib/tasker/orchestration/response_processor.rb

Overview

ResponseProcessor handles API response validation and error detection

This component provides focused responsibility for processing API responses, determining if they indicate errors that require backoff handling, and preparing context for error handling.

Constant Summary collapse

BACKOFF_ERROR_CODES =

HTTP status codes that should trigger backoff behavior (temporary failures)

[429, 503].freeze
PERMANENT_ERROR_CODES =

HTTP status codes that indicate permanent failures (don't retry)

[400, 401, 403, 404, 422].freeze
SUCCESS_CODES =

HTTP status codes that indicate a successful request

(200..226)

Instance Method Summary collapse

Methods included from Concerns::EventPublisher

#infer_step_event_type_from_state, #publish_custom_event, #publish_no_viable_steps, #publish_step_backoff, #publish_step_before_handle, #publish_step_cancelled, #publish_step_completed, #publish_step_event_for_context, #publish_step_failed, #publish_step_retry_requested, #publish_step_started, #publish_steps_execution_completed, #publish_steps_execution_started, #publish_task_completed, #publish_task_enqueue, #publish_task_failed, #publish_task_finalization_completed, #publish_task_finalization_started, #publish_task_pending_transition, #publish_task_reenqueue_delayed, #publish_task_reenqueue_failed, #publish_task_reenqueue_requested, #publish_task_reenqueue_started, #publish_task_retry_requested, #publish_task_started, #publish_viable_steps_discovered, #publish_workflow_state_unclear, #publish_workflow_step_completed, #publish_workflow_task_started

Instance Method Details

#process_response(step, response) ⇒ Hash?

Process an API response and determine if it requires error handling

Parameters:

  • step (Tasker::WorkflowStep)

    The current step being processed

  • response (Faraday::Response, Hash)

    The API response to process

Returns:

  • (Hash, nil)

    Context for error handling if response indicates error, nil if successful

Raises:

  • (Faraday::Error)

    If response indicates a backoff-requiring error



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/tasker/orchestration/response_processor.rb', line 31

def process_response(step, response)
  status = extract_status(response)

  # Successful responses don't need special handling
  return nil if SUCCESS_CODES.include?(status)

  # Categorize error type and raise appropriate Tasker error
  if BACKOFF_ERROR_CODES.include?(status)
    # Temporary failures that should be retried with backoff
    context = build_error_context(step, response, status)
    retry_after = extract_retry_after(response)

    Rails.logger.warn(
      'ResponseProcessor: API response requires backoff handling - ' \
      "Status: #{status}, Step: #{step.name} (#{step.workflow_step_id})"
    )

    # Parse retry_after immediately to avoid timing issues with date formats
    parsed_retry_after = if retry_after&.match?(/^\d+$/)
                           retry_after.to_i
                         elsif retry_after
                           # Parse HTTP date format with error handling
                           begin
                             retry_time = Time.zone.parse(retry_after)
                             [(retry_time - Time.zone.now).to_i, 1].max
                           rescue ArgumentError => e
                             Rails.logger.warn("Invalid Retry-After date format: #{retry_after}, error: #{e.message}")
                             nil # Let exponential backoff handle it
                           end
                         end

    raise Tasker::RetryableError.new(
      "API call failed with retryable status #{status}",
      retry_after: parsed_retry_after,
      context: context
    )
  elsif PERMANENT_ERROR_CODES.include?(status)
    # Permanent failures that should not be retried
    body = extract_body(response)
    context = build_error_context(step, response, status)

    Rails.logger.error(
      'ResponseProcessor: API response indicates permanent error - ' \
      "Status: #{status}, Step: #{step.name} (#{step.workflow_step_id})"
    )

    raise Tasker::PermanentError.new(
      "API call failed with permanent status #{status}: #{body}",
      error_code: "HTTP_#{status}",
      context: context
    )
  else
    # Other server errors (5xx) - treat as retryable but without forced backoff
    body = extract_body(response)
    context = build_error_context(step, response, status)

    Rails.logger.error(
      'ResponseProcessor: API response indicates server error - ' \
      "Status: #{status}, Step: #{step.name} (#{step.workflow_step_id})"
    )

    # Create a special RetryableError that doesn't apply backoff
    error = Tasker::RetryableError.new(
      "API call failed with server error #{status}: #{body}",
      context: context
    )

    # Mark this error as not requiring backoff
    error.define_singleton_method(:skip_backoff?) { true }

    raise error
  end
end