dependabot-cargo 0.305.0 → 0.308.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7bb1d1d01e8722e5d2e2b4d7d5ebd81308db522828484bc57e3a5f7199acd16c
4
- data.tar.gz: 5c9fee65711dbb7fb55fa211d48578e46189053ad017b616576bee87530d066e
3
+ metadata.gz: b830b897ee246742059ad0d56924be8b7ca0c646b9f0777637f1f0c608752054
4
+ data.tar.gz: 4db462646457521b6256f413d149a2ca3bf2ad768e243485f938528a1b740808
5
5
  SHA512:
6
- metadata.gz: da0510df1c5a53673c4cdae9278e8c5d8b674f44d88ae279eb79d97d14980d658fe48d2caba0c4390d472289f3788a78808f505d069803a3d19e5c86722f75e5
7
- data.tar.gz: 4634cef9a1f15590dba5127a23cfdf43d0661c10994cef61a0919a5dd2fd8b4f19f4e51acaf95d12a5e781453d0aa98f63218d0f3e5ae58eca331dd9304e64d2
6
+ metadata.gz: f6954c5b0a79e3764f1050f037b877a4f934de71db5af4630bcbd9c50823e457e596c28a8e83dcdaf966b63ba851c431e304d8efddf170ee6912f77ee4b74056
7
+ data.tar.gz: 66b3541bd6f79a1787b3083051e59f2693a2bb57932a97f7f59052abdfa33e3cd85b138de68eff778a312f46c924fd36e0a7924aa8ccbd86af1be487ea6666e0
@@ -0,0 +1,231 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "time"
6
+ require "cgi"
7
+ require "excon"
8
+ require "nokogiri"
9
+ require "sorbet-runtime"
10
+ require "dependabot/registry_client"
11
+ require "dependabot/cargo"
12
+ require "dependabot/package/package_release"
13
+ require "dependabot/package/package_details"
14
+
15
+ module Dependabot
16
+ module Cargo
17
+ module Package
18
+ class PackageDetailsFetcher
19
+ extend T::Sig
20
+
21
+ CRATES_IO_API = "https://crates.io/api/v1/crates"
22
+ PACKAGE_TYPE = "gem"
23
+ PACKAGE_LANGUAGE = "rust"
24
+
25
+ # fallback for empty requirement
26
+ RUST_REQUIREMENT = "1.0"
27
+
28
+ sig do
29
+ params(
30
+ dependency: Dependabot::Dependency,
31
+ dependency_files: T::Array[Dependabot::DependencyFile],
32
+ credentials: T::Array[Dependabot::Credential]
33
+ ).void
34
+ end
35
+ def initialize(dependency:, dependency_files:, credentials:)
36
+ @dependency = dependency
37
+ @dependency_files = dependency_files
38
+ @credentials = credentials
39
+
40
+ @source_type = T.let(nil, T.nilable(String))
41
+ end
42
+
43
+ sig { returns(Dependabot::Dependency) }
44
+ attr_reader :dependency
45
+
46
+ sig { returns(T::Array[T.untyped]) }
47
+ attr_reader :dependency_files
48
+
49
+ sig { returns(T::Array[T.untyped]) }
50
+ attr_reader :credentials
51
+
52
+ sig do
53
+ returns(T.nilable(Dependabot::Package::PackageDetails))
54
+ end
55
+ def fetch
56
+ package_releases = crates_listing.fetch("versions", []).reject { |v| v["yanked"] }.map do |release|
57
+ package_release(
58
+ version: release["num"] || release["vers"],
59
+ released_at: release["created_at"] ? Time.parse(release["created_at"]) : nil,
60
+ downloads: release["downloads"],
61
+ url: "#{CRATES_IO_API}/#{release['dl_path']}",
62
+ rust_version: release["rust"]
63
+ )
64
+ end
65
+
66
+ package_details(package_releases)
67
+ end
68
+
69
+ private
70
+
71
+ sig do
72
+ returns(T.untyped)
73
+ end
74
+ def crates_listing
75
+ return @crates_listing unless @crates_listing.nil?
76
+
77
+ info = fetch_dependency_info
78
+ index = fetch_index(info)
79
+
80
+ hdrs = default_headers
81
+ hdrs.merge!(auth_headers(info)) if index != CRATES_IO_API
82
+
83
+ url = metadata_fetch_url(dependency, index)
84
+
85
+ Dependabot.logger.info("Calling #{url} to fetch metadata for #{dependency.name} from #{index}")
86
+
87
+ response = fetch_response(url, hdrs)
88
+ return {} if response.status == 404
89
+
90
+ @crates_listing = T.let(parse_response(response, index), T.nilable(T::Hash[T.untyped, T.untyped]))
91
+
92
+ Dependabot.logger.info("Fetched metadata for #{dependency.name} from #{index} successfully")
93
+
94
+ @crates_listing
95
+ end
96
+
97
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
98
+ def fetch_dependency_info
99
+ dependency.requirements.filter_map { |r| r[:source] }.first
100
+ end
101
+
102
+ sig { params(info: T.untyped).returns(String) }
103
+ def fetch_index(info)
104
+ (info && info[:index]) || CRATES_IO_API
105
+ end
106
+
107
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
108
+ def default_headers
109
+ { "User-Agent" => "Dependabot (dependabot.com)" }
110
+ end
111
+
112
+ sig { params(info: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
113
+ def auth_headers(info)
114
+ registry_creds = credentials.find do |cred|
115
+ cred["type"] == "cargo_registry" && cred["registry"] == info[:name]
116
+ end
117
+
118
+ return {} if registry_creds.nil?
119
+
120
+ token = registry_creds["token"] || "placeholder_token"
121
+ { "Authorization" => token }
122
+ end
123
+
124
+ sig { params(url: String, headers: T.untyped).returns(Excon::Response) }
125
+ def fetch_response(url, headers)
126
+ Excon.get(
127
+ url,
128
+ idempotent: true,
129
+ **SharedHelpers.excon_defaults(headers: headers)
130
+ )
131
+ end
132
+
133
+ sig { params(response: Excon::Response, index: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
134
+ def parse_response(response, index)
135
+ if index.start_with?("sparse+")
136
+ parsed_response = response.body.lines.map { |line| JSON.parse(line) }
137
+ { "versions" => parsed_response }
138
+ else
139
+ JSON.parse(response.body)
140
+ end
141
+ end
142
+
143
+ sig { params(dependency: T.untyped, index: T.untyped).returns(String) }
144
+ def metadata_fetch_url(dependency, index)
145
+ return "#{index}/#{dependency.name}" if index == CRATES_IO_API
146
+
147
+ # Determine cargo's index file path for the dependency
148
+ index = index.delete_prefix("sparse+")
149
+ name_length = dependency.name.length
150
+ dependency_path = case name_length
151
+ when 1, 2
152
+ "#{name_length}/#{dependency.name}"
153
+ when 3
154
+ "#{name_length}/#{dependency.name[0..1]}/#{dependency.name}"
155
+ else
156
+ "#{dependency.name[0..1]}/#{dependency.name[2..3]}/#{dependency.name}"
157
+ end
158
+
159
+ "#{index}#{'/' unless index.end_with?('/')}#{dependency_path}"
160
+ end
161
+
162
+ sig { returns(T::Boolean) }
163
+ def wants_prerelease?
164
+ return true if dependency.numeric_version&.prerelease?
165
+
166
+ dependency.requirements.any? do |req|
167
+ reqs = (req.fetch(:requirement) || "").split(",").map(&:strip)
168
+ reqs.any? { |r| r.match?(/[A-Za-z]/) }
169
+ end
170
+ end
171
+
172
+ sig { returns(T.class_of(Dependabot::Version)) }
173
+ def version_class
174
+ dependency.version_class
175
+ end
176
+
177
+ sig { returns(T.class_of(Dependabot::Requirement)) }
178
+ def requirement_class
179
+ dependency.requirement_class
180
+ end
181
+
182
+ sig do
183
+ params(
184
+ version: String,
185
+ released_at: T.nilable(Time),
186
+ downloads: T.nilable(Integer),
187
+ url: T.nilable(String),
188
+ rust_version: T.nilable(String),
189
+ yanked: T::Boolean
190
+ ).returns(Dependabot::Package::PackageRelease)
191
+ end
192
+ def package_release(version:, released_at:, downloads:, url:, rust_version:, yanked: false)
193
+ Dependabot::Package::PackageRelease.new(
194
+ version: Cargo::Version.new(version),
195
+ released_at: released_at,
196
+ yanked: yanked,
197
+ yanked_reason: nil,
198
+ downloads: downloads,
199
+ url: url,
200
+ package_type: PACKAGE_TYPE,
201
+ language: Dependabot::Package::PackageLanguage.new(
202
+ name: PACKAGE_LANGUAGE,
203
+ version: nil,
204
+ requirement: language_requirement(rust_version)
205
+ )
206
+ )
207
+ end
208
+
209
+ sig { params(req_string: T.nilable(String)).returns(T.nilable(Requirement)) }
210
+ def language_requirement(req_string)
211
+ return Requirement.new(req_string) if req_string && !req_string.empty?
212
+
213
+ nil
214
+ end
215
+
216
+ sig do
217
+ params(releases: T::Array[Dependabot::Package::PackageRelease])
218
+ .returns(Dependabot::Package::PackageDetails)
219
+ end
220
+ def package_details(releases)
221
+ @package_details ||= T.let(
222
+ Dependabot::Package::PackageDetails.new(
223
+ dependency: dependency,
224
+ releases: releases.reverse.uniq(&:version)
225
+ ), T.nilable(Dependabot::Package::PackageDetails)
226
+ )
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -5,25 +5,25 @@ require "excon"
5
5
  require "dependabot/cargo/update_checker"
6
6
  require "dependabot/update_checkers/version_filters"
7
7
  require "dependabot/registry_client"
8
+ require "dependabot/cargo/package/package_details_fetcher"
9
+ require "dependabot/package/package_latest_version_finder"
8
10
  require "sorbet-runtime"
9
11
 
10
12
  module Dependabot
11
13
  module Cargo
12
14
  class UpdateChecker
13
- class LatestVersionFinder
15
+ class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
14
16
  extend T::Sig
15
17
 
16
- CRATES_IO_API = "https://crates.io/api/v1/crates"
17
-
18
- def initialize(dependency:, dependency_files:, credentials:,
19
- ignored_versions:, raise_on_ignored: false,
20
- security_advisories:)
21
- @dependency = dependency
22
- @dependency_files = dependency_files
23
- @credentials = credentials
24
- @ignored_versions = ignored_versions
25
- @raise_on_ignored = raise_on_ignored
26
- @security_advisories = security_advisories
18
+ sig do
19
+ override.returns(T.nilable(Dependabot::Package::PackageDetails))
20
+ end
21
+ def package_details
22
+ @package_details ||= Package::PackageDetailsFetcher.new(
23
+ dependency: dependency,
24
+ dependency_files: dependency_files,
25
+ credentials: credentials
26
+ ).fetch
27
27
  end
28
28
 
29
29
  def latest_version
@@ -31,164 +31,12 @@ module Dependabot
31
31
  end
32
32
 
33
33
  def lowest_security_fix_version
34
- @lowest_security_fix_version ||= fetch_lowest_security_fix_version
35
- end
36
-
37
- private
38
-
39
- attr_reader :dependency
40
- attr_reader :dependency_files
41
- attr_reader :credentials
42
- attr_reader :ignored_versions
43
- attr_reader :security_advisories
44
-
45
- def fetch_latest_version
46
- versions = available_versions
47
- versions = filter_prerelease_versions(versions)
48
- versions = filter_ignored_versions(versions)
49
- versions.max
50
- end
51
-
52
- def fetch_lowest_security_fix_version
53
- versions = available_versions
54
- versions = filter_prerelease_versions(versions)
55
- versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(versions,
56
- security_advisories)
57
- versions = filter_ignored_versions(versions)
58
- versions = filter_lower_versions(versions)
59
-
60
- versions.min
61
- end
62
-
63
- sig { params(versions_array: T::Array[Gem::Version]).returns(T::Array[Gem::Version]) }
64
- def filter_prerelease_versions(versions_array)
65
- return versions_array if wants_prerelease?
66
-
67
- filtered = versions_array.reject(&:prerelease?)
68
- if versions_array.count > filtered.count
69
- Dependabot.logger.info("Filtered out #{versions_array.count - filtered.count} pre-release versions")
70
- end
71
- filtered
34
+ @lowest_security_fix_version ||= fetch_lowest_security_fix_version(language_version: nil)
72
35
  end
73
36
 
74
- sig { params(versions_array: T::Array[Gem::Version]).returns(T::Array[Gem::Version]) }
75
- def filter_ignored_versions(versions_array)
76
- filtered = versions_array
77
- .reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
78
- if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any?
79
- raise Dependabot::AllVersionsIgnored
80
- end
81
-
82
- if versions_array.count > filtered.count
83
- Dependabot.logger.info("Filtered out #{versions_array.count - filtered.count} ignored versions")
84
- end
85
-
86
- filtered
87
- end
88
-
89
- def filter_lower_versions(versions_array)
90
- return versions_array unless dependency.numeric_version
91
-
92
- versions_array
93
- .select { |version| version > dependency.numeric_version }
94
- end
95
-
96
- def available_versions
97
- crates_listing
98
- .fetch("versions", [])
99
- .reject { |v| v["yanked"] }
100
- # Handle both default and sparse registry responses.
101
- # Default registry uses "num" for version number.
102
- # Sparse registry uses "vers" for version number.
103
- .map do |v|
104
- version_number = v["num"] || v["vers"]
105
- version_class.new(version_number)
106
- end
107
- end
108
-
109
- def crates_listing
110
- return @crates_listing unless @crates_listing.nil?
111
-
112
- info = fetch_dependency_info
113
- index = fetch_index(info)
114
-
115
- hdrs = default_headers
116
- hdrs.merge!(auth_headers(info)) if index != CRATES_IO_API
117
-
118
- url = metadata_fetch_url(dependency, index)
119
-
120
- # B4PR
121
- Dependabot.logger.info("Calling #{url} to fetch metadata for #{dependency.name} from #{index}")
122
-
123
- response = fetch_response(url, hdrs)
124
- return {} if response.status == 404
125
-
126
- @crates_listing = parse_response(response, index)
127
-
128
- # B4PR
129
- Dependabot.logger.info("Fetched metadata for #{dependency.name} from #{index} successfully")
130
-
131
- @crates_listing
132
- end
133
-
134
- def fetch_dependency_info
135
- dependency.requirements.filter_map { |r| r[:source] }.first
136
- end
137
-
138
- def fetch_index(info)
139
- (info && info[:index]) || CRATES_IO_API
140
- end
141
-
142
- def default_headers
143
- { "User-Agent" => "Dependabot (dependabot.com)" }
144
- end
145
-
146
- def auth_headers(info)
147
- registry_creds = credentials.find do |cred|
148
- cred["type"] == "cargo_registry" && cred["registry"] == info[:name]
149
- end
150
-
151
- return {} if registry_creds.nil?
152
-
153
- token = registry_creds["token"] || "placeholder_token"
154
- { "Authorization" => token }
155
- end
156
-
157
- def fetch_response(url, headers)
158
- Excon.get(
159
- url,
160
- idempotent: true,
161
- **SharedHelpers.excon_defaults(headers: headers)
162
- )
163
- end
164
-
165
- def parse_response(response, index)
166
- if index.start_with?("sparse+")
167
- parsed_response = response.body.lines.map { |line| JSON.parse(line) }
168
- { "versions" => parsed_response }
169
- else
170
- JSON.parse(response.body)
171
- end
172
- end
173
-
174
- def metadata_fetch_url(dependency, index)
175
- return "#{index}/#{dependency.name}" if index == CRATES_IO_API
176
-
177
- # Determine cargo's index file path for the dependency
178
- index = index.delete_prefix("sparse+")
179
- name_length = dependency.name.length
180
- dependency_path = case name_length
181
- when 1, 2
182
- "#{name_length}/#{dependency.name}"
183
- when 3
184
- "#{name_length}/#{dependency.name[0..1]}/#{dependency.name}"
185
- else
186
- "#{dependency.name[0..1]}/#{dependency.name[2..3]}/#{dependency.name}"
187
- end
188
-
189
- "#{index}#{'/' unless index.end_with?('/')}#{dependency_path}"
190
- end
37
+ protected
191
38
 
39
+ sig { override.returns(T::Boolean) }
192
40
  def wants_prerelease?
193
41
  return true if dependency.numeric_version&.prerelease?
194
42
 
@@ -198,16 +46,21 @@ module Dependabot
198
46
  end
199
47
  end
200
48
 
201
- def ignore_requirements
202
- ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
49
+ sig { override.returns(T::Boolean) }
50
+ def cooldown_enabled?
51
+ Dependabot::Experiments.enabled?(:enable_cooldown_for_cargo)
203
52
  end
204
53
 
205
- def version_class
206
- dependency.version_class
207
- end
54
+ private
55
+
56
+ attr_reader :dependency
57
+ attr_reader :dependency_files
58
+ attr_reader :credentials
59
+ attr_reader :ignored_versions
60
+ attr_reader :security_advisories
208
61
 
209
- def requirement_class
210
- dependency.requirement_class
62
+ def apply_post_fetch_lowest_security_fix_versions_filter(versions)
63
+ filter_prerelease_versions(versions)
211
64
  end
212
65
  end
213
66
  end
@@ -116,14 +116,16 @@ module Dependabot
116
116
  end
117
117
 
118
118
  def latest_version_finder
119
- @latest_version_finder ||= LatestVersionFinder.new(
120
- dependency: dependency,
121
- dependency_files: dependency_files,
122
- credentials: credentials,
123
- ignored_versions: ignored_versions,
124
- raise_on_ignored: raise_on_ignored,
125
- security_advisories: security_advisories
126
- )
119
+ @latest_version_finder ||=
120
+ LatestVersionFinder.new(
121
+ dependency: dependency,
122
+ dependency_files: dependency_files,
123
+ credentials: credentials,
124
+ ignored_versions: ignored_versions,
125
+ security_advisories: security_advisories,
126
+ cooldown_options: update_cooldown,
127
+ raise_on_ignored: raise_on_ignored
128
+ )
127
129
  end
128
130
 
129
131
  def latest_version_for_git_dependency
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-cargo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.305.0
4
+ version: 0.308.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-06 00:00:00.000000000 Z
10
+ date: 2025-04-12 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: dependabot-common
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - '='
18
17
  - !ruby/object:Gem::Version
19
- version: 0.305.0
18
+ version: 0.308.0
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - '='
25
24
  - !ruby/object:Gem::Version
26
- version: 0.305.0
25
+ version: 0.308.0
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: debug
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -251,6 +250,7 @@ files:
251
250
  - lib/dependabot/cargo/helpers.rb
252
251
  - lib/dependabot/cargo/language.rb
253
252
  - lib/dependabot/cargo/metadata_finder.rb
253
+ - lib/dependabot/cargo/package/package_details_fetcher.rb
254
254
  - lib/dependabot/cargo/package_manager.rb
255
255
  - lib/dependabot/cargo/registry_fetcher.rb
256
256
  - lib/dependabot/cargo/requirement.rb
@@ -265,8 +265,7 @@ licenses:
265
265
  - MIT
266
266
  metadata:
267
267
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
268
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.305.0
269
- post_install_message:
268
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.308.0
270
269
  rdoc_options: []
271
270
  require_paths:
272
271
  - lib
@@ -281,8 +280,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
280
  - !ruby/object:Gem::Version
282
281
  version: 3.1.0
283
282
  requirements: []
284
- rubygems_version: 3.5.22
285
- signing_key:
283
+ rubygems_version: 3.6.3
286
284
  specification_version: 4
287
285
  summary: Provides Dependabot support for Rust (Cargo)
288
286
  test_files: []