Class: SpecForge::Attribute::Matcher

Inherits:
Parameterized show all
Defined in:
lib/spec_forge/attribute/matcher.rb

Overview

Represents an attribute that uses RSpec matchers for response validation

This class allows SpecForge to integrate with RSpec's powerful matchers for flexible response validation. It supports most of the built-in RSpec matchers and most custom matchers, assuming they do not require more Ruby code

Examples:

Basic matchers in YAML

be_true: be.true
include_admin:
  matcher.include:
  - admin

Comparison matchers

count:
  be.greater_than: 5

Type checking

name: kind_of.string
id: kind_of.integer

Defined Under Namespace

Classes: RSpecMatchers

Constant Summary collapse

KEYWORD_REGEX =

Regular expression pattern that matches attribute keywords with this prefix Used for identifying this attribute type during parsing

Returns:

  • (Regexp)
/^matchers?\.|^be\.|^kind_of\./i
MATCHER_METHODS =

Instance of Methods providing access to all RSpec matchers

RSpecMatchers.new.freeze
LITERAL_MAPPINGS =

Mapping of literal string values to their Ruby equivalents Used for be.nil, be.true, and be.false matchers

{
  "nil" => nil,
  "true" => true,
  "false" => false
}.freeze

Instance Attribute Summary collapse

Attributes inherited from Parameterized

#arguments

Instance Method Summary collapse

Methods inherited from Parameterized

from_hash

Constructor Details

#initializeMatcher

Creates a new matcher attribute with the specified matcher and arguments

Raises:

See Also:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/spec_forge/attribute/matcher.rb', line 69

def initialize(...)
  super

  namespace, method = extract_namespace_and_method

  @matcher_method =
    case namespace
    when "be"
      resolve_be_matcher(method)
    when "kind_of"
      resolve_kind_of_matcher(method)
    else
      resolve_base_matcher(method)
    end

  prepare_arguments

  # An argument can be an expanded version of something (such as matcher.include)
  # Move it to where it belongs
  if (keyword = arguments[:keyword]) && !keyword.is_a?(Hash)
    arguments[:positional] << keyword
    arguments[:keyword] = {}
  end
end

Instance Attribute Details

#matcher_methodObject (readonly)

The resolved RSpec matcher method to call



60
61
62
# File 'lib/spec_forge/attribute/matcher.rb', line 60

def matcher_method
  @matcher_method
end

Instance Method Details

#resolve_as_matcherRSpec::Matchers::BuiltIn::BaseMatcher

Ensures proper conversion of nested matcher arguments based on context

This method overrides handles a special case of matchers that take arguments which themselves might need to be converted to matchers. It skips conversion for string arguments that should remain strings (like with include, start_with, and end_with) while correctly handling nested matchers and other argument types.

Examples:

Problem case handled

# In YAML:
matcher.all:
  matcher.include:
  - /@/  # Should become match(/@/) when used with include

Edge case handled

# In YAML:
matcher.include: "." # Should remain a string, not eq(".")

Returns:

  • (RSpec::Matchers::BuiltIn::BaseMatcher)

    The properly configured matcher with all arguments correctly converted based on context



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/spec_forge/attribute/matcher.rb', line 136

def resolve_as_matcher
  # Argument conversion only matters for the base matchers
  if input.start_with?("matcher")
    block = lambda do |argument|
      next argument unless convert_argument?(argument)

      argument.resolve_as_matcher
    end

    arguments[:positional].map!(&block)
    arguments[:keyword].transform_values!(&block)
  end

  super
end

#valueRSpec::Matchers::BuiltIn::BaseMatcher

Returns the result of applying the matcher with the given arguments Creates an RSpec matcher that can be used in expectations

Returns:

  • (RSpec::Matchers::BuiltIn::BaseMatcher)

    The configured matcher



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/spec_forge/attribute/matcher.rb', line 100

def value
  if (positional = arguments[:positional]) && positional.present?
    positional = positional.resolved.each do |value|
      value.deep_stringify_keys! if value.respond_to?(:deep_stringify_keys!)
    end

    matcher_method.call(*positional)
  elsif (keyword = arguments[:keyword]) && keyword.present?
    matcher_method.call(**keyword.resolved.deep_stringify_keys)
  else
    matcher_method.call
  end
end