Skip to content

Commit 36643b2

Browse files
committed
Add tag limit and glob format
Use [wax] to validate globs. Since it checks for both `.` and `..` components, disallow both for paths and globs, except at the start of an expression. May or may not switch away from wax in the future, since its syntax is somewhat different from gitignore. For now, though, the RFC has been [updated] to reflect this change. Also limit the number of tags to 32, also reflecting a recent RFC change. [wax]: https://crates.io/crates/wax [updated]: pgxn/rfcs@4021dee
1 parent f7607e2 commit 36643b2

File tree

6 files changed

+109
-15
lines changed

6 files changed

+109
-15
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ All notable changes to this project will be documented in this file. It uses the
77
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
88
"Semantic Versioning 2.0.0"
99

10+
## [v0.6.1] — Unreleased
11+
12+
### ⚡ Improvements
13+
14+
* Added a limit to the number of tags in the v2 schema to 32.
15+
* Added glob format and validation using [wax].
16+
* Disallow parent directory components (`..`) in globs (already disallowed
17+
in paths)
18+
* Disallow current directory components (`.`) in paths and globs except at
19+
the start of the expression (e.g., `./README`).
20+
21+
[v0.6.1]: https://github.com/pgxn/meta/compare/v0.6.0...v0.6.1
22+
[wax]: https://crates.io/crates/wax
23+
1024
## [v0.6.0] — 2025-03-25
1125

1226
### ⬆️ Dependency Updates

schema/v2/glob.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "*Glob* defines a pattern to identify one or more files in the distribution.",
66
"type": "string",
77
"minLength": 2,
8-
"format": "",
8+
"format": "glob",
99
"pattern": "^(?:[^\\\\]|\\\\\\\\)+$",
1010
"$comment": "https://regex101.com/r/d49AVj; Crates: fast-glob or wax",
1111
"examples": ["/.git", "src/private.c", "doc/*.html"]

schema/v2/tags.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "A list of keywords that describe the distribution.",
66
"type": "array",
77
"minItems": 1,
8+
"maxItems": 32,
89
"uniqueItems": true,
910
"items": {
1011
"title": "Tag",

src/tests/common.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ pub fn test_tags_schema(mut compiler: Compiler, version: u8) -> Result<(), Error
200200
}
201201
}
202202

203+
if version > 1 {
204+
let strings: [String; 33] = core::array::from_fn(|i| format!("string {i}"));
205+
let val = serde_json::to_value(&strings[..]).unwrap();
206+
if schemas.validate(&val, idx).is_ok() {
207+
panic!("too many tags unexpectedly passed!")
208+
}
209+
}
210+
203211
Ok(())
204212
}
205213

src/tests/v2.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn test_v2_glob() -> Result<(), Error> {
7676
json!("foo.?tml"),
7777
json!("[xX]_*.*"),
7878
json!("[a-z]*.txt"),
79-
json!("this\\\\and\\\\that.txt"),
79+
json!("**/*.(?i){jpg,jpeg,png}"),
8080
] {
8181
if let Err(e) = schemas.validate(&valid, idx) {
8282
panic!("{} failed: {e}", valid);
@@ -86,6 +86,8 @@ fn test_v2_glob() -> Result<(), Error> {
8686
// Test invalid globs.
8787
for invalid in [
8888
json!("this\\and\\that.txt"),
89+
json!("/src/../pair.c"),
90+
json!("../outside/path"),
8991
json!(null),
9092
json!(""),
9193
json!("C:\\foo"),
@@ -1346,10 +1348,6 @@ fn test_v2_ignore() -> Result<(), Error> {
13461348
("foo.?tml", json!(["foo.?tml"])),
13471349
("[xX]_*.*", json!(["[xX]_*.*"])),
13481350
("[a-z]*.txt", json!(["[a-z]*.txt"])),
1349-
(
1350-
"this\\\\and\\\\that.txt",
1351-
json!(["this\\\\and\\\\that.txt"]),
1352-
),
13531351
(
13541352
"multiple files",
13551353
json!(["ignore_me.txt", "*.tmp", ".git*",]),

src/valid/compiler/mod.rs

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@ pub fn new() -> Compiler {
2727
}
2828

2929
/// Creates a new boon::compiler with format assertions enabled and validation
30-
/// for the custom `path` and `license` formats.
30+
/// for the custom `path`, `glob`, and `license` formats.
3131
pub fn spec_compiler() -> Compiler {
3232
let mut compiler = Compiler::new();
3333
compiler.enable_format_assertions();
3434
compiler.register_format(boon::Format {
3535
name: "path",
3636
func: is_path,
3737
});
38+
compiler.register_format(boon::Format {
39+
name: "glob",
40+
func: is_glob,
41+
});
3842
compiler.register_format(boon::Format {
3943
name: "license",
4044
func: is_license,
@@ -46,11 +50,25 @@ pub fn spec_compiler() -> Compiler {
4650
fn is_path(v: &Value) -> Result<(), Box<dyn std::error::Error>> {
4751
let Value::String(s) = v else { return Ok(()) };
4852

49-
let path = RelativePath::new(s);
53+
let path = RelativePath::new(s.strip_prefix("./").unwrap_or(s));
5054
for c in path.components() {
51-
if c == Component::ParentDir {
52-
Err("references parent dir")?
53-
};
55+
match c {
56+
Component::ParentDir => Err("references parent directory")?,
57+
Component::CurDir => Err("references current directory")?,
58+
_ => (),
59+
}
60+
}
61+
62+
Ok(())
63+
}
64+
65+
/// Returns an error if v is not a valid glob.
66+
fn is_glob(v: &Value) -> Result<(), Box<dyn std::error::Error>> {
67+
let Value::String(s) = v else { return Ok(()) };
68+
69+
let path = wax::Glob::new(s.strip_prefix("./").unwrap_or(s))?;
70+
if path.has_semantic_literals() {
71+
Err("references parent or current directory")?
5472
}
5573

5674
Ok(())
@@ -77,6 +95,7 @@ mod tests {
7795
json!("\\foo.md"),
7896
json!("this\\and\\that.txt"),
7997
json!("/absolute/path"),
98+
json!("./relative/path"),
8099
json!(""),
81100
json!("C:\\foo"),
82101
json!("README.txt"),
@@ -92,11 +111,20 @@ mod tests {
92111

93112
// Test invalid paths.
94113
for (name, invalid, err) in [
95-
("parent", json!("../outside/path"), "references parent dir"),
114+
(
115+
"parent",
116+
json!("../outside/path"),
117+
"references parent directory",
118+
),
119+
(
120+
"current",
121+
json!("/./outside/path"),
122+
"references current directory",
123+
),
96124
(
97125
"sub parent",
98126
json!("thing/../other"),
99-
"references parent dir",
127+
"references parent directory",
100128
),
101129
] {
102130
match is_path(&invalid) {
@@ -106,6 +134,42 @@ mod tests {
106134
}
107135
}
108136

137+
#[test]
138+
fn test_glob() {
139+
// Test valid globs.
140+
for valid in [
141+
json!(".gitignore"),
142+
json!(".git*"),
143+
json!("/git*"),
144+
json!("./git*"),
145+
json!(""),
146+
json!("README.*"),
147+
json!("**/*.(?i){jpg,jpeg}"),
148+
json!("**/{*.{go,rs}}"),
149+
json!("src/**/*.rs"),
150+
] {
151+
if let Err(e) = is_glob(&valid) {
152+
panic!("{} failed: {e}", valid);
153+
}
154+
}
155+
156+
// Test invalid paths.
157+
for (name, invalid) in [
158+
("parent", json!("../*.c")),
159+
("current", json!("/./*.c")),
160+
("sub parent", json!("/**/../passwd")),
161+
] {
162+
match is_glob(&invalid) {
163+
Ok(_) => panic!("{name} unexpectedly passed!"),
164+
Err(e) => assert_eq!(
165+
"references parent or current directory",
166+
e.to_string(),
167+
"{name}"
168+
),
169+
}
170+
}
171+
}
172+
109173
#[test]
110174
fn test_license() {
111175
// Test valid relative licenses.
@@ -163,7 +227,7 @@ mod tests {
163227
fn test_spec_compiler() -> Result<(), Error> {
164228
let mut c = spec_compiler();
165229
let id = "format";
166-
// Compile simple schema to validate license and path.
230+
// Compile simple schema to validate license, path, and glob.
167231
c.add_resource(
168232
id,
169233
json!({
@@ -173,6 +237,10 @@ mod tests {
173237
"type": "string",
174238
"format": "path",
175239
},
240+
"glob": {
241+
"type": "string",
242+
"format": "glob",
243+
},
176244
"license": {
177245
"type": "string",
178246
"format": "license",
@@ -198,7 +266,12 @@ mod tests {
198266
(
199267
"parent path",
200268
json!({"path": "../foo"}),
201-
"'../foo' is not valid path: references parent dir",
269+
"'../foo' is not valid path: references parent directory",
270+
),
271+
(
272+
"parent glob",
273+
json!({"glob": "../*.c"}),
274+
"'../*.c' is not valid glob: references parent or current directory",
202275
),
203276
] {
204277
match schemas.validate(&json, idx) {

0 commit comments

Comments
 (0)