pact_broker 2.106.0 → 2.107.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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/Gemfile +3 -0
  4. data/db/migrations/20230131_add_cons_ver_id_ndx_to_latest_pp_id_for_cons_ver.rb +8 -4
  5. data/db/migrations/20230216_add_branch_heads_branch_version_id_index.rb +21 -0
  6. data/db/migrations/20230428_add_index_for_webhook_executions_pact_publication_id.rb +17 -0
  7. data/lib/pact_broker/api/contracts/base_contract.rb +22 -7
  8. data/lib/pact_broker/api/contracts/can_i_deploy_query_schema.rb +34 -0
  9. data/lib/pact_broker/api/contracts/configuration.rb +2 -0
  10. data/lib/pact_broker/api/contracts/consumer_version_selector_contract.rb +140 -0
  11. data/lib/pact_broker/api/contracts/dry_validation_errors_formatter.rb +50 -0
  12. data/lib/pact_broker/api/contracts/dry_validation_macros.rb +79 -0
  13. data/lib/pact_broker/api/contracts/dry_validation_methods.rb +71 -0
  14. data/lib/pact_broker/api/contracts/environment_schema.rb +19 -33
  15. data/lib/pact_broker/api/contracts/pacticipant_create_schema.rb +4 -17
  16. data/lib/pact_broker/api/contracts/pacticipant_schema.rb +15 -24
  17. data/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb +37 -146
  18. data/lib/pact_broker/api/contracts/pacts_for_verification_query_string_schema.rb +7 -20
  19. data/lib/pact_broker/api/contracts/publish_contracts_contract_contract.rb +62 -0
  20. data/lib/pact_broker/api/contracts/publish_contracts_schema.rb +25 -124
  21. data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +21 -26
  22. data/lib/pact_broker/api/contracts/utf_8_validation.rb +2 -0
  23. data/lib/pact_broker/api/contracts/validation_helpers.rb +71 -0
  24. data/lib/pact_broker/api/contracts/verification_contract.rb +10 -29
  25. data/lib/pact_broker/api/contracts/webhook_contract.rb +20 -172
  26. data/lib/pact_broker/api/contracts/webhook_pacticipant_contract.rb +33 -0
  27. data/lib/pact_broker/api/contracts/webhook_request_contract.rb +125 -0
  28. data/lib/pact_broker/api/contracts.rb +3 -0
  29. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +4 -4
  30. data/lib/pact_broker/api/decorators/dashboard_text_decorator.rb +2 -2
  31. data/lib/pact_broker/api/decorators/extended_pact_decorator.rb +1 -1
  32. data/lib/pact_broker/api/decorators/matrix_decorator.rb +4 -4
  33. data/lib/pact_broker/api/decorators/matrix_text_decorator.rb +1 -1
  34. data/lib/pact_broker/api/decorators/pact_decorator.rb +1 -1
  35. data/lib/pact_broker/api/decorators/pacticipant_collection_decorator.rb +2 -2
  36. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +2 -1
  37. data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +4 -1
  38. data/lib/pact_broker/api/decorators/pagination_links.rb +6 -6
  39. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +4 -4
  40. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +6 -6
  41. data/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb +4 -4
  42. data/lib/pact_broker/api/decorators/webhook_decorator.rb +2 -3
  43. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +5 -12
  44. data/lib/pact_broker/api/resources/all_webhooks.rb +5 -11
  45. data/lib/pact_broker/api/resources/base_resource.rb +3 -20
  46. data/lib/pact_broker/api/resources/branch_version.rb +3 -3
  47. data/lib/pact_broker/api/resources/can_i_deploy.rb +4 -19
  48. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb +1 -4
  49. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb +0 -2
  50. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_badge.rb +1 -2
  51. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +2 -2
  52. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +2 -2
  53. data/lib/pact_broker/api/resources/dashboard.rb +3 -3
  54. data/lib/pact_broker/api/resources/deployed_version.rb +1 -1
  55. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +2 -2
  56. data/lib/pact_broker/api/resources/environment.rb +1 -1
  57. data/lib/pact_broker/api/resources/environments.rb +2 -2
  58. data/lib/pact_broker/api/resources/error_handling_methods.rb +2 -2
  59. data/lib/pact_broker/api/resources/integrations.rb +1 -1
  60. data/lib/pact_broker/api/resources/label.rb +1 -1
  61. data/lib/pact_broker/api/resources/latest_pact.rb +2 -2
  62. data/lib/pact_broker/api/resources/latest_pacts.rb +1 -1
  63. data/lib/pact_broker/api/resources/latest_verifications_for_consumer_version.rb +1 -1
  64. data/lib/pact_broker/api/resources/matrix.rb +2 -2
  65. data/lib/pact_broker/api/resources/matrix_for_consumer_and_provider.rb +1 -1
  66. data/lib/pact_broker/api/resources/pact.rb +7 -4
  67. data/lib/pact_broker/api/resources/pact_triggered_webhooks.rb +1 -1
  68. data/lib/pact_broker/api/resources/pact_versions.rb +1 -1
  69. data/lib/pact_broker/api/resources/pact_webhooks.rb +7 -14
  70. data/lib/pact_broker/api/resources/pact_webhooks_status.rb +6 -2
  71. data/lib/pact_broker/api/resources/pacticipant.rb +1 -1
  72. data/lib/pact_broker/api/resources/pacticipant_webhooks.rb +7 -5
  73. data/lib/pact_broker/api/resources/pacticipants.rb +2 -2
  74. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +1 -1
  75. data/lib/pact_broker/api/resources/previous_distinct_pact_version.rb +1 -1
  76. data/lib/pact_broker/api/resources/provider_pacts.rb +1 -1
  77. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +4 -13
  78. data/lib/pact_broker/api/resources/publish_contracts.rb +8 -3
  79. data/lib/pact_broker/api/resources/released_version.rb +1 -1
  80. data/lib/pact_broker/api/resources/released_versions_for_version_and_environment.rb +2 -2
  81. data/lib/pact_broker/api/resources/tag.rb +1 -1
  82. data/lib/pact_broker/api/resources/tagged_pact_versions.rb +1 -1
  83. data/lib/pact_broker/api/resources/triggered_webhook_logs.rb +2 -2
  84. data/lib/pact_broker/api/resources/verification.rb +2 -2
  85. data/lib/pact_broker/api/resources/verification_triggered_webhooks.rb +1 -1
  86. data/lib/pact_broker/api/resources/verifications.rb +4 -6
  87. data/lib/pact_broker/api/resources/version.rb +1 -1
  88. data/lib/pact_broker/api/resources/versions.rb +1 -1
  89. data/lib/pact_broker/api/resources/webhook.rb +7 -6
  90. data/lib/pact_broker/api/resources/webhook_execution.rb +6 -4
  91. data/lib/pact_broker/api.rb +3 -12
  92. data/lib/pact_broker/certificates/certificate.rb +1 -0
  93. data/lib/pact_broker/config/setting.rb +1 -0
  94. data/lib/pact_broker/contracts/service.rb +1 -0
  95. data/lib/pact_broker/date_helper.rb +1 -1
  96. data/lib/pact_broker/db/clean_incremental.rb +1 -1
  97. data/lib/pact_broker/db/delete_overwritten_data.rb +6 -2
  98. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -0
  99. data/lib/pact_broker/deployments/deployed_version.rb +2 -0
  100. data/lib/pact_broker/deployments/deployed_version_service.rb +5 -1
  101. data/lib/pact_broker/deployments/environment.rb +2 -0
  102. data/lib/pact_broker/deployments/environment_service.rb +4 -3
  103. data/lib/pact_broker/deployments/released_version.rb +2 -0
  104. data/lib/pact_broker/deployments/released_version_service.rb +4 -0
  105. data/lib/pact_broker/diagnostic/resources/base_resource.rb +1 -1
  106. data/lib/pact_broker/doc/views/index/publish-contracts.markdown +5 -5
  107. data/lib/pact_broker/domain/label.rb +1 -0
  108. data/lib/pact_broker/domain/tag.rb +2 -0
  109. data/lib/pact_broker/domain/verification.rb +1 -1
  110. data/lib/pact_broker/domain/version.rb +4 -1
  111. data/lib/pact_broker/domain/webhook.rb +1 -1
  112. data/lib/pact_broker/index/service.rb +1 -1
  113. data/lib/pact_broker/integrations/integration.rb +1 -0
  114. data/lib/pact_broker/locale/en.yml +35 -14
  115. data/lib/pact_broker/matrix/query_ids.rb +4 -4
  116. data/lib/pact_broker/matrix/resolved_selector.rb +6 -1
  117. data/lib/pact_broker/matrix/service.rb +1 -0
  118. data/lib/pact_broker/messages.rb +5 -1
  119. data/lib/pact_broker/pacticipants/repository.rb +12 -3
  120. data/lib/pact_broker/pacticipants/service.rb +7 -0
  121. data/lib/pact_broker/pacts/pact_params.rb +6 -17
  122. data/lib/pact_broker/pacts/pact_version.rb +1 -0
  123. data/lib/pact_broker/policies.rb +4 -4
  124. data/lib/pact_broker/test/http_test_data_builder.rb +46 -2
  125. data/lib/pact_broker/ui/app.rb +2 -2
  126. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +1 -1
  127. data/lib/pact_broker/verifications/pact_version_provider_tag_successful_verification.rb +1 -0
  128. data/lib/pact_broker/verifications/service.rb +0 -6
  129. data/lib/pact_broker/version.rb +1 -1
  130. data/lib/pact_broker/versions/branch.rb +1 -0
  131. data/lib/pact_broker/versions/branch_head.rb +2 -1
  132. data/lib/pact_broker/versions/branch_version.rb +11 -0
  133. data/lib/pact_broker/webhooks/execution.rb +1 -1
  134. data/lib/pact_broker/webhooks/repository.rb +1 -1
  135. data/lib/pact_broker/webhooks/service.rb +3 -25
  136. data/lib/pact_broker/webhooks/triggered_webhook.rb +1 -0
  137. data/lib/pact_broker/webhooks/webhook_event.rb +1 -0
  138. data/lib/pact_broker/webmachine.rb +22 -0
  139. data/lib/rack/pact_broker/invalid_uri_protection.rb +1 -1
  140. data/lib/rack/pact_broker/use_when.rb +6 -5
  141. data/lib/sequel/plugins/age.rb +13 -0
  142. data/lib/webmachine/application_monkey_patch.rb +5 -0
  143. data/lib/webmachine/describe_routes.rb +35 -8
  144. data/lib/webmachine/render_error_monkey_patch.rb +1 -1
  145. data/pact_broker.gemspec +7 -17
  146. metadata +65 -67
  147. data/lib/pact/doc/README.md +0 -5
  148. data/lib/pact_broker/api/contracts/dry_validation_predicates.rb +0 -36
  149. data/lib/pact_broker/api/contracts/dry_validation_workarounds.rb +0 -39
  150. data/lib/pact_broker/api/contracts/pacticipant_name_contract.rb +0 -24
  151. data/lib/pact_broker/api/contracts/pacticipant_name_validation.rb +0 -30
  152. data/lib/pact_broker/api/contracts/request_validations.rb +0 -33
  153. data/lib/pact_broker/api/resources/webhook_resource_methods.rb +0 -17
  154. data/lib/pact_broker/matrix/can_i_deploy_query_schema.rb +0 -46
@@ -1,33 +1,24 @@
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"
5
2
 
6
3
  module PactBroker
7
4
  module Api
8
5
  module Contracts
9
- class PacticipantSchema
10
- extend DryValidationWorkarounds
11
- extend PactBroker::Messages
12
- using PactBroker::HashRefinements
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
- optional(:name).filled(:str?, :single_line?)
20
- optional(:displayName).maybe(:str?, :single_line?, :not_blank?)
21
- optional(:mainBranch).maybe(:str?, :single_line?, :no_spaces?)
22
- optional(:repositoryUrl).maybe(:str?, :single_line?)
23
- optional(:repositoryName).maybe(:str?, :single_line?)
24
- optional(:repositoryNamespace).maybe(:str?, :single_line?)
6
+ class PacticipantSchema < BaseContract
7
+ json do
8
+ optional(:name).filled(:string)
9
+ optional(:displayName).maybe(:string)
10
+ optional(:mainBranch).maybe(:string)
11
+ optional(:repositoryUrl).maybe(:string)
12
+ optional(:repositoryName).maybe(:string)
13
+ optional(:repositoryNamespace).maybe(:string)
25
14
  end
26
15
 
27
- def self.call(params_with_string_keys)
28
- params = params_with_string_keys&.symbolize_keys
29
- select_first_message(flatten_indexed_messages(SCHEMA.call(params).messages(full: true)))
30
- end
16
+ rule(:name).validate(:not_multiple_lines, :not_blank_if_present)
17
+ rule(:displayName).validate(:not_multiple_lines, :not_blank_if_present)
18
+ rule(:mainBranch).validate(:not_multiple_lines, :no_spaces_if_present, :not_blank_if_present)
19
+ rule(:repositoryUrl).validate(:not_multiple_lines)
20
+ rule(:repositoryName).validate(:not_multiple_lines)
21
+ rule(:repositoryNamespace).validate(:not_multiple_lines)
31
22
  end
32
23
  end
33
24
  end
@@ -1,161 +1,52 @@
1
- require "dry-validation"
2
- require "pact_broker/hash_refinements"
3
- require "pact_broker/string_refinements"
4
- require "pact_broker/api/contracts/dry_validation_workarounds"
5
- require "pact_broker/api/contracts/dry_validation_predicates"
6
- require "pact_broker/messages"
1
+ require "pact_broker/api/contracts/base_contract"
2
+ require "pact_broker/api/contracts/consumer_version_selector_contract"
3
+ require "pact_broker/logging"
7
4
 
8
5
  module PactBroker
9
6
  module Api
10
7
  module Contracts
11
- class PactsForVerificationJSONQuerySchema
12
- extend DryValidationWorkarounds
13
- extend PactBroker::Messages
14
-
15
- using PactBroker::HashRefinements
16
- using PactBroker::StringRefinements
17
-
18
- BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :matchingBranch]
19
- ENVIRONMENT_KEYS = [:environment, :deployed, :released, :deployedOrReleased]
20
- ALL_KEYS = BRANCH_KEYS + ENVIRONMENT_KEYS
21
-
22
- SCHEMA = Dry::Validation.Schema do
23
- configure do
24
- predicates(DryValidationPredicates)
25
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
26
- end
8
+ class PactsForVerificationJSONQuerySchema < BaseContract
9
+ json do
10
+ optional(:providerVersionBranch).maybe(:string)
27
11
  optional(:providerVersionTags).maybe(:array?)
28
- optional(:consumerVersionSelectors).each do
29
- schema do
30
- # configure do
31
- # def self.messages
32
- # super.merge(en: { errors: { fallbackTagMustBeForLatest: 'can only be set if latest=true' }})
33
- # end
34
- # end
35
-
36
- optional(:mainBranch).filled(included_in?: [true])
37
- optional(:tag).filled(:str?)
38
- optional(:branch).filled(:str?)
39
- optional(:matchingBranch).filled(included_in?: [true])
40
- optional(:latest).filled(included_in?: [true, false])
41
- optional(:fallbackTag).filled(:str?)
42
- optional(:fallbackBranch).filled(:str?)
43
- optional(:consumer).filled(:str?, :not_blank?)
44
- optional(:deployed).filled(included_in?: [true])
45
- optional(:released).filled(included_in?: [true])
46
- optional(:deployedOrReleased).filled(included_in?: [true])
47
- optional(:environment).filled(:str?, :environment_with_name_exists?)
48
-
49
- # rule(fallbackTagMustBeForLatest: [:fallbackTag, :latest]) do | fallback_tag, latest |
50
- # fallback_tag.filled?.then(latest.eql?(true))
51
- # end
52
- end
53
- end
12
+ optional(:consumerVersionSelectors).array(:hash)
54
13
  optional(:includePendingStatus).filled(included_in?: [true, false])
55
- optional(:includeWipPactsSince).filled(:date?)
14
+ optional(:includeWipPactsSince).filled(:date)
56
15
  end
57
16
 
58
- def self.call(params)
59
- symbolized_params = params&.symbolize_keys
60
- results = select_first_message(flatten_indexed_messages(SCHEMA.call(symbolized_params).messages(full: true)))
61
- add_cross_field_validation_errors(symbolized_params, results)
62
- results
63
- end
64
-
65
- def self.add_cross_field_validation_errors(params, results)
66
- # This is a ducking joke. Need to get rid of dry-validation
67
- if params[:consumerVersionSelectors].is_a?(Array)
68
- errors = params[:consumerVersionSelectors].each_with_index.flat_map do | selector, index |
69
- validate_consumer_version_selector(selector, index, params)
70
- end
71
- if errors.any?
72
- results[:consumerVersionSelectors] ||= []
73
- results[:consumerVersionSelectors].concat(errors)
17
+ # The original implementation of pacts-for-verification unfortunately went out without any validation on the
18
+ # providerVersionBranch at all (most likely unintentionally.)
19
+ # When we added
20
+ # optional(:providerVersionBranch).filled(:string)
21
+ # during the dry-validation upgrade, we discovered that some users/pact clients were requesting pacts for verification
22
+ # with an empty string in the providerVersionBranch
23
+ # This complicated logic tries to not break those customers as much as possible, while still raising an error
24
+ # when the blank string is most likely a configuration error
25
+ # (eg. when the request performs logic that uses the provider version branch)
26
+ # It allows the providerVersionBranch to be unspecified/nil, as that most likely means the user did not
27
+ # specify the branch at all.
28
+ rule(:providerVersionBranch, :providerVersionTags, :includePendingStatus, :includeWipPactsSince) do
29
+ branch = values[:providerVersionBranch]
30
+
31
+ # a space is a clear user error - don't bother checking further
32
+ if branch && branch.size > 0
33
+ validate_not_blank_if_present(branch, key)
34
+ end
35
+
36
+ if !rule_error?
37
+ tags = values[:providerVersionTags]
38
+ include_pending = values[:includePendingStatus]
39
+ wip = values[:includeWipPactsSince]
40
+
41
+ # There are no tags, the user specified wip or pending, and they set a branch, but the branch is an empty or blank string...
42
+ if !tags&.any? && (wip || include_pending) && branch && ValidationHelpers.blank?(branch)
43
+ # most likely a user error - return a message
44
+ key.failure(validation_message("pacts_for_verification_selector_provider_version_branch_empty"))
74
45
  end
75
46
  end
76
- end
77
-
78
- def self.not_provided?(value)
79
- value.nil? || value.blank?
80
- end
81
-
82
- # when setting a tag, latest=true and latest=false are both valid
83
- # when setting the branch, it doesn't make sense to verify all pacts for a branch,
84
- # so latest is not required, but cannot be set to false
85
- # rubocop: disable Metrics/CyclomaticComplexity, Metrics/MethodLength
86
- def self.validate_consumer_version_selector(selector, index, params)
87
- errors = []
88
-
89
- if selector[:fallbackTag] && !selector[:latest]
90
- errors << "fallbackTag can only be set if latest is true (at index #{index})"
91
- end
92
-
93
- if selector[:fallbackBranch] && selector[:latest] == false
94
- errors << "fallbackBranch can only be set if latest is true (at index #{index})"
95
- end
96
-
97
- if not_provided?(selector[:mainBranch]) &&
98
- not_provided?(selector[:tag]) &&
99
- not_provided?(selector[:branch]) &&
100
- not_provided?(selector[:environment]) &&
101
- selector[:matchingBranch] != true &&
102
- selector[:deployed] != true &&
103
- selector[:released] != true &&
104
- selector[:deployedOrReleased] != true &&
105
- selector[:latest] != true
106
- errors << "must specify a value for environment or tag or branch, or specify mainBranch=true, matchingBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true (at index #{index})"
107
- end
108
-
109
- if selector[:mainBranch] && selector.slice(*ALL_KEYS - [:consumer, :mainBranch]).any?
110
- errors << "cannot specify mainBranch=true with any other criteria apart from consumer (at index #{index})"
111
- end
112
-
113
- if selector[:matchingBranch] && selector.slice(*ALL_KEYS - [:consumer, :matchingBranch]).any?
114
- errors << "cannot specify matchingBranch=true with any other criteria apart from consumer (at index #{index})"
115
- end
116
-
117
- if selector[:tag] && selector[:branch]
118
- errors << "cannot specify both a tag and a branch (at index #{index})"
119
- end
120
-
121
- if selector[:fallbackBranch] && !selector[:branch]
122
- errors << "a branch must be specified when a fallbackBranch is specified (at index #{index})"
123
- end
124
-
125
- if selector[:fallbackTag] && !selector[:tag]
126
- errors << "a tag must be specified when a fallbackTag is specified (at index #{index})"
127
- end
128
-
129
- if selector[:branch] && selector[:latest] == false
130
- errors << "cannot specify a branch with latest=false (at index #{index})"
131
- end
132
-
133
- if selector[:deployed] && selector[:released]
134
- errors << "cannot specify both deployed=true and released=true (at index #{index})"
135
- end
136
-
137
- if selector[:deployed] && selector[:deployedOrReleased]
138
- errors << "cannot specify both deployed=true and deployedOrReleased=true (at index #{index})"
139
- end
140
-
141
- if selector[:released] && selector[:deployedOrReleased]
142
- errors << "cannot specify both released=true and deployedOrReleased=true (at index #{index})"
143
- end
144
-
145
- if selector[:matchingBranch] && not_provided?(params[:providerVersionBranch])
146
- errors << "the providerVersionBranch must be specified when the selector matchingBranch=true is used (at index #{index})"
147
- end
148
-
149
- non_environment_fields = selector.slice(*BRANCH_KEYS).keys.sort
150
- environment_related_fields = selector.slice(*ENVIRONMENT_KEYS).keys.sort
151
-
152
- if (non_environment_fields.any? && environment_related_fields.any?)
153
- errors << "cannot specify the #{pluralize("field", non_environment_fields.count)} #{non_environment_fields.join("/")} with the #{pluralize("field", environment_related_fields.count)} #{environment_related_fields.join("/")} (at index #{index})"
154
- end
155
47
 
156
- errors
157
48
  end
158
- # rubocop: enable Metrics/CyclomaticComplexity, Metrics/MethodLength
49
+ rule(:consumerVersionSelectors).validate(validate_each_with_contract: ConsumerVersionSelectorContract)
159
50
  end
160
51
  end
161
52
  end
@@ -1,34 +1,21 @@
1
- require "dry-validation"
2
- require "pact_broker/api/contracts/dry_validation_workarounds"
3
- require "pact_broker/api/contracts/dry_validation_predicates"
1
+ require "pact_broker/api/contracts/base_contract"
4
2
 
5
3
  module PactBroker
6
4
  module Api
7
5
  module Contracts
8
- class PactsForVerificationQueryStringSchema
9
- extend DryValidationWorkarounds
10
- using PactBroker::HashRefinements
11
-
12
- SCHEMA = Dry::Validation.Schema do
13
- configure do
14
- predicates(DryValidationPredicates)
15
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
16
- end
6
+ class PactsForVerificationQueryStringSchema < BaseContract
7
+ params do
17
8
  optional(:provider_version_tags).maybe(:array?)
18
9
  optional(:consumer_version_selectors).each do
19
10
  schema do
20
- required(:tag).filled(:str?)
11
+ required(:tag).filled(:string)
21
12
  optional(:latest).filled(included_in?: ["true", "false"])
22
- optional(:fallback_tag).filled(:str?)
23
- optional(:consumer).filled(:str?, :not_blank?)
13
+ optional(:fallback_tag).filled(:string)
14
+ optional(:consumer).filled(:string)
24
15
  end
25
16
  end
26
17
  optional(:include_pending_status).filled(included_in?: ["true", "false"])
27
- optional(:include_wip_pacts_since).filled(:date?)
28
- end
29
-
30
- def self.call(params)
31
- select_first_message(flatten_indexed_messages(SCHEMA.call(params&.symbolize_keys).messages(full: true)))
18
+ optional(:include_wip_pacts_since).filled(:date)
32
19
  end
33
20
  end
34
21
  end
@@ -0,0 +1,62 @@
1
+ require "pact_broker/api/contracts/base_contract"
2
+ require "pact_broker/api/contracts/utf_8_validation"
3
+
4
+ # The contract for the contract object in the publish contracts request
5
+ module PactBroker
6
+ module Api
7
+ module Contracts
8
+ class PublishContractsContractContract < BaseContract
9
+ json do
10
+ required(:consumerName).filled(:string)
11
+ required(:providerName).filled(:string)
12
+ required(:content).filled(:string)
13
+ required(:contentType).filled(included_in?: ["application/json"])
14
+ required(:specification).filled(included_in?: ["pact"])
15
+ optional(:onConflict).filled(included_in?:["overwrite", "merge"])
16
+ optional(:decodedParsedContent) # set in the resource
17
+ optional(:decodedContent) # set in the resource
18
+ end
19
+
20
+ rule(:consumerName).validate(:not_blank_if_present)
21
+ rule(:providerName).validate(:not_blank_if_present)
22
+
23
+ # validate_consumer_name_in_content
24
+ rule(:decodedParsedContent, :consumerName, :specification) do
25
+ consumer_name_in_content = values.dig(:decodedParsedContent, :consumer, :name)
26
+ if consumer_name_in_content && consumer_name_in_content != values[:consumerName]
27
+ base.failure(validation_message("consumer_name_in_content_mismatch", { consumer_name_in_content: consumer_name_in_content, consumer_name: values[:consumerName] }))
28
+ end
29
+ end
30
+
31
+ # validate_provider_name_in_content
32
+ rule(:decodedParsedContent, :providerName) do
33
+ provider_name_in_content = values.dig(:decodedParsedContent, :provider, :name)
34
+ if provider_name_in_content && provider_name_in_content != values[:providerName]
35
+ base.failure(validation_message("provider_name_in_content_mismatch", { provider_name_in_content: provider_name_in_content, provider_name: values[:providerName] }))
36
+ end
37
+ end
38
+
39
+ # validate_encoding
40
+ rule(:decodedContent) do
41
+ if value.nil?
42
+ base.failure(validation_message("base64"))
43
+ end
44
+
45
+ if value
46
+ char_number, fragment = PactBroker::Api::Contracts::UTF8Validation.fragment_before_invalid_utf_8_char(value)
47
+ if char_number
48
+ base.failure(validation_message("non_utf_8_char_in_contract", char_number: char_number, fragment: fragment))
49
+ end
50
+ end
51
+ end
52
+
53
+ # validate_content_matches_content_type
54
+ rule(:decodedParsedContent, :contentType) do
55
+ if values[:decodedParsedContent].nil? && values[:contentType]
56
+ base.failure(validation_message("invalid_content_for_content_type", { content_type: values[:contentType] }))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,134 +1,35 @@
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"
5
- require "pact_broker/api/contracts/utf_8_validation"
1
+ require "pact_broker/api/contracts/base_contract"
2
+ require "pact_broker/api/contracts/publish_contracts_contract_contract"
6
3
 
7
4
  module PactBroker
8
5
  module Api
9
6
  module Contracts
10
- class PublishContractsSchema
11
- extend DryValidationWorkarounds
12
- using PactBroker::HashRefinements
13
- extend PactBroker::Messages
14
-
15
- class << self
16
- include PactBroker::Api::Contracts::UTF8Validation
17
- end
18
-
19
- SCHEMA = Dry::Validation.Schema do
20
- configure do
21
- predicates(DryValidationPredicates)
22
- config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
23
- end
24
-
25
- required(:pacticipantName).filled(:str?, :not_blank?)
26
- required(:pacticipantVersionNumber).filled(:not_blank?, :single_line?)
27
- optional(:tags).each(:not_blank?, :single_line?)
28
- optional(:branch).maybe(:not_blank?, :single_line?)
29
- optional(:buildUrl).maybe(:single_line?)
30
-
31
- required(:contracts).each do
32
- required(:consumerName).filled(:str?, :not_blank?)
33
- required(:providerName).filled(:str?, :not_blank?)
34
- required(:content).filled(:str?)
35
- required(:contentType).filled(included_in?: ["application/json"])
36
- required(:specification).filled(included_in?: ["pact"])
37
- optional(:onConflict).filled(included_in?:["overwrite", "merge"])
38
- end
39
- end
40
-
41
- def self.call(params)
42
- dry_results = SCHEMA.call(params&.symbolize_keys).messages(full: true)
43
- dry_results.then do | results |
44
- add_cross_field_validation_errors(params&.symbolize_keys, results)
45
- end.then do | results |
46
- select_first_message(results)
47
- end.then do | results |
48
- flatten_indexed_messages(results)
49
- end
50
- end
51
-
52
- def self.add_cross_field_validation_errors(params, errors)
53
- if params[:contracts].is_a?(Array)
54
- params[:contracts].each_with_index do | contract, i |
55
- if contract.is_a?(Hash)
56
- validate_consumer_name(params, contract, i, errors)
57
- validate_consumer_name_in_content(params, contract, i, errors)
58
- validate_provider_name_in_content(contract, i, errors)
59
- validate_encoding(contract, i, errors)
60
- validate_content_matches_content_type(contract, i, errors)
61
- end
62
- end
63
- end
64
- errors
65
- end
66
-
67
- def self.validate_consumer_name(params, contract, i, errors)
68
- if params[:pacticipantName] && contract[:consumerName] && (contract[:consumerName] != params[:pacticipantName])
69
- 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)
70
- end
71
- end
72
-
73
- def self.validate_consumer_name_in_content(params, contract, i, errors)
74
- consumer_name_in_content = contract.dig(:decodedParsedContent, :consumer, :name)
75
- if consumer_name_in_content && consumer_name_in_content != params[:pacticipantName]
76
- 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)
77
- end
78
- end
79
-
80
- def self.validate_provider_name_in_content(contract, i, errors)
81
- provider_name_in_content = contract.dig(:decodedParsedContent, :provider, :name)
82
- if provider_name_in_content && provider_name_in_content != contract[:providerName]
83
- 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)
84
- end
85
- end
86
-
87
- def self.validate_encoding(contract, i, errors)
88
- if contract[:decodedContent].nil?
89
- add_contract_error(:content, message("errors.base64?", scope: nil), i, errors)
90
- end
91
-
92
- if contract[:decodedContent]
93
- char_number, fragment = fragment_before_invalid_utf_8_char(contract[:decodedContent])
94
- if char_number
95
- error_message = message("errors.non_utf_8_char_in_contract", char_number: char_number, fragment: fragment)
96
- add_contract_error(:content, error_message, i, errors)
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] }))
97
30
  end
98
31
  end
99
32
  end
100
-
101
- def self.validate_content_matches_content_type(contract, i, errors)
102
- if contract[:decodedParsedContent].nil? && contract[:contentType]
103
- add_contract_error(:content, validation_message("invalid_content_for_content_type", { content_type: contract[:contentType]}), i, errors)
104
- end
105
- end
106
-
107
- def self.add_contract_error(field, message, i, errors)
108
- errors[:contracts] ||= {}
109
- errors[:contracts][i] ||= {}
110
- errors[:contracts][i][field] ||= []
111
- errors[:contracts][i][field] << message
112
- errors
113
- end
114
-
115
- # Need to fix this whole dry-validation eff up
116
- def self.select_first_message(results)
117
- case results
118
- when Hash then results.each_with_object({}) { |(key, value), new_hash| new_hash[key] = select_first_message(value) }
119
- when Array then select_first_message_from_array(results)
120
- else
121
- results
122
- end
123
- end
124
-
125
- def self.select_first_message_from_array(results)
126
- if results.all?{ |value| value.is_a?(String) }
127
- results[0...1]
128
- else
129
- results.collect { |value| select_first_message(value) }
130
- end
131
- end
132
33
  end
133
34
  end
134
35
  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
@@ -2,6 +2,8 @@ module PactBroker
2
2
  module Api
3
3
  module Contracts
4
4
  module UTF8Validation
5
+ extend self
6
+
5
7
  def fragment_before_invalid_utf_8_char(string)
6
8
  string.force_encoding("UTF-8").each_char.with_index do | char, index |
7
9
  if !char.valid_encoding?
@@ -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