Skip to content

Commit 4b4bcaa

Browse files
committed
Enhance documentation and error handling in PURL library
- Added API documentation links to README.md - Improved RDoc task configuration in Rakefile - Expanded comments and examples in PURL, PackageURL, and errors.rb files - Enhanced validation error classes with additional context
1 parent 3bbc41b commit 4b4bcaa

File tree

5 files changed

+327
-8
lines changed

5 files changed

+327
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This library features comprehensive error handling with namespaced error types,
88
[![Gem Version](https://badge.fury.io/rb/purl.svg)](https://rubygems.org/gems/purl)
99
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
1010

11-
**[Available on RubyGems](https://rubygems.org/gems/purl)**
11+
**[Available on RubyGems](https://rubygems.org/gems/purl)** | **[API Documentation](https://rdoc.info/github/andrew/purl)**
1212

1313
## Features
1414

Rakefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@
22

33
require "bundler/gem_tasks"
44
require "minitest/test_task"
5+
require "rdoc/task"
56

67
Minitest::TestTask.create
78

9+
RDoc::Task.new do |rdoc|
10+
rdoc.rdoc_dir = "doc"
11+
rdoc.title = "PURL - Package URL Library"
12+
rdoc.main = "README.md"
13+
rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
14+
rdoc.options << "--line-numbers"
15+
rdoc.options << "--all"
16+
rdoc.options << "--charset=UTF-8"
17+
end
18+
819
task default: :test
920

1021
namespace :spec do

lib/purl.rb

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,25 @@
55
require_relative "purl/package_url"
66
require_relative "purl/registry_url"
77

8+
# The main PURL (Package URL) module providing functionality to parse,
9+
# validate, and generate package URLs according to the PURL specification.
10+
#
11+
# A Package URL is a mostly universal standard to reference a software package
12+
# in a uniform way across many tools, programming languages and ecosystems.
13+
#
14+
# @example Basic usage
15+
# purl = Purl.parse("pkg:gem/[email protected]")
16+
# puts purl.type # "gem"
17+
# puts purl.name # "rails"
18+
# puts purl.version # "7.0.0"
19+
#
20+
# @example Registry URL conversion
21+
# purl = Purl.from_registry_url("https://rubygems.org/gems/rails")
22+
# puts purl.to_s # "pkg:gem/rails"
23+
#
24+
# @see https://github.com/package-url/purl-spec PURL Specification
825
module Purl
26+
# Base error class for all PURL-related errors
927
class Error < StandardError; end
1028

1129
# Load PURL types configuration from JSON file
@@ -21,6 +39,15 @@ def self.load_types_config
2139
KNOWN_TYPES = load_types_config["types"].keys.sort.freeze
2240

2341
# Convenience method for parsing PURL strings
42+
#
43+
# @param purl_string [String] a PURL string starting with "pkg:"
44+
# @return [PackageURL] parsed package URL object
45+
# @raise [InvalidSchemeError] if string doesn't start with "pkg:"
46+
# @raise [MalformedUrlError] if string is malformed
47+
#
48+
# @example
49+
# purl = Purl.parse("pkg:gem/[email protected]")
50+
# puts purl.name # "rails"
2451
def self.parse(purl_string)
2552
PackageURL.parse(purl_string)
2653
end
@@ -33,26 +60,66 @@ def self.from_registry_url(registry_url, type: nil)
3360
end
3461

3562
# Returns all known PURL types
63+
#
64+
# @return [Array<String>] sorted array of known PURL type names
65+
#
66+
# @example
67+
# types = Purl.known_types
68+
# puts types.include?("gem") # true
3669
def self.known_types
3770
KNOWN_TYPES.dup
3871
end
3972

4073
# Returns types that have registry URL support
74+
#
75+
# @return [Array<String>] sorted array of types that can generate registry URLs
76+
#
77+
# @example
78+
# types = Purl.registry_supported_types
79+
# puts types.include?("npm") # true if npm has registry support
4180
def self.registry_supported_types
4281
RegistryURL.supported_types
4382
end
4483

4584
# Returns types that support reverse parsing from registry URLs
85+
#
86+
# @return [Array<String>] sorted array of types that can parse registry URLs back to PURLs
87+
#
88+
# @example
89+
# types = Purl.reverse_parsing_supported_types
90+
# puts types.include?("gem") # true if gem has reverse parsing support
4691
def self.reverse_parsing_supported_types
4792
RegistryURL.supported_reverse_types
4893
end
4994

5095
# Check if a type is known/valid
96+
#
97+
# @param type [String, Symbol] the type to check
98+
# @return [Boolean] true if type is known, false otherwise
99+
#
100+
# @example
101+
# Purl.known_type?("gem") # true
102+
# Purl.known_type?("unknown") # false
51103
def self.known_type?(type)
52104
KNOWN_TYPES.include?(type.to_s.downcase)
53105
end
54106

55-
# Get type information including registry support
107+
# Get comprehensive type information including registry support
108+
#
109+
# @param type [String, Symbol] the type to get information for
110+
# @return [Hash] hash containing type information with keys:
111+
# - +:type+: normalized type name
112+
# - +:known+: whether type is known
113+
# - +:description+: human-readable description
114+
# - +:default_registry+: default registry URL
115+
# - +:examples+: array of example PURLs
116+
# - +:registry_url_generation+: whether registry URL generation is supported
117+
# - +:reverse_parsing+: whether reverse parsing is supported
118+
# - +:route_patterns+: array of URL patterns for this type
119+
#
120+
# @example
121+
# info = Purl.type_info("gem")
122+
# puts info[:description] # "Ruby gems from RubyGems.org"
56123
def self.type_info(type)
57124
normalized_type = type.to_s.downcase
58125
{
@@ -68,6 +135,13 @@ def self.type_info(type)
68135
end
69136

70137
# Get comprehensive information about all types
138+
#
139+
# @return [Hash<String, Hash>] hash mapping type names to their information
140+
# @see #type_info for structure of individual type information
141+
#
142+
# @example
143+
# all_info = Purl.all_type_info
144+
# gem_info = all_info["gem"]
71145
def self.all_type_info
72146
result = {}
73147

@@ -87,20 +161,38 @@ def self.all_type_info
87161
end
88162

89163
# Get type configuration from JSON
164+
#
165+
# @param type [String, Symbol] the type to get configuration for
166+
# @return [Hash, nil] configuration hash or nil if type not found
167+
# @api private
90168
def self.type_config(type)
91169
config = load_types_config["types"][type.to_s.downcase]
92170
return nil unless config
93171

94172
config.dup # Return a copy to prevent modification
95173
end
96174

97-
# Get description for a type
175+
# Get human-readable description for a type
176+
#
177+
# @param type [String, Symbol] the type to get description for
178+
# @return [String, nil] description string or nil if not available
179+
#
180+
# @example
181+
# desc = Purl.type_description("gem")
182+
# puts desc # "Ruby gems from RubyGems.org"
98183
def self.type_description(type)
99184
config = type_config(type)
100185
config ? config["description"] : nil
101186
end
102187

103-
# Get examples for a type
188+
# Get example PURLs for a type
189+
#
190+
# @param type [String, Symbol] the type to get examples for
191+
# @return [Array<String>] array of example PURL strings
192+
#
193+
# @example
194+
# examples = Purl.type_examples("gem")
195+
# puts examples.first # "pkg:gem/[email protected]"
104196
def self.type_examples(type)
105197
config = type_config(type)
106198
return [] unless config
@@ -109,6 +201,10 @@ def self.type_examples(type)
109201
end
110202

111203
# Get registry configuration for a type
204+
#
205+
# @param type [String, Symbol] the type to get registry config for
206+
# @return [Hash, nil] registry configuration hash or nil if not available
207+
# @api private
112208
def self.registry_config(type)
113209
config = type_config(type)
114210
return nil unless config
@@ -117,6 +213,13 @@ def self.registry_config(type)
117213
end
118214

119215
# Get default registry URL for a type
216+
#
217+
# @param type [String, Symbol] the type to get default registry for
218+
# @return [String, nil] default registry URL or nil if not available
219+
#
220+
# @example
221+
# registry = Purl.default_registry("gem")
222+
# puts registry # "https://rubygems.org"
120223
def self.default_registry(type)
121224
config = type_config(type)
122225
return nil unless config
@@ -125,6 +228,19 @@ def self.default_registry(type)
125228
end
126229

127230
# Get metadata about the types configuration
231+
#
232+
# @return [Hash] metadata hash with keys:
233+
# - +:version+: configuration version
234+
# - +:description+: configuration description
235+
# - +:source+: source of the configuration
236+
# - +:last_updated+: when configuration was last updated
237+
# - +:total_types+: total number of types
238+
# - +:registry_supported_types+: number of types with registry support
239+
# - +:types_with_default_registry+: number of types with default registry
240+
#
241+
# @example
242+
# metadata = Purl.types_config_metadata
243+
# puts "Total types: #{metadata[:total_types]}"
128244
def self.types_config_metadata
129245
config = load_types_config
130246
{

lib/purl/errors.rb

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,31 @@ module Purl
55
class Error < StandardError; end
66

77
# Validation errors for PURL components
8+
#
9+
# Contains additional context about which component failed validation
10+
# and what rule was violated.
11+
#
12+
# @example
13+
# begin
14+
# PackageURL.new(type: "123invalid", name: "test")
15+
# rescue ValidationError => e
16+
# puts e.component # :type
17+
# puts e.rule # "cannot start with number"
18+
# end
819
class ValidationError < Error
9-
attr_reader :component, :value, :rule
20+
# @return [Symbol, nil] the PURL component that failed validation
21+
attr_reader :component
22+
23+
# @return [Object, nil] the value that failed validation
24+
attr_reader :value
25+
26+
# @return [String, nil] the validation rule that was violated
27+
attr_reader :rule
1028

29+
# @param message [String] error message
30+
# @param component [Symbol, nil] component that failed validation
31+
# @param value [Object, nil] value that failed validation
32+
# @param rule [String, nil] validation rule that was violated
1133
def initialize(message, component: nil, value: nil, rule: nil)
1234
super(message)
1335
@component = component
@@ -19,46 +41,78 @@ def initialize(message, component: nil, value: nil, rule: nil)
1941
# Parsing errors for malformed PURL strings
2042
class ParseError < Error; end
2143

22-
# Specific validation errors
44+
# Specific validation errors for PURL components
45+
46+
# Raised when a PURL type is invalid
2347
class InvalidTypeError < ValidationError; end
48+
49+
# Raised when a PURL name is invalid
2450
class InvalidNameError < ValidationError; end
51+
52+
# Raised when a PURL namespace is invalid
2553
class InvalidNamespaceError < ValidationError; end
54+
55+
# Raised when a PURL qualifier is invalid
2656
class InvalidQualifierError < ValidationError; end
57+
58+
# Raised when a PURL version is invalid
2759
class InvalidVersionError < ValidationError; end
60+
61+
# Raised when a PURL subpath is invalid
2862
class InvalidSubpathError < ValidationError; end
2963

3064
# Parsing-specific errors
65+
66+
# Raised when a PURL string doesn't start with "pkg:"
3167
class InvalidSchemeError < ParseError; end
68+
69+
# Raised when a PURL string is malformed
3270
class MalformedUrlError < ParseError; end
3371

3472
# Registry URL generation errors
73+
#
74+
# Contains additional context about which type caused the error.
3575
class RegistryError < Error
76+
# @return [String, nil] the PURL type that caused the error
3677
attr_reader :type
3778

79+
# @param message [String] error message
80+
# @param type [String, nil] PURL type that caused the error
3881
def initialize(message, type: nil)
3982
super(message)
4083
@type = type
4184
end
4285
end
4386

87+
# Raised when trying to generate registry URLs for unsupported types
4488
class UnsupportedTypeError < RegistryError
89+
# @return [Array<String>] list of supported types
4590
attr_reader :supported_types
4691

92+
# @param message [String] error message
93+
# @param type [String, nil] unsupported type
94+
# @param supported_types [Array<String>] list of supported types
4795
def initialize(message, type: nil, supported_types: [])
4896
super(message, type: type)
4997
@supported_types = supported_types
5098
end
5199
end
52100

101+
# Raised when required registry information is missing
53102
class MissingRegistryInfoError < RegistryError
103+
# @return [String, nil] the missing information (e.g., "namespace")
54104
attr_reader :missing
55105

106+
# @param message [String] error message
107+
# @param type [String, nil] PURL type
108+
# @param missing [String, nil] what information is missing
56109
def initialize(message, type: nil, missing: nil)
57110
super(message, type: type)
58111
@missing = missing
59112
end
60113
end
61114

62115
# Legacy compatibility - matches packageurl-ruby's exception name
116+
# @deprecated Use {ParseError} instead
63117
InvalidPackageURL = ParseError
64118
end

0 commit comments

Comments
 (0)