pact_broker 2.106.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 (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 +6 -17
  146. metadata +56 -72
  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