Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 6, 2025

Description

Introduces OptionsLike marker module and BaseOptions abstract class as foundation for the Options architecture refactor. Enables coexistence of legacy Struct-based Options and new explicit OOP classes while maintaining full backward compatibility.

Key Components:

  • Faraday::OptionsLike - Marker module for duck-typed interop and Utils.deep_merge! integration
  • Faraday::BaseOptions - Abstract base with .from, #initialize, #update, #merge, #merge!, #deep_dup, #to_hash, #inspect, and nested coercion via MEMBERS/COERCIONS constants
  • Utils.deep_merge! - Extended to handle OptionsLike objects (converts to hash, merges, converts back via .from)
  • Utils.deep_dup - New utility for deep duplication of hashes, arrays, and OptionsLike objects

Usage Pattern:

class ProxyOptions < Faraday::BaseOptions
  MEMBERS = [:uri, :user, :password].freeze
  COERCIONS = { uri: URI }.freeze
  
  attr_accessor :uri, :user, :password
end

opts = ProxyOptions.new(uri: "http://proxy.com", user: "admin")
opts.merge!(user: "user2")  # Deep merges, preserves nested types

Subclasses must define MEMBERS (attribute names) and COERCIONS (nested type conversions). Accepts both positional hash and keyword arguments. Handles nil coercion correctly.

Todos

  • Tests (34 new tests, all 657 pass)
  • Documentation (YARD comments on all public methods)
  • Code review
  • Security scan

Additional Notes

Two acceptable RuboCop warnings: Metrics/ModuleLength (Utils module 101/100 lines) and Lint/ConstantDefinitionInBlock (test helper classes).

This unblocks subsequent issues for migrating ProxyOptions, RequestOptions, SSLOptions, ConnectionOptions, and Env to BaseOptions.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • coveralls.io
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec spec/faraday/base_options_spec.rb --format documentation u/13/liblto_plug-I _64-linux-gnu/13/usr/include/ruby-3.2.0/ruby/backward aOHJG5.res (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec spec/faraday/base_options_spec.rb --format documentation (dns block)
  • example.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • example0.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • example1.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • example2.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • google.co.uk
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • prefixedexample.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)
  • subdomain.example.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format progress conf�� in.so /lto-wrapper prism-1.6.0/incl/tmp/cc40zo1T.s user.email prism-1.6.0/ext (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/faraday/faraday/vendor/bundle/ruby/3.2.0/bin/rspec --format documentation conf�� by-3.2.0 user.email prism-1.6.0/incl--gdwarf-5 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Foundation: Create OptionsLike module and BaseOptions class</issue_title>
<issue_description># Foundation: Create OptionsLike and BaseOptions

Phase: 1 - Foundation
Release Target: v2.x.0
Tracking Issue: #1647
RFC: docs/options-detach-plan.md

Overview

Create the foundational components for the new Options architecture:

  • Faraday::OptionsLike - Marker module for duck-typed interop
  • Faraday::BaseOptions - Abstract superclass with shared behavior

This establishes the base architecture that all Options subclasses will use.

Implementation Details

1. Create OptionsLike Module

File: lib/faraday/options_like.rb

# frozen_string_literal: true

module Faraday
  # Marker module for Options-like objects.
  # Enables duck-typed interop and integration with Utils.deep_merge!
  module OptionsLike
  end
end

Purpose: Allows both legacy Options and new BaseOptions classes to be treated uniformly in duck-typed contexts.

2. Create BaseOptions Class

File: lib/faraday/base_options.rb

# frozen_string_literal: true

module Faraday
  # Abstract base class for Options-like classes.
  # Provides common functionality for nested coercion, deep merging, and duplication.
  class BaseOptions
    include OptionsLike

    # Subclasses must define:
    # - MEMBERS: Array of attribute names (symbols)
    # - COERCIONS: Hash mapping attribute names to coercion classes

    class << self
      # Create new instance from hash or existing instance
      def from(value)
        return value if value.is_a?(self)
        return new if value.nil?
        new(value)
      end
    end

    def initialize(options = {})
      options = options.to_hash if options.respond_to?(:to_hash)
      self.class::MEMBERS.each do |key|
        value = options[key] || options[key.to_s]
        value = coerce(key, value)
        instance_variable_set(:"@#{key}", value)
      end
    end

    # Update this instance with values from another hash/instance
    def update(obj)
      obj = obj.to_hash if obj.respond_to?(:to_hash)
      obj.each do |key, value|
        key = key.to_sym
        if self.class::MEMBERS.include?(key)
          value = coerce(key, value)
          instance_variable_set(:"@#{key}", value)
        end
      end
      self
    end

    # Non-destructive merge
    def merge(obj)
      deep_dup.merge!(obj)
    end

    # Destructive merge (uses Utils.deep_merge!)
    def merge!(obj)
      obj = obj.to_hash if obj.respond_to?(:to_hash)
      Utils.deep_merge!(to_hash, obj)
      update(to_hash)
    end

    # Deep duplication
    def deep_dup
      self.class.new(
        self.class::MEMBERS.each_with_object({}) do |key, hash|
          value = instance_variable_get(:"@#{key}")
          hash[key] = Utils.deep_dup(value)
        end
      )
    end

    # Convert to hash
    def to_hash
      self.class::MEMBERS.each_with_object({}) do |key, hash|
        hash[key] = instance_variable_get(:"@#{key}")
      end
    end

    # Inspect
    def inspect
      "#<#{self.class} #{to_hash.inspect}>"
    end

    private

    def coerce(key, value)
      coercion = self.class::COERCIONS[key]
      return value unless coercion
      return value if value.is_a?(coercion)
      coercion.from(value)
    end
  end
end

3. Update Autoload

File: lib/faraday.rb

Add to autoloads section:

autoload :OptionsLike, 'faraday/options_like'
autoload :BaseOptions, 'faraday/base_options'

Tasks

  • Create lib/faraday/options_like.rb
  • Create lib/faraday/base_options.rb with full implementation
  • Add autoloads to lib/faraday.rb
  • Create spec/faraday/base_options_spec.rb with comprehensive tests:
    • Test .from class method
    • Test initialization from hash
    • Test initialization from instance
    • Test update method
    • Test merge (non-destructive)
    • Test merge! (destructive)
    • Test deep_dup
    • Test to_hash
    • Test inspect
    • Test nested coercion behavior
  • Run full test suite to ensure no regressions
  • Integration tests pass (Add comprehensive integration tests using faraday-live approach #1648)

Acceptance Criteria

  • OptionsLike module exists and can be included
  • BaseOptions provides all shared functionality
  • All tests pass
  • No breaking changes to existing Options classes
  • Documentation includes YARD comments

Dependencies

Files to Create

  • lib/faraday/options_like.rb
  • lib/faraday/base_options.rb
  • spec/faraday/base_options_spec.rb

Files to Modify

  • lib/faraday.rb (add autoloads)

Backward Compatibility

No breaking changes - this only adds new classes, doesn't modify existing ones...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Create OptionsLike module and BaseOptions class Foundation: Create OptionsLike module and BaseOptions class Dec 6, 2025
Copilot AI requested a review from iMacTia December 6, 2025 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Foundation: Create OptionsLike module and BaseOptions class

2 participants