Skip to content

Commit

Permalink
Strict type some more nuget (#9293)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Mar 15, 2024
1 parent d710546 commit e25ccee
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 20 deletions.
84 changes: 76 additions & 8 deletions nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "nokogiri"
require "zip"
require "stringio"
require "sorbet-runtime"
require "zip"

require "dependabot/nuget/http_response_helpers"

module Dependabot
module Nuget
class NupkgFetcher
extend T::Sig

require_relative "repository_finder"

sig do
params(
dependency_urls: T::Array[T::Hash[Symbol, String]],
package_id: String,
package_version: String
)
.returns(T.nilable(String))
end
def self.fetch_nupkg_buffer(dependency_urls, package_id, package_version)
# check all repositories for the first one that has the nupkg
dependency_urls.reduce(nil) do |nupkg_buffer, repository_details|
dependency_urls.reduce(T.let(nil, T.nilable(String))) do |nupkg_buffer, repository_details|
nupkg_buffer || fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
end
end

sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: T.nilable(String),
package_version: T.nilable(String)
)
.returns(T.nilable(String))
end
def self.fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
return unless package_id && package_version && !package_version.empty?

Expand All @@ -35,6 +55,14 @@ def self.fetch_nupkg_url_from_repository(repository_details, package_id, package
package_url
end

sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: String,
package_version: String
)
.returns(T.nilable(String))
end
def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
package_url = fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
return unless package_url
Expand All @@ -43,6 +71,14 @@ def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, pack
fetch_stream(package_url, auth_header)
end

sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: String,
package_version: String
)
.returns(T.nilable(String))
end
def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
base_url = repository_details[:base_url]
unless base_url
Expand All @@ -57,15 +93,23 @@ def self.get_nuget_v3_package_url(repository_details, package_id, package_versio

# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: String,
package_version: String
)
.returns(T.nilable(String))
end
def self.get_nuget_v3_package_url_from_search(repository_details, package_id, package_version)
search_url = repository_details[:search_url]
return nil unless search_url

# get search result
search_result_response = fetch_url(search_url, repository_details)
return nil unless search_result_response.status == 200
return nil unless search_result_response&.status == 200

search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(search_result_response.body)
search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(T.must(search_result_response).body)
search_results = JSON.parse(search_response_body)

# find matching package and version
Expand All @@ -90,22 +134,38 @@ def self.get_nuget_v3_package_url_from_search(repository_details, package_id, pa
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity

sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: String,
package_version: String
)
.returns(T.nilable(String))
end
def self.get_nuget_v2_package_url(repository_details, package_id, package_version)
# get package XML
base_url = repository_details[:base_url].delete_suffix("/")
package_url = "#{base_url}/Packages(Id='#{package_id}',Version='#{package_version}')"
response = fetch_url(package_url, repository_details)
return nil unless response.status == 200
return nil unless response&.status == 200

# find relevant element
doc = Nokogiri::XML(response.body)
doc = Nokogiri::XML(T.must(response).body)
doc.remove_namespaces!

content_element = doc.xpath("/entry/content")
nupkg_url = content_element&.attribute("src")&.value
nupkg_url
end

sig do
params(
stream_url: String,
auth_header: T::Hash[String, String],
max_redirects: Integer
)
.returns(T.nilable(String))
end
def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
current_url = stream_url
current_redirects = 0
Expand All @@ -128,17 +188,25 @@ def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
current_redirects += 1
return nil if current_redirects > max_redirects

current_url = response.headers["Location"]
current_url = T.must(response.headers["Location"])
else
return nil
end
end
end

sig do
params(
url: String,
repository_details: T::Hash[Symbol, T.untyped]
)
.returns(T.nilable(Excon::Response))
end
def self.fetch_url(url, repository_details)
fetch_url_with_auth(url, repository_details.fetch(:auth_header))
end

sig { params(url: String, auth_header: T::Hash[T.any(String, Symbol), T.untyped]).returns(Excon::Response) }
def self.fetch_url_with_auth(url, auth_header)
cache = CacheManager.cache("nupkg_fetcher_cache")
cache[url] ||= Dependabot::RegistryClient.get(
Expand Down
28 changes: 25 additions & 3 deletions nuget/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "nokogiri"
require "zip"
require "stringio"
require "sorbet-runtime"
require "zip"

module Dependabot
module Nuget
class NuspecFetcher
extend T::Sig

require_relative "nupkg_fetcher"
require_relative "repository_finder"

sig do
params(
dependency_urls: T::Array[T::Hash[Symbol, String]],
package_id: String,
package_version: String
)
.returns(T.nilable(Nokogiri::XML::Document))
end
def self.fetch_nuspec(dependency_urls, package_id, package_version)
# check all repositories for the first one that has the nuspec
dependency_urls.reduce(nil) do |nuspec_xml, repository_details|
dependency_urls.reduce(T.let(nil, T.nilable(Nokogiri::XML::Document))) do |nuspec_xml, repository_details|
nuspec_xml || fetch_nuspec_from_repository(repository_details, package_id, package_version)
end
end

sig do
params(
repository_details: T::Hash[Symbol, T.untyped],
package_id: T.nilable(String),
package_version: T.nilable(String)
)
.returns(T.nilable(Nokogiri::XML::Document))
end
def self.fetch_nuspec_from_repository(repository_details, package_id, package_version)
return unless package_id && package_version && !package_version.empty?

Expand Down Expand Up @@ -55,6 +74,7 @@ def self.fetch_nuspec_from_repository(repository_details, package_id, package_ve
nuspec_xml
end

sig { params(feed_url: String).returns(T::Boolean) }
def self.feed_supports_nuspec_download?(feed_url)
feed_regexs = [
# nuget
Expand All @@ -67,6 +87,7 @@ def self.feed_supports_nuspec_download?(feed_url)
feed_regexs.any? { |reg| reg.match(feed_url) }
end

sig { params(zip_stream: String, package_id: String).returns(T.nilable(String)) }
def self.extract_nuspec(zip_stream, package_id)
Zip::File.open_buffer(zip_stream) do |zip|
nuspec_entry = zip.find { |entry| entry.name == "#{package_id}.nuspec" }
Expand All @@ -75,6 +96,7 @@ def self.extract_nuspec(zip_stream, package_id)
nil
end

sig { params(string: String).returns(String) }
def self.remove_invalid_characters(string)
string.dup
.force_encoding(Encoding::UTF_8)
Expand Down
41 changes: 32 additions & 9 deletions nuget/lib/dependabot/nuget/update_checker/requirements_updater.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
# typed: true
# typed: strict
# frozen_string_literal: true

#######################################################################
# For more details on Dotnet version constraints, see: #
# https://docs.microsoft.com/en-us/nuget/reference/package-versioning #
#######################################################################

require "sorbet-runtime"

require "dependabot/update_checkers/base"
require "dependabot/nuget/version"

module Dependabot
module Nuget
class UpdateChecker < Dependabot::UpdateCheckers::Base
class RequirementsUpdater
extend T::Sig

sig do
params(
requirements: T::Array[T::Hash[Symbol, T.untyped]],
latest_version: T.nilable(T.any(String, Dependabot::Nuget::Version)),
source_details: T.nilable(T::Hash[Symbol, T.untyped])
)
.void
end
def initialize(requirements:, latest_version:, source_details:)
@requirements = requirements
@source_details = source_details
return unless latest_version

@latest_version = version_class.new(latest_version)
@latest_version = T.let(version_class.new(latest_version), Dependabot::Nuget::Version)
end

sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def updated_requirements
return requirements unless latest_version

Expand Down Expand Up @@ -52,32 +65,42 @@ def updated_requirements

private

attr_reader :requirements, :latest_version, :source_details
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
attr_reader :requirements

sig { returns(T.nilable(Dependabot::Nuget::Version)) }
attr_reader :latest_version

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
attr_reader :source_details

sig { returns(T.class_of(Dependabot::Nuget::Version)) }
def version_class
Nuget::Version
Dependabot::Nuget::Version
end

sig { params(req_string: String).returns(String) }
def update_wildcard_requirement(req_string)
return req_string if req_string == "*-*"

return req_string if req_string == "*"

precision = req_string.split("*").first.split(/\.|\-/).count
precision = T.must(req_string.split("*").first).split(/\.|\-/).count
wildcard_section = req_string.partition(/(?=[.\-]\*)/).last

version_parts = latest_version.segments.first(precision)
version_parts = T.must(latest_version).segments.first(precision)
version = version_parts.join(".")

version + wildcard_section
end

sig { returns(T::Hash[Symbol, T.untyped]) }
def updated_source
{
type: "nuget_repo",
url: source_details.fetch(:repo_url),
nuspec_url: source_details.fetch(:nuspec_url),
source_url: source_details.fetch(:source_url)
url: source_details&.fetch(:repo_url),
nuspec_url: source_details&.fetch(:nuspec_url),
source_url: source_details&.fetch(:source_url)
}
end
end
Expand Down

0 comments on commit e25ccee

Please sign in to comment.