pact_broker 2.105.0 → 2.106.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/README.md +1 -1
  4. data/db/migrations/20221130_add_provider_version_id_index_to_verifications.rb +28 -0
  5. data/db/migrations/20221208_add_index_to_pact_version_provider_tag_successful_verifications.rb +21 -0
  6. data/db/migrations/20221215_add_prov_ver_id_ndx_to_latest_verifi_id_for_pact_ver_and_prov_ver.rb +21 -0
  7. data/db/migrations/20230131_add_cons_ver_id_ndx_to_latest_pp_id_for_cons_ver.rb +17 -0
  8. data/docs/CONFIGURATION.md +1 -1
  9. data/docs/api/PAGINATION.md +43 -0
  10. data/lib/pact_broker/api/contracts/publish_contracts_schema.rb +13 -1
  11. data/lib/pact_broker/api/contracts/utf_8_validation.rb +17 -0
  12. data/lib/pact_broker/api/contracts/webhook_contract.rb +2 -0
  13. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +36 -0
  14. data/lib/pact_broker/api/decorators/pacticipant_collection_decorator.rb +3 -0
  15. data/lib/pact_broker/api/decorators/pagination_links.rb +11 -0
  16. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +34 -0
  17. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +66 -0
  18. data/lib/pact_broker/api/resources/badge_methods.rb +1 -1
  19. data/lib/pact_broker/api/resources/base_resource.rb +28 -48
  20. data/lib/pact_broker/api/resources/error_handling_methods.rb +57 -0
  21. data/lib/pact_broker/api/resources/error_response_generator.rb +70 -0
  22. data/lib/pact_broker/api/resources/latest_verifications_for_consumer_version.rb +1 -1
  23. data/lib/pact_broker/api/resources/pact_version.rb +1 -1
  24. data/lib/pact_broker/api/resources/pacticipants.rb +4 -1
  25. data/lib/pact_broker/api/resources/pagination_methods.rb +8 -4
  26. data/lib/pact_broker/api/resources/publish_contracts.rb +1 -1
  27. data/lib/pact_broker/api/resources/versions.rb +5 -12
  28. data/lib/pact_broker/api.rb +3 -4
  29. data/lib/pact_broker/app.rb +0 -2
  30. data/lib/pact_broker/application_context.rb +4 -4
  31. data/lib/pact_broker/db/clean_incremental.rb +67 -59
  32. data/lib/pact_broker/index/service.rb +1 -1
  33. data/lib/pact_broker/locale/en.yml +1 -0
  34. data/lib/pact_broker/pacticipants/repository.rb +4 -11
  35. data/lib/pact_broker/pacticipants/service.rb +4 -8
  36. data/lib/pact_broker/pacts/selected_pact.rb +4 -0
  37. data/lib/pact_broker/repositories/helpers.rb +11 -0
  38. data/lib/pact_broker/repositories/page.rb +24 -0
  39. data/lib/pact_broker/string_refinements.rb +4 -0
  40. data/lib/pact_broker/tasks/clean_task.rb +7 -3
  41. data/lib/pact_broker/ui/views/matrix/show.haml +1 -1
  42. data/lib/pact_broker/verifications/repository.rb +14 -11
  43. data/lib/pact_broker/version.rb +1 -1
  44. data/lib/pact_broker/versions/repository.rb +12 -0
  45. data/lib/pact_broker/versions/service.rb +4 -0
  46. data/lib/rack/pact_broker/request_target.rb +1 -1
  47. data/lib/webmachine/application_monkey_patch.rb +10 -0
  48. data/lib/webmachine/render_error_monkey_patch.rb +70 -0
  49. data/pact_broker.gemspec +1 -1
  50. metadata +19 -7
  51. data/lib/pact_broker/api/resources/error_response_body_generator.rb +0 -41
  52. data/lib/rack/pact_broker/convert_404_to_hal.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c43c0dbf719c9e927fad6ffcee28b559036726a61a41813a3451554e5f1db087
4
- data.tar.gz: '0964dcfea819d1ccc1c1ccc15d8a7493670dfbefbde75aedb63ac9405c7c764d'
3
+ metadata.gz: 8cbd7d902684d2fc82497cc897310eabd8b9d9fdf1e8fd8073202349d659d82a
4
+ data.tar.gz: 3eaa5717c1bd08a4eda4bde312d120f18a95c7ed7a2297a603e351fed1bac93a
5
5
  SHA512:
6
- metadata.gz: 12f323b624a2ff4dcc7827e254ec71c83a1d3ffe89532746feb67e360aa5fda938927f43176b86ea08b7b3fddf66d4ef0a5c8cf9ec6b4190332e572a2fd4ad0a
7
- data.tar.gz: a3a5aed03639c5ef8ab602da938e502e4b0434248c99de262bac7a1d6a09e42e4991d32748ae542bfdf1376db133957ff69652b99e49cc1fdfc22f0845907f22
6
+ metadata.gz: 98abd64cec0b9ba9496de89d32aaf3598d4a7058e6e72e328fd9e8049d9ef9d2429dc0a9275b4e138c8b3a8dcde14ccb554b3b537fd00ae0ac6b06bd5a8b0e47
7
+ data.tar.gz: 2ad84a36e17e170bed9f617e3f5b93bd9fff140dbf47c36490cf2df46e2222677cca04aab6542f0808492f20494766a76fa69b765cd4ddb9ed1557b6741f830b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ <a name="v2.106.0"></a>
2
+ ### v2.106.0 (2023-01-31)
3
+
4
+ #### Features
5
+
6
+ * add consumer_version_id index to latest_pact_publication_ids_for_consumer_versions ([b75ca5ee](/../../commit/b75ca5ee))
7
+ * improve the performance of the incremental clean queries ([c3a07c79](/../../commit/c3a07c79))
8
+ * add index to provider_version_id column in latest_verification_id_for_pact_version_and_provider_version ([0e1c43dd](/../../commit/0e1c43dd))
9
+ * Pacticipant pagination (#585) ([f1a9be20](/../../commit/f1a9be20))
10
+ * do not allow JSON request bodies that are not Objects or Arrays ([3d917286](/../../commit/3d917286))
11
+ * add index for verification_id in pact_version_provider_tag_successful_verifications table ([b82a773a](/../../commit/b82a773a))
12
+ * monkey patch Webmachine render_error method to support problem+json (#584) ([508f7ce2](/../../commit/508f7ce2))
13
+ * support problem+json for error messages (#583) ([92957ebb](/../../commit/92957ebb))
14
+ * add index to provider_version_id column in verifications table ([aac33725](/../../commit/aac33725))
15
+
16
+ * **clean**
17
+ * log automatically added selectors ([135c1c0e](/../../commit/135c1c0e))
18
+
19
+ #### Bug Fixes
20
+
21
+ * check that request body does not contain any invalid UTF-8 characters before JSON parsing ([0a08d644](/../../commit/0a08d644))
22
+
23
+ * **versions**
24
+ * add missing next and previous relations to paginated response ([3b46847e](/../../commit/3b46847e))
25
+ * eager load associations for versions endpoint ([2a57dc42](/../../commit/2a57dc42))
26
+
27
+ * **ui**
28
+ * no space after `tag:` (#581) ([1b9ebdfe](/../../commit/1b9ebdfe))
29
+
30
+ * **webhooks**
31
+ * correctly validate HTTP method when the given method is not a valid class name ([6da5a4f3](/../../commit/6da5a4f3))
32
+
1
33
  <a name="v2.105.0"></a>
2
34
  ### v2.105.0 (2022-10-19)
3
35
 
@@ -940,7 +972,7 @@
940
972
  * log schema versions and migration info on startup ([b385e535](/../../commit/b385e535))
941
973
  * allow options to be passed to Sequel migrate via the MigrationTask ([143613e7](/../../commit/143613e7))
942
974
 
943
- * allow Pactflow messages in logs to be hidden by setting PACT_BROKER_HIDE_PACTFLOW_MESSAGES=true ([a7550105](/../../commit/a7550105))
975
+ * allow PactFlow messages in logs to be hidden by setting PACT_BROKER_HIDE_PACTFLOW_MESSAGES=true ([a7550105](/../../commit/a7550105))
944
976
 
945
977
  * **can-i-deploy**
946
978
  * experimental - add a warning message if there are interactions missing verification test results. ([f7ab8cc5](/../../commit/f7ab8cc5))
data/README.md CHANGED
@@ -10,7 +10,7 @@ The Pact Broker is an application for sharing of consumer driven contracts and v
10
10
  <a href="https://pactflow.io/?utm_source=github&utm_campaign=pact_broker_intro"><img src="docs/images/Pactflow logo - black small.png"></a>
11
11
  <br/>
12
12
 
13
- You can try out a Pact Broker for free at <a href="https://pactflow.io/?utm_source=github&utm_campaign=pact_broker_intro"/>pactflow.io</a>. Built by a group of core Pact maintainers, Pactflow is a fork of the OSS Pact Broker with extra goodies like an improved UI, field level verification results and federated login.
13
+ You can try out a Pact Broker for free at <a href="https://pactflow.io/?utm_source=github&utm_campaign=pact_broker_intro"/>pactflow.io</a>. Built by a group of core Pact maintainers, PactFlow is a fork of the OSS Pact Broker with extra goodies like an improved UI, field level verification results and federated login.
14
14
 
15
15
  **Why do I need a Pact Broker?**
16
16
 
@@ -0,0 +1,28 @@
1
+ require_relative "migration_helper"
2
+
3
+ include PactBroker::MigrationHelper
4
+
5
+ Sequel.migration do
6
+ up do
7
+ # MySQL automatically creates indexes for foreign keys then complains if you
8
+ # re-create it with a different name and try to drop it.
9
+
10
+ # https://stackoverflow.com/a/52274628/832671 - "When there is only one index that can be used
11
+ # for the foreign key, it can't be dropped. If you really wan't to drop it, you either have to drop
12
+ # the foreign key constraint or to create another index for it first."
13
+ if !mysql?
14
+ alter_table(:verifications) do
15
+ add_index([:provider_version_id], name: "verifications_provider_version_id_index")
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ down do
22
+ if !mysql?
23
+ alter_table(:verifications) do
24
+ drop_index([:provider_version_id], name: "verifications_provider_version_id_index")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "migration_helper"
2
+
3
+ include PactBroker::MigrationHelper
4
+
5
+ Sequel.migration do
6
+ up do
7
+ if !mysql?
8
+ alter_table(:pact_version_provider_tag_successful_verifications) do
9
+ add_index([:verification_id], name: "pact_ver_prov_tag_success_verif_verif_id_ndx")
10
+ end
11
+ end
12
+ end
13
+
14
+ down do
15
+ if !mysql?
16
+ alter_table(:pact_version_provider_tag_successful_verifications) do
17
+ drop_index([:verification_id], name: "pact_ver_prov_tag_success_verif_verif_id_ndx")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "migration_helper"
2
+
3
+ include PactBroker::MigrationHelper
4
+
5
+ Sequel.migration do
6
+ up do
7
+ if !mysql?
8
+ alter_table(:latest_verification_id_for_pact_version_and_provider_version) do
9
+ add_index([:provider_version_id], name: "latest_verif_id_for_pact_ver_and_prov_ver_prov_ver_id_ndx")
10
+ end
11
+ end
12
+ end
13
+
14
+ down do
15
+ if !mysql?
16
+ alter_table(:latest_verification_id_for_pact_version_and_provider_version) do
17
+ drop_index([:provider_version_id], name: "latest_verif_id_for_pact_ver_and_prov_ver_prov_ver_id_ndx")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "migration_helper"
2
+
3
+ include PactBroker::MigrationHelper
4
+
5
+ Sequel.migration do
6
+ up do
7
+ alter_table(:latest_pact_publication_ids_for_consumer_versions) do
8
+ add_index([:consumer_version_id], name: "latest_pp_ids_for_cons_ver_con_ver_id_ndx")
9
+ end
10
+ end
11
+
12
+ down do
13
+ alter_table(:latest_pact_publication_ids_for_consumer_versions) do
14
+ drop_index([:consumer_version_id], name: "latest_pp_ids_for_cons_ver_con_ver_id_ndx")
15
+ end
16
+ end
17
+ end
@@ -67,7 +67,7 @@ Ensure the application [`log_level`](#log_level) is set to `debug` when this set
67
67
 
68
68
  ### hide_pactflow_messages
69
69
 
70
- Set to `true` to hide the messages in the logs about Pactflow
70
+ Set to `true` to hide the messages in the logs about PactFlow
71
71
 
72
72
  **Environment variable name:** `PACT_BROKER_HIDE_PACTFLOW_MESSAGES`<br/>
73
73
  **YAML configuration key name:** `hide_pactflow_messages`<br/>
@@ -0,0 +1,43 @@
1
+ # Pagination
2
+
3
+ Pagination of results is available for some endpoints used by the Pact Broker. These paginated responses can be used to programmatically load smaller subsections of the api results, in turn enabling improved performance and easier client side management of the data.
4
+
5
+ For endpoints that support pagination a paginated response can be retrieved using the following query parameters:
6
+
7
+ * `pageNumber=1`
8
+ * `pageSize=100`
9
+
10
+ eg.
11
+
12
+ ```
13
+ https://pact-broker/pacticipants?pageSize=100&pageNumber=1
14
+ ```
15
+
16
+ Including one or both of these in the api call will result in a paginated response. Where only one parameter is included the other will use the default value specified above.
17
+
18
+ To retrieve the next or previous page of the paginated response use the URL provided in the response body under the `_links` section, labeled `next` or `previous`.
19
+ The first page will not have a previous link, while the last page will not have a next link.
20
+
21
+ The recommended approach to iterate the full list of resources is to fetch the first page, then call the `href` of the `next` relation until there is no `next` relation returned.
22
+
23
+ ```
24
+ "_links": {
25
+ "next": {
26
+ "href": "http://pact-broker/pacticipants?pageSize=100&pageNumber=2",
27
+ "title": "Next page"
28
+ }
29
+ }
30
+
31
+ ```
32
+
33
+ ## Paginated endpoints
34
+
35
+ The following endpoints in the Pact Broker support pagination via the above query parameters:
36
+
37
+ ### [Pacticipants](https://docs.pact.io/pact_broker/api/pacticipants)
38
+
39
+ Ordered Alphabetically by the Pacticipant name.
40
+
41
+ ### [Pacticipant Versions](https://docs.pact.io/pact_broker/overview#pacticipant-versions)
42
+
43
+ Ordered by date in reverse (most recent Pacticipant Version will be first).
@@ -2,6 +2,7 @@ require "dry-validation"
2
2
  require "pact_broker/api/contracts/dry_validation_workarounds"
3
3
  require "pact_broker/api/contracts/dry_validation_predicates"
4
4
  require "pact_broker/messages"
5
+ require "pact_broker/api/contracts/utf_8_validation"
5
6
 
6
7
  module PactBroker
7
8
  module Api
@@ -11,6 +12,10 @@ module PactBroker
11
12
  using PactBroker::HashRefinements
12
13
  extend PactBroker::Messages
13
14
 
15
+ class << self
16
+ include PactBroker::Api::Contracts::UTF8Validation
17
+ end
18
+
14
19
  SCHEMA = Dry::Validation.Schema do
15
20
  configure do
16
21
  predicates(DryValidationPredicates)
@@ -83,6 +88,14 @@ module PactBroker
83
88
  if contract[:decodedContent].nil?
84
89
  add_contract_error(:content, message("errors.base64?", scope: nil), i, errors)
85
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)
97
+ end
98
+ end
86
99
  end
87
100
 
88
101
  def self.validate_content_matches_content_type(contract, i, errors)
@@ -91,7 +104,6 @@ module PactBroker
91
104
  end
92
105
  end
93
106
 
94
-
95
107
  def self.add_contract_error(field, message, i, errors)
96
108
  errors[:contracts] ||= {}
97
109
  errors[:contracts][i] ||= {}
@@ -0,0 +1,17 @@
1
+ module PactBroker
2
+ module Api
3
+ module Contracts
4
+ module UTF8Validation
5
+ def fragment_before_invalid_utf_8_char(string)
6
+ string.force_encoding("UTF-8").each_char.with_index do | char, index |
7
+ if !char.valid_encoding?
8
+ fragment = index < 100 ? string[0...index] : string[index-100...index]
9
+ return index + 1, fragment
10
+ end
11
+ end
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -130,6 +130,8 @@ module PactBroker
130
130
 
131
131
  def valid_method?(http_method)
132
132
  Net::HTTP.const_defined?(http_method.capitalize)
133
+ rescue StandardError
134
+ false
133
135
  end
134
136
 
135
137
  def valid_url?(url)
@@ -0,0 +1,36 @@
1
+ # Formats a message string into application/problem+json format.
2
+
3
+ module PactBroker
4
+ module Api
5
+ module Decorators
6
+ class CustomErrorProblemJSONDecorator
7
+
8
+ # @option title [String]
9
+ # @option type [String]
10
+ # @option detail [String]
11
+ # @option status [Integer] HTTP status code
12
+ def initialize(title:, type:, detail:, status: )
13
+ @title = title
14
+ @type = type
15
+ @detail = detail
16
+ @status = status
17
+ end
18
+
19
+ # @return [Hash]
20
+ def to_hash(decorator_options = {})
21
+ {
22
+ "title" => @title,
23
+ "type" => "#{decorator_options.dig(:user_options, :base_url)}/problem/#{@type}",
24
+ "detail" => @detail,
25
+ "status" => @status
26
+ }
27
+ end
28
+
29
+ # @return [String] JSON
30
+ def to_json(decorator_options = {})
31
+ to_hash(decorator_options).to_json
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,6 +1,7 @@
1
1
  require "roar/json/hal"
2
2
  require "pact_broker/api/pact_broker_urls"
3
3
  require_relative "embedded_version_decorator"
4
+ require_relative "pagination_links"
4
5
  require "pact_broker/domain/pacticipant"
5
6
  require "pact_broker/api/decorators/pacticipant_decorator"
6
7
 
@@ -11,6 +12,8 @@ module PactBroker
11
12
 
12
13
  collection :entries, :as => :pacticipants, :class => PactBroker::Domain::Pacticipant, :extend => PactBroker::Api::Decorators::PacticipantDecorator, embedded: true
13
14
 
15
+ include PaginationLinks
16
+
14
17
  link :self do | options |
15
18
  pacticipants_url options[:base_url]
16
19
  end
@@ -8,6 +8,17 @@ module PactBroker
8
8
  include Roar::JSON::HAL
9
9
  include Roar::JSON::HAL::Links
10
10
 
11
+ property :page, getter: lambda { |context|
12
+ if context[:represented].respond_to?(:current_page)
13
+ {
14
+ number: context[:represented].current_page,
15
+ size: context[:represented].page_size,
16
+ totalElements: context[:represented].pagination_record_count,
17
+ totalPages: context[:represented].page_count,
18
+ }
19
+ end
20
+ }
21
+
11
22
  link :next do | context |
12
23
  if represented.respond_to?(:current_page) &&
13
24
  represented.respond_to?(:page_count) &&
@@ -0,0 +1,34 @@
1
+ # Formats a message string into application/problem+json format.
2
+
3
+ module PactBroker
4
+ module Api
5
+ module Decorators
6
+ class RuntimeErrorProblemJSONDecorator
7
+
8
+ # @param message [String]
9
+ def initialize(message)
10
+ @message = message
11
+ end
12
+
13
+ # @return [Hash]
14
+ def to_hash(decorator_options = {})
15
+ {
16
+ "title" => "Server error",
17
+ "type" => "#{decorator_options.dig(:user_options, :base_url)}/problems/server_error",
18
+ "detail" => message,
19
+ "status" => 500
20
+ }
21
+ end
22
+
23
+ # @return [String] JSON
24
+ def to_json(decorator_options = {})
25
+ to_hash(decorator_options).to_json
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :message
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ # Formats a nested Hash of errors as it comes out of the Dry Validation library
2
+ # into application/problem+json format.
3
+ require "pact_broker/string_refinements"
4
+
5
+ module PactBroker
6
+ module Api
7
+ module Decorators
8
+ class ValidationErrorsProblemJSONDecorator
9
+ using PactBroker::StringRefinements
10
+
11
+
12
+ # @param errors [Hash]
13
+ def initialize(errors)
14
+ @errors = errors
15
+ end
16
+
17
+ # @return [Hash]
18
+ def to_hash(decorator_options = {})
19
+ error_list = []
20
+ walk_errors(errors, error_list, "", decorator_options.dig(:user_options, :base_url))
21
+ {
22
+ "title" => "Validation errors",
23
+ "type" => "#{decorator_options.dig(:user_options, :base_url)}/problems/validation-error",
24
+ "status" => 400,
25
+ "instance" => "/",
26
+ "errors" => error_list
27
+ }
28
+ end
29
+
30
+ # @return [String] JSON
31
+ def to_json(decorator_options = {})
32
+ to_hash(decorator_options).to_json
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :errors
38
+
39
+ # The path is meant to be implemented using JSON Pointer, but this will probably do for now.
40
+ # As per https://gregsdennis.github.io/Manatee.Json/usage/pointer.html
41
+ # the only things that need to be escaped are "~" and "/", which are unlikely to be used
42
+ # in a key name. You get what you deserve if you've done that.
43
+ def walk_errors(object, list, path, base_url)
44
+ if object.is_a?(Hash)
45
+ object.each { | key, value | walk_errors(value, list, "#{path}/#{key}", base_url) }
46
+ elsif object.is_a?(Array)
47
+ object.each { | value | walk_errors(value, list, path, base_url) }
48
+ elsif object.is_a?(String)
49
+ append_error(list, object, path, base_url)
50
+ end
51
+ end
52
+
53
+ def append_error(list, message, path, base_url)
54
+ error = {
55
+ "type" => "#{base_url}/problems/invalid-body-property-value",
56
+ "title" => "Validation error",
57
+ "detail" => message,
58
+ "status" => 400
59
+ }
60
+ error["pointer"] = path if path.present?
61
+ list << error
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -37,7 +37,7 @@ module PactBroker
37
37
  badge_url
38
38
  rescue StandardError => e
39
39
  # Want to render a badge, even if there's an error
40
- badge_service.error_badge_url("error", ErrorResponseBodyGenerator.display_message(e, "reference: #{PactBroker::Errors.generate_error_reference}"))
40
+ badge_service.error_badge_url("error", ErrorResponseGenerator.display_message(e, "reference: #{PactBroker::Errors.generate_error_reference}"))
41
41
  end
42
42
  end
43
43
 
@@ -10,6 +10,8 @@ require "pact_broker/pacts/pact_params"
10
10
  require "pact_broker/api/resources/authentication"
11
11
  require "pact_broker/api/resources/authorization"
12
12
  require "pact_broker/errors"
13
+ require "pact_broker/api/resources/error_handling_methods"
14
+ require "pact_broker/api/contracts/utf_8_validation"
13
15
 
14
16
  module PactBroker
15
17
  module Api
@@ -22,6 +24,8 @@ module PactBroker
22
24
  include PactBroker::Api::PactBrokerUrls
23
25
  include PactBroker::Api::Resources::Authentication
24
26
  include PactBroker::Api::Resources::Authorization
27
+ include PactBroker::Api::Resources::ErrorHandlingMethods
28
+ include PactBroker::Api::Contracts::UTF8Validation
25
29
 
26
30
  include PactBroker::Logging
27
31
 
@@ -111,45 +115,27 @@ module PactBroker
111
115
  { user_options: decorator_context(options) }
112
116
  end
113
117
 
114
- def handle_exception(error)
115
- error_reference = PactBroker::Errors.generate_error_reference
116
- application_context.error_logger.call(error, error_reference, request.env)
117
- if PactBroker::Errors.reportable_error?(error)
118
- PactBroker::Errors.report(error, error_reference, request.env)
119
- end
120
- response.body = application_context.error_response_body_generator.call(error, error_reference, request.env)
121
- end
122
-
123
118
  # rubocop: disable Metrics/CyclomaticComplexity
124
119
  def params(options = {})
125
120
  return options[:default] if options.key?(:default) && request_body.empty?
126
121
 
127
122
  symbolize_names = !options.key?(:symbolize_names) || options[:symbolize_names]
128
- if symbolize_names
129
- @params_with_symbol_keys ||= JSON.parse(request_body, { symbolize_names: true }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
130
- else
131
- @params_with_string_keys ||= JSON.parse(request_body, { symbolize_names: false }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
123
+ parsed_params = if symbolize_names
124
+ @params_with_symbol_keys ||= JSON.parse(request_body, { symbolize_names: true }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
125
+ else
126
+ @params_with_string_keys ||= JSON.parse(request_body, { symbolize_names: false }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
127
+ end
128
+
129
+ if !parsed_params.is_a?(Hash) && !parsed_params.is_a?(Array)
130
+ raise "Expected JSON Object in request body but found #{parsed_params.class.name}"
132
131
  end
133
- rescue StandardError => e
134
- fragment = fragment_before_invalid_utf_8_char
135
132
 
136
- if fragment
137
- raise NonUTF8CharacterFound.new(message("errors.non_utf_8_char_in_request_body", char_number: fragment.length + 1, fragment: fragment))
138
- else
139
- raise InvalidJsonError.new(e.message)
140
- end
133
+ parsed_params
134
+ rescue StandardError => e
135
+ raise InvalidJsonError.new(e.message)
141
136
  end
142
137
  # rubocop: enable Metrics/CyclomaticComplexity
143
138
 
144
- def fragment_before_invalid_utf_8_char
145
- request_body.each_char.with_index do | char, index |
146
- if !char.valid_encoding?
147
- return index < 100 ? request_body[0...index] : request_body[index-100...index]
148
- end
149
- end
150
- nil
151
- end
152
-
153
139
  def params_with_string_keys
154
140
  params(symbolize_names: false)
155
141
  end
@@ -158,16 +144,6 @@ module PactBroker
158
144
  @pact_params ||= PactBroker::Pacts::PactParams.from_request(request, identifier_from_path)
159
145
  end
160
146
 
161
- def set_json_error_message message
162
- response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
163
- response.body = { error: message }.to_json
164
- end
165
-
166
- def set_json_validation_error_messages errors
167
- response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
168
- response.body = { errors: errors }.to_json
169
- end
170
-
171
147
  def request_body
172
148
  @request_body ||= request.body.to_s
173
149
  end
@@ -210,18 +186,20 @@ module PactBroker
210
186
 
211
187
  def invalid_json?
212
188
  begin
213
- params
214
- false
215
- rescue NonUTF8CharacterFound => e
216
- logger.info(e.message) # Don't use the default SemanticLogger error logging method because it will try and print out the cause which will contain non UTF-8 chars in the message
217
- set_json_error_message(e.message)
218
- response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
219
- true
189
+ char_number, fragment = fragment_before_invalid_utf_8_char(request_body)
190
+ if char_number
191
+ error_message = message("errors.non_utf_8_char_in_request_body", char_number: char_number, fragment: fragment)
192
+ logger.info(error_message)
193
+ set_json_error_message(error_message)
194
+ true
195
+ else
196
+ params
197
+ false
198
+ end
220
199
  rescue StandardError => e
221
200
  message = "#{e.cause ? e.cause.class.name : e.class.name} - #{e.message}"
222
201
  logger.info(message)
223
202
  set_json_error_message(message)
224
- response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
225
203
  true
226
204
  end
227
205
  end
@@ -244,7 +222,9 @@ module PactBroker
244
222
 
245
223
  def find_pacticipant name, role
246
224
  pacticipant_service.find_pacticipant_by_name(name).tap do | pacticipant |
247
- set_json_error_message("No #{role} with name '#{name}' found") if pacticipant.nil?
225
+ if pacticipant.nil?
226
+ set_json_error_message("No #{role} with name '#{name}' found", title: "Not found", type: "not_found", status: 404)
227
+ end
248
228
  end
249
229
  end
250
230
 
@@ -0,0 +1,57 @@
1
+ require "pact_broker/api/decorators/validation_errors_problem_json_decorator"
2
+ require "pact_broker/api/decorators/custom_error_problem_json_decorator"
3
+
4
+ module PactBroker
5
+ module Api
6
+ module Resources
7
+ module ErrorHandlingMethods
8
+
9
+ # @override
10
+ def handle_exception(error)
11
+ error_reference = PactBroker::Errors.generate_error_reference
12
+ application_context.error_logger.call(error, error_reference, request.env)
13
+ if PactBroker::Errors.reportable_error?(error)
14
+ PactBroker::Errors.report(error, error_reference, request.env)
15
+ end
16
+ headers, body = application_context.error_response_generator.call(error, error_reference, request.env)
17
+ headers.each { | key, value | response.headers[key] = value }
18
+ response.body = body
19
+ end
20
+
21
+ def set_json_error_message detail, title: "Server error", type: "server_error", status: 500
22
+ response.headers["Content-Type"] = error_response_content_type
23
+ response.body = error_response_body(detail, title, type, status)
24
+ end
25
+
26
+ def set_json_validation_error_messages errors
27
+ response.headers["Content-Type"] = error_response_content_type
28
+ if problem_json_error_content_type?
29
+ response.body = PactBroker::Api::Decorators::ValidationErrorsProblemJSONDecorator.new(errors).to_json(decorator_options)
30
+ else
31
+ response.body = { errors: errors }.to_json
32
+ end
33
+ end
34
+
35
+ def error_response_content_type
36
+ if problem_json_error_content_type?
37
+ "application/problem+json;charset=utf-8"
38
+ else
39
+ "application/hal+json;charset=utf-8"
40
+ end
41
+ end
42
+
43
+ def error_response_body(detail, title, type, status)
44
+ if problem_json_error_content_type?
45
+ PactBroker::Api::Decorators::CustomErrorProblemJSONDecorator.new(detail: detail, title: title, type: type, status: status).to_json(decorator_options)
46
+ else
47
+ { error: detail }.to_json
48
+ end
49
+ end
50
+
51
+ def problem_json_error_content_type?
52
+ request.headers["Accept"]&.include?("application/problem+json")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end