pact_broker 2.100.0 → 2.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile +1 -0
  4. data/docs/api/PACTICIPANTS.md +290 -0
  5. data/docs/api/WEBHOOKS.md +40 -40
  6. data/lib/db.rb +1 -1
  7. data/lib/pact_broker/api/decorators/triggered_webhook_decorator.rb +1 -2
  8. data/lib/pact_broker/api/resources/all_webhooks.rb +1 -4
  9. data/lib/pact_broker/api/resources/base_resource.rb +51 -5
  10. data/lib/pact_broker/api/resources/branch_version.rb +10 -1
  11. data/lib/pact_broker/api/resources/clean.rb +11 -9
  12. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +0 -4
  13. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +0 -4
  14. data/lib/pact_broker/api/resources/deployed_version.rb +12 -14
  15. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +4 -0
  16. data/lib/pact_broker/api/resources/environment.rb +5 -5
  17. data/lib/pact_broker/api/resources/environments.rb +1 -5
  18. data/lib/pact_broker/api/resources/label.rb +4 -0
  19. data/lib/pact_broker/api/resources/pact.rb +10 -5
  20. data/lib/pact_broker/api/resources/pact_webhooks.rb +1 -4
  21. data/lib/pact_broker/api/resources/pacticipant.rb +11 -5
  22. data/lib/pact_broker/api/resources/pacticipant_webhooks.rb +1 -4
  23. data/lib/pact_broker/api/resources/pacticipants.rb +1 -4
  24. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +19 -11
  25. data/lib/pact_broker/api/resources/publish_contracts.rb +11 -15
  26. data/lib/pact_broker/api/resources/released_version.rb +12 -6
  27. data/lib/pact_broker/api/resources/released_versions_for_version_and_environment.rb +10 -6
  28. data/lib/pact_broker/api/resources/tag.rb +7 -3
  29. data/lib/pact_broker/api/resources/verifications.rb +7 -9
  30. data/lib/pact_broker/api/resources/version.rb +8 -8
  31. data/lib/pact_broker/api/resources/webhook.rb +5 -4
  32. data/lib/pact_broker/api/resources/webhook_execution.rb +4 -6
  33. data/lib/pact_broker/doc/views/index/pacticipant-branch-version.markdown +13 -2
  34. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +1 -1
  35. data/lib/pact_broker/domain/pacticipant.rb +1 -0
  36. data/lib/pact_broker/locale/en.yml +1 -0
  37. data/lib/pact_broker/pacticipants/repository.rb +5 -4
  38. data/lib/pact_broker/pacts/generate_sha.rb +1 -0
  39. data/lib/pact_broker/pacts/verifiable_pact_messages.rb +1 -1
  40. data/lib/pact_broker/test/test_data_builder.rb +20 -0
  41. data/lib/pact_broker/version.rb +1 -1
  42. data/lib/pact_broker/versions/branch_service.rb +7 -0
  43. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  44. data/lib/rack/pact_broker/cascade.rb +87 -0
  45. data/lib/webmachine/describe_routes.rb +43 -9
  46. metadata +5 -4
  47. data/lib/pact_broker/api/resources/default_base_resource.rb +0 -0
@@ -15,6 +15,7 @@ module PactBroker
15
15
  module Api
16
16
  module Resources
17
17
  class InvalidJsonError < PactBroker::Error ; end
18
+ class NonUTF8CharacterFound < PactBroker::Error ; end
18
19
 
19
20
  class BaseResource < Webmachine::Resource
20
21
  include PactBroker::Services
@@ -39,6 +40,10 @@ module PactBroker
39
40
  super + ["PATCH"]
40
41
  end
41
42
 
43
+ def malformed_request?
44
+ content_type_is_json_but_invalid_json_provided?
45
+ end
46
+
42
47
  def finish_request
43
48
  application_context.after_resource&.call(self)
44
49
  PactBroker.configuration.after_resource.call(self)
@@ -125,17 +130,32 @@ module PactBroker
125
130
  else
126
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.
127
132
  end
128
- rescue JSON::JSONError => e
129
- raise InvalidJsonError.new("Error parsing JSON - #{e.message}")
133
+ rescue StandardError => e
134
+ fragment = fragment_before_invalid_utf_8_char
135
+
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
130
141
  end
131
142
  # rubocop: enable Metrics/CyclomaticComplexity
132
143
 
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
+
133
153
  def params_with_string_keys
134
154
  params(symbolize_names: false)
135
155
  end
136
156
 
137
157
  def pact_params
138
- @pact_params ||= PactBroker::Pacts::PactParams.from_request request, identifier_from_path
158
+ @pact_params ||= PactBroker::Pacts::PactParams.from_request(request, identifier_from_path)
139
159
  end
140
160
 
141
161
  def set_json_error_message message
@@ -192,9 +212,15 @@ module PactBroker
192
212
  begin
193
213
  params
194
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
195
220
  rescue StandardError => e
196
- logger.info "Error parsing JSON #{e} - #{request_body}"
197
- set_json_error_message "Error parsing JSON - #{e.message}"
221
+ message = "#{e.cause ? e.cause.class.name : e.class.name} - #{e.message}"
222
+ logger.info(message)
223
+ set_json_error_message(message)
198
224
  response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
199
225
  true
200
226
  end
@@ -279,6 +305,26 @@ module PactBroker
279
305
  def malformed_request_for_json_with_schema?(schema_to_use = schema, params_to_validate = params)
280
306
  invalid_json? || validation_errors_for_schema?(schema_to_use, params_to_validate)
281
307
  end
308
+
309
+ def content_type_is_json_but_invalid_json_provided?
310
+ content_type_json? && any_request_body? && invalid_json?
311
+ end
312
+
313
+ def content_type_json?
314
+ request.content_type&.include?("json")
315
+ end
316
+
317
+ # Not a Webmachine method. This is used by security policy code to identify whether
318
+ # a PUT to a non existing resource can create a new object.
319
+ def put_can_create?
320
+ false
321
+ end
322
+
323
+ # Not a Webmachine method. This is used by security policy code to identify whether
324
+ # a PATCH to a non existing resource can create a new object.
325
+ def patch_can_create?
326
+ false
327
+ end
282
328
  end
283
329
  end
284
330
  end
@@ -14,7 +14,11 @@ module PactBroker
14
14
  end
15
15
 
16
16
  def allowed_methods
17
- ["GET", "PUT", "OPTIONS"]
17
+ ["GET", "PUT", "DELETE", "OPTIONS"]
18
+ end
19
+
20
+ def put_can_create?
21
+ true
18
22
  end
19
23
 
20
24
  def resource_exists?
@@ -25,6 +29,11 @@ module PactBroker
25
29
  decorator_class(:branch_version_decorator).new(branch_version).to_json(decorator_options)
26
30
  end
27
31
 
32
+ def delete_resource
33
+ branch_service.delete_branch_version(branch_version)
34
+ true
35
+ end
36
+
28
37
  def from_json
29
38
  already_existed = !!branch_version
30
39
  @branch_version = branch_service.find_or_create_branch_version(identifier_from_path)
@@ -2,14 +2,12 @@ require "pact_broker/api/resources/base_resource"
2
2
  require "pact_broker/db/clean"
3
3
  require "pact_broker/matrix/unresolved_selector"
4
4
 
5
+ # Not exposed yet as we'd need to support administrator auth first
6
+
5
7
  module PactBroker
6
8
  module Api
7
9
  module Resources
8
10
  class Clean < BaseResource
9
- def content_types_accepted
10
- [["application/json"]]
11
- end
12
-
13
11
  def content_types_provided
14
12
  [["application/hal+json"]]
15
13
  end
@@ -19,12 +17,16 @@ module PactBroker
19
17
  end
20
18
 
21
19
  def process_post
22
- keep_selectors = (params[:keep] || []).collect do | hash |
23
- PactBroker::Matrix::UnresolvedSelector.new(hash)
24
- end
20
+ if content_type_json?
21
+ keep_selectors = (params[:keep] || []).collect do | hash |
22
+ PactBroker::Matrix::UnresolvedSelector.new(hash)
23
+ end
25
24
 
26
- result = PactBroker::DB::Clean.call(Sequel::Model.db, { keep: keep_selectors })
27
- response.body = result.to_json
25
+ result = PactBroker::DB::Clean.call(Sequel::Model.db, { keep: keep_selectors })
26
+ response.body = result.to_json
27
+ else
28
+ 415
29
+ end
28
30
  end
29
31
 
30
32
  def policy_name
@@ -8,10 +8,6 @@ module PactBroker
8
8
  class CurrentlyDeployedVersionsForEnvironment < BaseResource
9
9
  using PactBroker::StringRefinements
10
10
 
11
- def content_types_accepted
12
- [["application/json", :from_json]]
13
- end
14
-
15
11
  def content_types_provided
16
12
  [["application/hal+json", :to_json]]
17
13
  end
@@ -8,10 +8,6 @@ module PactBroker
8
8
  class CurrentlySupportedVersionsForEnvironment < BaseResource
9
9
  using PactBroker::StringRefinements
10
10
 
11
- def content_types_accepted
12
- [["application/json", :from_json]]
13
- end
14
-
15
11
  def content_types_provided
16
12
  [["application/hal+json", :to_json]]
17
13
  end
@@ -8,11 +8,6 @@ module PactBroker
8
8
  class DeployedVersion < BaseResource
9
9
  include PactBroker::Messages
10
10
 
11
- def initialize
12
- super
13
- @currently_deployed_param = params(default: {})[:currentlyDeployed]
14
- end
15
-
16
11
  def content_types_provided
17
12
  [
18
13
  ["application/hal+json", :to_json]
@@ -29,16 +24,12 @@ module PactBroker
29
24
  ["GET", "PATCH", "OPTIONS"]
30
25
  end
31
26
 
32
- def resource_exists?
33
- !!deployed_version
27
+ def patch_can_create?
28
+ false
34
29
  end
35
30
 
36
- def malformed_request?
37
- if request.patch?
38
- return invalid_json?
39
- else
40
- false
41
- end
31
+ def resource_exists?
32
+ !!deployed_version
42
33
  end
43
34
 
44
35
  def to_json
@@ -73,7 +64,14 @@ module PactBroker
73
64
 
74
65
  private
75
66
 
76
- attr_reader :currently_deployed_param
67
+ # can't use ||= with a potentially nil value
68
+ def currently_deployed_param
69
+ if defined?(@currently_deployed_param)
70
+ @currently_deployed_param
71
+ else
72
+ @currently_deployed_param = params(default: {})[:currentlyDeployed]
73
+ end
74
+ end
77
75
 
78
76
  def process_currently_deployed_param
79
77
  if currently_deployed_param == false
@@ -43,6 +43,10 @@ module PactBroker
43
43
  :'versions::deployed_versions'
44
44
  end
45
45
 
46
+ def policy_record
47
+ environment
48
+ end
49
+
46
50
  private
47
51
 
48
52
  attr_reader :deployed_version, :existing_deployed_version
@@ -17,16 +17,16 @@ module PactBroker
17
17
  ["GET", "PUT", "DELETE", "OPTIONS"]
18
18
  end
19
19
 
20
+ def put_can_create?
21
+ false
22
+ end
23
+
20
24
  def resource_exists?
21
25
  !!environment
22
26
  end
23
27
 
24
28
  def malformed_request?
25
- if request.put? && environment
26
- invalid_json? || validation_errors_for_schema?(schema, params.merge(uuid: uuid))
27
- else
28
- false
29
- end
29
+ super || (request.put? && environment && validation_errors_for_schema?(schema, params.merge(uuid: uuid)))
30
30
  end
31
31
 
32
32
  def from_json
@@ -27,11 +27,7 @@ module PactBroker
27
27
  end
28
28
 
29
29
  def malformed_request?
30
- if request.post?
31
- invalid_json? || validation_errors_for_schema?(schema, params.merge(uuid: uuid))
32
- else
33
- false
34
- end
30
+ super || (request.post? && validation_errors_for_schema?(schema, params.merge(uuid: uuid)))
35
31
  end
36
32
 
37
33
  def create_path
@@ -17,6 +17,10 @@ module PactBroker
17
17
  ["GET", "PUT", "DELETE", "OPTIONS"]
18
18
  end
19
19
 
20
+ def put_can_create?
21
+ true
22
+ end
23
+
20
24
  def from_json
21
25
  unless label
22
26
  @label = label_service.create(identifier_from_path)
@@ -36,18 +36,23 @@ module PactBroker
36
36
  ["GET", "PUT", "DELETE", "PATCH", "OPTIONS"]
37
37
  end
38
38
 
39
+ def put_can_create?
40
+ true
41
+ end
42
+
43
+ def patch_can_create?
44
+ true
45
+ end
46
+
39
47
  def is_conflict?
40
48
  merge_conflict = request.patch? && resource_exists? && Pacts::Merger.conflict?(pact.json_content, pact_params.json_content)
41
49
 
42
50
  potential_duplicate_pacticipants?(pact_params.pacticipant_names) || merge_conflict || disallowed_modification?
43
51
  end
44
52
 
53
+
45
54
  def malformed_request?
46
- if request.patch? || request.put?
47
- invalid_json? || contract_validation_errors?(Contracts::PutPactParamsContract.new(pact_params), pact_params)
48
- else
49
- false
50
- end
55
+ super || ((request.patch? || request.really_put?) && contract_validation_errors?(Contracts::PutPactParamsContract.new(pact_params), pact_params))
51
56
  end
52
57
 
53
58
  def resource_exists?
@@ -25,10 +25,7 @@ module PactBroker
25
25
  end
26
26
 
27
27
  def malformed_request?
28
- if request.post?
29
- return invalid_json? || validation_errors?(webhook)
30
- end
31
- false
28
+ super || (request.post? && validation_errors?(webhook))
32
29
  end
33
30
 
34
31
  def validation_errors? webhook
@@ -21,18 +21,23 @@ module PactBroker
21
21
  ["GET", "PUT", "PATCH", "DELETE", "OPTIONS"]
22
22
  end
23
23
 
24
+ def put_can_create?
25
+ false
26
+ end
27
+
28
+ def patch_can_create?
29
+ true
30
+ end
31
+
24
32
  def known_methods
25
33
  super + ["PATCH"]
26
34
  end
27
35
 
28
36
  def malformed_request?
29
- if request.patch? || request.put?
30
- invalid_json? || validation_errors_for_schema?
31
- else
32
- false
33
- end
37
+ super || ((request.patch? || request.really_put?) && validation_errors_for_schema?)
34
38
  end
35
39
 
40
+ # PUT or PATCH with content-type application/json
36
41
  def from_json
37
42
  if pacticipant
38
43
  @pacticipant = update_existing_pacticipant
@@ -47,6 +52,7 @@ module PactBroker
47
52
  response.body = to_json
48
53
  end
49
54
 
55
+ # PUT or PATCH with content-type application/merge-patch+json
50
56
  def from_merge_patch_json
51
57
  if request.patch?
52
58
  from_json
@@ -27,10 +27,7 @@ module PactBroker
27
27
  end
28
28
 
29
29
  def malformed_request?
30
- if request.post?
31
- return invalid_json? || webhook_validation_errors?(webhook)
32
- end
33
- false
30
+ super || (request.post? && webhook_validation_errors?(webhook))
34
31
  end
35
32
 
36
33
  def create_path
@@ -23,10 +23,7 @@ module PactBroker
23
23
  end
24
24
 
25
25
  def malformed_request?
26
- if request.post?
27
- return invalid_json? || validation_errors_for_schema?
28
- end
29
- false
26
+ super || (request.post? && validation_errors_for_schema?)
30
27
  end
31
28
 
32
29
  def post_is_create?
@@ -11,28 +11,27 @@ module PactBroker
11
11
  class ProviderPactsForVerification < ProviderPacts
12
12
  using PactBroker::HashRefinements
13
13
 
14
+ def content_types_provided
15
+ [["application/hal+json", :to_json]]
16
+ end
17
+
14
18
  def allowed_methods
15
19
  ["GET", "POST", "OPTIONS"]
16
20
  end
17
21
 
18
- def content_types_accepted
19
- [["application/json"]]
22
+ def malformed_request?
23
+ super || ((request.get? || (request.post? && content_type_json?)) && schema_validation_errors?)
20
24
  end
21
25
 
22
- def malformed_request?
23
- if (errors = query_schema.call(query)).any?
24
- set_json_validation_error_messages(errors)
26
+ def process_post
27
+ if content_type_json?
28
+ response.body = to_json
25
29
  true
26
30
  else
27
- false
31
+ 415
28
32
  end
29
33
  end
30
34
 
31
- def process_post
32
- response.body = to_json
33
- true
34
- end
35
-
36
35
  def read_methods
37
36
  super + %w{POST}
38
37
  end
@@ -96,6 +95,15 @@ module PactBroker
96
95
  def nested_query
97
96
  @nested_query ||= Rack::Utils.parse_nested_query(request.uri.query)
98
97
  end
98
+
99
+ def schema_validation_errors?
100
+ if (errors = query_schema.call(query)).any?
101
+ set_json_validation_error_messages(errors)
102
+ true
103
+ else
104
+ false
105
+ end
106
+ end
99
107
  end
100
108
  end
101
109
  end
@@ -11,11 +11,7 @@ module PactBroker
11
11
  include WebhookExecutionMethods
12
12
 
13
13
  def content_types_provided
14
- [["application/hal+json", :to_json]]
15
- end
16
-
17
- def content_types_accepted
18
- [["application/json"]]
14
+ [["application/hal+json"]]
19
15
  end
20
16
 
21
17
  def allowed_methods
@@ -23,20 +19,20 @@ module PactBroker
23
19
  end
24
20
 
25
21
  def malformed_request?
26
- if request.post?
27
- invalid_json? || validation_errors_for_schema?
28
- else
29
- false
30
- end
22
+ super || (request.post? && content_type_json? && validation_errors_for_schema?)
31
23
  end
32
24
 
33
25
  def process_post
34
- if conflict_notices.any?
35
- set_conflict_response
36
- 409
26
+ if content_type_json?
27
+ if conflict_notices.any?
28
+ set_conflict_response
29
+ 409
30
+ else
31
+ publish_contracts
32
+ true
33
+ end
37
34
  else
38
- publish_contracts
39
- true
35
+ 415
40
36
  end
41
37
  end
42
38
 
@@ -8,11 +8,6 @@ module PactBroker
8
8
  class ReleasedVersion < BaseResource
9
9
  include PactBroker::Messages
10
10
 
11
- def initialize
12
- super
13
- @currently_supported_param = params(default: {})[:currentlySupported]
14
- end
15
-
16
11
  def content_types_provided
17
12
  [["application/hal+json", :to_json]]
18
13
  end
@@ -27,6 +22,10 @@ module PactBroker
27
22
  ["GET", "PATCH", "OPTIONS"]
28
23
  end
29
24
 
25
+ def patch_can_create?
26
+ false
27
+ end
28
+
30
29
  def resource_exists?
31
30
  !!released_version
32
31
  end
@@ -63,7 +62,14 @@ module PactBroker
63
62
 
64
63
  private
65
64
 
66
- attr_reader :currently_supported_param
65
+ # can't use ||= with a potentially nil value
66
+ def currently_supported_param
67
+ if defined?(@currently_deployed_param)
68
+ @currently_supported_param
69
+ else
70
+ @currently_supported_param = params(default: {})[:currentlySupported]
71
+ end
72
+ end
67
73
 
68
74
  def process_currently_supported_param
69
75
  if currently_supported_param == false
@@ -5,11 +5,6 @@ module PactBroker
5
5
  module Api
6
6
  module Resources
7
7
  class ReleasedVersionsForVersionAndEnvironment < BaseResource
8
- def initialize
9
- super
10
- @existing_released_version = version && environment && released_version_service.find_released_version_for_version_and_environment(version, environment)
11
- end
12
-
13
8
  def content_types_accepted
14
9
  [["application/json", :from_json]]
15
10
  end
@@ -35,6 +30,7 @@ module PactBroker
35
30
  end
36
31
 
37
32
  def from_json
33
+ existing_released_version # make sure we have this before we update the database
38
34
  @released_version = released_version_service.create_or_update(next_released_version_uuid, version, environment)
39
35
  response.body = decorator_class(:released_version_decorator).new(released_version).to_json(decorator_options)
40
36
  true
@@ -61,7 +57,15 @@ module PactBroker
61
57
 
62
58
  private
63
59
 
64
- attr_reader :released_version, :existing_released_version
60
+ attr_reader :released_version
61
+
62
+ def existing_released_version
63
+ if defined?(@existing_released_version)
64
+ @existing_released_version
65
+ else
66
+ @existing_released_version = version && environment && released_version_service.find_released_version_for_version_and_environment(version, environment)
67
+ end
68
+ end
65
69
 
66
70
  def version
67
71
  @version ||= version_service.find_by_pacticipant_name_and_number(identifier_from_path)
@@ -17,9 +17,13 @@ module PactBroker
17
17
  ["GET","PUT","DELETE", "OPTIONS"]
18
18
  end
19
19
 
20
+ def put_can_create?
21
+ true
22
+ end
23
+
20
24
  def from_json
21
25
  unless tag
22
- @tag = tag_service.create identifier_from_path
26
+ @tag = tag_service.create(identifier_from_path)
23
27
  # Make it return a 201 by setting the Location header
24
28
  response.headers["Location"] = tag_url(base_url, tag)
25
29
  end
@@ -36,11 +40,11 @@ module PactBroker
36
40
  end
37
41
 
38
42
  def tag
39
- @tag ||= tag_service.find identifier_from_path
43
+ @tag ||= tag_service.find(identifier_from_path)
40
44
  end
41
45
 
42
46
  def delete_resource
43
- tag_service.delete identifier_from_path
47
+ tag_service.delete(identifier_from_path)
44
48
  true
45
49
  end
46
50
 
@@ -34,15 +34,7 @@ module PactBroker
34
34
  end
35
35
 
36
36
  def malformed_request?
37
- if request.post?
38
- return true if invalid_json?
39
- errors = verification_service.errors(params)
40
- if !errors.empty?
41
- set_json_validation_error_messages(errors.messages)
42
- return true
43
- end
44
- end
45
- false
37
+ super || (request.post? && any_validation_errors?)
46
38
  end
47
39
 
48
40
  def create_path
@@ -91,6 +83,12 @@ module PactBroker
91
83
  def verification_params
92
84
  params(symbolize_names: false).merge("wip" => wip?, "pending" => pending?)
93
85
  end
86
+
87
+ def any_validation_errors?
88
+ errors = verification_service.errors(params)
89
+ set_json_validation_error_messages(errors.messages) if !errors.empty?
90
+ !errors.empty?
91
+ end
94
92
  end
95
93
  end
96
94
  end
@@ -21,16 +21,16 @@ module PactBroker
21
21
  ["GET", "PUT", "PATCH", "DELETE", "OPTIONS"]
22
22
  end
23
23
 
24
- def resource_exists?
25
- !!version
24
+ def put_can_create?
25
+ true
26
26
  end
27
27
 
28
- def malformed_request?
29
- if request.put? && any_request_body?
30
- invalid_json?
31
- else
32
- false
33
- end
28
+ def patch_can_create?
29
+ true
30
+ end
31
+
32
+ def resource_exists?
33
+ !!version
34
34
  end
35
35
 
36
36
  def from_json