Class: FhirPackagesManager::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/fhir_packages_manager/registry.rb,
sig/fhir_packages_manager/registry.rbs

Overview

Client for a single FHIR package registry (e.g. https://packages.fhir.org or https://packages.simplifier.net). Both implement the same npm-style registry API: GET /<package> returns metadata with a "versions" map and "dist-tags", and GET /<package>/<version> streams the .tgz tarball.

Constant Summary collapse

MAX_REDIRECTS =

Returns maximum HTTP redirects followed before raising HttpError.

Returns:

  • (Integer)

    maximum HTTP redirects followed before raising HttpError

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url) ⇒ Registry

Returns a new instance of Registry.

Parameters:



21
22
23
24
# File 'lib/fhir_packages_manager/registry.rb', line 21

def initialize(base_url)
  @base_url = base_url.to_s.chomp('/')
  @metadata_cache = {} # : Hash[String, Hash[untyped, untyped]]
end

Instance Attribute Details

#base_urlString (readonly)

Returns the registry's base URL, with any trailing slash stripped.

Returns:

  • (String)

    the registry's base URL, with any trailing slash stripped



18
19
20
# File 'lib/fhir_packages_manager/registry.rb', line 18

def base_url
  @base_url
end

Instance Method Details

#download(name, version, destination_path) ⇒ String

Downloads the package tarball to destination_path, creating parent directories as needed.

Parameters:

  • name (String)

    the package name

  • version (String)

    an exact version, as returned by #version?

  • destination_path (String)

    where to write the downloaded .tgz file

Returns:

  • (String)

    destination_path

Raises:



80
81
82
83
84
# File 'lib/fhir_packages_manager/registry.rb', line 80

def download(name, version, destination_path)
  FileUtils.mkdir_p(File.dirname(destination_path))
  download_file(tarball_url(name, version), destination_path)
  destination_path
end

#download_file(url, destination_path) ⇒ void

This method returns an undefined value.

Parameters:

  • url (String)
  • destination_path (String)


98
99
100
101
102
103
104
# File 'lib/fhir_packages_manager/registry.rb', line 98

def download_file(url, destination_path)
  request(url, read_timeout: 120) do |http, uri|
    http.request_get(uri.request_uri) do |response|
      save_body(response, destination_path) if response.is_a?(Net::HTTPSuccess)
    end
  end
end

#follow(response, url, read_timeout:, redirects_left:) {|arg0, arg1| ... } ⇒ Net::HTTPResponse

Parameters:

  • response (Net::HTTPResponse)
  • url (String)
  • read_timeout: (Integer)
  • redirects_left: (Integer)

Yields:

Yield Parameters:

  • arg0 (Net::HTTP)
  • arg1 (URI::HTTP)

Yield Returns:

  • (Object)

Returns:

  • (Net::HTTPResponse)


130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/fhir_packages_manager/registry.rb', line 130

def follow(response, url, read_timeout:, redirects_left:, &)
  case response
  when Net::HTTPSuccess
    response
  when Net::HTTPRedirection
    location = response['location'] or raise HttpError.new("Redirect from #{url} has no Location header", nil)

    request(location, read_timeout:, redirects_left: redirects_left - 1, &)
  else
    status = response.code.to_i
    raise HttpError.new("GET #{url} failed: #{status} #{response.message}", status)
  end
end

#get(url) ⇒ String

Parameters:

  • url (String)

Returns:

  • (String)


94
95
96
# File 'lib/fhir_packages_manager/registry.rb', line 94

def get(url)
  request(url, read_timeout: 30) { |http, uri| http.get(uri.request_uri, 'Accept' => 'application/json') }.body
end

#metadata(name) ⇒ Hash

Fetches (and caches) the full registry metadata document for a package name.

Parameters:

  • name (String)

    the package name

Returns:

  • (Hash)

    the parsed npm-style registry metadata ("dist-tags", "versions", etc.)

Raises:



32
33
34
35
36
37
38
# File 'lib/fhir_packages_manager/registry.rb', line 32

def (name)
  @metadata_cache[name] ||= JSON.parse(get("#{base_url}/#{name}"))
rescue HttpError => e
  raise PackageNotFoundError, "#{name} not found on #{base_url}" if e.status == 404

  raise
end

#request(url, read_timeout:, redirects_left: MAX_REDIRECTS) {|arg0, arg1| ... } ⇒ Net::HTTPResponse

Issues a GET, yielding (http, uri) to perform it, and follows any redirect response by re-issuing the same block against the new location.

Parameters:

  • url (String)
  • read_timeout: (Integer)
  • redirects_left: (Integer) (defaults to: MAX_REDIRECTS)

Yields:

Yield Parameters:

  • arg0 (Net::HTTP)
  • arg1 (URI::HTTP)

Yield Returns:

  • (Object)

Returns:

  • (Net::HTTPResponse)

Raises:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/fhir_packages_manager/registry.rb', line 114

def request(url, read_timeout:, redirects_left: MAX_REDIRECTS, &)
  raise HttpError.new("Too many redirects for #{url}", nil) if redirects_left <= 0

  uri = URI(url)
  raise HttpError.new("Unsupported URL scheme: #{url}", nil) unless uri.is_a?(URI::HTTP)

  host = uri.host or raise HttpError.new("URL has no host: #{url}", nil)

  response = Net::HTTP.start(host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 10,
                                             read_timeout: read_timeout) do |http|
    yield http, uri
  end

  follow(response, url, read_timeout:, redirects_left:, &)
end

#resolve_version(meta, version) ⇒ String?

Parameters:

  • meta (Hash[untyped, untyped])
  • version (String, nil)

Returns:

  • (String, nil)


88
89
90
91
92
# File 'lib/fhir_packages_manager/registry.rb', line 88

def resolve_version(meta, version)
  return meta.dig('dist-tags', 'latest') if version.nil? || version == 'latest'

  version
end

#save_body(response, destination_path) ⇒ void

This method returns an undefined value.

Parameters:

  • response (Net::HTTPResponse)
  • destination_path (String)


106
107
108
109
110
# File 'lib/fhir_packages_manager/registry.rb', line 106

def save_body(response, destination_path)
  File.open(destination_path, 'wb') do |file|
    response.read_body { |chunk| file.write(chunk) }
  end
end

#tarball_url(name, version) ⇒ String

Returns the tarball's download URL.

Parameters:

  • name (String)

    the package name

  • version (String)

    an exact version, as returned by #version?

Returns:

  • (String)

    the tarball's download URL

Raises:



64
65
66
67
68
69
70
# File 'lib/fhir_packages_manager/registry.rb', line 64

def tarball_url(name, version)
  meta = (name)
  entry = meta.dig('versions', version)
  raise PackageNotFoundError, "#{name}@#{version} not found on #{base_url}" unless entry

  entry.dig('dist', 'tarball') || "#{base_url}/#{name}/#{version}"
end

#version?(name, version = nil) ⇒ String?

Returns the resolved version string if it exists on this registry, or nil if the package or version doesn't exist (never raises).

Parameters:

  • name (String)

    the package name

  • version (String, nil) (defaults to: nil)

    a specific version, or nil/"latest" for the newest

Returns:

  • (String, nil)

    the resolved version string if it exists on this registry, or nil if the package or version doesn't exist (never raises)



44
45
46
47
48
49
50
# File 'lib/fhir_packages_manager/registry.rb', line 44

def version?(name, version = nil)
  meta = (name)
  resolved = resolve_version(meta, version)
  resolved && meta['versions']&.key?(resolved) ? resolved : nil
rescue PackageNotFoundError
  nil
end

#versions(name) ⇒ Array<String>

Returns every version published for this package on this registry.

Parameters:

  • name (String)

    the package name

Returns:

  • (Array<String>)

    every version published for this package on this registry

Raises:



56
57
58
# File 'lib/fhir_packages_manager/registry.rb', line 56

def versions(name)
  (name)['versions']&.keys || []
end