purl 1.0.0 → 1.1.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: 7b04edce0acf5c2872aea527f2b3845dcff94e8079d23ed394abd688779e335e
4
- data.tar.gz: 504d83a02eb4b6574b42bbc790820c1f22335f594667d2e239e1edf2569d5300
3
+ metadata.gz: 25661f74cff7bd498cd8b82d64887231cd1a576b8ce0ef59c5397c6d378759eb
4
+ data.tar.gz: 0e82bbe5c1600601b3f27a82c79d9ea579587961ae97df1f1272ce31da35877e
5
5
  SHA512:
6
- metadata.gz: 5a7e76a9dfb4042cece6132a19c390c0ab648335d2d4e9a5acb519310c1475c37030c9c20c7f4ef43f6027805c53d713a3e58c1b391bbea592f92f1bfefc3736
7
- data.tar.gz: 1c8c1662e1f893b7f62278951778a59e3af27df05d2b61a54fb8b7cbb3b418ee00815f0996bcd4c93ae28290bd457c0c2cd0010327fb39cb886704444a573eb7
6
+ metadata.gz: d95b6ca47f39eccb13394340e6a63a9acfa9c0efc8a031897e876fe0350e354ae1d1e4f45f747463e14e1060b89e455e03e3dcbedcefca3a99522fe4f4b54fa1
7
+ data.tar.gz: 3e5322963a75161291bead8050759a0578c07c8e2b8a732a6a3ca7180b0ffa5a099a63f9cb95785e9dcc42674f1933b10aa6acc9450f4e30ec8f233aa9bb6983
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,167 @@
1
+ # Contributing to Purl
2
+
3
+ Thank you for your interest in contributing to the Purl Ruby library! This document provides guidelines and information for contributors.
4
+
5
+ ## Code of Conduct
6
+
7
+ This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
8
+
9
+ ## How to Contribute
10
+
11
+ ### Reporting Issues
12
+
13
+ Before creating an issue, please:
14
+ 1. Search existing issues to avoid duplicates
15
+ 2. Use the latest version of the gem
16
+ 3. Provide a clear, descriptive title
17
+ 4. Include steps to reproduce the issue
18
+ 5. Share relevant code examples or error messages
19
+
20
+ ### Suggesting Enhancements
21
+
22
+ Enhancement suggestions are welcome! Please:
23
+ 1. Check if the enhancement is already requested
24
+ 2. Explain the use case and expected behavior
25
+ 3. Consider if it fits the project's scope
26
+ 4. Be willing to help implement if accepted
27
+
28
+ ### Pull Requests
29
+
30
+ 1. **Fork** the repository
31
+ 2. **Create** a feature branch (`git checkout -b my-new-feature`)
32
+ 3. **Make** your changes following our coding standards
33
+ 4. **Add** tests for your changes
34
+ 5. **Ensure** all tests pass (`rake test`)
35
+ 6. **Run** the full test suite including compliance tests
36
+ 7. **Commit** your changes with clear, descriptive messages
37
+ 8. **Push** to your branch (`git push origin my-new-feature`)
38
+ 9. **Create** a Pull Request with a clear description
39
+
40
+ ## Development Setup
41
+
42
+ ### Prerequisites
43
+
44
+ - Ruby 3.1 or higher
45
+ - Bundler gem
46
+
47
+ ### Setup
48
+
49
+ ```bash
50
+ git clone https://github.com/package-url/purl-ruby.git
51
+ cd purl-ruby
52
+ bundle install
53
+ ```
54
+
55
+ ### Running Tests
56
+
57
+ ```bash
58
+ # Run all tests
59
+ rake test
60
+
61
+ # Run PURL specification compliance tests
62
+ rake spec:compliance
63
+
64
+ # Validate JSON schemas
65
+ rake spec:validate_schemas
66
+
67
+ # Validate PURL examples
68
+ rake spec:validate_examples
69
+
70
+ # Show all available tasks
71
+ rake -T
72
+ ```
73
+
74
+ ### Coding Standards
75
+
76
+ - Follow Ruby best practices and conventions
77
+ - Use meaningful variable and method names
78
+ - Add documentation for public methods
79
+ - Keep methods focused and concise
80
+ - Follow the existing code style
81
+
82
+ ### Testing Requirements
83
+
84
+ All contributions must include appropriate tests:
85
+
86
+ - **Unit tests** for new functionality
87
+ - **Integration tests** for feature interactions
88
+ - **Compliance tests** must continue to pass
89
+ - **Example validation** for any new PURL examples
90
+
91
+ ### Documentation
92
+
93
+ - Update the README.md if adding new features
94
+ - Add inline documentation for complex methods
95
+ - Update CHANGELOG.md following the format
96
+ - Include examples in documentation
97
+
98
+ ## Project Structure
99
+
100
+ ```
101
+ ├── lib/
102
+ │ ├── purl.rb # Main module
103
+ │ └── purl/
104
+ │ ├── errors.rb # Error classes
105
+ │ ├── package_url.rb # Core PURL parsing
106
+ │ └── registry_url.rb # Registry URL handling
107
+ ├── test/ # Test files
108
+ ├── schemas/ # JSON schemas
109
+ ├── purl-types.json # Package types configuration
110
+ └── test-suite-data.json # Official test cases
111
+ ```
112
+
113
+ ## Adding New Package Types
114
+
115
+ To add support for a new package type:
116
+
117
+ 1. **Add type definition** to `purl-types.json`
118
+ 2. **Include examples** from the official specification
119
+ 3. **Add registry configuration** if applicable
120
+ 4. **Update tests** to verify the new type
121
+ 5. **Run validation** to ensure compliance
122
+
123
+ ## JSON Schema Updates
124
+
125
+ When modifying JSON files:
126
+
127
+ 1. **Validate** against schemas: `rake spec:validate_schemas`
128
+ 2. **Update schemas** if structure changes
129
+ 3. **Test examples** are valid: `rake spec:validate_examples`
130
+
131
+ ## PURL Specification Compliance
132
+
133
+ This library maintains 100% compliance with the official PURL specification:
134
+
135
+ - All changes must maintain compliance
136
+ - Run `rake spec:compliance` before submitting
137
+ - New features should align with the spec
138
+ - Report spec issues upstream when discovered
139
+
140
+ ## Release Process
141
+
142
+ Releases are handled by maintainers:
143
+
144
+ 1. Update version in `lib/purl/version.rb`
145
+ 2. Update `CHANGELOG.md` with changes
146
+ 3. Run full test suite
147
+ 4. Create release tag
148
+ 5. Publish to RubyGems
149
+
150
+ ## Getting Help
151
+
152
+ - **Issues**: Use GitHub issues for bugs and feature requests
153
+ - **Discussions**: Use GitHub discussions for questions
154
+ - **Security**: Follow our [Security Policy](SECURITY.md)
155
+
156
+ ## Recognition
157
+
158
+ Contributors will be recognized in:
159
+ - Git commit history
160
+ - CHANGELOG.md for significant contributions
161
+ - Project documentation where appropriate
162
+
163
+ ## License
164
+
165
+ By contributing to Purl, you agree that your contributions will be licensed under the [MIT License](LICENSE).
166
+
167
+ Thank you for contributing to make Purl better for everyone!
data/README.md CHANGED
@@ -1,26 +1,26 @@
1
1
  # Purl - Package URL Parser for Ruby
2
2
 
3
- A comprehensive Ruby library for parsing, validating, and working with Package URLs (PURLs) as defined by the [PURL specification](https://github.com/package-url/purl-spec).
3
+ A Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the [PURL specification](https://github.com/package-url/purl-spec).
4
4
 
5
- This library provides better error handling than existing solutions with namespaced error types, bidirectional registry URL conversion, and JSON-based configuration for cross-language compatibility.
5
+ This library features comprehensive error handling with namespaced error types, bidirectional registry URL conversion, and JSON-based configuration for cross-language compatibility.
6
6
 
7
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-red.svg)](https://www.ruby-lang.org/)
7
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-red.svg)](https://www.ruby-lang.org/)
8
8
  [![Gem Version](https://badge.fury.io/rb/purl.svg)](https://rubygems.org/gems/purl)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
 
11
- **🔗 [Available on RubyGems](https://rubygems.org/gems/purl)**
11
+ **[Available on RubyGems](https://rubygems.org/gems/purl)**
12
12
 
13
13
  ## Features
14
14
 
15
- - 🎯 **Comprehensive PURL parsing and validation** with 37 package types (32 official + 5 additional ecosystems)
16
- - 🔥 **Better error handling** with namespaced error classes and contextual information
17
- - 🔄 **Bidirectional registry URL conversion** - generate registry URLs from PURLs and parse PURLs from registry URLs
18
- - 📋 **Type-specific validation** for conan, cran, and swift packages
19
- - 🌐 **Registry URL generation** for 20 package ecosystems (npm, gem, maven, pypi, etc.)
20
- - 🎨 **Rails-style route patterns** for registry URL templates
21
- - 📊 **100% compliance** with official PURL specification test suite (59/59 tests passing)
22
- - 🤝 **Cross-language compatibility** with JSON-based configuration
23
- - 📚 **Comprehensive documentation** and examples
15
+ - **Comprehensive PURL parsing and validation** with 37 package types (32 official + 5 additional ecosystems)
16
+ - **Better error handling** with namespaced error classes and contextual information
17
+ - **Bidirectional registry URL conversion** - generate registry URLs from PURLs and parse PURLs from registry URLs
18
+ - **Type-specific validation** for conan, cran, and swift packages
19
+ - **Registry URL generation** for 20 package ecosystems (npm, gem, maven, pypi, etc.)
20
+ - **Rails-style route patterns** for registry URL templates
21
+ - **100% compliance** with official PURL specification test suite (59/59 tests passing)
22
+ - **Cross-language compatibility** with JSON-based configuration
23
+ - **Comprehensive documentation** and examples
24
24
 
25
25
  ## Installation
26
26
 
@@ -79,6 +79,42 @@ purl = Purl::PackageURL.new(
79
79
  puts purl.to_s # => "pkg:maven/org.apache.commons/commons-lang3@3.12.0"
80
80
  ```
81
81
 
82
+ ### Modifying PURL Objects
83
+
84
+ PURL objects are immutable by design, but you can create new objects with modified attributes using the `with` method:
85
+
86
+ ```ruby
87
+ # Create original PURL
88
+ original = Purl::PackageURL.new(
89
+ type: "npm",
90
+ namespace: "@babel",
91
+ name: "core",
92
+ version: "7.20.0",
93
+ qualifiers: { "arch" => "x64" }
94
+ )
95
+
96
+ # Create new PURL with updated version
97
+ updated = original.with(version: "7.21.0")
98
+ puts updated.to_s # => "pkg:npm/@babel/core@7.21.0?arch=x64"
99
+
100
+ # Update qualifiers
101
+ with_new_qualifiers = original.with(
102
+ qualifiers: { "arch" => "arm64", "os" => "linux" }
103
+ )
104
+ puts with_new_qualifiers.to_s # => "pkg:npm/@babel/core@7.20.0?arch=arm64&os=linux"
105
+
106
+ # Update multiple attributes at once
107
+ fully_updated = original.with(
108
+ version: "8.0.0",
109
+ qualifiers: { "dev" => "true" },
110
+ subpath: "lib/index.js"
111
+ )
112
+ puts fully_updated.to_s # => "pkg:npm/@babel/core@8.0.0#lib/index.js?dev=true"
113
+
114
+ # Original remains unchanged
115
+ puts original.to_s # => "pkg:npm/@babel/core@7.20.0?arch=x64"
116
+ ```
117
+
82
118
  ### Registry URL Generation
83
119
 
84
120
  ```ruby
@@ -110,6 +146,30 @@ purl = Purl.from_registry_url("https://pypi.org/project/django/4.0.0/")
110
146
  puts purl.to_s # => "pkg:pypi/django@4.0.0"
111
147
  ```
112
148
 
149
+ ### Custom Registry Domains
150
+
151
+ You can parse registry URLs from custom domains or generate URLs for private registries:
152
+
153
+ ```ruby
154
+ # Parse from custom domain (specify type to help with parsing)
155
+ purl = Purl.from_registry_url("https://npm.company.com/package/@babel/core", type: "npm")
156
+ puts purl.to_s # => "pkg:npm/@babel/core"
157
+
158
+ # Generate URLs for custom registries
159
+ purl = Purl.parse("pkg:gem/rails@7.0.0")
160
+ custom_url = purl.registry_url(base_url: "https://gems.internal.com/gems")
161
+ puts custom_url # => "https://gems.internal.com/gems/rails"
162
+
163
+ # With version-specific URLs
164
+ with_version = purl.registry_url_with_version(base_url: "https://gems.internal.com/gems")
165
+ puts with_version # => "https://gems.internal.com/gems/rails/versions/7.0.0"
166
+
167
+ # Works with all supported package types
168
+ composer_purl = Purl.parse("pkg:composer/symfony/console@5.4.0")
169
+ private_composer = composer_purl.registry_url(base_url: "https://packagist.company.com/packages")
170
+ puts private_composer # => "https://packagist.company.com/packages/symfony/console"
171
+ ```
172
+
113
173
  ### Route Patterns
114
174
 
115
175
  ```ruby
@@ -123,21 +183,64 @@ puts all_patterns["npm"]
123
183
  # => ["https://www.npmjs.com/package/:namespace/:name", "https://www.npmjs.com/package/:name", ...]
124
184
  ```
125
185
 
186
+ ### Working with Qualifiers
187
+
188
+ Qualifiers are key-value pairs that provide additional metadata about packages:
189
+
190
+ ```ruby
191
+ # Create PURL with qualifiers
192
+ purl = Purl::PackageURL.new(
193
+ type: "apk",
194
+ name: "curl",
195
+ version: "7.83.0-r0",
196
+ qualifiers: {
197
+ "distro" => "alpine-3.16",
198
+ "arch" => "x86_64",
199
+ "repository_url" => "https://dl-cdn.alpinelinux.org"
200
+ }
201
+ )
202
+ puts purl.to_s # => "pkg:apk/curl@7.83.0-r0?arch=x86_64&distro=alpine-3.16&repository_url=https://dl-cdn.alpinelinux.org"
203
+
204
+ # Access qualifiers
205
+ puts purl.qualifiers["distro"] # => "alpine-3.16"
206
+ puts purl.qualifiers["arch"] # => "x86_64"
207
+
208
+ # Parse PURL with qualifiers
209
+ parsed = Purl.parse("pkg:rpm/httpd@2.4.53?distro=fedora-36&arch=x86_64")
210
+ puts parsed.qualifiers # => {"distro" => "fedora-36", "arch" => "x86_64"}
211
+
212
+ # Add qualifiers to existing PURL
213
+ with_qualifiers = purl.with(qualifiers: purl.qualifiers.merge("signed" => "true"))
214
+ ```
215
+
126
216
  ### Package Type Information
127
217
 
128
218
  ```ruby
129
219
  # Get all known PURL types
130
- puts Purl.known_types.length # => 32
220
+ puts Purl.known_types.length # => 37
131
221
  puts Purl.known_types.include?("gem") # => true
132
222
 
133
223
  # Check type support
134
224
  puts Purl.known_type?("gem") # => true
135
225
  puts Purl.registry_supported_types # => ["cargo", "gem", "maven", "npm", ...]
136
- puts Purl.reverse_parsing_supported_types # => ["cargo", "gem", "maven", "npm", ...]
226
+ puts Purl.reverse_parsing_supported_types # => ["bioconductor", "cargo", "clojars", ...]
227
+
228
+ # Get default registry for a type
229
+ puts Purl.default_registry("gem") # => "https://rubygems.org"
230
+ puts Purl.default_registry("npm") # => "https://registry.npmjs.org"
231
+ puts Purl.default_registry("golang") # => nil (no default)
232
+
233
+ # Get official examples for a type
234
+ puts Purl.type_examples("gem") # => ["pkg:gem/rails@7.0.4", "pkg:gem/bundler@2.3.26", ...]
235
+ puts Purl.type_examples("npm") # => ["pkg:npm/lodash@4.17.21", "pkg:npm/@babel/core@7.20.0", ...]
236
+ puts Purl.type_examples("unknown") # => []
137
237
 
138
238
  # Get detailed type information
139
239
  info = Purl.type_info("gem")
140
240
  puts info[:known] # => true
241
+ puts info[:description] # => "RubyGems"
242
+ puts info[:default_registry] # => "https://rubygems.org"
243
+ puts info[:examples] # => ["pkg:gem/rails@7.0.4", ...]
141
244
  puts info[:registry_url_generation] # => true
142
245
  puts info[:reverse_parsing] # => true
143
246
  puts info[:route_patterns] # => ["https://rubygems.org/gems/:name", ...]
@@ -191,8 +294,8 @@ The library supports 37 package types (32 official + 5 additional ecosystems):
191
294
  - `pypi` (Python) - pypi.org
192
295
  - `swift` (Swift) - swiftpackageindex.com
193
296
 
194
- **Reverse Parsing (10 types):**
195
- - `cargo`, `deno`, `elm`, `gem`, `golang`, `hackage`, `homebrew`, `maven`, `npm`, `pypi`
297
+ **Reverse Parsing (20 types):**
298
+ - `bioconductor`, `cargo`, `clojars`, `cocoapods`, `composer`, `conda`, `cpan`, `deno`, `elm`, `gem`, `golang`, `hackage`, `hex`, `homebrew`, `maven`, `npm`, `nuget`, `pub`, `pypi`, `swift`
196
299
 
197
300
  **All 37 Supported Types:**
198
301
  `alpm`, `apk`, `bioconductor`, `bitbucket`, `bitnami`, `cargo`, `clojars`, `cocoapods`, `composer`, `conan`, `conda`, `cpan`, `cran`, `deb`, `deno`, `docker`, `elm`, `gem`, `generic`, `github`, `golang`, `hackage`, `hex`, `homebrew`, `huggingface`, `luarocks`, `maven`, `mlflow`, `npm`, `nuget`, `oci`, `pub`, `pypi`, `qpkg`, `rpm`, `swid`, `swift`
@@ -212,20 +315,51 @@ Package types and registry patterns are stored in `purl-types.json` for easy con
212
315
  {
213
316
  "version": "1.0.0",
214
317
  "description": "PURL types and registry URL patterns for package ecosystems",
318
+ "source": "https://github.com/package-url/purl-spec/blob/main/PURL-TYPES.rst",
319
+ "last_updated": "2025-07-24",
215
320
  "types": {
216
321
  "gem": {
217
322
  "description": "RubyGems",
218
- "registry_support": true,
323
+ "default_registry": "https://rubygems.org",
219
324
  "registry_config": {
220
- "base_url": "https://rubygems.org/gems",
221
- "route_patterns": ["https://rubygems.org/gems/:name"],
222
- "reverse_parsing": true
325
+ "path_template": "/gems/:name",
326
+ "version_path_template": "/gems/:name/versions/:version",
327
+ "reverse_regex": "/gems/([^/?#]+)(?:/versions/([^/?#]+))?",
328
+ "components": {
329
+ "namespace": false,
330
+ "version_in_url": true,
331
+ "version_path": "/versions/"
332
+ }
223
333
  }
224
334
  }
225
335
  }
226
336
  }
227
337
  ```
228
338
 
339
+ **Key Configuration Improvements:**
340
+ - **Domain-agnostic patterns**: `path_template` without hardcoded domains enables custom registries
341
+ - **Flexible URL generation**: Combine `default_registry` + `path_template` for any domain
342
+ - **Cleaner JSON**: Reduced duplication and easier maintenance
343
+ - **Cross-registry compatibility**: Same URL structure works with public and private registries
344
+
345
+ ## JSON Schema Validation
346
+
347
+ The library includes JSON schemas for validation and documentation:
348
+
349
+ - **`schemas/purl-types.schema.json`** - Schema for the PURL types configuration file
350
+ - **`schemas/test-suite-data.schema.json`** - Schema for the official test suite data
351
+
352
+ These schemas provide:
353
+ - **Structure validation** - Ensure JSON files conform to expected format
354
+ - **Documentation** - Self-documenting configuration with descriptions
355
+ - **IDE support** - Enable autocomplete and validation in editors
356
+ - **CI/CD integration** - Validate configuration in automated pipelines
357
+
358
+ Validate JSON files against their schemas:
359
+ ```bash
360
+ rake spec:validate_schemas
361
+ ```
362
+
229
363
  ## Development
230
364
 
231
365
  After checking out the repo, run `bin/setup` to install dependencies. Then:
@@ -253,14 +387,16 @@ rake spec:verify_types
253
387
  - `rake spec:compliance` - Run compliance tests against official test suite
254
388
  - `rake spec:types` - Show information about all PURL types and their support
255
389
  - `rake spec:verify_types` - Verify our types list against official specification
390
+ - `rake spec:validate_schemas` - Validate JSON files against their schemas
256
391
  - `rake spec:debug` - Show detailed info about failing test cases
257
392
 
258
393
  ## Funding
259
394
 
260
395
  If you find this project useful, please consider supporting its development:
261
396
 
262
- - 💝 [GitHub Sponsors](https://github.com/sponsors/andrew)
263
- - [Buy me a coffee](https://www.buymeacoffee.com/andrew)
397
+ - [GitHub Sponsors](https://github.com/sponsors/andrew)
398
+ - [Ko-fi](https://ko-fi.com/andrewnez)
399
+ - [Buy Me a Coffee](https://www.buymeacoffee.com/andrewnez)
264
400
 
265
401
  Your support helps maintain and improve this library for the entire Ruby community.
266
402
 
data/Rakefile CHANGED
@@ -18,6 +18,8 @@ namespace :spec do
18
18
  puts "rake spec:debug - Show detailed info about failing test cases"
19
19
  puts "rake spec:types - Show information about all PURL types and their support"
20
20
  puts "rake spec:verify_types - Verify our types list against the official specification"
21
+ puts "rake spec:validate_schemas - Validate JSON files against their schemas"
22
+ puts "rake spec:validate_examples - Validate all PURL examples in purl-types.json"
21
23
  puts "rake spec:help - Show this help message"
22
24
  puts
23
25
  puts "Example workflow:"
@@ -390,4 +392,155 @@ namespace :spec do
390
392
  puts "Overall success rate: #{success_rate}% (#{test_data.length - failed_cases.length}/#{test_data.length})"
391
393
  end
392
394
  end
395
+
396
+ desc "Validate JSON files against their schemas"
397
+ task :validate_schemas do
398
+ require "json"
399
+ require "json-schema"
400
+
401
+ puts "🔍 Validating JSON files against schemas..."
402
+ puts "=" * 50
403
+
404
+ schemas_dir = File.join(__dir__, "schemas")
405
+
406
+ validations = [
407
+ {
408
+ name: "PURL Types Configuration",
409
+ data_file: "purl-types.json",
410
+ schema_file: File.join(schemas_dir, "purl-types.schema.json")
411
+ },
412
+ {
413
+ name: "Test Suite Data",
414
+ data_file: "test-suite-data.json",
415
+ schema_file: File.join(schemas_dir, "test-suite-data.schema.json")
416
+ }
417
+ ]
418
+
419
+ all_valid = true
420
+
421
+ validations.each do |validation|
422
+ puts "\n📋 Validating #{validation[:name]}..."
423
+
424
+ data_path = File.join(__dir__, validation[:data_file])
425
+ schema_path = validation[:schema_file]
426
+
427
+ # Check if files exist
428
+ unless File.exist?(data_path)
429
+ puts " ❌ Data file not found: #{validation[:data_file]}"
430
+ all_valid = false
431
+ next
432
+ end
433
+
434
+ unless File.exist?(schema_path)
435
+ puts " ❌ Schema file not found: #{validation[:schema_file]}"
436
+ all_valid = false
437
+ next
438
+ end
439
+
440
+ begin
441
+ # Load and parse files
442
+ data = JSON.parse(File.read(data_path))
443
+ schema = JSON.parse(File.read(schema_path))
444
+
445
+ # Validate
446
+ errors = JSON::Validator.fully_validate(schema, data)
447
+
448
+ if errors.empty?
449
+ puts " ✅ Valid - conforms to schema"
450
+ else
451
+ puts " ❌ Invalid - found #{errors.length} error(s):"
452
+ errors.first(5).each { |error| puts " • #{error}" }
453
+ if errors.length > 5
454
+ puts " • ... and #{errors.length - 5} more errors"
455
+ end
456
+ all_valid = false
457
+ end
458
+
459
+ rescue JSON::ParserError => e
460
+ puts " ❌ JSON parsing error: #{e.message}"
461
+ all_valid = false
462
+ rescue => e
463
+ puts " ❌ Validation error: #{e.message}"
464
+ all_valid = false
465
+ end
466
+ end
467
+
468
+ puts "\n" + "=" * 50
469
+ if all_valid
470
+ puts "🎉 All JSON files are valid according to their schemas!"
471
+ else
472
+ puts "❌ One or more JSON files failed schema validation"
473
+ exit 1
474
+ end
475
+ end
476
+
477
+ desc "Validate all PURL examples in purl-types.json"
478
+ task :validate_examples do
479
+ require "json"
480
+ require_relative "lib/purl"
481
+
482
+ puts "🔍 Validating PURL examples in purl-types.json..."
483
+ puts "=" * 60
484
+
485
+ project_root = __dir__
486
+ purl_types_data = JSON.parse(File.read(File.join(project_root, "purl-types.json")))
487
+
488
+ total_examples = 0
489
+ invalid_examples = []
490
+
491
+ purl_types_data["types"].each do |type_name, type_config|
492
+ examples = type_config["examples"]
493
+ next unless examples && examples.is_a?(Array)
494
+
495
+ puts "\n📦 #{type_name} (#{examples.length} examples):"
496
+
497
+ examples.each do |example_purl|
498
+ total_examples += 1
499
+
500
+ begin
501
+ # Try to parse the example PURL
502
+ parsed = Purl::PackageURL.parse(example_purl)
503
+
504
+ # Verify the type matches
505
+ if parsed.type == type_name
506
+ puts " ✅ #{example_purl}"
507
+ else
508
+ puts " ❌ #{example_purl} - Type mismatch: expected '#{type_name}', got '#{parsed.type}'"
509
+ invalid_examples << {
510
+ type: type_name,
511
+ example: example_purl,
512
+ error: "Type mismatch: expected '#{type_name}', got '#{parsed.type}'"
513
+ }
514
+ end
515
+
516
+ rescue => e
517
+ puts " ❌ #{example_purl} - #{e.class}: #{e.message}"
518
+ invalid_examples << {
519
+ type: type_name,
520
+ example: example_purl,
521
+ error: "#{e.class}: #{e.message}"
522
+ }
523
+ end
524
+ end
525
+ end
526
+
527
+ puts "\n" + "=" * 60
528
+ puts "📊 Validation Summary:"
529
+ puts " Total examples: #{total_examples}"
530
+ puts " Valid examples: #{total_examples - invalid_examples.length}"
531
+ puts " Invalid examples: #{invalid_examples.length}"
532
+
533
+ if invalid_examples.empty?
534
+ puts "\n🎉 All PURL examples are valid!"
535
+ else
536
+ puts "\n❌ Found #{invalid_examples.length} invalid examples:"
537
+ invalid_examples.each do |invalid|
538
+ puts " • #{invalid[:type]}: #{invalid[:example]}"
539
+ puts " Error: #{invalid[:error]}"
540
+ end
541
+
542
+ puts "\n📝 These examples should be reported upstream to the PURL specification maintainers."
543
+ exit 1
544
+ end
545
+ end
393
546
  end