spec_forge 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -3
  3. data/CHANGELOG.md +217 -2
  4. data/README.md +162 -25
  5. data/flake.lock +3 -3
  6. data/flake.nix +11 -5
  7. data/lib/spec_forge/attribute/chainable.rb +208 -20
  8. data/lib/spec_forge/attribute/factory.rb +92 -15
  9. data/lib/spec_forge/attribute/faker.rb +62 -13
  10. data/lib/spec_forge/attribute/global.rb +96 -0
  11. data/lib/spec_forge/attribute/literal.rb +15 -2
  12. data/lib/spec_forge/attribute/matcher.rb +186 -11
  13. data/lib/spec_forge/attribute/parameterized.rb +45 -12
  14. data/lib/spec_forge/attribute/regex.rb +55 -5
  15. data/lib/spec_forge/attribute/resolvable.rb +48 -5
  16. data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
  17. data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
  18. data/lib/spec_forge/attribute/store.rb +65 -0
  19. data/lib/spec_forge/attribute/transform.rb +33 -5
  20. data/lib/spec_forge/attribute/variable.rb +37 -6
  21. data/lib/spec_forge/attribute.rb +166 -66
  22. data/lib/spec_forge/backtrace_formatter.rb +26 -3
  23. data/lib/spec_forge/callbacks.rb +88 -0
  24. data/lib/spec_forge/cli/actions.rb +27 -0
  25. data/lib/spec_forge/cli/command.rb +78 -24
  26. data/lib/spec_forge/cli/docs/generate.rb +72 -0
  27. data/lib/spec_forge/cli/docs.rb +92 -0
  28. data/lib/spec_forge/cli/init.rb +51 -9
  29. data/lib/spec_forge/cli/new.rb +67 -6
  30. data/lib/spec_forge/cli/run.rb +32 -4
  31. data/lib/spec_forge/cli/serve.rb +155 -0
  32. data/lib/spec_forge/cli.rb +26 -7
  33. data/lib/spec_forge/configuration.rb +96 -24
  34. data/lib/spec_forge/context/callbacks.rb +91 -0
  35. data/lib/spec_forge/context/global.rb +72 -0
  36. data/lib/spec_forge/context/store.rb +131 -0
  37. data/lib/spec_forge/context/variables.rb +91 -0
  38. data/lib/spec_forge/context.rb +36 -0
  39. data/lib/spec_forge/core_ext/array.rb +27 -0
  40. data/lib/spec_forge/core_ext/rspec.rb +22 -4
  41. data/lib/spec_forge/documentation/builder.rb +383 -0
  42. data/lib/spec_forge/documentation/document/operation.rb +47 -0
  43. data/lib/spec_forge/documentation/document/parameter.rb +22 -0
  44. data/lib/spec_forge/documentation/document/request_body.rb +24 -0
  45. data/lib/spec_forge/documentation/document/response.rb +39 -0
  46. data/lib/spec_forge/documentation/document/response_body.rb +27 -0
  47. data/lib/spec_forge/documentation/document.rb +48 -0
  48. data/lib/spec_forge/documentation/generators/base.rb +81 -0
  49. data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
  50. data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
  51. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
  52. data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
  53. data/lib/spec_forge/documentation/generators.rb +17 -0
  54. data/lib/spec_forge/documentation/loader/cache.rb +138 -0
  55. data/lib/spec_forge/documentation/loader.rb +159 -0
  56. data/lib/spec_forge/documentation/openapi/base.rb +33 -0
  57. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
  58. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
  59. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
  60. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
  61. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
  62. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
  63. data/lib/spec_forge/documentation/openapi.rb +23 -0
  64. data/lib/spec_forge/documentation.rb +27 -0
  65. data/lib/spec_forge/error.rb +284 -113
  66. data/lib/spec_forge/factory.rb +35 -16
  67. data/lib/spec_forge/filter.rb +86 -0
  68. data/lib/spec_forge/forge.rb +171 -0
  69. data/lib/spec_forge/http/backend.rb +101 -29
  70. data/lib/spec_forge/http/client.rb +23 -13
  71. data/lib/spec_forge/http/request.rb +85 -62
  72. data/lib/spec_forge/http/verb.rb +79 -0
  73. data/lib/spec_forge/http.rb +105 -0
  74. data/lib/spec_forge/loader.rb +244 -0
  75. data/lib/spec_forge/matchers.rb +130 -0
  76. data/lib/spec_forge/normalizer/default.rb +51 -0
  77. data/lib/spec_forge/normalizer/definition.rb +248 -0
  78. data/lib/spec_forge/normalizer/validators.rb +99 -0
  79. data/lib/spec_forge/normalizer.rb +486 -115
  80. data/lib/spec_forge/normalizers/_shared.yml +74 -0
  81. data/lib/spec_forge/normalizers/configuration.yml +23 -0
  82. data/lib/spec_forge/normalizers/constraint.yml +8 -0
  83. data/lib/spec_forge/normalizers/expectation.yml +47 -0
  84. data/lib/spec_forge/normalizers/factory.yml +12 -0
  85. data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
  86. data/lib/spec_forge/normalizers/global_context.yml +28 -0
  87. data/lib/spec_forge/normalizers/spec.yml +50 -0
  88. data/lib/spec_forge/runner/adapter.rb +183 -0
  89. data/lib/spec_forge/runner/callbacks.rb +246 -0
  90. data/lib/spec_forge/runner/debug_proxy.rb +213 -0
  91. data/lib/spec_forge/runner/listener.rb +54 -0
  92. data/lib/spec_forge/runner/metadata.rb +58 -0
  93. data/lib/spec_forge/runner/state.rb +98 -0
  94. data/lib/spec_forge/runner.rb +50 -125
  95. data/lib/spec_forge/spec/expectation/constraint.rb +100 -21
  96. data/lib/spec_forge/spec/expectation.rb +47 -51
  97. data/lib/spec_forge/spec.rb +50 -108
  98. data/lib/spec_forge/type.rb +36 -4
  99. data/lib/spec_forge/version.rb +4 -1
  100. data/lib/spec_forge.rb +168 -76
  101. data/lib/templates/openapi.yml.tt +22 -0
  102. data/lib/templates/redoc.html.tt +28 -0
  103. data/lib/templates/swagger.html.tt +59 -0
  104. metadata +109 -16
  105. data/lib/spec_forge/normalizer/configuration.rb +0 -77
  106. data/lib/spec_forge/normalizer/constraint.rb +0 -47
  107. data/lib/spec_forge/normalizer/expectation.rb +0 -86
  108. data/lib/spec_forge/normalizer/factory.rb +0 -65
  109. data/lib/spec_forge/normalizer/factory_reference.rb +0 -71
  110. data/lib/spec_forge/normalizer/spec.rb +0 -74
  111. data/spec_forge/factories/user.yml +0 -4
  112. data/spec_forge/forge_helper.rb +0 -48
  113. data/spec_forge/specs/users.yml +0 -65
  114. /data/lib/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
  115. /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
  116. /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
@@ -4,44 +4,123 @@ module SpecForge
4
4
  class Spec
5
5
  class Expectation
6
6
  #
7
- # Represents the "expect" hash
7
+ # Represents the expected response constraints for an expectation
8
8
  #
9
- class Constraint < Data.define(:status, :json) # :xml, :html
9
+ # A Constraint defines what the API response should look like,
10
+ # including status code and body content with support for matchers.
11
+ #
12
+ # @example In code
13
+ # constraint = Constraint.new(
14
+ # status: 200,
15
+ # headers: {response_header: "kind_of.string"},
16
+ # json: {name: {"matcher.eq" => "John"}}
17
+ # )
18
+ #
19
+ class Constraint < Data.define(:status, :headers, :json) # :xml, :html
10
20
  #
11
- # Creates a new Constraint
21
+ # Creates a new constraint
12
22
  #
13
- # @param status [Integer] The expected HTTP status code
23
+ # @param status [Integer, String] The expected HTTP status code, or reference to one
24
+ # @param headers [Hash] The expected headers with matchers
14
25
  # @param json [Hash, Array] The expected JSON with matchers
15
26
  #
16
- def initialize(status:, json:)
17
- super(status:, json: convert_to_matchers(json))
27
+ # @return [Constraint] A new constraint instance
28
+ #
29
+ def initialize(status:, headers: {}, json: {})
30
+ super(
31
+ status: Attribute.from(status),
32
+ headers: Attribute.from(headers),
33
+ json: Attribute.from(json)
34
+ )
18
35
  end
19
36
 
37
+ #
38
+ # Converts the constraint to a hash with resolved values
39
+ #
40
+ # @return [Hash] Hash representation with resolved values
41
+ #
20
42
  def to_h
21
43
  super.transform_values(&:resolve)
22
44
  end
23
45
 
24
- private
46
+ #
47
+ # Converts constraints to RSpec matchers for validation
48
+ #
49
+ # Transforms the defined constraints (status and JSON expectations) into
50
+ # appropriate RSpec matchers that can be used in test expectations.
51
+ # This method resolves all values and applies the appropriate matcher
52
+ # conversions to create a complete expectation structure.
53
+ #
54
+ # @return [Hash] A hash containing resolved matchers
55
+ #
56
+ # @example
57
+ # constraint = Constraint.new(status: 200, json: {name: "John"})
58
+ # matchers = constraint.as_matchers
59
+ # # => {status: eq(200), json: include("name" => eq("John"))}
60
+ #
61
+ def as_matchers
62
+ {
63
+ status: status.resolve_as_matcher,
64
+ json: resolve_json_matcher,
65
+ headers: resolve_hash_matcher(headers)
66
+ }
67
+ end
68
+
69
+ #
70
+ # Generates a human-readable description of what this constraint expects in the response
71
+ #
72
+ # Creates a description string for RSpec examples that clearly explains the expected
73
+ # status code and JSON structure. This makes test output more informative and helps
74
+ # developers understand what's being tested at a glance.
75
+ #
76
+ # @return [String] A human-readable description of the constraint expectations
77
+ #
78
+ # @example Status code with JSON object
79
+ # constraint.description
80
+ # # => "is expected to respond with \"200 OK\" and a JSON object that contains keys: \"id\", \"name\""
81
+ #
82
+ # @example Status code with JSON array
83
+ # constraint.description
84
+ # # => "is expected to respond with \"201 Created\" and a JSON array that contains 3 items"
85
+ #
86
+ def description
87
+ description = "is expected to respond with"
88
+
89
+ description += if status.is_a?(Attribute::Literal)
90
+ " #{HTTP.status_code_to_description(status.input).in_quotes}"
91
+ else
92
+ " the expected status code"
93
+ end
94
+
95
+ size = json.size
25
96
 
26
- def convert_to_matchers(value)
27
- # This makes it easier to check if json was provided
28
- return Attribute.from(nil) if value.blank?
97
+ if Type.array?(json)
98
+ description +=
99
+ " and a JSON array that contains #{size} #{"item".pluralize(size)}"
100
+ elsif Type.hash?(json) && size > 0
101
+ keys = json.keys.join_map(", ", &:in_quotes)
29
102
 
30
- case value
103
+ description +=
104
+ " and a JSON object that contains #{"key".pluralize(size)}: #{keys}"
105
+ end
106
+
107
+ description
108
+ end
109
+
110
+ private
111
+
112
+ def resolve_json_matcher
113
+ case json
31
114
  when HashLike
32
- value = value.transform_values { |i| convert_to_matchers(i) }
33
- Attribute.from("matcher.include" => value)
34
- when ArrayLike
35
- value = value.map { |i| convert_to_matchers(i) }
36
- Attribute.from("matcher.contain_exactly" => value)
37
- when Attribute::Regex
38
- Attribute.from("matcher.match" => value)
39
- when Attribute::Literal
40
- Attribute.from("matcher.eq" => value)
115
+ resolve_hash_matcher(json)
41
116
  else
42
- value
117
+ json.resolve_as_matcher
43
118
  end
44
119
  end
120
+
121
+ def resolve_hash_matcher(hash)
122
+ hash.transform_values(&:resolve_as_matcher).stringify_keys
123
+ end
45
124
  end
46
125
  end
47
126
  end
@@ -1,72 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "expectation/constraint"
4
-
5
3
  module SpecForge
6
4
  class Spec
7
- class Expectation
5
+ #
6
+ # Represents a single test expectation within a spec
7
+ #
8
+ # An Expectation defines what should be tested for a specific API request,
9
+ # including the expected status code and response structure.
10
+ #
11
+ # @example YAML representation
12
+ # - name: "Get user successfully"
13
+ # expect:
14
+ # status: 200
15
+ # json:
16
+ # name: kind_of.string
17
+ #
18
+ class Expectation < Data.define(
19
+ :id, :name, :line_number,
20
+ :debug, :store_as, :documentation, :constraints
21
+ )
22
+ #
23
+ # @return [Boolean] True if debugging is enabled
24
+ #
8
25
  attr_predicate :debug
9
26
 
10
- attr_reader :name, :variables, :constraints, :http_client
27
+ #
28
+ # @return [Boolean] True if store_as is set
29
+ #
30
+ attr_predicate :store_as
11
31
 
12
32
  #
13
- # Creates a new Expectation
33
+ # Creates a new expectation with constraints
14
34
  #
15
- # @param input [Hash] A hash containing the various attributes to control the expectation
16
- # @param name [String] The name of the expectation
35
+ # @param id [String] Unique identifier
36
+ # @param name [String] Human-readable name
37
+ # @param line_number [Integer] Line number in source
38
+ # @param debug [Boolean] Whether to enable debugging
39
+ # @param store_as [String] Unique Context::Store identifier
40
+ # @param documentation [Boolean] Whether to include in documentation generation
41
+ # @param expect [Hash] Expected constraints
17
42
  #
18
- def initialize(input, global_options: {})
19
- # This allows defining spec level attributes that can be overwritten by the expectation
20
- input = Attribute.from(Configuration.overlay_options(global_options, input))
21
-
22
- load_debug(input)
23
- load_variables(input)
24
-
25
- # Must be after load_variables
26
- load_constraints(input)
27
-
28
- @http_client = HTTP::Client.new(
29
- variables:, **input.except(:name, :variables, :expect, :debug)
30
- )
43
+ # @return [Expectation] A new expectation instance
44
+ #
45
+ def initialize(id:, name:, line_number:, debug:, store_as:, expect:, documentation:)
46
+ constraints = Constraint.new(**expect)
31
47
 
32
- # Must be after http_client
33
- load_name(input)
48
+ super(id:, name:, line_number:, debug:, store_as:, documentation:, constraints:)
34
49
  end
35
50
 
51
+ #
52
+ # Converts the expectation to a hash representation
53
+ #
54
+ # @return [Hash] Hash representation
55
+ #
36
56
  def to_h
37
57
  {
38
58
  name:,
39
- debug: debug?,
40
- variables: variables.resolve,
41
- request: http_client.request.to_h,
42
- constraints: constraints.to_h
59
+ line_number:,
60
+ debug:,
61
+ expect: constraints.to_h
43
62
  }
44
63
  end
45
-
46
- private
47
-
48
- def load_name(input)
49
- # GET /users
50
- @name = "#{http_client.request.http_verb.upcase} #{http_client.request.url}"
51
-
52
- # GET /users - Returns a 404
53
- if (name = input[:name].resolve.presence)
54
- @name += " - #{name}"
55
- end
56
- end
57
-
58
- def load_variables(input)
59
- @variables = Attribute.bind_variables(input[:variables], input[:variables])
60
- end
61
-
62
- def load_debug(input)
63
- @debug = input[:debug].resolve
64
- end
65
-
66
- def load_constraints(input)
67
- constraints = Attribute.bind_variables(input[:expect], variables)
68
- @constraints = Constraint.new(**constraints)
69
- end
70
64
  end
71
65
  end
72
66
  end
67
+
68
+ require_relative "expectation/constraint"
@@ -1,126 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "spec/expectation"
4
-
5
3
  module SpecForge
6
- class Spec
7
- #
8
- # Loads and defines specs with the runner. Specs can be filtered using the optional parameters
4
+ #
5
+ # Represents a test specification in SpecForge
6
+ #
7
+ # A Spec contains one or more Expectations and defines the base configuration
8
+ # for those expectations. It maps directly to a test defined in YAML.
9
+ #
10
+ # @example YAML representation
11
+ # get_users:
12
+ # path: /users
13
+ # expectations:
14
+ # - expect:
15
+ # status: 200
16
+ #
17
+ class Spec < Data.define(
18
+ :id, :name, :file_path, :file_name, :line_number,
19
+ :debug, :documentation, :expectations
20
+ )
9
21
  #
10
- # @param file_name [String, nil] The name of the file without the extension.
11
- # @param spec_name [String, nil] The name of the spec in a yaml file
12
- # @param expectation_name [String, nil] The name of the expectation for a spec.
22
+ # @return [Boolean] True if debugging is enabled
13
23
  #
14
- # @return [Array<Spec>]
15
- #
16
- def self.load_and_define(file_name: nil, spec_name: nil, expectation_name: nil)
17
- specs = load_from_files
18
-
19
- filter_specs(specs, file_name:, spec_name:, expectation_name:)
20
-
21
- # Announce if we're using a filter
22
- if file_name
23
- filter = {file_name:, spec_name:, expectation_name:}.delete_if { |k, v| v.blank? }
24
- filter.stringify_keys!
25
- puts "Using filter: #{filter}"
26
- end
27
-
28
- specs.each(&:define)
29
- end
24
+ attr_predicate :debug
30
25
 
31
26
  #
32
- # Loads any specs defined in the spec files. A single file can contain one or more specs
27
+ # Creates a new spec instance
33
28
  #
34
- # @return [Array<Spec>] An array of specs that were loaded.
29
+ # @param id [String] Unique identifier
30
+ # @param name [String] Human-readable name
31
+ # @param file_path [String] Absolute path to source file
32
+ # @param file_name [String] Base name of file
33
+ # @param debug [Boolean] Whether to enable debugging
34
+ # @param line_number [Integer] Line number in source
35
+ # @param documentation [Boolean] Whether to include in documentation generation
36
+ # @param expectations [Array<Hash>] Expectation configurations
35
37
  #
36
- def self.load_from_files
37
- path = SpecForge.forge.join("specs")
38
- specs = []
39
-
40
- Dir[path.join("**/*.yml")].each do |file_path|
41
- content = File.read(file_path)
42
- hash = YAML.load(content).deep_symbolize_keys
43
-
44
- hash.each do |spec_name, spec_hash|
45
- line_number = content.lines.index { |line| line.start_with?("#{spec_name}:") }
46
-
47
- spec_hash[:name] = spec_name.to_s
48
- spec_hash[:file_path] = file_path
49
- spec_hash[:file_name] = file_path.delete_prefix("#{path}/").delete_suffix(".yml")
50
- spec_hash[:line_number] = line_number ? line_number + 1 : -1
51
-
52
- specs << new(**spec_hash)
53
- end
54
- end
55
-
56
- specs
57
- end
58
-
59
- # @private
60
- def self.filter_specs(specs, file_name: nil, spec_name: nil, expectation_name: nil)
61
- # Guard against invalid partial filters
62
- if expectation_name && spec_name.blank?
63
- raise ArgumentError, "The spec's name is required when filtering by an expectation's name"
64
- end
65
-
66
- if spec_name && file_name.blank?
67
- raise ArgumentError, "The spec's filename is required when filtering by a spec's name"
68
- end
69
-
70
- specs.select! { |spec| spec.file_name == file_name } if file_name
71
- specs.select! { |spec| spec.name == spec_name } if spec_name
72
-
73
- if expectation_name
74
- specs.each do |spec|
75
- spec.expectations.select! { |expectation| expectation.name == expectation_name }
76
- end
77
- end
38
+ # @return [Spec] A new spec instance
39
+ #
40
+ def initialize(
41
+ id:, name:, file_path:, file_name:, line_number:,
42
+ debug:, documentation:, expectations:
43
+ )
44
+ expectations = expectations.map { |e| Expectation.new(**e) }
78
45
 
79
- specs
46
+ super
80
47
  end
81
48
 
82
- ############################################################################
83
-
84
- attr_predicate :debug
85
-
86
- attr_reader :name, :file_path, :file_name, :line_number, :expectations
87
-
88
49
  #
89
- # Creates a Spec based on the input
50
+ # Converts the spec to a hash representation
90
51
  #
91
- # @param name [String] The identifier for this spec
92
- # @param file_path [String] The path where this spec is defined
93
- # @param **input [Hash] Any attributes related to the spec, including expectations
94
- # See Normalizer::Spec
52
+ # @return [Hash] Hash representation
95
53
  #
96
- def initialize(name:, file_path:, file_name:, line_number:, **input)
97
- @name = name
98
- @file_path = file_path
99
- @file_name = file_name
100
- @line_number = line_number
101
-
102
- input = Normalizer.normalize_spec!(input)
103
-
104
- # Don't pass this down to the expectations
105
- @debug = input.delete(:debug) || false
106
-
107
- global_options = normalize_global_options(input)
108
-
109
- @expectations =
110
- input[:expectations].map.with_index do |expectation_input, index|
111
- Expectation.new(expectation_input, global_options:)
112
- end
113
- end
114
-
115
- def define
116
- Runner.define_spec(self)
117
- end
118
-
119
- private
120
-
121
- def normalize_global_options(input)
122
- config = SpecForge.configuration.to_h.slice(:base_url, :headers, :query)
123
- Configuration.overlay_options(config, input.except(:expectations))
54
+ def to_h
55
+ {
56
+ name:,
57
+ file_path:,
58
+ file_name:,
59
+ debug:,
60
+ line_number:,
61
+ documentation:,
62
+ expectations: expectations.map(&:to_h)
63
+ }
124
64
  end
125
65
  end
126
66
  end
67
+
68
+ require_relative "spec/expectation"
@@ -1,24 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecForge
4
+ #
5
+ # Provides helper methods for checking types
6
+ # Useful for working with both regular objects and Attribute delegators
7
+ #
4
8
  module Type
5
9
  #
6
- # Checks if the object is a Hash, or a ResolvableHash (delegator)
10
+ # Checks if the object is a Hash or a ResolvableHash delegator
7
11
  #
8
12
  # @param object [Object] The object to check
9
13
  #
10
- # @return [Boolean]
14
+ # @return [Boolean] True if the object is a hash-like structure
11
15
  #
12
16
  def self.hash?(object)
13
17
  object.is_a?(Hash) || object.is_a?(Attribute::ResolvableHash)
14
18
  end
15
19
 
16
20
  #
17
- # Checks if the object is an Array, or a ResolvableArray (delegator)
21
+ # Checks if the object is an Array or a ResolvableArray delegator
18
22
  #
19
23
  # @param object [Object] The object to check
20
24
  #
21
- # @return [Boolean]
25
+ # @return [Boolean] True if the object is an array-like structure
22
26
  #
23
27
  def self.array?(object)
24
28
  object.is_a?(Array) || object.is_a?(Attribute::ResolvableArray)
@@ -28,8 +32,22 @@ end
28
32
 
29
33
  #
30
34
  # Represents Hash/ResolvableHash in a form that can be used in a case statement
35
+ # Allows for type switching on hash-like objects
36
+ #
37
+ # @example
38
+ # case value
39
+ # when HashLike
40
+ # # Handle hash-like objects
41
+ # end
31
42
  #
32
43
  class HashLike
44
+ #
45
+ # Provides custom type matching for use in case statements
46
+ #
47
+ # @param object [Object] The object to check against the type
48
+ #
49
+ # @return [Boolean] Whether the object matches the type
50
+ #
33
51
  def self.===(object)
34
52
  SpecForge::Type.hash?(object)
35
53
  end
@@ -37,8 +55,22 @@ end
37
55
 
38
56
  #
39
57
  # Represents Array/ResolvableArray in a form that can be used in a case statement
58
+ # Allows for type switching on array-like objects
59
+ #
60
+ # @example
61
+ # case value
62
+ # when ArrayLike
63
+ # # Handle array-like objects
64
+ # end
40
65
  #
41
66
  class ArrayLike
67
+ #
68
+ # Provides custom type matching for use in case statements
69
+ #
70
+ # @param object [Object] The object to check against the type
71
+ #
72
+ # @return [Boolean] Whether the object matches the type
73
+ #
42
74
  def self.===(object)
43
75
  SpecForge::Type.array?(object)
44
76
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecForge
4
- VERSION = "0.5.0"
4
+ #
5
+ # Current version of SpecForge
6
+ #
7
+ VERSION = "0.7.0"
5
8
  end