pact_broker 2.105.0 → 2.107.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/Gemfile +3 -0
  4. data/README.md +1 -1
  5. data/db/migrations/20221130_add_provider_version_id_index_to_verifications.rb +28 -0
  6. data/db/migrations/20221208_add_index_to_pact_version_provider_tag_successful_verifications.rb +21 -0
  7. data/db/migrations/20221215_add_prov_ver_id_ndx_to_latest_verifi_id_for_pact_ver_and_prov_ver.rb +21 -0
  8. data/db/migrations/20230131_add_cons_ver_id_ndx_to_latest_pp_id_for_cons_ver.rb +21 -0
  9. data/db/migrations/20230216_add_branch_heads_branch_version_id_index.rb +21 -0
  10. data/db/migrations/20230428_add_index_for_webhook_executions_pact_publication_id.rb +17 -0
  11. data/docs/CONFIGURATION.md +1 -1
  12. data/docs/api/PAGINATION.md +43 -0
  13. data/lib/pact_broker/api/contracts/base_contract.rb +22 -7
  14. data/lib/pact_broker/api/contracts/can_i_deploy_query_schema.rb +34 -0
  15. data/lib/pact_broker/api/contracts/configuration.rb +2 -0
  16. data/lib/pact_broker/api/contracts/consumer_version_selector_contract.rb +140 -0
  17. data/lib/pact_broker/api/contracts/dry_validation_errors_formatter.rb +50 -0
  18. data/lib/pact_broker/api/contracts/dry_validation_macros.rb +79 -0
  19. data/lib/pact_broker/api/contracts/dry_validation_methods.rb +71 -0
  20. data/lib/pact_broker/api/contracts/environment_schema.rb +19 -33
  21. data/lib/pact_broker/api/contracts/pacticipant_create_schema.rb +4 -17
  22. data/lib/pact_broker/api/contracts/pacticipant_schema.rb +15 -24
  23. data/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb +37 -146
  24. data/lib/pact_broker/api/contracts/pacts_for_verification_query_string_schema.rb +7 -20
  25. data/lib/pact_broker/api/contracts/publish_contracts_contract_contract.rb +62 -0
  26. data/lib/pact_broker/api/contracts/publish_contracts_schema.rb +25 -112
  27. data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +21 -26
  28. data/lib/pact_broker/api/contracts/utf_8_validation.rb +19 -0
  29. data/lib/pact_broker/api/contracts/validation_helpers.rb +71 -0
  30. data/lib/pact_broker/api/contracts/verification_contract.rb +10 -29
  31. data/lib/pact_broker/api/contracts/webhook_contract.rb +20 -170
  32. data/lib/pact_broker/api/contracts/webhook_pacticipant_contract.rb +33 -0
  33. data/lib/pact_broker/api/contracts/webhook_request_contract.rb +125 -0
  34. data/lib/pact_broker/api/contracts.rb +3 -0
  35. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +36 -0
  36. data/lib/pact_broker/api/decorators/dashboard_text_decorator.rb +2 -2
  37. data/lib/pact_broker/api/decorators/extended_pact_decorator.rb +1 -1
  38. data/lib/pact_broker/api/decorators/matrix_decorator.rb +4 -4
  39. data/lib/pact_broker/api/decorators/matrix_text_decorator.rb +1 -1
  40. data/lib/pact_broker/api/decorators/pact_decorator.rb +1 -1
  41. data/lib/pact_broker/api/decorators/pacticipant_collection_decorator.rb +5 -2
  42. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +2 -1
  43. data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +4 -1
  44. data/lib/pact_broker/api/decorators/pagination_links.rb +11 -0
  45. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +34 -0
  46. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +66 -0
  47. data/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb +4 -4
  48. data/lib/pact_broker/api/decorators/webhook_decorator.rb +2 -3
  49. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +5 -12
  50. data/lib/pact_broker/api/resources/all_webhooks.rb +5 -11
  51. data/lib/pact_broker/api/resources/badge_methods.rb +1 -1
  52. data/lib/pact_broker/api/resources/base_resource.rb +31 -68
  53. data/lib/pact_broker/api/resources/branch_version.rb +3 -3
  54. data/lib/pact_broker/api/resources/can_i_deploy.rb +4 -19
  55. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb +1 -4
  56. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb +0 -2
  57. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_badge.rb +1 -2
  58. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +2 -2
  59. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +2 -2
  60. data/lib/pact_broker/api/resources/dashboard.rb +3 -3
  61. data/lib/pact_broker/api/resources/deployed_version.rb +1 -1
  62. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +2 -2
  63. data/lib/pact_broker/api/resources/environment.rb +1 -1
  64. data/lib/pact_broker/api/resources/environments.rb +2 -2
  65. data/lib/pact_broker/api/resources/error_handling_methods.rb +57 -0
  66. data/lib/pact_broker/api/resources/error_response_generator.rb +70 -0
  67. data/lib/pact_broker/api/resources/integrations.rb +1 -1
  68. data/lib/pact_broker/api/resources/label.rb +1 -1
  69. data/lib/pact_broker/api/resources/latest_pact.rb +2 -2
  70. data/lib/pact_broker/api/resources/latest_pacts.rb +1 -1
  71. data/lib/pact_broker/api/resources/latest_verifications_for_consumer_version.rb +2 -2
  72. data/lib/pact_broker/api/resources/matrix.rb +2 -2
  73. data/lib/pact_broker/api/resources/matrix_for_consumer_and_provider.rb +1 -1
  74. data/lib/pact_broker/api/resources/pact.rb +7 -4
  75. data/lib/pact_broker/api/resources/pact_triggered_webhooks.rb +1 -1
  76. data/lib/pact_broker/api/resources/pact_version.rb +1 -1
  77. data/lib/pact_broker/api/resources/pact_versions.rb +1 -1
  78. data/lib/pact_broker/api/resources/pact_webhooks.rb +7 -14
  79. data/lib/pact_broker/api/resources/pact_webhooks_status.rb +6 -2
  80. data/lib/pact_broker/api/resources/pacticipant.rb +1 -1
  81. data/lib/pact_broker/api/resources/pacticipant_webhooks.rb +7 -5
  82. data/lib/pact_broker/api/resources/pacticipants.rb +6 -3
  83. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +1 -1
  84. data/lib/pact_broker/api/resources/pagination_methods.rb +8 -4
  85. data/lib/pact_broker/api/resources/previous_distinct_pact_version.rb +1 -1
  86. data/lib/pact_broker/api/resources/provider_pacts.rb +1 -1
  87. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +4 -13
  88. data/lib/pact_broker/api/resources/publish_contracts.rb +8 -3
  89. data/lib/pact_broker/api/resources/released_version.rb +1 -1
  90. data/lib/pact_broker/api/resources/released_versions_for_version_and_environment.rb +2 -2
  91. data/lib/pact_broker/api/resources/tag.rb +1 -1
  92. data/lib/pact_broker/api/resources/tagged_pact_versions.rb +1 -1
  93. data/lib/pact_broker/api/resources/triggered_webhook_logs.rb +2 -2
  94. data/lib/pact_broker/api/resources/verification.rb +2 -2
  95. data/lib/pact_broker/api/resources/verification_triggered_webhooks.rb +1 -1
  96. data/lib/pact_broker/api/resources/verifications.rb +4 -6
  97. data/lib/pact_broker/api/resources/version.rb +1 -1
  98. data/lib/pact_broker/api/resources/versions.rb +6 -13
  99. data/lib/pact_broker/api/resources/webhook.rb +7 -6
  100. data/lib/pact_broker/api/resources/webhook_execution.rb +6 -4
  101. data/lib/pact_broker/api.rb +3 -13
  102. data/lib/pact_broker/app.rb +0 -2
  103. data/lib/pact_broker/application_context.rb +4 -4
  104. data/lib/pact_broker/certificates/certificate.rb +1 -0
  105. data/lib/pact_broker/config/setting.rb +1 -0
  106. data/lib/pact_broker/contracts/service.rb +1 -0
  107. data/lib/pact_broker/date_helper.rb +1 -1
  108. data/lib/pact_broker/db/clean_incremental.rb +67 -59
  109. data/lib/pact_broker/db/delete_overwritten_data.rb +6 -2
  110. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -0
  111. data/lib/pact_broker/deployments/deployed_version.rb +2 -0
  112. data/lib/pact_broker/deployments/deployed_version_service.rb +5 -1
  113. data/lib/pact_broker/deployments/environment.rb +2 -0
  114. data/lib/pact_broker/deployments/environment_service.rb +4 -3
  115. data/lib/pact_broker/deployments/released_version.rb +2 -0
  116. data/lib/pact_broker/deployments/released_version_service.rb +4 -0
  117. data/lib/pact_broker/diagnostic/resources/base_resource.rb +1 -1
  118. data/lib/pact_broker/doc/views/index/publish-contracts.markdown +5 -5
  119. data/lib/pact_broker/domain/label.rb +1 -0
  120. data/lib/pact_broker/domain/tag.rb +2 -0
  121. data/lib/pact_broker/domain/verification.rb +1 -1
  122. data/lib/pact_broker/domain/version.rb +4 -1
  123. data/lib/pact_broker/domain/webhook.rb +1 -1
  124. data/lib/pact_broker/index/service.rb +2 -2
  125. data/lib/pact_broker/integrations/integration.rb +1 -0
  126. data/lib/pact_broker/locale/en.yml +36 -14
  127. data/lib/pact_broker/matrix/query_ids.rb +4 -4
  128. data/lib/pact_broker/matrix/resolved_selector.rb +6 -1
  129. data/lib/pact_broker/matrix/service.rb +1 -0
  130. data/lib/pact_broker/messages.rb +5 -1
  131. data/lib/pact_broker/pacticipants/repository.rb +16 -14
  132. data/lib/pact_broker/pacticipants/service.rb +11 -8
  133. data/lib/pact_broker/pacts/pact_params.rb +6 -17
  134. data/lib/pact_broker/pacts/pact_version.rb +1 -0
  135. data/lib/pact_broker/pacts/selected_pact.rb +4 -0
  136. data/lib/pact_broker/policies.rb +4 -4
  137. data/lib/pact_broker/repositories/helpers.rb +11 -0
  138. data/lib/pact_broker/repositories/page.rb +24 -0
  139. data/lib/pact_broker/string_refinements.rb +4 -0
  140. data/lib/pact_broker/tasks/clean_task.rb +7 -3
  141. data/lib/pact_broker/test/http_test_data_builder.rb +46 -2
  142. data/lib/pact_broker/ui/app.rb +2 -2
  143. data/lib/pact_broker/ui/views/matrix/show.haml +1 -1
  144. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +1 -1
  145. data/lib/pact_broker/verifications/pact_version_provider_tag_successful_verification.rb +1 -0
  146. data/lib/pact_broker/verifications/repository.rb +14 -11
  147. data/lib/pact_broker/verifications/service.rb +0 -6
  148. data/lib/pact_broker/version.rb +1 -1
  149. data/lib/pact_broker/versions/branch.rb +1 -0
  150. data/lib/pact_broker/versions/branch_head.rb +2 -1
  151. data/lib/pact_broker/versions/branch_version.rb +11 -0
  152. data/lib/pact_broker/versions/repository.rb +12 -0
  153. data/lib/pact_broker/versions/service.rb +4 -0
  154. data/lib/pact_broker/webhooks/execution.rb +1 -1
  155. data/lib/pact_broker/webhooks/repository.rb +1 -1
  156. data/lib/pact_broker/webhooks/service.rb +3 -25
  157. data/lib/pact_broker/webhooks/triggered_webhook.rb +1 -0
  158. data/lib/pact_broker/webhooks/webhook_event.rb +1 -0
  159. data/lib/pact_broker/webmachine.rb +22 -0
  160. data/lib/rack/pact_broker/invalid_uri_protection.rb +1 -1
  161. data/lib/rack/pact_broker/request_target.rb +1 -1
  162. data/lib/rack/pact_broker/use_when.rb +6 -5
  163. data/lib/sequel/plugins/age.rb +13 -0
  164. data/lib/webmachine/application_monkey_patch.rb +15 -0
  165. data/lib/webmachine/describe_routes.rb +35 -8
  166. data/lib/webmachine/render_error_monkey_patch.rb +70 -0
  167. data/pact_broker.gemspec +7 -18
  168. metadata +72 -76
  169. data/lib/pact/doc/README.md +0 -5
  170. data/lib/pact_broker/api/contracts/dry_validation_predicates.rb +0 -36
  171. data/lib/pact_broker/api/contracts/dry_validation_workarounds.rb +0 -39
  172. data/lib/pact_broker/api/contracts/pacticipant_name_contract.rb +0 -24
  173. data/lib/pact_broker/api/contracts/pacticipant_name_validation.rb +0 -30
  174. data/lib/pact_broker/api/contracts/request_validations.rb +0 -33
  175. data/lib/pact_broker/api/resources/error_response_body_generator.rb +0 -41
  176. data/lib/pact_broker/api/resources/webhook_resource_methods.rb +0 -17
  177. data/lib/pact_broker/matrix/can_i_deploy_query_schema.rb +0 -46
  178. data/lib/rack/pact_broker/convert_404_to_hal.rb +0 -20
@@ -1,121 +1,34 @@
1
- require "dry-validation"
2
- require "pact_broker/api/contracts/dry_validation_workarounds"
3
- require "pact_broker/api/contracts/dry_validation_predicates"
4
- require "pact_broker/messages"
1
+ require "pact_broker/api/contracts/base_contract"
2
+ require "pact_broker/api/contracts/publish_contracts_contract_contract"
5
3
 
6
4
  module PactBroker
7
5
  module Api
8
6
  module Contracts
9
- class PublishContractsSchema
10
- extend DryValidationWorkarounds
11
- using PactBroker::HashRefinements
12
- extend PactBroker::Messages
13
-
14
- SCHEMA = Dry::Validation.Schema do
15
- configure do
16
- predicates(DryValidationPredicates)
17
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
18
- end
19
-
20
- required(:pacticipantName).filled(:str?, :not_blank?)
21
- required(:pacticipantVersionNumber).filled(:not_blank?, :single_line?)
22
- optional(:tags).each(:not_blank?, :single_line?)
23
- optional(:branch).maybe(:not_blank?, :single_line?)
24
- optional(:buildUrl).maybe(:single_line?)
25
-
26
- required(:contracts).each do
27
- required(:consumerName).filled(:str?, :not_blank?)
28
- required(:providerName).filled(:str?, :not_blank?)
29
- required(:content).filled(:str?)
30
- required(:contentType).filled(included_in?: ["application/json"])
31
- required(:specification).filled(included_in?: ["pact"])
32
- optional(:onConflict).filled(included_in?:["overwrite", "merge"])
33
- end
34
- end
35
-
36
- def self.call(params)
37
- dry_results = SCHEMA.call(params&.symbolize_keys).messages(full: true)
38
- dry_results.then do | results |
39
- add_cross_field_validation_errors(params&.symbolize_keys, results)
40
- end.then do | results |
41
- select_first_message(results)
42
- end.then do | results |
43
- flatten_indexed_messages(results)
44
- end
45
- end
46
-
47
- def self.add_cross_field_validation_errors(params, errors)
48
- if params[:contracts].is_a?(Array)
49
- params[:contracts].each_with_index do | contract, i |
50
- if contract.is_a?(Hash)
51
- validate_consumer_name(params, contract, i, errors)
52
- validate_consumer_name_in_content(params, contract, i, errors)
53
- validate_provider_name_in_content(contract, i, errors)
54
- validate_encoding(contract, i, errors)
55
- validate_content_matches_content_type(contract, i, errors)
56
- end
7
+ class PublishContractsSchema < BaseContract
8
+ json do
9
+ required(:pacticipantName).filled(:string)
10
+ required(:pacticipantVersionNumber).filled(:string)
11
+ optional(:tags).maybe{ array? & each { filled? } }
12
+ optional(:branch).maybe(:string)
13
+ optional(:buildUrl).maybe(:string)
14
+ required(:contracts).array(:hash)
15
+ end
16
+
17
+ rule(:pacticipantName).validate(:not_blank_if_present)
18
+ rule(:pacticipantVersionNumber).validate(:not_blank_if_present, :not_multiple_lines)
19
+ rule(:branch).validate(:not_blank_if_present, :not_multiple_lines)
20
+ rule(:buildUrl).validate(:not_multiple_lines)
21
+ rule(:tags).validate(:array_values_not_blank_if_any)
22
+
23
+ rule(:contracts).validate(validate_each_with_contract: PublishContractsContractContract)
24
+
25
+ # validate_consumer_name_matches_pacticipant_name
26
+ rule(:contracts, :pacticipantName) do
27
+ values[:contracts]&.each_with_index do | contract, index |
28
+ if values[:pacticipantName] && contract[:consumerName] && (contract[:consumerName] != values[:pacticipantName])
29
+ key([:contracts, index]).failure(validation_message("consumer_name_in_contract_mismatch_pacticipant_name", { consumer_name_in_contract: contract[:consumerName], pacticipant_name: values[:pacticipantName] }))
57
30
  end
58
31
  end
59
- errors
60
- end
61
-
62
- def self.validate_consumer_name(params, contract, i, errors)
63
- if params[:pacticipantName] && contract[:consumerName] && (contract[:consumerName] != params[:pacticipantName])
64
- add_contract_error(:consumerName, validation_message("consumer_name_in_contract_mismatch_pacticipant_name", { consumer_name_in_contract: contract[:consumerName], pacticipant_name: params[:pacticipantName] } ), i, errors)
65
- end
66
- end
67
-
68
- def self.validate_consumer_name_in_content(params, contract, i, errors)
69
- consumer_name_in_content = contract.dig(:decodedParsedContent, :consumer, :name)
70
- if consumer_name_in_content && consumer_name_in_content != params[:pacticipantName]
71
- add_contract_error(:consumerName, validation_message("consumer_name_in_content_mismatch_pacticipant_name", { consumer_name_in_content: consumer_name_in_content, pacticipant_name: params[:pacticipantName] } ), i, errors)
72
- end
73
- end
74
-
75
- def self.validate_provider_name_in_content(contract, i, errors)
76
- provider_name_in_content = contract.dig(:decodedParsedContent, :provider, :name)
77
- if provider_name_in_content && provider_name_in_content != contract[:providerName]
78
- add_contract_error(:providerName, validation_message("provider_name_in_content_mismatch", { provider_name_in_content: provider_name_in_content, provider_name: contract[:providerName] } ), i, errors)
79
- end
80
- end
81
-
82
- def self.validate_encoding(contract, i, errors)
83
- if contract[:decodedContent].nil?
84
- add_contract_error(:content, message("errors.base64?", scope: nil), i, errors)
85
- end
86
- end
87
-
88
- def self.validate_content_matches_content_type(contract, i, errors)
89
- if contract[:decodedParsedContent].nil? && contract[:contentType]
90
- add_contract_error(:content, validation_message("invalid_content_for_content_type", { content_type: contract[:contentType]}), i, errors)
91
- end
92
- end
93
-
94
-
95
- def self.add_contract_error(field, message, i, errors)
96
- errors[:contracts] ||= {}
97
- errors[:contracts][i] ||= {}
98
- errors[:contracts][i][field] ||= []
99
- errors[:contracts][i][field] << message
100
- errors
101
- end
102
-
103
- # Need to fix this whole dry-validation eff up
104
- def self.select_first_message(results)
105
- case results
106
- when Hash then results.each_with_object({}) { |(key, value), new_hash| new_hash[key] = select_first_message(value) }
107
- when Array then select_first_message_from_array(results)
108
- else
109
- results
110
- end
111
- end
112
-
113
- def self.select_first_message_from_array(results)
114
- if results.all?{ |value| value.is_a?(String) }
115
- results[0...1]
116
- else
117
- results.collect { |value| select_first_message(value) }
118
- end
119
32
  end
120
33
  end
121
34
  end
@@ -1,45 +1,40 @@
1
1
  require "pact_broker/api/contracts/base_contract"
2
+ require "pact_broker/api/contracts/validation_helpers"
2
3
 
3
4
  module PactBroker
4
5
  module Api
5
6
  module Contracts
6
7
  class PutPacticipantNameContract < BaseContract
7
- property :name
8
- property :name_in_pact
9
- property :pacticipant
10
- property :message_args
8
+ json do
9
+ required(:name).maybe(:string)
10
+ required(:name_in_pact).maybe(:string)
11
+ end
11
12
 
12
- validation do
13
- configure do
14
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
13
+ rule(:name, :name_in_pact) do
14
+ if name_in_pact_does_not_match_name_in_url_path?(values)
15
+ key.failure(validation_message("pact_name_in_path_mismatch_name_in_pact", name_in_pact: values[:name_in_pact], name_in_path: values[:name]))
15
16
  end
17
+ end
16
18
 
17
- required(:name).maybe
18
- required(:name_in_pact).maybe
19
-
20
- rule(name_in_path_matches_name_in_pact?: [:name, :name_in_pact]) do |name, name_in_pact|
21
- name_in_pact.filled?.then(name.eql?(value(:name_in_pact)))
22
- end
19
+ def name_in_pact_does_not_match_name_in_url_path?(values)
20
+ provided?(values[:name_in_pact]) && values[:name] != values[:name_in_pact]
23
21
  end
24
22
  end
25
23
 
26
24
  class PutPactParamsContract < BaseContract
27
- property :consumer_version_number
28
- property :consumer, form: PutPacticipantNameContract
29
- property :provider, form: PutPacticipantNameContract
25
+ json do
26
+ required(:consumer).filled(:hash)
27
+ required(:provider).filled(:hash)
28
+ required(:consumer_version_number).filled(:string)
29
+ end
30
30
 
31
- validation do
32
- configure do
33
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
31
+ rule(:consumer).validate(validate_with_contract: PutPacticipantNameContract)
32
+ rule(:provider).validate(validate_with_contract: PutPacticipantNameContract)
34
33
 
35
- def valid_consumer_version_number?(value)
36
- return true if PactBroker.configuration.order_versions_by_date
37
- parsed_version_number = PactBroker.configuration.version_parser.call(value)
38
- !parsed_version_number.nil?
39
- end
40
- end
34
+ rule(:consumer_version_number).validate(:not_blank_if_present)
41
35
 
42
- required(:consumer_version_number).filled(:valid_consumer_version_number?)
36
+ rule(:consumer_version_number) do
37
+ validate_version_number(value, key) if !rule_error?(:consumer_version_number)
43
38
  end
44
39
  end
45
40
  end
@@ -0,0 +1,19 @@
1
+ module PactBroker
2
+ module Api
3
+ module Contracts
4
+ module UTF8Validation
5
+ extend self
6
+
7
+ def fragment_before_invalid_utf_8_char(string)
8
+ string.force_encoding("UTF-8").each_char.with_index do | char, index |
9
+ if !char.valid_encoding?
10
+ fragment = index < 100 ? string[0...index] : string[index-100...index]
11
+ return index + 1, fragment
12
+ end
13
+ end
14
+ nil
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ require "pact_broker/services"
2
+ require "pact_broker/string_refinements"
3
+ require "pact_broker/configuration"
4
+ require "uri"
5
+
6
+ module PactBroker
7
+ module Api
8
+ module Contracts
9
+ module ValidationHelpers
10
+ extend self
11
+ using PactBroker::StringRefinements
12
+
13
+ def multiple_lines?(value)
14
+ value && value.is_a?(String) && value.include?("\n")
15
+ end
16
+
17
+ def includes_space?(value)
18
+ value && value.is_a?(String) && value.include?(" ")
19
+ end
20
+
21
+ # @return true if there is a value present, and it only contains whitespace
22
+ def blank?(value)
23
+ value&.blank?
24
+ end
25
+
26
+ # The tins gem has screwed up the present? method by not using refinements
27
+ # Return true if the object is not nil, and if a String, is not blank.
28
+ # @param [Object]
29
+ def provided?(value)
30
+ if value.is_a?(String)
31
+ value.strip.size > 0
32
+ else
33
+ !value.nil?
34
+ end
35
+ end
36
+
37
+ def not_provided?(value)
38
+ !provided?(value)
39
+ end
40
+
41
+ def valid_url?(url)
42
+ URI(url)
43
+ rescue URI::InvalidURIError, ArgumentError
44
+ false
45
+ end
46
+
47
+ def valid_http_method?(http_method)
48
+ Net::HTTP.const_defined?(http_method.capitalize)
49
+ rescue StandardError
50
+ false
51
+ end
52
+
53
+ def pacticipant_with_name_exists?(value)
54
+ PactBroker::Services.pacticipant_service.find_pacticipant_by_name(value)
55
+ end
56
+
57
+ def environment_with_name_exists?(value)
58
+ PactBroker::Services.environment_service.find_by_name(value)
59
+ end
60
+
61
+ def valid_version_number?(value)
62
+ if PactBroker.configuration.order_versions_by_date
63
+ true
64
+ else
65
+ !!PactBroker.configuration.version_parser.call(value)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,41 +1,22 @@
1
1
  require "pact_broker/api/contracts/base_contract"
2
- require "uri"
3
2
 
4
3
  module PactBroker
5
4
  module Api
6
5
  module Contracts
7
6
  class VerificationContract < BaseContract
7
+ json do
8
+ required(:success).filled(:bool)
9
+ required(:providerApplicationVersion).filled(:string)
10
+ optional(:buildUrl).maybe(:string)
11
+ end
8
12
 
9
- property :success
10
- property :provider_version, as: :providerApplicationVersion
11
- property :build_url, as: :buildUrl
12
-
13
- validation do
14
- configure do
15
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
16
-
17
- def not_blank? value
18
- value && value.to_s.strip.size > 0
19
- end
20
-
21
- def valid_url? url
22
- URI(url)
23
- rescue URI::InvalidURIError, ArgumentError
24
- nil
25
- end
26
-
27
- def valid_version_number?(value)
28
- return true if PactBroker.configuration.order_versions_by_date
29
-
30
- parsed_version_number = PactBroker.configuration.version_parser.call(value)
31
- !!parsed_version_number
32
- end
33
- end
13
+ rule(:providerApplicationVersion).validate(:not_blank_if_present)
34
14
 
35
- required(:success).filled(:bool?)
36
- required(:provider_version) { not_blank? & valid_version_number? }
37
- optional(:build_url).maybe(:valid_url?)
15
+ rule(:providerApplicationVersion) do
16
+ validate_version_number(value, key) unless rule_error?(:providerApplicationVersion)
38
17
  end
18
+
19
+ rule(:buildUrl).validate(:valid_url_if_present)
39
20
  end
40
21
  end
41
22
  end
@@ -1,7 +1,6 @@
1
1
  require "pact_broker/api/contracts/base_contract"
2
- require "pact_broker/webhooks/check_host_whitelist"
3
- require "pact_broker/webhooks/render"
4
- require "pact_broker/pacticipants/service"
2
+ require "pact_broker/api/contracts/webhook_request_contract"
3
+ require "pact_broker/api/contracts/webhook_pacticipant_contract"
5
4
  require "pact_broker/webhooks/webhook_event"
6
5
 
7
6
  module PactBroker
@@ -9,180 +8,31 @@ module PactBroker
9
8
  module Contracts
10
9
  class WebhookContract < BaseContract
11
10
 
12
- def validate(*)
13
- result = super
14
- # I just cannot seem to get the validation to stop on the first error.
15
- # If one rule fails, they all come back failed, and it's driving me nuts.
16
- # Why on earth would I want that behaviour?
17
- # I cannot believe I have to do this shit.
18
- @first_errors = errors
19
- @first_errors.messages.keys.each do | key |
20
- @first_errors.messages[key] = @first_errors.messages[key][0...1]
21
- end
22
-
23
- # rubocop: disable Lint/NestedMethodDefinition
24
- def self.errors; @first_errors end
25
- # rubocop: enable Lint/NestedMethodDefinition
26
-
27
- result
28
- end
29
-
30
- validation do
31
- configure do
32
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
33
- end
34
-
35
- optional(:consumer)
36
- optional(:provider)
37
- required(:request).filled
38
- optional(:events).maybe(min_size?: 1)
39
- end
40
-
41
- property :consumer do
42
- property :name
43
- property :label
44
-
45
- validation do
46
- configure do
47
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
48
-
49
- def pacticipant_exists?(name)
50
- !!PactBroker::Pacticipants::Service.find_pacticipant_by_name(name)
51
- end
52
- end
53
-
54
- optional(:name)
55
- .maybe(:pacticipant_exists?)
56
- .when(:none?) { value(:label).filled? }
57
-
58
- optional(:label)
59
- .maybe(:str?)
60
- .when(:none?) { value(:name).filled? }
61
-
62
- rule(label: [:name, :label]) do |name, label|
63
- (name.filled? & label.filled?) > label.none?
64
- end
65
- end
66
- end
67
-
68
- property :provider do
69
- property :name
70
- property :label
71
-
72
- validation do
73
- configure do
74
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
75
-
76
- def pacticipant_exists?(name)
77
- !!PactBroker::Pacticipants::Service.find_pacticipant_by_name(name)
78
- end
79
- end
11
+ UUID_REGEX = /^[A-Za-z0-9_\-]{16,}$/
80
12
 
81
- optional(:name)
82
- .maybe(:pacticipant_exists?)
83
- .when(:none?) { value(:label).filled? }
84
-
85
- optional(:label)
86
- .maybe(:str?)
87
- .when(:none?) { value(:name).filled? }
88
-
89
- rule(label: [:name, :label]) do |name, label|
90
- (name.filled? & label.filled?) > label.none?
91
- end
13
+ class EventContract < BaseContract
14
+ json do
15
+ required(:name).filled(included_in?: PactBroker::Webhooks::WebhookEvent::EVENT_NAMES)
92
16
  end
93
17
  end
94
18
 
95
- property :request do
96
- property :url
97
- property :http_method
98
-
99
- validation do
100
- configure do
101
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
102
-
103
- def self.messages
104
- super.merge(
105
- en: {
106
- errors: {
107
- allowed_webhook_method?: http_method_error_message,
108
- allowed_webhook_scheme?: scheme_error_message,
109
- allowed_webhook_host?: host_error_message
110
- }
111
- }
112
- )
113
- end
114
-
115
- def self.http_method_error_message
116
- if PactBroker.configuration.webhook_http_method_whitelist.size == 1
117
- "must be #{PactBroker.configuration.webhook_http_method_whitelist.first}. See /doc/webhooks#whitelist for more information."
118
- else
119
- "must be one of #{PactBroker.configuration.webhook_http_method_whitelist.join(", ")}. See /doc/webhooks#whitelist for more information."
120
- end
121
- end
122
-
123
- def self.scheme_error_message
124
- "scheme must be #{PactBroker.configuration.webhook_scheme_whitelist.join(", ")}. See /doc/webhooks#whitelist for more information."
125
- end
126
-
127
- def self.host_error_message
128
- "host must be in the whitelist #{PactBroker.configuration.webhook_host_whitelist.join(",")}. See /doc/webhooks#whitelist for more information."
129
- end
130
-
131
- def valid_method?(http_method)
132
- Net::HTTP.const_defined?(http_method.capitalize)
133
- end
134
-
135
- def valid_url?(url)
136
- uri = parse_uri(url)
137
- uri.scheme && uri.host
138
- rescue URI::InvalidURIError, ArgumentError
139
- nil
140
- end
141
-
142
- def allowed_webhook_method?(http_method)
143
- PactBroker.configuration.webhook_http_method_whitelist.any? do | allowed_method |
144
- http_method.downcase == allowed_method.downcase
145
- end
146
- end
147
-
148
- def allowed_webhook_scheme?(url)
149
- scheme = parse_uri(url).scheme
150
- PactBroker.configuration.webhook_scheme_whitelist.any? do | allowed_scheme |
151
- scheme.downcase == allowed_scheme.downcase
152
- end
153
- end
154
-
155
- def allowed_webhook_host?(url)
156
- if host_whitelist.any?
157
- PactBroker::Webhooks::CheckHostWhitelist.call(parse_uri(url).host, host_whitelist).any?
158
- else
159
- true
160
- end
161
- end
162
-
163
- def non_templated_host?(url)
164
- parse_uri(url).host == parse_uri(url, "differentplaceholder").host
165
- end
166
-
167
- def host_whitelist
168
- PactBroker.configuration.webhook_host_whitelist
169
- end
170
-
171
- def parse_uri(uri_string, placeholder = "placeholder")
172
- URI(PactBroker::Webhooks::Render.render_with_placeholder(uri_string, placeholder))
173
- end
174
- end
175
-
176
- required(:http_method).filled(:valid_method?, :allowed_webhook_method?)
177
- required(:url).filled(:valid_url?, :allowed_webhook_scheme?, :allowed_webhook_host?, :non_templated_host?)
178
- end
19
+ json do
20
+ optional(:uuid).maybe(:string) # set in resource class from the path info - doesn't come in in the request body
21
+ optional(:consumer).maybe(:hash)
22
+ optional(:provider).maybe(:hash)
23
+ required(:request).filled(:hash)
24
+ optional(:events).maybe(min_size?: 1)
25
+ optional(:enabled).filled(:bool)
179
26
  end
180
27
 
181
- collection :events do
182
- property :name
28
+ rule(:consumer).validate(validate_with_contract: WebhookPacticipantContract)
29
+ rule(:provider).validate(validate_with_contract: WebhookPacticipantContract)
30
+ rule(:request).validate(validate_with_contract: WebhookRequestContract)
31
+ rule(:events).validate(validate_each_with_contract: EventContract)
183
32
 
184
- validation do
185
- required(:name).filled(included_in?: PactBroker::Webhooks::WebhookEvent::EVENT_NAMES)
33
+ rule(:uuid) do
34
+ if value && !(value =~ UUID_REGEX)
35
+ key.failure(validation_message("invalid_webhook_uuid"))
186
36
  end
187
37
  end
188
38
  end
@@ -0,0 +1,33 @@
1
+ require "pact_broker/api/contracts/base_contract"
2
+
3
+ module PactBroker
4
+ module Api
5
+ module Contracts
6
+ class WebhookPacticipantContract < BaseContract
7
+ json do
8
+ optional(:name).maybe(:string)
9
+ optional(:label).maybe(:string)
10
+ end
11
+
12
+ register_macro(:name_or_label_required) do
13
+ if !provided?(values[:name]) && !provided?(values[:label])
14
+ key(path.keys).failure(validation_message("blank"))
15
+ end
16
+ end
17
+
18
+ register_macro(:name_and_label_exclusive) do
19
+ if provided?(values[:name]) && provided?(values[:label])
20
+ key([:label]).failure(validation_message("cannot_be_provided_at_same_time", name_1: "name", name_2: "label"))
21
+ end
22
+ end
23
+
24
+ rule(:name, :label).validate(:name_or_label_required)
25
+ rule(:name, :label).validate(:name_and_label_exclusive)
26
+
27
+ rule(:name) do
28
+ validate_pacticipant_with_name_exists(value, key) if provided?(value)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end