pact 1.66.2 → 1.67.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/lib/pact/v2/configuration.rb +23 -0
  4. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +194 -0
  5. data/lib/pact/v2/consumer/http_interaction_builder.rb +162 -0
  6. data/lib/pact/v2/consumer/interaction_contents.rb +62 -0
  7. data/lib/pact/v2/consumer/message_interaction_builder.rb +287 -0
  8. data/lib/pact/v2/consumer/mock_server.rb +100 -0
  9. data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
  10. data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
  11. data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
  12. data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
  13. data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +26 -0
  14. data/lib/pact/v2/consumer/pact_config/plugin_http.rb +26 -0
  15. data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +26 -0
  16. data/lib/pact/v2/consumer/pact_config.rb +30 -0
  17. data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +171 -0
  18. data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +201 -0
  19. data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +180 -0
  20. data/lib/pact/v2/consumer.rb +8 -0
  21. data/lib/pact/v2/generators/base.rb +287 -0
  22. data/lib/pact/v2/generators.rb +49 -0
  23. data/lib/pact/v2/matchers/base.rb +74 -0
  24. data/lib/pact/v2/matchers/v1/equality.rb +19 -0
  25. data/lib/pact/v2/matchers/v2/regex.rb +19 -0
  26. data/lib/pact/v2/matchers/v2/type.rb +17 -0
  27. data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
  28. data/lib/pact/v2/matchers/v3/content_type.rb +32 -0
  29. data/lib/pact/v2/matchers/v3/date.rb +18 -0
  30. data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
  31. data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
  32. data/lib/pact/v2/matchers/v3/each.rb +42 -0
  33. data/lib/pact/v2/matchers/v3/include.rb +17 -0
  34. data/lib/pact/v2/matchers/v3/integer.rb +17 -0
  35. data/lib/pact/v2/matchers/v3/null.rb +16 -0
  36. data/lib/pact/v2/matchers/v3/number.rb +17 -0
  37. data/lib/pact/v2/matchers/v3/semver.rb +23 -0
  38. data/lib/pact/v2/matchers/v3/time.rb +18 -0
  39. data/lib/pact/v2/matchers/v3/values.rb +16 -0
  40. data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
  41. data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
  42. data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
  43. data/lib/pact/v2/matchers/v4/not_empty.rb +24 -0
  44. data/lib/pact/v2/matchers/v4/status_code.rb +17 -0
  45. data/lib/pact/v2/matchers.rb +110 -0
  46. data/lib/pact/v2/native/blocking_verifier.rb +17 -0
  47. data/lib/pact/v2/native/logger.rb +25 -0
  48. data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
  49. data/lib/pact/v2/provider/base_verifier.rb +242 -0
  50. data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
  51. data/lib/pact/v2/provider/gruf_server.rb +75 -0
  52. data/lib/pact/v2/provider/http_server.rb +79 -0
  53. data/lib/pact/v2/provider/http_verifier.rb +43 -0
  54. data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
  55. data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
  56. data/lib/pact/v2/provider/pact_broker_proxy.rb +66 -0
  57. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
  58. data/lib/pact/v2/provider/pact_config/async.rb +29 -0
  59. data/lib/pact/v2/provider/pact_config/base.rb +101 -0
  60. data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
  61. data/lib/pact/v2/provider/pact_config/http.rb +27 -0
  62. data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
  63. data/lib/pact/v2/provider/pact_config.rb +26 -0
  64. data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
  65. data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
  66. data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
  67. data/lib/pact/v2/provider.rb +8 -0
  68. data/lib/pact/v2/railtie.rb +13 -0
  69. data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +93 -0
  70. data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
  71. data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
  72. data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  73. data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
  74. data/lib/pact/v2/rspec.rb +17 -0
  75. data/lib/pact/v2/tasks/pact.rake +13 -0
  76. data/lib/pact/v2.rb +71 -0
  77. data/lib/pact/version.rb +1 -1
  78. data/lib/pact.rb +1 -0
  79. data/pact.gemspec +52 -7
  80. metadata +419 -65
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02eee4d7bd2a7a04b184b4814bb8b100266faaba00b7a7e49b7c29a30828c6b4
4
- data.tar.gz: 8304d327027f35c8feb14f95f0f256a9970c2f4a4b589676f6b0192d7a227c25
3
+ metadata.gz: e428577e1d928336891dcf91170bc79aa001209bc8a8c13bc107903843e2357e
4
+ data.tar.gz: 51d3b1d10dc8731eef772a4b08a234f094e1423129c7f3782f3154606ebe5b9f
5
5
  SHA512:
6
- metadata.gz: 7e278f323f6bce1117b0901f8045b596809478b509d71aa7da921efa9e06ede795a2f585d60a80e5ca5c85baf764468fa464935929029d412723f5e127196451
7
- data.tar.gz: 4fb50dd10844143083b1387a4a2883291789332dec8c8b4a62c1f6ce476fe20b8307e9b6400b7d311990ef8a60ec6071752feba4381b6eaa9a2444dfc6347752
6
+ metadata.gz: fb09f250a67d22dfa816616d974f327835a1162e56e5a4cfdcc266730322a671ce4c2e45d87e2edf1793cf74cc9a2e5fcd4103764d772a9c41ba82be46376ca8
7
+ data.tar.gz: 1baec82403e89bcb98014c958819a1304fe19b98683b380a4a1c38ddb0fc3a5a08b69f90e50f2d43a3f5756bb7029a887a449cc22e282d6ab772529a9176c0a0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ <a name="v1.67.0"></a>
2
+ ### v1.67.0 (2025-10-16)
3
+
4
+ #### Features
5
+
6
+ * add support for generate_from_provider_state ([4ff7812](/../../commit/4ff7812))
7
+ * add pact-ruby v2 generators ([4db935b](/../../commit/4db935b))
8
+ * add pact plugin consumer interfaces for http/async/sync messages ([ca19f86](/../../commit/ca19f86))
9
+ * add more matchers ([ccb82b9](/../../commit/ccb82b9))
10
+ * load pact/v2 into namespace ([4ac42ec](/../../commit/4ac42ec))
11
+ * add pact v2 interface ([4595015](/../../commit/4595015))
12
+
13
+ #### Bug Fixes
14
+
15
+ * ensure we always print mismatchs in consumer tests for better dx ([f9cb106](/../../commit/f9cb106))
16
+ * json load diff regression downstream, relax restrictions ([481eab9](/../../commit/481eab9))
17
+
1
18
  <a name="v1.66.2"></a>
2
19
  ### v1.66.2 (2025-10-03)
3
20
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ class Configuration
6
+ attr_reader :before_provider_state_proc, :after_provider_state_proc
7
+
8
+ class GlobalProviderConfigurationError < ::Pact::V2::Error; end
9
+
10
+ def before_provider_state_setup(&block)
11
+ raise GlobalProviderConfigurationError, "no block given" unless block
12
+
13
+ @before_provider_state_proc = block
14
+ end
15
+
16
+ def after_provider_state_teardown(&block)
17
+ raise GlobalProviderConfigurationError, "no block given" unless block
18
+
19
+ @after_provider_state_proc = block
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/sync_message_consumer"
4
+ require "pact/ffi/plugin_consumer"
5
+ require "pact/ffi/logger"
6
+
7
+ module Pact
8
+ module V2
9
+ module Consumer
10
+ class GrpcInteractionBuilder
11
+ CONTENT_TYPE = "application/protobuf"
12
+ GRPC_CONTENT_TYPE = "application/grpc"
13
+ PROTOBUF_PLUGIN_NAME = "protobuf"
14
+ PROTOBUF_PLUGIN_VERSION = "0.6.5"
15
+
16
+ class PluginInitError < Pact::V2::FfiError; end
17
+
18
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
19
+ INIT_PLUGIN_ERRORS = {
20
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
21
+ 2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
22
+ 3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
23
+ }.freeze
24
+
25
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
26
+ CREATE_INTERACTION_ERRORS = {
27
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
28
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
29
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
30
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
31
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
32
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
33
+ }.freeze
34
+
35
+ class CreateInteractionError < Pact::V2::FfiError; end
36
+
37
+ class InteractionMismatchesError < Pact::V2::Error; end
38
+
39
+ class InteractionBuilderError < Pact::V2::Error; end
40
+
41
+ def initialize(pact_config, description: nil)
42
+ @pact_config = pact_config
43
+ @description = description || ""
44
+ @proto_path = nil
45
+ @proto_include_dirs = []
46
+ @service_name = nil
47
+ @method_name = nil
48
+ @request = nil
49
+ @response = nil
50
+ @response_meta = nil
51
+ @provider_state_meta = nil
52
+ end
53
+
54
+ def with_service(proto_path, method, include_dirs = [])
55
+ raise InteractionBuilderError.new("invalid grpc method: cannot be blank") if method.blank?
56
+
57
+ service_name, method_name = method.split("/") || []
58
+ raise InteractionBuilderError.new("invalid grpc method: #{method}, should be like service/SomeMethod") if service_name.blank? || method_name.blank?
59
+
60
+ absolute_path = File.expand_path(proto_path)
61
+ raise InteractionBuilderError.new("proto file #{proto_path} does not exist") unless File.exist?(absolute_path)
62
+
63
+ @proto_path = absolute_path
64
+ @service_name = service_name
65
+ @method_name = method_name
66
+ @proto_include_dirs = include_dirs.map { |dir| File.expand_path(dir) }
67
+
68
+ self
69
+ end
70
+
71
+ def with_pact_protobuf_plugin_version(version)
72
+ raise InteractionBuilderError.new("version is required") if version.blank?
73
+
74
+ @proto_plugin_version = version
75
+ self
76
+ end
77
+
78
+ def given(provider_state, metadata = {})
79
+ @provider_state_meta = {provider_state => metadata}
80
+ self
81
+ end
82
+
83
+ def upon_receiving(description)
84
+ @description = description
85
+ self
86
+ end
87
+
88
+ def with_request(req_hash)
89
+ @request = InteractionContents.plugin(req_hash)
90
+ self
91
+ end
92
+
93
+ def will_respond_with(resp_hash)
94
+ @response = InteractionContents.plugin(resp_hash)
95
+ self
96
+ end
97
+
98
+ def will_respond_with_meta(meta_hash)
99
+ @response_meta = InteractionContents.plugin(meta_hash)
100
+ self
101
+ end
102
+
103
+ def interaction_json
104
+ result = {
105
+ "pact:proto": @proto_path,
106
+ "pact:proto-service": "#{@service_name}/#{@method_name}",
107
+ "pact:content-type": CONTENT_TYPE,
108
+ request: @request
109
+ }
110
+
111
+ result["pact:protobuf-config"] = {additionalIncludes: @proto_include_dirs} if @proto_include_dirs.present?
112
+
113
+ result[:response] = @response if @response.is_a?(Hash)
114
+ result[:responseMetadata] = @response_meta if @response_meta.is_a?(Hash)
115
+
116
+ JSON.dump(result)
117
+ end
118
+
119
+ def validate!
120
+ raise InteractionBuilderError.new("uninitialized service params, use #with_service to configure") if @proto_path.blank? || @service_name.blank? || @method_name.blank?
121
+ raise InteractionBuilderError.new("invalid request format, should be a hash") unless @request.is_a?(Hash)
122
+ raise InteractionBuilderError.new("invalid response format, should be a hash") unless @response.is_a?(Hash) || @response_meta.is_a?(Hash)
123
+ end
124
+
125
+ def execute(&block)
126
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
127
+
128
+ validate!
129
+
130
+ pact_handle = init_pact
131
+ init_plugin!(pact_handle)
132
+
133
+ message_pact = PactFfi::SyncMessageConsumer.new_interaction(pact_handle, @description)
134
+ @provider_state_meta&.each_pair do |provider_state, meta|
135
+ if meta.present?
136
+ meta.each_pair { |k, v| PactFfi.given_with_param(message_pact, provider_state, k.to_s, v.to_s) }
137
+ else
138
+ PactFfi.given(message_pact, provider_state)
139
+ end
140
+ end
141
+
142
+ result = PactFfi::PluginConsumer.interaction_contents(message_pact, 0, GRPC_CONTENT_TYPE, interaction_json)
143
+ if CREATE_INTERACTION_ERRORS[result].present?
144
+ error = CREATE_INTERACTION_ERRORS[result]
145
+ raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
146
+ end
147
+
148
+ mock_server = MockServer.create_for_grpc!(pact: pact_handle, host: @pact_config.mock_host, port: @pact_config.mock_port)
149
+
150
+ yield(message_pact, mock_server)
151
+
152
+ ensure
153
+ if mock_server.matched?
154
+ mock_server.write_pacts!(@pact_config.pact_dir)
155
+ else
156
+ msg = mismatches_error_msg(mock_server)
157
+ raise InteractionMismatchesError.new(msg)
158
+ end
159
+ @used = true
160
+ mock_server&.cleanup
161
+ PactFfi::PluginConsumer.cleanup_plugins(pact_handle)
162
+ PactFfi.free_pact_handle(pact_handle)
163
+ end
164
+
165
+ private
166
+
167
+ def mismatches_error_msg(mock_server)
168
+ rspec_example_desc = RSpec.current_example&.description
169
+ return "interaction for #{@service_name}/#{@method_name} has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
170
+
171
+ "#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
172
+ end
173
+
174
+ def init_pact
175
+ handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
176
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
177
+ PactFfi.with_pact_metadata(handle, "pact-ruby-v2", "pact-ffi", PactFfi.version)
178
+
179
+ Pact::V2::Native::Logger.log_to_stdout(@pact_config.log_level)
180
+
181
+ handle
182
+ end
183
+
184
+ def init_plugin!(pact_handle)
185
+ result = PactFfi::PluginConsumer.using_plugin(pact_handle, PROTOBUF_PLUGIN_NAME, @proto_plugin_version || PROTOBUF_PLUGIN_VERSION)
186
+ return result if INIT_PLUGIN_ERRORS[result].blank?
187
+
188
+ error = INIT_PLUGIN_ERRORS[result]
189
+ raise PluginInitError.new("There was an error while trying to initialize plugin #{PROTOBUF_PLUGIN_NAME}/#{@proto_plugin_version || PROTOBUF_PLUGIN_VERSION}", error[:reason], error[:status])
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/sync_message_consumer"
4
+ require "pact/ffi/plugin_consumer"
5
+ require "pact/ffi/logger"
6
+ require "json"
7
+
8
+ module Pact
9
+ module V2
10
+ module Consumer
11
+ class HttpInteractionBuilder
12
+
13
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
14
+ CREATE_INTERACTION_ERRORS = {
15
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
16
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
17
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
18
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
19
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
20
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
21
+ }.freeze
22
+
23
+ class CreateInteractionError < Pact::V2::FfiError; end
24
+
25
+ class InteractionMismatchesError < Pact::V2::Error; end
26
+
27
+ class InteractionBuilderError < Pact::V2::Error; end
28
+
29
+ class << self
30
+ def create_finalizer(pact_handle)
31
+ proc { PactFfi.free_pact_handle(pact_handle) }
32
+ end
33
+ end
34
+
35
+ def initialize(pact_config, description: nil)
36
+ @pact_config = pact_config
37
+ @description = description || ""
38
+
39
+ @pact_handle = pact_config.pact_handle ||= init_pact
40
+ @pact_interaction = PactFfi.new_interaction(pact_handle, @description)
41
+
42
+ ObjectSpace.define_finalizer(self, self.class.create_finalizer(pact_interaction))
43
+ end
44
+
45
+ def given(provider_state, metadata = {})
46
+ if metadata.present?
47
+ PactFfi.given_with_params(pact_interaction, provider_state, JSON.dump(metadata))
48
+ else
49
+ PactFfi.given(pact_interaction, provider_state)
50
+ end
51
+
52
+ self
53
+ end
54
+
55
+ def upon_receiving(description)
56
+ @description = description
57
+ PactFfi.upon_receiving(pact_interaction, @description)
58
+ self
59
+ end
60
+
61
+ def with_request(method: nil, path: nil, query: {}, headers: {}, body: nil)
62
+ interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_REQUEST"]
63
+ PactFfi.with_request(pact_interaction, method.to_s, format_value(path))
64
+
65
+ # Processing as an array of hashes, allows us to consider duplicate keys
66
+ # which should be passed to the core, at a non 0 index
67
+ if query.is_a?(Array)
68
+ key_index = Hash.new(0)
69
+ query.each do |query_item|
70
+ InteractionContents.basic(query_item).each_pair do |key, value_item|
71
+ PactFfi.with_query_parameter_v2(pact_interaction, key.to_s, key_index[key], format_value(value_item))
72
+ key_index[key] += 1
73
+ end
74
+ end
75
+ else
76
+ InteractionContents.basic(query).each_pair do |key, value_item|
77
+ PactFfi.with_query_parameter_v2(pact_interaction, key.to_s, 0, format_value(value_item))
78
+ end
79
+ end
80
+
81
+ InteractionContents.basic(headers).each_pair do |key, value_item|
82
+ PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
83
+ end
84
+
85
+ if body
86
+ PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body)))
87
+ end
88
+
89
+ self
90
+ end
91
+
92
+ def will_respond_with(status: nil, headers: {}, body: nil)
93
+ interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_RESPONSE"]
94
+ PactFfi.response_status(pact_interaction, status)
95
+
96
+ InteractionContents.basic(headers).each_pair do |key, value_item|
97
+ PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
98
+ end
99
+
100
+ if body
101
+ PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body)))
102
+ end
103
+
104
+ self
105
+ end
106
+
107
+ def execute(&block)
108
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
109
+
110
+ mock_server = MockServer.create_for_http!(
111
+ pact: pact_handle, host: pact_config.mock_host, port: pact_config.mock_port
112
+ )
113
+
114
+ yield(mock_server)
115
+
116
+ ensure
117
+ if mock_server.matched?
118
+ mock_server.write_pacts!(pact_config.pact_dir)
119
+ else
120
+ msg = mismatches_error_msg(mock_server)
121
+ raise InteractionMismatchesError.new(msg)
122
+ end
123
+ @used = true
124
+ mock_server&.cleanup
125
+ # Reset the pact handle to allow for a new interaction to be built
126
+ # without previous interactions being included
127
+ @pact_config.reset_pact
128
+ end
129
+
130
+ private
131
+
132
+ attr_reader :pact_handle, :pact_interaction, :pact_config
133
+
134
+ def mismatches_error_msg(mock_server)
135
+ rspec_example_desc = RSpec.current_example&.description
136
+ mismatches = JSON.pretty_generate(JSON.parse(mock_server.mismatches))
137
+ mismatches_with_colored_keys = mismatches.gsub(/"([^"]+)":/) { |match| "\e[34m#{match}\e[0m" } # Blue keys / white values
138
+
139
+ "#{rspec_example_desc} has mismatches: #{mismatches_with_colored_keys}"
140
+ end
141
+
142
+ def init_pact
143
+ handle = PactFfi.new_pact(pact_config.consumer_name, pact_config.provider_name)
144
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_#{pact_config.pact_specification}"])
145
+ PactFfi.with_pact_metadata(handle, "pact-ruby-v2", "pact-ffi", PactFfi.version)
146
+
147
+ Pact::V2::Native::Logger.log_to_stdout(pact_config.log_level)
148
+
149
+ handle
150
+ end
151
+
152
+ def format_value(obj)
153
+ return obj if obj.is_a?(String)
154
+
155
+ return JSON.dump({value: obj}) if obj.is_a?(Array)
156
+
157
+ JSON.dump(obj)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ module Consumer
6
+ class InteractionContents < Hash
7
+ BASIC_FORMAT = :basic
8
+ PLUGIN_FORMAT = :plugin
9
+
10
+ attr_reader :format
11
+
12
+ def self.basic(contents_hash)
13
+ new(contents_hash, BASIC_FORMAT)
14
+ end
15
+
16
+ def self.plugin(contents_hash)
17
+ new(contents_hash, PLUGIN_FORMAT)
18
+ end
19
+
20
+ def initialize(contents_hash, format)
21
+ init_hash(contents_hash, format).each_pair { |k, v| self[k] = v }
22
+ @format = format
23
+ end
24
+
25
+ private
26
+
27
+ def serialize(hash, format)
28
+ # serialize recursively
29
+ if hash.is_a?(String)
30
+ return hash
31
+ end
32
+ if hash.is_a?(Pact::V2::Matchers::Base)
33
+ return hash.as_basic if format == :basic
34
+ return hash.as_plugin if format == :plugin
35
+ end
36
+ if hash.is_a?(Pact::V2::Generators::Base)
37
+ return hash.as_basic if format == :basic
38
+ return hash.as_plugin if format == :plugin
39
+ end
40
+ hash.each_pair do |key, value|
41
+ next serialize(value, format) if value.is_a?(Hash)
42
+ next hash[key] = value.map { |v| serialize(v, format) } if value.is_a?(Array)
43
+ if value.is_a?(Pact::V2::Matchers::Base)
44
+ hash[key] = value.as_basic if format == :basic
45
+ hash[key] = value.as_plugin if format == :plugin
46
+ end
47
+ if value.is_a?(Pact::V2::Generators::Base)
48
+ hash[key] = value.as_basic if format == :basic
49
+ hash[key] = value.as_plugin if format == :plugin
50
+ end
51
+ end
52
+
53
+ hash
54
+ end
55
+
56
+ def init_hash(hash, format)
57
+ serialize(hash.deep_dup, format)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end