Module: ClientApiBuilder::Router

Included in:
NestedRouter
Defined in:
lib/client_api_builder/router.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



8
9
10
11
12
13
14
15
# File 'lib/client_api_builder/router.rb', line 8

def self.included(base)
  base.extend InheritanceHelper::Methods
  base.extend ClassMethods
  base.include ::ClientApiBuilder::Section
  base.include ::ClientApiBuilder::NetHTTP::Request
  base.include(::ClientApiBuilder::ActiveSupportNotifications) if defined?(ActiveSupport)
  base.send(:attr_reader, :response, :request_options, :total_request_time, :request_attempts)
end

Instance Method Details

#base_urlObject



437
438
439
# File 'lib/client_api_builder/router.rb', line 437

def base_url
  self.class.base_url
end

#build_body(body, options) ⇒ Object



490
491
492
493
494
495
496
497
# File 'lib/client_api_builder/router.rb', line 490

def build_body(body, options)
  body = options[:body] if options.key?(:body)

  return nil unless body
  return body if body.is_a?(String)

  self.class.build_body(self, body)
end

#build_connection_options(options) ⇒ Object



461
462
463
464
465
466
467
# File 'lib/client_api_builder/router.rb', line 461

def build_connection_options(options)
  if options[:connection_options]
    self.class.default_connection_options.merge(options[:connection_options])
  else
    self.class.default_connection_options
  end
end

#build_headers(options) ⇒ Object



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/client_api_builder/router.rb', line 441

def build_headers(options)
  headers = {}

  add_header_proc = proc do |name, value|
    headers[name] =
      if value.is_a?(Proc)
        root_router.instance_eval(&value)
      elsif value.is_a?(Symbol)
        root_router.send(value)
      else
        value
      end
  end

  self.class.default_headers.each(&add_header_proc)
  options[:headers]&.each(&add_header_proc)

  headers
end

#build_query(query, options) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/client_api_builder/router.rb', line 469

def build_query(query, options)
  query_params = {}

  add_query_param_proc = proc do |name, value|
    query_params[name] =
      if value.is_a?(Proc)
        root_router.instance_eval(&value)
      elsif value.is_a?(Symbol)
        root_router.send(value)
      else
        value
      end
  end

  self.class.default_query_params.each(&add_query_param_proc)
  query&.each(&add_query_param_proc)
  options[:query]&.each(&add_query_param_proc)

  query_params.empty? ? nil : self.class.build_query(self, query_params)
end

#build_uri(path, query, options) ⇒ Object



499
500
501
502
503
504
505
506
507
508
509
# File 'lib/client_api_builder/router.rb', line 499

def build_uri(path, query, options)
  # Properly join base_url and path to handle missing/extra slashes
  base = base_url.to_s
  base = base.chomp('/') if base.end_with?('/')
  path = path.to_s
  path = "/#{path}" unless path.start_with?('/')

  uri = URI(base + path)
  uri.query = build_query(query, options)
  uri
end

#escape_path(path) ⇒ Object



552
553
554
# File 'lib/client_api_builder/router.rb', line 552

def escape_path(path)
  path
end

#expected_response_code!(response, expected_response_codes, _options) ⇒ Object



511
512
513
514
515
516
# File 'lib/client_api_builder/router.rb', line 511

def expected_response_code!(response, expected_response_codes, _options)
  return if expected_response_codes.empty? && response.is_a?(Net::HTTPSuccess)
  return if expected_response_codes.include?(response.code)

  raise(::ClientApiBuilder::UnexpectedResponse.new("unexpected response code #{response.code}", response))
end

#get_retry_request_max_retries(options) ⇒ Object



584
585
586
# File 'lib/client_api_builder/router.rb', line 584

def get_retry_request_max_retries(options)
  options[:retries] || self.class.default_options[:max_retries] || 1
end

#get_retry_request_sleep_time(_e, options) ⇒ Object



580
581
582
# File 'lib/client_api_builder/router.rb', line 580

def get_retry_request_sleep_time(_e, options)
  options[:sleep] || self.class.default_options[:sleep] || 0.05
end

#handle_response(response, options, &block) ⇒ Object



530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/client_api_builder/router.rb', line 530

def handle_response(response, options, &block)
  data =
    case options[:return]
    when :response
      response
    when :body
      response.body
    else
      parse_response(response, options)
    end

  if block
    instance_exec(data, &block)
  else
    data
  end
end

#instrument_requestObject



556
557
558
559
560
561
# File 'lib/client_api_builder/router.rb', line 556

def instrument_request
  start_time = Time.now
  yield
ensure
  @total_request_time = Time.now - start_time
end

#log_request_exception(exception) ⇒ Object



607
608
609
# File 'lib/client_api_builder/router.rb', line 607

def log_request_exception(exception)
  ::ClientApiBuilder.logger&.error(exception)
end

#parse_response(response, _options) ⇒ Object



518
519
520
521
522
523
524
525
526
527
528
# File 'lib/client_api_builder/router.rb', line 518

def parse_response(response, _options)
  body = response.body
  return nil if body.nil? || body.empty?

  JSON.parse(body)
rescue JSON::ParserError => e
  raise ::ClientApiBuilder::UnexpectedResponse.new(
    "Invalid JSON in response: #{e.message}",
    response
  )
end

#request_log_messageObject



611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/client_api_builder/router.rb', line 611

def request_log_message
  return '' unless request_options

  method = request_options[:method].to_s.upcase
  uri = request_options[:uri]
  return "#{method} [no URI]" unless uri

  response_code = response ? response.code : 'UNKNOWN'
  duration = total_request_time ? (total_request_time * 1000).to_i : 0

  "#{method} #{uri.scheme}://#{uri.host}#{uri.path}[#{response_code}] took #{duration}ms"
end

#request_wrapper(options, &block) ⇒ Object



588
589
590
591
592
# File 'lib/client_api_builder/router.rb', line 588

def request_wrapper(options, &block)
  retry_request(options) do
    instrument_request(&block)
  end
end

#retry_request(options) ⇒ Object



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# File 'lib/client_api_builder/router.rb', line 563

def retry_request(options)
  @request_attempts = 0
  max_attempts = get_retry_request_max_retries(options)
  begin
    @request_attempts += 1
    yield
  rescue StandardError => e
    # Use StandardError instead of Exception to allow SystemExit, Interrupt, etc. to propagate
    log_request_exception(e)
    raise(e) if @request_attempts >= max_attempts || !retry_request?(e, options)

    sleep_time = get_retry_request_sleep_time(e, options)
    sleep(sleep_time) if sleep_time&.positive?
    retry
  end
end

#retry_request?(exception, _options) ⇒ Boolean

Determines whether to retry on a given exception. Override this method to customize retry behavior. By default, only retries on network-related errors, not application errors.

Returns:

  • (Boolean)


597
598
599
600
601
602
603
604
605
# File 'lib/client_api_builder/router.rb', line 597

def retry_request?(exception, _options)
  case exception
  when Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET,
       Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError, EOFError
    true
  else
    false
  end
end

#root_routerObject



548
549
550
# File 'lib/client_api_builder/router.rb', line 548

def root_router
  self
end