pact_broker 2.107.1 → 2.108.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/Gemfile +5 -4
  4. data/README.md +1 -0
  5. data/db/migrations/20230615_add_integrations_contract_data_updated_at.rb +13 -0
  6. data/db/migrations/20230616_set_integrations_contract_data_updated_at.rb +11 -0
  7. data/db/migrations/20231002_add_version_id_index_to_released_version.rb +21 -0
  8. data/db/migrations/20231003_add_version_id_index_to_deployed_version.rb +21 -0
  9. data/docs/CONFIGURATION.md +10 -0
  10. data/lib/pact_broker/api/contracts/base_contract.rb +2 -1
  11. data/lib/pact_broker/api/contracts/dry_validation_errors_formatter.rb +2 -0
  12. data/lib/pact_broker/api/contracts/pagination_query_params_schema.rb +19 -0
  13. data/lib/pact_broker/api/contracts/publish_contracts_contract_contract.rb +29 -18
  14. data/lib/pact_broker/api/decorators/base_decorator.rb +40 -1
  15. data/lib/pact_broker/api/decorators/branch_decorator.rb +35 -0
  16. data/lib/pact_broker/api/decorators/branch_version_decorator.rb +16 -0
  17. data/lib/pact_broker/api/decorators/configuration.rb +19 -0
  18. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +1 -1
  19. data/lib/pact_broker/api/decorators/decorator_context_creator.rb +47 -2
  20. data/lib/pact_broker/api/decorators/deployed_version_decorator.rb +2 -1
  21. data/lib/pact_broker/api/decorators/deployed_versions_decorator.rb +2 -2
  22. data/lib/pact_broker/api/decorators/dry_validation_errors_decorator.rb +32 -0
  23. data/lib/pact_broker/api/decorators/dry_validation_errors_problem_json_decorator.rb +24 -0
  24. data/lib/pact_broker/api/decorators/embedded_branch_version_decorator.rb +2 -2
  25. data/lib/pact_broker/api/decorators/embedded_deployed_version_decorator.rb +30 -0
  26. data/lib/pact_broker/api/decorators/embedded_error_problem_json_decorator.rb +84 -0
  27. data/lib/pact_broker/api/decorators/embedded_released_version_decorator.rb +27 -0
  28. data/lib/pact_broker/api/decorators/embedded_version_decorator.rb +0 -3
  29. data/lib/pact_broker/api/decorators/error_decorator.rb +30 -0
  30. data/lib/pact_broker/api/decorators/extended_pact_decorator.rb +6 -1
  31. data/lib/pact_broker/api/decorators/integration_decorator.rb +3 -2
  32. data/lib/pact_broker/api/decorators/integrations_decorator.rb +3 -0
  33. data/lib/pact_broker/api/decorators/notices_decorator.rb +11 -0
  34. data/lib/pact_broker/api/decorators/pact_pacticipant_decorator.rb +1 -5
  35. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +0 -1
  36. data/lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb +0 -1
  37. data/lib/pact_broker/api/decorators/pacticipant_branches_decorator.rb +32 -0
  38. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +11 -1
  39. data/lib/pact_broker/api/decorators/{pacticipant_collection_decorator.rb → pacticipants_decorator.rb} +8 -4
  40. data/lib/pact_broker/api/decorators/pagination_links.rb +2 -2
  41. data/lib/pact_broker/api/decorators/released_versions_decorator.rb +2 -2
  42. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +2 -2
  43. data/lib/pact_broker/api/decorators/validation_errors_decorator.rb +30 -0
  44. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +4 -6
  45. data/lib/pact_broker/api/decorators/version_decorator.rb +5 -3
  46. data/lib/pact_broker/api/decorators/versions_decorator.rb +24 -14
  47. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +2 -0
  48. data/lib/pact_broker/api/middleware/configuration.rb +2 -0
  49. data/lib/pact_broker/api/pact_broker_urls.rb +18 -2
  50. data/lib/pact_broker/api/resources/after_reply.rb +15 -0
  51. data/lib/pact_broker/api/resources/all_webhooks.rb +3 -3
  52. data/lib/pact_broker/api/resources/badge_methods.rb +2 -1
  53. data/lib/pact_broker/api/resources/base_resource.rb +6 -4
  54. data/lib/pact_broker/api/resources/branch.rb +40 -0
  55. data/lib/pact_broker/api/resources/branch_versions.rb +59 -0
  56. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +1 -1
  57. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +1 -1
  58. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +1 -1
  59. data/lib/pact_broker/api/resources/dashboard.rb +10 -0
  60. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +1 -1
  61. data/lib/pact_broker/api/resources/environments.rb +1 -1
  62. data/lib/pact_broker/api/resources/error_handler.rb +18 -52
  63. data/lib/pact_broker/api/resources/error_handling_methods.rb +40 -14
  64. data/lib/pact_broker/api/resources/error_response_generator.rb +23 -6
  65. data/lib/pact_broker/api/resources/event_methods.rb +15 -0
  66. data/lib/pact_broker/api/resources/filter_methods.rb +15 -0
  67. data/lib/pact_broker/api/resources/group.rb +11 -2
  68. data/lib/pact_broker/api/resources/index.rb +6 -0
  69. data/lib/pact_broker/api/resources/integrations.rb +18 -4
  70. data/lib/pact_broker/api/resources/latest_version.rb +2 -0
  71. data/lib/pact_broker/api/resources/pact.rb +11 -6
  72. data/lib/pact_broker/api/resources/pacticipant_branches.rb +67 -0
  73. data/lib/pact_broker/api/resources/pacticipants.rb +26 -7
  74. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +2 -2
  75. data/lib/pact_broker/api/resources/pagination_methods.rb +11 -1
  76. data/lib/pact_broker/api/resources/verifications.rb +9 -4
  77. data/lib/pact_broker/api/resources/versions.rb +12 -0
  78. data/lib/pact_broker/api.rb +5 -0
  79. data/lib/pact_broker/app.rb +10 -4
  80. data/lib/pact_broker/application_context.rb +29 -25
  81. data/lib/pact_broker/async/after_reply.rb +30 -0
  82. data/lib/pact_broker/config/runtime_configuration.rb +9 -21
  83. data/lib/pact_broker/config/runtime_configuration_coercion_methods.rb +32 -2
  84. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  85. data/lib/pact_broker/config/runtime_configuration_logging_methods.rb +15 -5
  86. data/lib/pact_broker/configuration.rb +29 -12
  87. data/lib/pact_broker/contracts/contracts_to_publish.rb +8 -0
  88. data/lib/pact_broker/contracts/service.rb +7 -1
  89. data/lib/pact_broker/dataset/page.rb +22 -0
  90. data/lib/pact_broker/dataset.rb +122 -0
  91. data/lib/pact_broker/db/data_migrations/set_contract_data_updated_at_for_integrations.rb +47 -0
  92. data/lib/pact_broker/db/migrate_data.rb +1 -0
  93. data/lib/pact_broker/db/models.rb +1 -1
  94. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -5
  95. data/lib/pact_broker/deployments/deployed_version.rb +3 -3
  96. data/lib/pact_broker/deployments/environment.rb +0 -2
  97. data/lib/pact_broker/deployments/released_version.rb +4 -5
  98. data/lib/pact_broker/doc/views/index/pacticipant-branch.markdown +25 -0
  99. data/lib/pact_broker/domain/label.rb +6 -3
  100. data/lib/pact_broker/domain/pact.rb +10 -5
  101. data/lib/pact_broker/domain/pacticipant.rb +4 -34
  102. data/lib/pact_broker/domain/tag.rb +4 -5
  103. data/lib/pact_broker/domain/verification.rb +2 -4
  104. data/lib/pact_broker/domain/version.rb +12 -19
  105. data/lib/pact_broker/errors/error_reporter.rb +30 -0
  106. data/lib/pact_broker/errors.rb +2 -15
  107. data/lib/pact_broker/events/subscriber.rb +12 -4
  108. data/lib/pact_broker/feature_toggle.rb +1 -1
  109. data/lib/pact_broker/groups/service.rb +38 -5
  110. data/lib/pact_broker/index/service.rb +1 -2
  111. data/lib/pact_broker/integrations/event_listener.rb +23 -0
  112. data/lib/pact_broker/integrations/integration.rb +24 -2
  113. data/lib/pact_broker/integrations/repository.rb +34 -1
  114. data/lib/pact_broker/integrations/service.rb +17 -18
  115. data/lib/pact_broker/labels/repository.rb +4 -8
  116. data/lib/pact_broker/locale/en.yml +5 -0
  117. data/lib/pact_broker/matrix/every_row.rb +58 -40
  118. data/lib/pact_broker/matrix/integration_row.rb +95 -0
  119. data/lib/pact_broker/matrix/integrations_repository.rb +133 -0
  120. data/lib/pact_broker/matrix/matrix_row.rb +88 -0
  121. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +185 -0
  122. data/lib/pact_broker/matrix/matrix_row_instance_methods.rb +150 -0
  123. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +83 -0
  124. data/lib/pact_broker/matrix/parse_query.rb +1 -0
  125. data/lib/pact_broker/matrix/repository.rb +62 -285
  126. data/lib/pact_broker/matrix/resolved_selector.rb +13 -4
  127. data/lib/pact_broker/matrix/resolved_selector_builder.rb +84 -0
  128. data/lib/pact_broker/matrix/resolved_selectors_builder.rb +39 -0
  129. data/lib/pact_broker/matrix/row_ignorer.rb +36 -0
  130. data/lib/pact_broker/matrix/selector_ignorer.rb +59 -0
  131. data/lib/pact_broker/matrix/selector_resolver.rb +130 -0
  132. data/lib/pact_broker/metrics/service.rb +4 -9
  133. data/lib/pact_broker/pacticipants/latest_version_for_pacticipant_eager_loader.rb +33 -0
  134. data/lib/pact_broker/pacticipants/repository.rb +7 -9
  135. data/lib/pact_broker/pacticipants/service.rb +2 -2
  136. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +2 -4
  137. data/lib/pact_broker/pacts/metadata.rb +3 -1
  138. data/lib/pact_broker/pacts/pact_publication.rb +23 -5
  139. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +5 -1
  140. data/lib/pact_broker/pacts/pact_version.rb +2 -3
  141. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +2 -5
  142. data/lib/pact_broker/pacts/placeholder_pact.rb +3 -1
  143. data/lib/pact_broker/pacts/repository.rb +12 -12
  144. data/lib/pact_broker/pacts/service.rb +1 -1
  145. data/lib/pact_broker/repositories.rb +9 -1
  146. data/lib/pact_broker/string_refinements.rb +4 -0
  147. data/lib/pact_broker/tags/head_pact_tags.rb +2 -5
  148. data/lib/pact_broker/tags/repository.rb +3 -7
  149. data/lib/pact_broker/test/test_data_builder.rb +50 -1
  150. data/lib/pact_broker/ui/controllers/groups.rb +2 -1
  151. data/lib/pact_broker/ui/view_models/matrix_line.rb +4 -4
  152. data/lib/pact_broker/ui/views/groups/show.html.erb +2 -1
  153. data/lib/pact_broker/verifications/latest_verification_for_consumer_and_provider.rb +0 -3
  154. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +2 -4
  155. data/lib/pact_broker/verifications/repository.rb +2 -5
  156. data/lib/pact_broker/verifications/sequence.rb +1 -4
  157. data/lib/pact_broker/verifications/service.rb +4 -0
  158. data/lib/pact_broker/version.rb +1 -1
  159. data/lib/pact_broker/versions/branch.rb +2 -5
  160. data/lib/pact_broker/versions/branch_head.rb +0 -3
  161. data/lib/pact_broker/versions/branch_repository.rb +76 -0
  162. data/lib/pact_broker/versions/branch_service.rb +13 -25
  163. data/lib/pact_broker/versions/branch_version.rb +0 -3
  164. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  165. data/lib/pact_broker/versions/repository.rb +19 -2
  166. data/lib/pact_broker/versions/sequence.rb +1 -3
  167. data/lib/pact_broker/versions/service.rb +4 -0
  168. data/lib/pact_broker/webhooks/execution.rb +3 -7
  169. data/lib/pact_broker/webhooks/job.rb +16 -7
  170. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +2 -2
  171. data/lib/pact_broker/webhooks/repository.rb +0 -1
  172. data/lib/pact_broker/webhooks/trigger_service.rb +11 -12
  173. data/lib/pact_broker/webhooks/triggered_webhook.rb +3 -4
  174. data/lib/pact_broker/webhooks/webhook.rb +2 -2
  175. data/lib/pact_broker/webhooks/webhook_event.rb +2 -5
  176. data/lib/rack/pact_broker/add_cache_header.rb +14 -0
  177. data/lib/rack/pact_broker/application_context.rb +16 -0
  178. data/lib/rack/pact_broker/configurable_make_it_later.rb +1 -1
  179. data/lib/rack/pact_broker/invalid_uri_protection.rb +19 -3
  180. data/lib/webmachine/describe_routes.rb +55 -39
  181. data/lib/webmachine/render_error_monkey_patch.rb +13 -4
  182. data/pact_broker.gemspec +4 -4
  183. metadata +52 -29
  184. data/lib/pact_broker/api/decorators/decorator_context.rb +0 -22
  185. data/lib/pact_broker/matrix/query_builder.rb +0 -90
  186. data/lib/pact_broker/matrix/query_ids.rb +0 -40
  187. data/lib/pact_broker/matrix/quick_row.rb +0 -458
  188. data/lib/pact_broker/relationships/groupify.rb +0 -45
  189. data/lib/pact_broker/repositories/helpers.rb +0 -96
  190. data/lib/pact_broker/repositories/page.rb +0 -24
  191. data/lib/pact_broker/tags/tag_with_latest_flag.rb +0 -28
@@ -36,11 +36,15 @@ module PactBroker
36
36
  def perform_with_triggered_webhook
37
37
  @error_count = data[:error_count] || 0
38
38
  begin
39
- webhook_execution_result = PactBroker::Webhooks::TriggerService.execute_triggered_webhook_now(triggered_webhook, webhook_options(data))
40
- if webhook_execution_result.success?
41
- handle_success
39
+ if triggered_webhook.webhook
40
+ webhook_execution_result = PactBroker::Webhooks::TriggerService.execute_triggered_webhook_now(triggered_webhook, webhook_options(data))
41
+ if webhook_execution_result.success?
42
+ handle_success
43
+ else
44
+ handle_failure
45
+ end
42
46
  else
43
- handle_failure
47
+ handle_webhook_deleted
44
48
  end
45
49
  rescue StandardError => e
46
50
  handle_error e
@@ -73,19 +77,24 @@ module PactBroker
73
77
  end
74
78
 
75
79
  def handle_success
76
- update_triggered_webhook_status TriggeredWebhook::STATUS_SUCCESS
80
+ update_triggered_webhook_status(TriggeredWebhook::STATUS_SUCCESS)
77
81
  end
78
82
 
79
83
  def handle_failure
80
84
  if reschedule_job?
81
85
  reschedule_job
82
- update_triggered_webhook_status TriggeredWebhook::STATUS_RETRYING
86
+ update_triggered_webhook_status(TriggeredWebhook::STATUS_RETRYING)
83
87
  else
84
88
  logger.info "Failed to execute webhook #{triggered_webhook.webhook_uuid} after #{retry_schedule.size + 1} attempts."
85
- update_triggered_webhook_status TriggeredWebhook::STATUS_FAILURE
89
+ update_triggered_webhook_status(TriggeredWebhook::STATUS_FAILURE)
86
90
  end
87
91
  end
88
92
 
93
+ def handle_webhook_deleted
94
+ logger.info("Webhook with uuid #{triggered_webhook.webhook_uuid} cannot be executed it has been deleted. Marking triggered webhook as failed.")
95
+ update_triggered_webhook_status(TriggeredWebhook::STATUS_FAILURE)
96
+ end
97
+
89
98
  def reschedule_job?
90
99
  error_count < retry_schedule.size
91
100
  end
@@ -134,7 +134,7 @@ module PactBroker
134
134
  webhook_context[:consumer_version_tags].join(", ")
135
135
  else
136
136
  if pact
137
- pact.consumer_version.tags.collect(&:name).join(", ")
137
+ pact.consumer_version_tag_names.join(", ")
138
138
  else
139
139
  ""
140
140
  end
@@ -145,7 +145,7 @@ module PactBroker
145
145
  if webhook_context.key?(:consumer_version_branch)
146
146
  webhook_context[:consumer_version_branch] || ""
147
147
  else
148
- pact&.consumer_version&.branch_names&.last || ""
148
+ pact&.consumer_version_branch_names&.last || ""
149
149
  end
150
150
  end
151
151
 
@@ -1,4 +1,3 @@
1
- require "sequel"
2
1
  require "pact_broker/domain/webhook"
3
2
  require "pact_broker/domain/pacticipant"
4
3
  require "pact_broker/db"
@@ -13,11 +13,21 @@ module PactBroker
13
13
  include PactBroker::Logging
14
14
  using PactBroker::HashRefinements
15
15
 
16
+ # the main entry point
17
+ def create_triggered_webhooks_for_event pact, verification, event_name, event_context
18
+ webhooks = webhook_repository.find_webhooks_to_trigger(consumer: pact.consumer, provider: pact.provider, event_name: event_name)
19
+
20
+ if webhooks.any?
21
+ create_triggered_webhooks_for_webhooks(webhooks, pact, verification, event_name, event_context.merge(event_name: event_name))
22
+ else
23
+ []
24
+ end
25
+ end
26
+
16
27
  def next_uuid
17
28
  SecureRandom.uuid
18
29
  end
19
30
 
20
- # TODO support currently deployed
21
31
  def test_execution webhook, event_context, execution_configuration
22
32
  merged_options = execution_configuration.with_failure_log_message("Webhook execution failed").to_hash
23
33
 
@@ -36,17 +46,6 @@ module PactBroker
36
46
  webhook_execution_result
37
47
  end
38
48
 
39
- # the main entry point
40
- def create_triggered_webhooks_for_event pact, verification, event_name, event_context
41
- webhooks = webhook_repository.find_webhooks_to_trigger(consumer: pact.consumer, provider: pact.provider, event_name: event_name)
42
-
43
- if webhooks.any?
44
- create_triggered_webhooks_for_webhooks(webhooks, pact, verification, event_name, event_context.merge(event_name: event_name))
45
- else
46
- []
47
- end
48
- end
49
-
50
49
  def schedule_webhooks(triggered_webhooks, options)
51
50
  triggered_webhooks.each_with_index do | triggered_webhook, i |
52
51
  logger.info "Scheduling job for webhook with uuid #{triggered_webhook.webhook.uuid}, context: #{triggered_webhook.event_context}"
@@ -1,5 +1,4 @@
1
- require "sequel"
2
- require "pact_broker/repositories/helpers"
1
+ require "pact_broker/dataset"
3
2
  require "pact_broker/webhooks/execution"
4
3
  require "pact_broker/hash_refinements"
5
4
 
@@ -23,7 +22,7 @@ module PactBroker
23
22
  STATUS_FAILURE = "failure".freeze
24
23
 
25
24
  dataset_module do
26
- include PactBroker::Repositories::Helpers
25
+ include PactBroker::Dataset
27
26
 
28
27
  def delete
29
28
  require "pact_broker/webhooks/execution"
@@ -70,7 +69,7 @@ module PactBroker
70
69
  def execute options
71
70
  # getting a random 'no method to_domain for null' error
72
71
  # not sure on which object, so splitting this out into two lines
73
- pact = pact_publication.to_domain
72
+ pact = pact_publication.with_version_branches_and_tags.to_domain
74
73
  webhook.to_domain.execute(pact, verification, event_context.symbolize_keys, options)
75
74
  end
76
75
 
@@ -1,4 +1,4 @@
1
- require "sequel"
1
+ require "pact_broker/dataset"
2
2
  require "pact_broker/domain/webhook"
3
3
  require "pact_broker/webhooks/webhook_request_template"
4
4
  require "pact_broker/domain/pacticipant"
@@ -15,7 +15,7 @@ module PactBroker
15
15
  one_to_many :events, :class => "PactBroker::Webhooks::WebhookEvent", :reciprocal => :webhook
16
16
 
17
17
  dataset_module do
18
- include PactBroker::Repositories::Helpers
18
+ include PactBroker::Dataset
19
19
 
20
20
  # Keep the triggered webhooks after the webhook has been deleted
21
21
  def delete
@@ -1,5 +1,4 @@
1
- require "sequel"
2
- require "pact_broker/repositories/helpers"
1
+ require "pact_broker/dataset"
3
2
 
4
3
  module PactBroker
5
4
  module Webhooks
@@ -17,9 +16,7 @@ module PactBroker
17
16
 
18
17
  EVENT_NAMES = [CONTRACT_PUBLISHED, CONTRACT_CONTENT_CHANGED, VERIFICATION_PUBLISHED, VERIFICATION_SUCCEEDED, VERIFICATION_FAILED, CONTRACT_REQUIRING_VERIFICATION_PUBLISHED]
19
18
 
20
- dataset_module do
21
- include PactBroker::Repositories::Helpers
22
- end
19
+ dataset_module(PactBroker::Dataset)
23
20
 
24
21
  def contract_published?
25
22
  name == CONTRACT_PUBLISHED
@@ -0,0 +1,14 @@
1
+ module Rack
2
+ module PactBroker
3
+ class AddCacheHeader
4
+ def initialize app
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, body = @app.call(env)
10
+ [status, { "Cache-Control" => "no-cache" }.merge(headers || {}), body]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # Sets the PactBroker::ApplicationContext on the rack env if it is not already set.
2
+
3
+ module Rack
4
+ module PactBroker
5
+ class ApplicationContext
6
+ def initialize(app, application_context)
7
+ @app = app
8
+ @application_context = application_context
9
+ end
10
+
11
+ def call(env)
12
+ @app.call({ "pactbroker.application_context" => @application_context }.merge(env))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
 
2
- # Allows a default Rack middlware implementation to be set,
2
+ # Allows a default Rack middleware implementation to be set,
3
3
  # and then be optionally changed out for a different implementation
4
4
  # after the app has been built.
5
5
  # Used for allowing the authorization code to set after the
@@ -12,6 +12,8 @@ module Rack
12
12
  class InvalidUriProtection
13
13
  include ::PactBroker::Messages
14
14
 
15
+ CONSECUTIVE_SLASH = /\/{2,}/
16
+
15
17
  def initialize app
16
18
  @app = app
17
19
  end
@@ -19,12 +21,12 @@ module Rack
19
21
  def call env
20
22
  if (uri = valid_uri?(env))
21
23
  if (error_message = validate(uri))
22
- [422, {"Content-Type" => "text/plain"}, [error_message]]
24
+ [422, headers, [body(env, error_message, "Unprocessable", "invalid-request-parameter-value", 422)]]
23
25
  else
24
26
  app.call(env)
25
27
  end
26
28
  else
27
- [404, {}, []]
29
+ [404, headers, [body(env, "Empty path component found", "Not Found", "not-found", 404)]]
28
30
  end
29
31
  end
30
32
 
@@ -34,7 +36,9 @@ module Rack
34
36
 
35
37
  def valid_uri? env
36
38
  begin
37
- parse(::Rack::Request.new(env).url)
39
+ uri = parse(::Rack::Request.new(env).url)
40
+ return nil if CONSECUTIVE_SLASH.match(uri.path)
41
+ uri
38
42
  rescue URI::InvalidURIError, ArgumentError
39
43
  nil
40
44
  end
@@ -52,6 +56,18 @@ module Rack
52
56
  message("errors.tab_in_url_path")
53
57
  end
54
58
  end
59
+
60
+ def headers
61
+ {"Content-Type" => "application/problem+json"}
62
+ end
63
+
64
+ def body(env, detail, title, type, status)
65
+ env["pactbroker.application_context"]
66
+ .decorator_configuration
67
+ .class_for(:custom_error_problem_json_decorator)
68
+ .new(detail: detail, title: title, type: type, status: status)
69
+ .to_json(user_options: { base_url: env["pactbroker.base_url"] })
70
+ end
55
71
  end
56
72
  end
57
73
  end
@@ -1,6 +1,10 @@
1
1
  require "webmachine/adapters/rack_mapped"
2
2
  require "pact_broker/string_refinements"
3
3
 
4
+ # Code to describe the routes in a Webmachine API, including
5
+ # path, resource class, allowed methods, schemas, policy class.
6
+ # Used in tests and in the pact_broker:routes task
7
+
4
8
  module Webmachine
5
9
  class DescribeRoutes
6
10
  using PactBroker::StringRefinements
@@ -12,18 +16,11 @@ module Webmachine
12
16
  :resource_name,
13
17
  :resource_class_location,
14
18
  :allowed_methods,
15
- :policy_class,
19
+ :policy_names,
20
+ :policy_classes, # only used by pactflow
16
21
  :schemas,
17
22
  keyword_init: true) do
18
23
 
19
- def [](key)
20
- if respond_to?(key)
21
- send(key)
22
- else
23
- nil
24
- end
25
- end
26
-
27
24
  def path_include?(component)
28
25
  path.include?(component)
29
26
  end
@@ -47,7 +44,7 @@ module Webmachine
47
44
  }.merge(path_params)
48
45
 
49
46
  rack_req = ::Rack::Request.new({ "REQUEST_METHOD" => "GET", "rack.input" => StringIO.new("") }.merge(env) )
50
- dummy_request = Webmachine::Adapters::Rack::RackRequest.new(
47
+ request = Webmachine::Adapters::Rack::RackRequest.new(
51
48
  rack_req.env["REQUEST_METHOD"],
52
49
  path,
53
50
  Webmachine::Headers.from_cgi({"HTTP_HOST" => "example.org"}.merge(env)),
@@ -56,13 +53,13 @@ module Webmachine
56
53
  {},
57
54
  rack_req.env
58
55
  )
59
- dummy_request.path_info = path_info
60
- resource_class.new(dummy_request, Webmachine::Response.new)
56
+ request.path_info = path_info
57
+ resource_class.new(request, Webmachine::Response.new)
61
58
  end
62
59
  end
63
60
 
64
61
  def self.call(webmachine_applications, search_term: nil)
65
- path_mappings = webmachine_applications.flat_map { | webmachine_application | paths_to_resource_class_mappings(webmachine_application) }
62
+ path_mappings = webmachine_applications.flat_map { | webmachine_application | build_routes(webmachine_application) }
66
63
 
67
64
  if search_term
68
65
  path_mappings = path_mappings.select{ |(route, _)| route[:path].include?(search_term) }
@@ -71,8 +68,10 @@ module Webmachine
71
68
  path_mappings.sort_by{ | mapping | mapping[:path] }
72
69
  end
73
70
 
74
- def self.paths_to_resource_class_mappings(webmachine_application)
75
- webmachine_application.routes.collect do | webmachine_route |
71
+ # Build a Route object to describe every Webmachine route defined in the app.routes block
72
+ # @return [Array<Webmachine::DescribeRoutes::Route>]
73
+ def self.build_routes(webmachine_application)
74
+ webmachine_routes_to_describe(webmachine_application).collect do | webmachine_route |
76
75
  resource_path_absolute = Pathname.new(source_location_for(webmachine_route.resource))
77
76
  Route.new({
78
77
  path: "/" + webmachine_route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
@@ -80,22 +79,23 @@ module Webmachine
80
79
  resource_class: webmachine_route.resource,
81
80
  resource_name: webmachine_route.instance_variable_get(:@bindings)[:resource_name],
82
81
  resource_class_location: resource_path_absolute.relative_path_from(Pathname.pwd).to_s
83
- }.merge(info_from_resource_instance(webmachine_route, webmachine_application.application_context)))
84
- end.reject{ | route | route.resource_class == Webmachine::Trace::TraceResource }
82
+ }.merge(properties_for_webmachine_route(webmachine_route, webmachine_application.application_context)))
83
+ end
84
+ end
85
+
86
+ def self.webmachine_routes_to_describe(webmachine_application)
87
+ webmachine_application.routes.reject{ | route | route.resource == Webmachine::Trace::TraceResource }.collect
85
88
  end
86
89
 
87
- def self.info_from_resource_instance(webmachine_route, application_context)
90
+ def self.properties_for_webmachine_route(webmachine_route, application_context)
88
91
  with_no_logging do
89
92
  path_info = { application_context: application_context, pacticipant_name: "foo", pacticipant_version_number: "1", resource_name: "foo" }
90
93
  path_info.default = "1"
91
- dummy_request = dummy_request(http_method: "GET", path_info: path_info)
92
-
93
- dummy_resource = webmachine_route.resource.new(dummy_request, Webmachine::Response.new)
94
- if dummy_resource
95
- {
96
- allowed_methods: dummy_resource.allowed_methods,
97
- schemas: dummy_resource.respond_to?(:schema, true) && dummy_resource.send(:schema) ? schemas(dummy_resource.allowed_methods, webmachine_route.resource, path_info) : nil
98
- }.compact
94
+ request = build_request(http_method: "GET", path_info: path_info)
95
+
96
+ resource = webmachine_route.resource.new(request, Webmachine::Response.new)
97
+ if resource
98
+ properties_for_resource(resource.allowed_methods - ["OPTIONS"], webmachine_route, application_context)
99
99
  else
100
100
  {}
101
101
  end
@@ -105,22 +105,38 @@ module Webmachine
105
105
  {}
106
106
  end
107
107
 
108
- # This is not entirely accurate, because some GET requests have schemas too, but we can't tell that statically at the moment
109
- def self.schemas(allowed_methods, resource, path_info)
110
- (allowed_methods - ["GET", "OPTIONS", "DELETE"]).collect do | http_method |
111
- resource.new(dummy_request(http_method: http_method, path_info: path_info), Webmachine::Response.new).send(:schema)
112
- end.uniq.collect do | schema_class |
113
- {
114
- class: schema_class,
115
- location: source_location_for(schema_class)
116
- }
108
+ # Return the properties of the resource that can only be determined by instantiating the resource
109
+ # @return [Hash]
110
+ def self.properties_for_resource(allowed_methods, webmachine_route, application_context)
111
+ schemas = []
112
+ policy_names = []
113
+ allowed_methods.each do | http_method |
114
+ resource = build_resource(webmachine_route, http_method, application_context)
115
+ if (schema_class = resource.respond_to?(:schema, true) && resource.send(:schema))
116
+ schemas << { http_method: http_method, class: schema_class, location: source_location_for(schema_class)}
117
+ end
118
+
119
+ policy_names << resource.policy_name
117
120
  end
121
+
122
+ {
123
+ allowed_methods: allowed_methods,
124
+ schemas: schemas,
125
+ policy_names: policy_names.uniq
126
+ }
127
+ end
128
+
129
+ def self.build_resource(webmachine_route, http_method, application_context)
130
+ path_info = { application_context: application_context, pacticipant_name: "foo", pacticipant_version_number: "1", resource_name: "foo" }
131
+ path_info.default = "1"
132
+ request = build_request(http_method: http_method, path_info: path_info)
133
+ webmachine_route.resource.new(request, Webmachine::Response.new)
118
134
  end
119
135
 
120
- def self.dummy_request(http_method: "GET", path_info: )
121
- dummy_request = Webmachine::Adapters::Rack::RackRequest.new(http_method, "/", Webmachine::Headers["host" => "example.org"], nil, {}, {}, { "REQUEST_METHOD" => http_method })
122
- dummy_request.path_info = path_info
123
- dummy_request
136
+ def self.build_request(http_method: "GET", path_info: )
137
+ request = Webmachine::Adapters::Rack::RackRequest.new(http_method, "/", Webmachine::Headers["host" => "example.org"], nil, {}, {}, { "REQUEST_METHOD" => http_method })
138
+ request.path_info = path_info
139
+ request
124
140
  end
125
141
 
126
142
  def self.source_location_for(clazz)
@@ -35,7 +35,7 @@ module Webmachine
35
35
  title = options[:title] if options[:title]
36
36
  message = options[:message] if options[:message]
37
37
 
38
- res.body = error_response_body(message, title, title.dasherize.gsub(/^\d+\-/, ""), code, req)
38
+ res.body = error_response_body(req, message, title, title.dasherize.gsub(/^\d+\-/, ""), code, req)
39
39
  res.headers[CONTENT_TYPE] = error_response_content_type(req)
40
40
  end
41
41
  ensure_content_length(res)
@@ -60,11 +60,20 @@ module Webmachine
60
60
  end
61
61
  end
62
62
 
63
- def self.error_response_body(detail, title, type, status, request)
63
+ # rubocop: disable Metrics/ParameterLists
64
+ def self.error_response_body(req, detail, title, type, status, request)
64
65
  if problem_json_error_content_type?(request)
65
- PactBroker::Api::Decorators::CustomErrorProblemJSONDecorator.new(detail: detail, title: title, type: type, status: status).to_json
66
+ decorator_configuration(req)
67
+ .class_for(:custom_error_problem_json_decorator)
68
+ .new(detail: detail, title: title, type: type, status: status)
69
+ .to_json(user_options: { base_url: req.env["pactbroker.base_url"] })
66
70
  else
67
- { error: detail }.to_json
71
+ decorator_configuration(req).class_for(:error_decorator).new(detail).to_json
68
72
  end
69
73
  end
74
+ # rubocop: enable Metrics/ParameterLists
75
+
76
+ def self.decorator_configuration(req)
77
+ req.env["pactbroker.application_context"].decorator_configuration
78
+ end
70
79
  end
data/pact_broker.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.summary = %q{See description}
15
15
  gem.homepage = "https://github.com/pact-foundation/pact_broker"
16
16
 
17
- gem.required_ruby_version = ">= 2.2.0"
17
+ gem.required_ruby_version = ">= 2.7.0"
18
18
 
19
19
  gem.files = begin
20
20
  if Dir.exist?(".git")
@@ -58,14 +58,14 @@ Gem::Specification.new do |gem|
58
58
  gem.add_runtime_dependency "webmachine", ">= 2.0.0.beta", "< 3.0"
59
59
  gem.add_runtime_dependency "webrick", "~> 1.8" # webmachine requires webrick, but doesn't declare it as a dependency :shrug:
60
60
  gem.add_runtime_dependency "semver2", "~> 3.4.2"
61
- gem.add_runtime_dependency "rack", ">= 2.2.3", "~> 2.2"
61
+ gem.add_runtime_dependency "rack", ">= 2.2.3", "~> 2.2" # TODO update to 3
62
62
  gem.add_runtime_dependency "redcarpet", ">= 3.5.1", "~>3.5"
63
63
  gem.add_runtime_dependency "pact-support" , "~> 1.16", ">= 1.16.4"
64
64
  gem.add_runtime_dependency "padrino-core", ">= 0.14.3", "~> 0.14"
65
- gem.add_runtime_dependency "sinatra", ">= 2.0.8.1", "< 3.0"
65
+ gem.add_runtime_dependency "sinatra", "~> 3.0"
66
66
  gem.add_runtime_dependency "haml", "~>5.0"
67
67
  gem.add_runtime_dependency "sucker_punch", "~>2.0"
68
- gem.add_runtime_dependency "rack-protection", ">= 2.0.8.1", "< 3.0"
68
+ gem.add_runtime_dependency "rack-protection", "~> 3.0"
69
69
  gem.add_runtime_dependency "table_print", "~> 1.5"
70
70
  gem.add_runtime_dependency "semantic_logger", "~> 4.11"
71
71
  gem.add_runtime_dependency "sanitize", "~> 6.0"