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 +4 -4
- data/CONTRIBUTING.md +167 -0
- data/README.md +159 -23
- data/Rakefile +153 -0
- data/SECURITY.md +164 -0
- data/lib/purl/package_url.rb +9 -1
- data/lib/purl/registry_url.rb +274 -40
- data/lib/purl/version.rb +1 -1
- data/lib/purl.rb +25 -3
- data/purl-types.json +242 -17
- data/schemas/purl-types.schema.json +154 -0
- data/schemas/test-suite-data.schema.json +134 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25661f74cff7bd498cd8b82d64887231cd1a576b8ce0ef59c5397c6d378759eb
|
|
4
|
+
data.tar.gz: 0e82bbe5c1600601b3f27a82c79d9ea579587961ae97df1f1272ce31da35877e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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
|
-
[](https://www.ruby-lang.org/)
|
|
8
8
|
[](https://rubygems.org/gems/purl)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**[Available on RubyGems](https://rubygems.org/gems/purl)**
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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 # =>
|
|
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 # => ["
|
|
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 (
|
|
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
|
-
"
|
|
323
|
+
"default_registry": "https://rubygems.org",
|
|
219
324
|
"registry_config": {
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
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
|
-
-
|
|
263
|
-
-
|
|
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
|