dependabot-common 0.392.0 → 0.394.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/clients/azure.rb +42 -22
  3. data/lib/dependabot/clients/bitbucket.rb +52 -26
  4. data/lib/dependabot/clients/bitbucket_with_retries.rb +11 -12
  5. data/lib/dependabot/clients/codecommit.rb +73 -53
  6. data/lib/dependabot/clients/github_resource_parser.rb +70 -0
  7. data/lib/dependabot/clients/github_with_retries.rb +63 -23
  8. data/lib/dependabot/clients/gitlab_with_retries.rb +19 -17
  9. data/lib/dependabot/clients/json_response_parser.rb +103 -0
  10. data/lib/dependabot/command_helpers.rb +79 -11
  11. data/lib/dependabot/credential.rb +12 -3
  12. data/lib/dependabot/errors.rb +4 -1
  13. data/lib/dependabot/file_fetchers/base.rb +199 -151
  14. data/lib/dependabot/file_filtering.rb +0 -2
  15. data/lib/dependabot/file_updaters/vendor_updater.rb +8 -2
  16. data/lib/dependabot/git_tag_details.rb +8 -14
  17. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +211 -110
  18. data/lib/dependabot/metadata_finders/base/commit_response_parser.rb +68 -0
  19. data/lib/dependabot/metadata_finders/base/commits_finder.rb +44 -38
  20. data/lib/dependabot/package/npm_package_manager_config.rb +72 -0
  21. data/lib/dependabot/package/npm_registry_package.rb +186 -0
  22. data/lib/dependabot/pull_request_creator/azure.rb +1 -1
  23. data/lib/dependabot/pull_request_creator/codecommit.rb +10 -12
  24. data/lib/dependabot/pull_request_creator/github.rb +55 -35
  25. data/lib/dependabot/pull_request_creator/gitlab.rb +37 -11
  26. data/lib/dependabot/pull_request_creator/labeler.rb +65 -15
  27. data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +179 -78
  28. data/lib/dependabot/pull_request_updater/azure.rb +63 -11
  29. data/lib/dependabot/pull_request_updater/github.rb +44 -34
  30. data/lib/dependabot/pull_request_updater/gitlab.rb +23 -12
  31. data/lib/dependabot/pull_request_updater.rb +3 -3
  32. data/lib/dependabot/shared_helpers.rb +77 -32
  33. data/lib/dependabot/update_checkers/base.rb +9 -5
  34. data/lib/dependabot/update_checkers/version_filters.rb +5 -0
  35. data/lib/dependabot/update_checkers/vulnerability_audit.rb +282 -0
  36. data/lib/dependabot/workspace/git.rb +35 -4
  37. data/lib/dependabot.rb +1 -1
  38. metadata +10 -4
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
@@ -7,51 +7,21 @@ require "sorbet-runtime"
7
7
  require "dependabot/clients/github_with_retries"
8
8
  require "dependabot/clients/gitlab_with_retries"
9
9
  require "dependabot/clients/bitbucket_with_retries"
10
+ require "dependabot/errors"
10
11
  require "dependabot/shared_helpers"
11
12
  require "dependabot/metadata_finders/base"
12
13
 
13
14
  module Dependabot
14
15
  module MetadataFinders
15
16
  class Base
16
- class ChangelogFile
17
- extend T::Sig
18
-
19
- sig { returns(String) }
20
- attr_reader :name
21
-
22
- sig { returns(String) }
23
- attr_reader :type
24
-
25
- sig { returns(Integer) }
26
- attr_reader :size
27
-
28
- sig { returns(String) }
29
- attr_reader :html_url
30
-
31
- sig { returns(String) }
32
- attr_reader :download_url
33
-
34
- sig { returns(T.nilable(String)) }
35
- attr_reader :path
36
-
37
- sig do
38
- params(
39
- name: String,
40
- type: String,
41
- size: Integer,
42
- html_url: String,
43
- download_url: String,
44
- path: T.nilable(String)
45
- ).void
46
- end
47
- def initialize(name:, type:, size:, html_url:, download_url:, path: nil)
48
- @name = name
49
- @type = type
50
- @size = size
51
- @html_url = html_url
52
- @download_url = download_url
53
- @path = path
54
- end
17
+ class ChangelogFile < T::ImmutableStruct
18
+ const :name, String
19
+ const :type, String
20
+ const :size, Integer
21
+ const :html_url, String
22
+ const :download_url, String
23
+ const :path, T.nilable(String), default: nil
24
+ const :api_url, T.nilable(String), default: nil
55
25
  end
56
26
 
57
27
  # rubocop:disable Metrics/ClassLength
@@ -63,6 +33,7 @@ module Dependabot
63
33
 
64
34
  # Earlier entries are preferred
65
35
  CHANGELOG_NAMES = %w(changelog news changes history release whatsnew releases).freeze
36
+ CHANGELOG_FILE_TYPES = %w(file dir).freeze
66
37
 
67
38
  sig { returns(T.nilable(Dependabot::Source)) }
68
39
  attr_reader :source
@@ -99,7 +70,9 @@ module Dependabot
99
70
  @suggested_changelog_url = @suggested_changelog_url&.split("#")&.first
100
71
 
101
72
  @new_version = T.let(nil, T.nilable(String))
102
- @changelog_from_suggested_url = T.let(nil, T.nilable(Sawyer::Resource))
73
+ @changelog_from_suggested_url = T.let(nil, T.nilable(ChangelogFile))
74
+ @file_text = T.let({}, T::Hash[String, T.nilable(String)])
75
+ @dependency_file_list = T.let({}, T::Hash[T.nilable(String), T::Array[ChangelogFile]])
103
76
  end
104
77
 
105
78
  sig { returns(T.nilable(String)) }
@@ -124,10 +97,11 @@ module Dependabot
124
97
 
125
98
  sig { returns(T.nilable(String)) }
126
99
  def upgrade_guide_text
127
- return unless upgrade_guide
100
+ guide = upgrade_guide
101
+ return unless guide
128
102
 
129
103
  @upgrade_guide_text ||= T.let(
130
- fetch_file_text(upgrade_guide),
104
+ fetch_file_text(guide),
131
105
  T.nilable(String)
132
106
  )
133
107
  end
@@ -135,22 +109,24 @@ module Dependabot
135
109
  private
136
110
 
137
111
  # rubocop:disable Metrics/PerceivedComplexity
138
- sig { returns(T.untyped) }
112
+ sig { returns(T.nilable(ChangelogFile)) }
139
113
  def changelog
140
114
  return unless changelog_from_suggested_url || source
141
115
  return if git_source? && !ref_changed?
142
116
  return changelog_from_suggested_url if changelog_from_suggested_url
143
117
 
144
118
  # If there is a changelog, and it includes the new version, return it
145
- if new_version && default_branch_changelog &&
146
- fetch_file_text(default_branch_changelog)&.include?(T.must(new_version))
147
- return default_branch_changelog
119
+ default_changelog = default_branch_changelog
120
+ if new_version && default_changelog &&
121
+ fetch_file_text(default_changelog)&.include?(T.must(new_version))
122
+ return default_changelog
148
123
  end
149
124
 
150
125
  # Otherwise, look for a changelog at the tag for this version
151
- if new_version && relevant_tag_changelog &&
152
- fetch_file_text(relevant_tag_changelog)&.include?(T.must(new_version))
153
- return relevant_tag_changelog
126
+ tag_changelog = relevant_tag_changelog
127
+ if new_version && tag_changelog &&
128
+ fetch_file_text(tag_changelog)&.include?(T.must(new_version))
129
+ return tag_changelog
154
130
  end
155
131
 
156
132
  # Fall back to the changelog (or nil) from the default branch
@@ -158,7 +134,7 @@ module Dependabot
158
134
  end
159
135
  # rubocop:enable Metrics/PerceivedComplexity
160
136
 
161
- sig { returns(T.nilable(Sawyer::Resource)) }
137
+ sig { returns(T.nilable(ChangelogFile)) }
162
138
  def changelog_from_suggested_url
163
139
  return @changelog_from_suggested_url unless @changelog_from_suggested_url.nil?
164
140
  return unless suggested_changelog_url
@@ -169,27 +145,27 @@ module Dependabot
169
145
 
170
146
  opts = { path: suggested_source&.directory, ref: suggested_source&.branch }.compact
171
147
  suggested_source_client = github_client_for_source(T.must(suggested_source))
172
- tmp_files = suggested_source_client.contents(T.must(suggested_source).repo, opts)
148
+ response = suggested_source_client.contents(T.must(suggested_source).repo, opts)
149
+ tmp_files = github_changelog_files(response, source_url: T.must(suggested_source).url)
173
150
 
174
151
  filename = T.must(T.must(suggested_changelog_url).split("/").last)
175
- @changelog_from_suggested_url =
176
- tmp_files.find { |f| f.name == filename }
152
+ @changelog_from_suggested_url = tmp_files.find { |f| f.name == filename }
177
153
  rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons
178
154
  @changelog_from_suggested_url = nil
179
155
  end
180
156
 
181
- sig { returns(T.nilable(T.any(ChangelogFile, Sawyer::Resource))) }
157
+ sig { returns(T.nilable(ChangelogFile)) }
182
158
  def default_branch_changelog
183
159
  return unless source
184
160
 
185
161
  @default_branch_changelog ||=
186
162
  T.let(
187
163
  changelog_from_ref(nil),
188
- T.nilable(T.any(ChangelogFile, Sawyer::Resource))
164
+ T.nilable(ChangelogFile)
189
165
  )
190
166
  end
191
167
 
192
- sig { returns(T.nilable(T.any(ChangelogFile, Sawyer::Resource))) }
168
+ sig { returns(T.nilable(ChangelogFile)) }
193
169
  def relevant_tag_changelog
194
170
  return unless source
195
171
  return unless tag_for_new_version
@@ -197,11 +173,11 @@ module Dependabot
197
173
  @relevant_tag_changelog ||=
198
174
  T.let(
199
175
  changelog_from_ref(tag_for_new_version),
200
- T.nilable(T.any(ChangelogFile, Sawyer::Resource))
176
+ T.nilable(ChangelogFile)
201
177
  )
202
178
  end
203
179
 
204
- sig { params(ref: T.nilable(String)).returns(T.nilable(T.any(ChangelogFile, Sawyer::Resource))) }
180
+ sig { params(ref: T.nilable(String)).returns(T.nilable(ChangelogFile)) }
205
181
  def changelog_from_ref(ref)
206
182
  files =
207
183
  dependency_file_list(ref)
@@ -216,7 +192,7 @@ module Dependabot
216
192
  end
217
193
 
218
194
  # rubocop:disable Metrics/PerceivedComplexity
219
- sig { params(files: T::Array[T.untyped]).returns(T.untyped) }
195
+ sig { params(files: T::Array[ChangelogFile]).returns(T.nilable(ChangelogFile)) }
220
196
  def select_best_changelog(files)
221
197
  CHANGELOG_NAMES.each do |name|
222
198
  candidates = files.select { |f| f.name =~ /\A#{name}/i }
@@ -257,15 +233,14 @@ module Dependabot
257
233
 
258
234
  sig { returns(T.nilable(String)) }
259
235
  def full_changelog_text
260
- return unless changelog
236
+ selected_changelog = changelog
237
+ return unless selected_changelog
261
238
 
262
- fetch_file_text(changelog)
239
+ fetch_file_text(selected_changelog)
263
240
  end
264
241
 
265
- sig { params(file: T.untyped).returns(T.nilable(String)) }
242
+ sig { params(file: ChangelogFile).returns(T.nilable(String)) }
266
243
  def fetch_file_text(file)
267
- @file_text ||= T.let({}, T.nilable(T::Hash[String, T.untyped]))
268
-
269
244
  unless @file_text.key?(file.download_url)
270
245
  file_source = T.must(Source.from_url(file.html_url))
271
246
  @file_text[file.download_url] =
@@ -279,19 +254,24 @@ module Dependabot
279
254
  end
280
255
  end
281
256
 
282
- return unless @file_text[file.download_url].valid_encoding?
257
+ text = @file_text[file.download_url]
258
+ return unless text&.valid_encoding?
283
259
 
284
- @file_text[file.download_url].rstrip
260
+ text.rstrip
285
261
  end
286
262
 
287
- sig { params(file_source: Dependabot::Source, file: T.untyped).returns(String) }
263
+ sig { params(file_source: Dependabot::Source, file: ChangelogFile).returns(String) }
288
264
  def fetch_github_file(file_source, file)
289
265
  # Hitting the download URL directly causes encoding problems
290
- raw_content = T.unsafe(github_client_for_source(file_source).get(file.url)).content
266
+ response = github_client_for_source(file_source).get(T.must(file.api_url))
267
+ raise_bad_metadata_response("GitHub", "file response must be an object", source_url: file_source.url) unless
268
+ response.is_a?(Sawyer::Resource)
269
+
270
+ raw_content = sawyer_string(response, :content, source_url: file_source.url)
291
271
  Base64.decode64(raw_content).force_encoding("UTF-8").encode
292
272
  end
293
273
 
294
- sig { params(file: T.untyped).returns(String) }
274
+ sig { params(file: ChangelogFile).returns(String) }
295
275
  def fetch_gitlab_file(file)
296
276
  Excon.get(
297
277
  file.download_url,
@@ -300,19 +280,19 @@ module Dependabot
300
280
  ).body.force_encoding("UTF-8").encode
301
281
  end
302
282
 
303
- sig { params(file: T.untyped).returns(String) }
283
+ sig { params(file: ChangelogFile).returns(String) }
304
284
  def fetch_bitbucket_file(file)
305
285
  bitbucket_client.get(file.download_url).body
306
286
  .force_encoding("UTF-8").encode
307
287
  end
308
288
 
309
- sig { params(file: T.untyped).returns(String) }
289
+ sig { params(file: ChangelogFile).returns(String) }
310
290
  def fetch_azure_file(file)
311
291
  azure_client.get(file.download_url).body
312
292
  .force_encoding("UTF-8").encode
313
293
  end
314
294
 
315
- sig { returns(T.untyped) }
295
+ sig { returns(T.nilable(ChangelogFile)) }
316
296
  def upgrade_guide
317
297
  return unless source
318
298
 
@@ -322,18 +302,17 @@ module Dependabot
322
302
 
323
303
  dependency_file_list
324
304
  .select { |f| f.type == "file" }
325
- .select { |f| f.name.casecmp("upgrade.md").zero? }
305
+ .select { |f| f.name.casecmp?("upgrade.md") }
326
306
  .reject { |f| f.size > 1_000_000 }
327
307
  .max_by(&:size)
328
308
  end
329
309
 
330
- sig { params(ref: T.nilable(String)).returns(T.untyped) }
310
+ sig { params(ref: T.nilable(String)).returns(T::Array[ChangelogFile]) }
331
311
  def dependency_file_list(ref = nil)
332
- @dependency_file_list ||= T.let({}, T.nilable(T::Hash[T.nilable(String), T.untyped]))
333
312
  @dependency_file_list[ref] ||= fetch_dependency_file_list(ref)
334
313
  end
335
314
 
336
- sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped]) }
315
+ sig { params(ref: T.nilable(String)).returns(T::Array[ChangelogFile]) }
337
316
  def fetch_dependency_file_list(ref)
338
317
  case T.must(source).provider
339
318
  when "github" then fetch_github_file_list(ref)
@@ -346,45 +325,55 @@ module Dependabot
346
325
  end
347
326
  end
348
327
 
349
- sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped]) }
328
+ sig { params(ref: T.nilable(String)).returns(T::Array[ChangelogFile]) }
350
329
  def fetch_github_file_list(ref)
351
- files = []
330
+ files = T.let([], T::Array[ChangelogFile])
352
331
 
353
- if T.must(source).directory
354
- opts = { path: T.must(source).directory, ref: ref }.compact
355
- tmp_files = github_client.contents(T.must(source).repo, opts)
356
- files += tmp_files if tmp_files.is_a?(Array)
332
+ directory = T.must(source).directory
333
+ if directory
334
+ options = { path: directory, ref: ref }.compact
335
+ response = github_client.contents(T.must(source).repo, options)
336
+ files.concat(github_changelog_files(response, source_url: T.must(source).url)) if response.is_a?(Array)
357
337
  end
358
-
359
- opts = { ref: ref }.compact
360
- files += github_client.contents(T.must(source).repo, opts)
338
+ files.concat(github_files_at(path: nil, ref: ref))
361
339
 
362
340
  files.uniq.each do |f|
363
341
  next unless f.type == "dir" && f.name.match?(/docs?/o)
364
342
 
365
- opts = { path: f.path, ref: ref }.compact
366
- files += github_client.contents(T.must(source).repo, opts)
343
+ files.concat(github_files_at(path: f.path, ref: ref))
367
344
  end
368
345
 
369
346
  files
370
347
  rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons
371
348
  []
372
349
  end
373
- sig { returns(T.untyped) }
350
+
351
+ sig { params(path: T.nilable(String), ref: T.nilable(String)).returns(T::Array[ChangelogFile]) }
352
+ def github_files_at(path:, ref:)
353
+ options = { path: path, ref: ref }.compact
354
+ github_changelog_files(
355
+ github_client.contents(T.must(source).repo, options),
356
+ source_url: T.must(source).url
357
+ )
358
+ end
359
+
360
+ sig { returns(T::Array[ChangelogFile]) }
374
361
  def fetch_bitbucket_file_list
375
362
  branch = default_bitbucket_branch
376
363
  bitbucket_client.fetch_repo_contents(T.must(source).repo).map do |file|
377
- type = case file.fetch("type")
364
+ object_type = object_string(file, "type", "Bitbucket")
365
+ path = object_string(file, "path", "Bitbucket")
366
+ type = case object_type
378
367
  when "commit_file" then "file"
379
368
  when "commit_directory" then "dir"
380
- else file.fetch("type")
369
+ else object_type
381
370
  end
382
371
  ChangelogFile.new(
383
- name: file.fetch("path").split("/").last,
372
+ name: T.must(path.split("/").last),
384
373
  type: type,
385
- size: file.fetch("size", 100),
386
- html_url: "#{T.must(source).url}/src/#{branch}/#{file['path']}",
387
- download_url: "#{T.must(source).url}/raw/#{branch}/#{file['path']}"
374
+ size: object_integer(file, "size", "Bitbucket", default: 100),
375
+ html_url: "#{T.must(source).url}/src/#{branch}/#{path}",
376
+ download_url: "#{T.must(source).url}/raw/#{branch}/#{path}"
388
377
  )
389
378
  end
390
379
  rescue Dependabot::Clients::Bitbucket::NotFound,
@@ -393,43 +382,48 @@ module Dependabot
393
382
  []
394
383
  end
395
384
 
396
- sig { returns(T.untyped) }
385
+ sig { returns(T::Array[ChangelogFile]) }
397
386
  def fetch_gitlab_file_list
398
387
  branch = default_gitlab_branch
399
- T.unsafe(gitlab_client.repo_tree(T.must(source).repo)).map do |file|
400
- type = case file.type
388
+ gitlab_client.repo_tree(T.must(source).repo).map do |file|
389
+ object_type = gitlab_string(file, "type")
390
+ type = case object_type
401
391
  when "blob" then "file"
402
392
  when "tree" then "dir"
403
- else file.fetch("type")
393
+ else object_type
404
394
  end
395
+ path = gitlab_string(file, "path")
405
396
  ChangelogFile.new(
406
- name: file.name,
397
+ name: gitlab_string(file, "name"),
407
398
  type: type,
408
399
  size: 100, # GitLab doesn't return file size
409
- html_url: "#{T.must(source).url}/blob/#{branch}/#{file.path}",
410
- download_url: "#{T.must(source).url}/raw/#{branch}/#{file.path}"
400
+ html_url: "#{T.must(source).url}/blob/#{branch}/#{path}",
401
+ download_url: "#{T.must(source).url}/raw/#{branch}/#{path}",
402
+ path: path
411
403
  )
412
404
  end
413
405
  rescue Gitlab::Error::NotFound
414
406
  []
415
407
  end
416
408
 
417
- sig { returns(T.untyped) }
409
+ sig { returns(T::Array[ChangelogFile]) }
418
410
  def fetch_azure_file_list
419
411
  azure_client.fetch_repo_contents.map do |entry|
420
- type = case entry.fetch("gitObjectType")
412
+ object_type = object_string(entry, "gitObjectType", "Azure")
413
+ relative_path = object_string(entry, "relativePath", "Azure")
414
+ type = case object_type
421
415
  when "blob" then "file"
422
416
  when "tree" then "dir"
423
- else entry.fetch("gitObjectType")
417
+ else object_type
424
418
  end
425
419
 
426
420
  ChangelogFile.new(
427
- name: File.basename(entry.fetch("relativePath")),
421
+ name: File.basename(relative_path),
428
422
  type: type,
429
- size: entry.fetch("size"),
430
- path: entry.fetch("relativePath"),
431
- html_url: "#{T.must(source).url}?path=/#{entry.fetch('relativePath')}",
432
- download_url: entry.fetch("url")
423
+ size: object_integer(entry, "size", "Azure"),
424
+ path: relative_path,
425
+ html_url: "#{T.must(source).url}?path=/#{relative_path}",
426
+ download_url: object_string(entry, "url", "Azure")
433
427
  )
434
428
  end
435
429
  rescue Dependabot::Clients::Azure::NotFound,
@@ -438,6 +432,113 @@ module Dependabot
438
432
  []
439
433
  end
440
434
 
435
+ sig do
436
+ params(
437
+ response: T.any(Sawyer::Resource, T::Array[Sawyer::Resource]),
438
+ source_url: String
439
+ )
440
+ .returns(T::Array[ChangelogFile])
441
+ end
442
+ def github_changelog_files(response, source_url:)
443
+ resources = response.is_a?(Array) ? response : [response]
444
+ resources.filter_map do |resource|
445
+ type = sawyer_string(resource, :type, source_url: source_url)
446
+ next unless CHANGELOG_FILE_TYPES.include?(type)
447
+
448
+ api_url = sawyer_string(resource, :url, source_url: source_url)
449
+ ChangelogFile.new(
450
+ name: sawyer_string(resource, :name, source_url: source_url),
451
+ type: type,
452
+ size: sawyer_optional_integer(resource, :size, source_url: source_url) || 0,
453
+ html_url: sawyer_string(resource, :html_url, source_url: source_url),
454
+ download_url: sawyer_optional_string(resource, :download_url, source_url: source_url) || api_url,
455
+ path: sawyer_optional_string(resource, :path, source_url: source_url),
456
+ api_url: api_url
457
+ )
458
+ end
459
+ end
460
+
461
+ sig { params(resource: Sawyer::Resource, key: Symbol, source_url: String).returns(String) }
462
+ def sawyer_string(resource, key, source_url:)
463
+ value = T.cast(resource[key], Object)
464
+ return value if value.is_a?(String)
465
+
466
+ raise_bad_metadata_response("GitHub", "#{key} must be a string", source_url: source_url)
467
+ end
468
+
469
+ sig do
470
+ params(resource: Sawyer::Resource, key: Symbol, source_url: String)
471
+ .returns(T.nilable(String))
472
+ end
473
+ def sawyer_optional_string(resource, key, source_url:)
474
+ value = T.cast(resource[key], Object)
475
+ return if value.nil?
476
+ return value if value.is_a?(String)
477
+
478
+ raise_bad_metadata_response("GitHub", "#{key} must be a string or nil", source_url: source_url)
479
+ end
480
+
481
+ sig do
482
+ params(resource: Sawyer::Resource, key: Symbol, source_url: String)
483
+ .returns(T.nilable(Integer))
484
+ end
485
+ def sawyer_optional_integer(resource, key, source_url:)
486
+ value = T.cast(resource[key], Object)
487
+ return if value.nil?
488
+ return value if value.is_a?(Integer)
489
+
490
+ raise_bad_metadata_response("GitHub", "#{key} must be an integer or nil", source_url: source_url)
491
+ end
492
+
493
+ sig { params(resource: Gitlab::ObjectifiedHash, key: String).returns(String) }
494
+ def gitlab_string(resource, key)
495
+ value = T.cast(resource[key], Object)
496
+ return value if value.is_a?(String)
497
+
498
+ raise_bad_metadata_response("GitLab", "#{key} must be a string")
499
+ end
500
+
501
+ sig do
502
+ params(object: T::Hash[String, Object], key: String, provider: String)
503
+ .returns(String)
504
+ end
505
+ def object_string(object, key, provider)
506
+ value = object[key]
507
+ return value if value.is_a?(String)
508
+
509
+ raise_bad_metadata_response(provider, "#{key} must be a string")
510
+ end
511
+
512
+ sig do
513
+ params(
514
+ object: T::Hash[String, Object],
515
+ key: String,
516
+ provider: String,
517
+ default: T.nilable(Integer)
518
+ ).returns(Integer)
519
+ end
520
+ def object_integer(object, key, provider, default: nil)
521
+ value = object[key]
522
+ return default if value.nil? && default
523
+ return value if value.is_a?(Integer)
524
+
525
+ raise_bad_metadata_response(provider, "#{key} must be an integer")
526
+ end
527
+
528
+ sig do
529
+ params(
530
+ provider: String,
531
+ message: String,
532
+ source_url: T.nilable(String)
533
+ ).returns(T.noreturn)
534
+ end
535
+ def raise_bad_metadata_response(provider, message, source_url: nil)
536
+ raise Dependabot::PrivateSourceBadResponse.new(
537
+ source_url || T.must(source).url,
538
+ "Malformed #{provider} metadata response: #{message}"
539
+ )
540
+ end
541
+
441
542
  sig { returns(T.nilable(String)) }
442
543
  def new_version
443
544
  return @new_version unless @new_version.nil?
@@ -0,0 +1,68 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/errors"
5
+ require "dependabot/clients/github_with_retries"
6
+ require "dependabot/clients/gitlab_with_retries"
7
+ require "dependabot/metadata_finders/base"
8
+ require "sorbet-runtime"
9
+
10
+ module Dependabot
11
+ module MetadataFinders
12
+ class Base
13
+ class CommitResponseParser
14
+ extend T::Sig
15
+
16
+ sig { params(source_url: String).void }
17
+ def initialize(source_url:)
18
+ @source_url = source_url
19
+ end
20
+
21
+ sig { params(commit: Sawyer::Resource).returns(String) }
22
+ def github_commit_sha(commit)
23
+ sawyer_string(commit, :sha, "commit")
24
+ end
25
+
26
+ sig { params(commit: Sawyer::Resource).returns(String) }
27
+ def github_commit_message(commit)
28
+ details = T.cast(commit[:commit], Object)
29
+ raise_bad_response("GitHub", "commit details must be an object") unless details.is_a?(Sawyer::Resource)
30
+
31
+ sawyer_string(details, :message, "commit")
32
+ end
33
+
34
+ sig { params(resource: Sawyer::Resource, key: Symbol, context: String).returns(String) }
35
+ def sawyer_string(resource, key, context)
36
+ value = T.cast(resource[key], Object)
37
+ return value if value.is_a?(String)
38
+
39
+ raise_bad_response("GitHub", "#{context} #{key} must be a string")
40
+ end
41
+
42
+ sig { params(resource: Gitlab::ObjectifiedHash, key: String, context: String).returns(String) }
43
+ def gitlab_string(resource, key, context)
44
+ value = T.cast(resource[key], Object)
45
+ return value if value.is_a?(String)
46
+
47
+ raise_bad_response("GitLab", "#{context} #{key} must be a string")
48
+ end
49
+
50
+ sig { params(object: T::Hash[String, Object], key: String, context: String).returns(String) }
51
+ def object_string(object, key, context)
52
+ value = object[key]
53
+ return value if value.is_a?(String)
54
+
55
+ raise_bad_response("Azure", "#{context} #{key} must be a string")
56
+ end
57
+
58
+ sig { params(provider: String, message: String).returns(T.noreturn) }
59
+ def raise_bad_response(provider, message)
60
+ raise Dependabot::PrivateSourceBadResponse.new(
61
+ @source_url,
62
+ "Malformed #{provider} commit response: #{message}"
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end