pact_broker 2.104.0 → 2.106.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -1
- data/README.md +1 -1
- data/db/migrations/20221130_add_provider_version_id_index_to_verifications.rb +28 -0
- data/db/migrations/20221208_add_index_to_pact_version_provider_tag_successful_verifications.rb +21 -0
- data/db/migrations/20221215_add_prov_ver_id_ndx_to_latest_verifi_id_for_pact_ver_and_prov_ver.rb +21 -0
- data/db/migrations/20230131_add_cons_ver_id_ndx_to_latest_pp_id_for_cons_ver.rb +17 -0
- data/docs/CONFIGURATION.md +1 -1
- data/docs/api/PAGINATION.md +43 -0
- data/lib/pact_broker/api/contracts/publish_contracts_schema.rb +13 -1
- data/lib/pact_broker/api/contracts/utf_8_validation.rb +17 -0
- data/lib/pact_broker/api/contracts/webhook_contract.rb +2 -0
- data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +36 -0
- data/lib/pact_broker/api/decorators/pacticipant_collection_decorator.rb +3 -0
- data/lib/pact_broker/api/decorators/pagination_links.rb +11 -0
- data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +34 -0
- data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +66 -0
- data/lib/pact_broker/api/middleware/http_debug_logs.rb +2 -2
- data/lib/pact_broker/api/resources/badge_methods.rb +1 -1
- data/lib/pact_broker/api/resources/base_resource.rb +28 -48
- data/lib/pact_broker/api/resources/error_handling_methods.rb +57 -0
- data/lib/pact_broker/api/resources/error_response_generator.rb +70 -0
- data/lib/pact_broker/api/resources/latest_verifications_for_consumer_version.rb +1 -1
- data/lib/pact_broker/api/resources/pact_version.rb +1 -1
- data/lib/pact_broker/api/resources/pacticipants.rb +4 -1
- data/lib/pact_broker/api/resources/pagination_methods.rb +8 -4
- data/lib/pact_broker/api/resources/publish_contracts.rb +1 -1
- data/lib/pact_broker/api/resources/versions.rb +5 -12
- data/lib/pact_broker/api.rb +3 -4
- data/lib/pact_broker/app.rb +1 -3
- data/lib/pact_broker/application_context.rb +4 -4
- data/lib/pact_broker/config/runtime_configuration.rb +1 -0
- data/lib/pact_broker/config/runtime_configuration_logging_methods.rb +3 -0
- data/lib/pact_broker/configuration.rb +1 -1
- data/lib/pact_broker/db/clean_incremental.rb +67 -59
- data/lib/pact_broker/db/data_migrations/set_pacticipant_main_branch.rb +2 -2
- data/lib/pact_broker/domain/pact.rb +7 -1
- data/lib/pact_broker/index/service.rb +1 -1
- data/lib/pact_broker/initializers/database_connection.rb +1 -1
- data/lib/pact_broker/locale/en.yml +2 -0
- data/lib/pact_broker/matrix/can_i_deploy_query_schema.rb +6 -0
- data/lib/pact_broker/matrix/deployment_status_summary.rb +1 -1
- data/lib/pact_broker/matrix/resolved_selector.rb +2 -2
- data/lib/pact_broker/pacticipants/repository.rb +4 -11
- data/lib/pact_broker/pacticipants/service.rb +4 -8
- data/lib/pact_broker/pacts/pact_version.rb +39 -0
- data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +74 -47
- data/lib/pact_broker/pacts/repository.rb +2 -2
- data/lib/pact_broker/pacts/selected_pact.rb +4 -0
- data/lib/pact_broker/pacts/service.rb +7 -8
- data/lib/pact_broker/pacts/squash_pacts_for_verification.rb +7 -4
- data/lib/pact_broker/repositories/helpers.rb +11 -0
- data/lib/pact_broker/repositories/page.rb +24 -0
- data/lib/pact_broker/string_refinements.rb +4 -0
- data/lib/pact_broker/tasks/clean_task.rb +8 -4
- data/lib/pact_broker/tasks/delete_overwritten_data_task.rb +1 -1
- data/lib/pact_broker/test/http_test_data_builder.rb +34 -26
- data/lib/pact_broker/test/test_data_builder.rb +28 -12
- data/lib/pact_broker/ui/views/matrix/show.haml +1 -1
- data/lib/pact_broker/verifications/repository.rb +14 -11
- data/lib/pact_broker/verifications/service.rb +1 -1
- data/lib/pact_broker/version.rb +1 -1
- data/lib/pact_broker/versions/branch.rb +1 -0
- data/lib/pact_broker/versions/repository.rb +12 -0
- data/lib/pact_broker/versions/service.rb +4 -0
- data/lib/pact_broker/webhooks/event_listener.rb +2 -2
- data/lib/pact_broker/webhooks/execution_configuration.rb +4 -0
- data/lib/pact_broker/webhooks/execution_configuration_creator.rb +1 -0
- data/lib/pact_broker/webhooks/webhook_request_logger.rb +1 -1
- data/lib/rack/pact_broker/request_target.rb +1 -1
- data/lib/webmachine/application_monkey_patch.rb +10 -0
- data/lib/webmachine/render_error_monkey_patch.rb +70 -0
- data/pact_broker.gemspec +2 -2
- metadata +21 -9
- data/lib/pact_broker/api/resources/error_response_body_generator.rb +0 -41
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cbd7d902684d2fc82497cc897310eabd8b9d9fdf1e8fd8073202349d659d82a
|
4
|
+
data.tar.gz: 3eaa5717c1bd08a4eda4bde312d120f18a95c7ed7a2297a603e351fed1bac93a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98abd64cec0b9ba9496de89d32aaf3598d4a7058e6e72e328fd9e8049d9ef9d2429dc0a9275b4e138c8b3a8dcde14ccb554b3b537fd00ae0ac6b06bd5a8b0e47
|
7
|
+
data.tar.gz: 2ad84a36e17e170bed9f617e3f5b93bd9fff140dbf47c36490cf2df46e2222677cca04aab6542f0808492f20494766a76fa69b765cd4ddb9ed1557b6741f830b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,53 @@
|
|
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
|
+
|
33
|
+
<a name="v2.105.0"></a>
|
34
|
+
### v2.105.0 (2022-10-19)
|
35
|
+
|
36
|
+
#### Features
|
37
|
+
|
38
|
+
* update wording of version description for version in environment ([d122fa68](/../../commit/d122fa68))
|
39
|
+
|
40
|
+
* **webhooks**
|
41
|
+
* allow auth headers to be logged for debugging purposes (#575) ([102b1930](/../../commit/102b1930))
|
42
|
+
|
43
|
+
#### Bug Fixes
|
44
|
+
|
45
|
+
* add validation to ensure an environment or to tag is specified for the /can-i-deploy endpoint ([e9d772eb](/../../commit/e9d772eb))
|
46
|
+
* implement pending logic for provider branches ([7cdf1a7c](/../../commit/7cdf1a7c))
|
47
|
+
|
48
|
+
* **wip pacts**
|
49
|
+
* fix performance issue encountered when removing explicitly specified pacts from the list of potential WIP pacts (#573) ([757f0301](/../../commit/757f0301))
|
50
|
+
|
1
51
|
<a name="v2.104.0"></a>
|
2
52
|
### v2.104.0 (2022-09-17)
|
3
53
|
|
@@ -922,7 +972,7 @@
|
|
922
972
|
* log schema versions and migration info on startup ([b385e535](/../../commit/b385e535))
|
923
973
|
* allow options to be passed to Sequel migrate via the MigrationTask ([143613e7](/../../commit/143613e7))
|
924
974
|
|
925
|
-
* allow
|
975
|
+
* allow PactFlow messages in logs to be hidden by setting PACT_BROKER_HIDE_PACTFLOW_MESSAGES=true ([a7550105](/../../commit/a7550105))
|
926
976
|
|
927
977
|
* **can-i-deploy**
|
928
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,
|
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
|
data/db/migrations/20221208_add_index_to_pact_version_provider_tag_successful_verifications.rb
ADDED
@@ -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
|
data/db/migrations/20221215_add_prov_ver_id_ndx_to_latest_verifi_id_for_pact_ver_and_prov_ver.rb
ADDED
@@ -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
|
data/docs/CONFIGURATION.md
CHANGED
@@ -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
|
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
|
@@ -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
|
@@ -21,9 +21,9 @@ module PactBroker
|
|
21
21
|
env_to_log = env.reject { | header, _ | header.start_with?(*EXCLUDE_HEADERS) }
|
22
22
|
env_to_log["rack.session"] = env["rack.session"].to_hash if env["rack.session"]
|
23
23
|
env_to_log["rack.input"] = request_body(env) if env["rack.input"]
|
24
|
-
logger.debug("env",
|
24
|
+
logger.debug("env", env_to_log)
|
25
25
|
status, headers, body = @app.call(env)
|
26
|
-
logger.debug("response",
|
26
|
+
logger.debug("response", "status" => status, "headers" => headers, "body" => body)
|
27
27
|
[status, headers, body]
|
28
28
|
else
|
29
29
|
@app.call(env)
|
@@ -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",
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
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
|
|