Skip to content

Commit 7950d57

Browse files
authored
Show precise location for config parsing error (#1530)
Finally, we can actually fix #783, big thanks to serde‑saphyr and @bourumir-wyngs! For this invalid config: ```yaml repos: - repo: 'https://github.com/pre-commit/pre-commit-hooks' rev: v6.0.0 hooks: - id: trailing-whitespace - name: end-of-file-fixer - id: check-yaml - id: check-added-large-files ``` Before: ```console ❯ prek validate-config .pre-commit-config.yaml error: Failed to parse `.pre-commit-config.yaml` caused by: Invalid remote repo: missing field `id` ``` After: ```console ❯ ~/code/rust/prek/target/debug/prek validate-config .pre-commit-config.yaml error: Failed to parse `.pre-commit-config.yaml` caused by: error: line 6 column 9: missing field `id` at line 6, column 9 --> <input>:6:9 | 4 | hooks: 5 | - id: trailing-whitespace 6 | - name: end-of-file-fixer | ^ missing field `id` at line 6, column 9 7 | - id: check-yaml 8 | - id: check-added-large-files | ```
1 parent b070282 commit 7950d57

19 files changed

+1352
-947
lines changed

crates/prek/src/config.rs

Lines changed: 350 additions & 914 deletions
Large diffs are not rendered by default.

crates/prek/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ mod process;
4141
#[cfg(all(unix, feature = "profiler"))]
4242
mod profiler;
4343
mod run;
44+
#[cfg(feature = "schemars")]
45+
mod schema;
4446
mod store;
4547
mod version;
4648
mod warnings;

crates/prek/src/schema.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use std::borrow::Cow;
2+
3+
use crate::config::{
4+
BuiltinHook, BuiltinRepo, FilePattern, LocalRepo, MetaHook, MetaRepo, RemoteHook, RemoteRepo,
5+
Repo,
6+
};
7+
8+
impl schemars::JsonSchema for FilePattern {
9+
fn schema_name() -> Cow<'static, str> {
10+
Cow::Borrowed("FilePattern")
11+
}
12+
13+
fn json_schema(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
14+
schemars::json_schema!({
15+
"type": "object",
16+
"description": "A file pattern, either a regex or glob pattern(s).",
17+
"oneOf": [
18+
{
19+
"type": "object",
20+
"properties": {
21+
"regex": {
22+
"type": "string",
23+
"description": "A regular expression pattern.",
24+
}
25+
},
26+
"required": ["regex"],
27+
},
28+
{
29+
"type": "object",
30+
"properties": {
31+
"glob": {
32+
"oneOf": [
33+
{
34+
"type": "string",
35+
"description": "A glob pattern.",
36+
},
37+
{
38+
"type": "array",
39+
"items": {
40+
"type": "string",
41+
},
42+
"description": "A list of glob patterns.",
43+
}
44+
]
45+
}
46+
},
47+
"required": ["glob"],
48+
}
49+
],
50+
})
51+
}
52+
}
53+
54+
fn predefined_hook_schema(
55+
schema_gen: &mut schemars::SchemaGenerator,
56+
description: &str,
57+
id_schema: schemars::Schema,
58+
) -> schemars::Schema {
59+
let mut schema = <RemoteHook as schemars::JsonSchema>::json_schema(schema_gen);
60+
61+
let root = schema.ensure_object();
62+
root.insert("description".to_string(), serde_json::json!(description));
63+
root.insert("required".to_string(), serde_json::json!(["id"]));
64+
65+
let properties = root
66+
.get_mut("properties")
67+
.and_then(serde_json::Value::as_object_mut);
68+
69+
if let Some(properties) = properties {
70+
properties.insert("id".to_string(), id_schema.into());
71+
properties.insert(
72+
"language".to_string(),
73+
serde_json::json!({
74+
"anyOf": [
75+
{
76+
"type": "string",
77+
"enum": ["system"],
78+
"description": "Language must be `system` for predefined hooks (or omitted).",
79+
},
80+
{ "type": "null" }
81+
]
82+
})
83+
);
84+
// `entry` is not allowed for predefined hooks.
85+
properties.insert(
86+
"entry".to_string(),
87+
serde_json::json!({
88+
"const": false,
89+
"description": "Entry is not allowed for predefined hooks.",
90+
}),
91+
);
92+
}
93+
94+
schema
95+
}
96+
97+
impl schemars::JsonSchema for MetaHook {
98+
fn schema_name() -> Cow<'static, str> {
99+
Cow::Borrowed("MetaHook")
100+
}
101+
102+
fn json_schema(schema_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
103+
use crate::hooks::MetaHooks;
104+
105+
let id_schema = schema_gen.subschema_for::<MetaHooks>();
106+
predefined_hook_schema(schema_gen, "A meta hook predefined in prek.", id_schema)
107+
}
108+
}
109+
110+
impl schemars::JsonSchema for BuiltinHook {
111+
fn schema_name() -> Cow<'static, str> {
112+
Cow::Borrowed("BuiltinHook")
113+
}
114+
115+
fn json_schema(r#gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
116+
use crate::hooks::BuiltinHooks;
117+
118+
let id_schema = r#gen.subschema_for::<BuiltinHooks>();
119+
predefined_hook_schema(r#gen, "A builtin hook predefined in prek.", id_schema)
120+
}
121+
}
122+
123+
pub(crate) fn schema_repo_local(
124+
_gen: &mut schemars::generate::SchemaGenerator,
125+
) -> schemars::Schema {
126+
schemars::json_schema!({
127+
"type": "string",
128+
"const": "local",
129+
"description": "Must be `local`.",
130+
})
131+
}
132+
133+
pub(crate) fn schema_repo_meta(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
134+
schemars::json_schema!({
135+
"type": "string",
136+
"const": "meta",
137+
"description": "Must be `meta`.",
138+
})
139+
}
140+
141+
pub(crate) fn schema_repo_builtin(
142+
_gen: &mut schemars::generate::SchemaGenerator,
143+
) -> schemars::Schema {
144+
schemars::json_schema!({
145+
"type": "string",
146+
"const": "builtin",
147+
"description": "Must be `builtin`.",
148+
})
149+
}
150+
151+
pub(crate) fn schema_repo_remote(
152+
_gen: &mut schemars::generate::SchemaGenerator,
153+
) -> schemars::Schema {
154+
schemars::json_schema!({
155+
"type": "string",
156+
"not": {
157+
"enum": ["local", "meta", "builtin"],
158+
},
159+
"description": "Remote repository location. Must not be `local`, `meta`, or `builtin`.",
160+
})
161+
}
162+
163+
impl schemars::JsonSchema for Repo {
164+
fn schema_name() -> Cow<'static, str> {
165+
Cow::Borrowed("Repo")
166+
}
167+
168+
fn json_schema(r#gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
169+
let remote_schema = r#gen.subschema_for::<RemoteRepo>();
170+
let local_schema = r#gen.subschema_for::<LocalRepo>();
171+
let meta_schema = r#gen.subschema_for::<MetaRepo>();
172+
let builtin_schema = r#gen.subschema_for::<BuiltinRepo>();
173+
174+
schemars::json_schema!({
175+
"type": "object",
176+
"description": "A repository of hooks, which can be remote, local, meta, or builtin.",
177+
"oneOf": [
178+
remote_schema,
179+
local_schema,
180+
meta_schema,
181+
builtin_schema,
182+
],
183+
})
184+
}
185+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
source: crates/prek/src/config.rs
3+
expression: result
4+
---
5+
Ok(
6+
Config {
7+
repos: [
8+
Local(
9+
LocalRepo {
10+
repo: "local",
11+
hooks: [
12+
LocalHook {
13+
id: "hook-1",
14+
name: "hook 1",
15+
entry: "echo hello world",
16+
language: System,
17+
priority: None,
18+
options: HookOptions {
19+
alias: None,
20+
files: None,
21+
exclude: None,
22+
types: None,
23+
types_or: None,
24+
exclude_types: None,
25+
additional_dependencies: None,
26+
args: None,
27+
env: None,
28+
always_run: None,
29+
fail_fast: None,
30+
pass_filenames: None,
31+
description: None,
32+
language_version: Some(
33+
"default",
34+
),
35+
log_file: None,
36+
require_serial: None,
37+
stages: None,
38+
verbose: None,
39+
minimum_prek_version: None,
40+
_unused_keys: {},
41+
},
42+
},
43+
LocalHook {
44+
id: "hook-2",
45+
name: "hook 2",
46+
entry: "echo hello world",
47+
language: System,
48+
priority: None,
49+
options: HookOptions {
50+
alias: None,
51+
files: None,
52+
exclude: None,
53+
types: None,
54+
types_or: None,
55+
exclude_types: None,
56+
additional_dependencies: None,
57+
args: None,
58+
env: None,
59+
always_run: None,
60+
fail_fast: None,
61+
pass_filenames: None,
62+
description: None,
63+
language_version: Some(
64+
"system",
65+
),
66+
log_file: None,
67+
require_serial: None,
68+
stages: None,
69+
verbose: None,
70+
minimum_prek_version: None,
71+
_unused_keys: {},
72+
},
73+
},
74+
LocalHook {
75+
id: "hook-3",
76+
name: "hook 3",
77+
entry: "echo hello world",
78+
language: System,
79+
priority: None,
80+
options: HookOptions {
81+
alias: None,
82+
files: None,
83+
exclude: None,
84+
types: None,
85+
types_or: None,
86+
exclude_types: None,
87+
additional_dependencies: None,
88+
args: None,
89+
env: None,
90+
always_run: None,
91+
fail_fast: None,
92+
pass_filenames: None,
93+
description: None,
94+
language_version: Some(
95+
"3.8",
96+
),
97+
log_file: None,
98+
require_serial: None,
99+
stages: None,
100+
verbose: None,
101+
minimum_prek_version: None,
102+
_unused_keys: {},
103+
},
104+
},
105+
],
106+
_unused_keys: {},
107+
},
108+
),
109+
],
110+
default_install_hook_types: None,
111+
default_language_version: None,
112+
default_stages: None,
113+
files: None,
114+
exclude: None,
115+
fail_fast: None,
116+
minimum_prek_version: None,
117+
orphan: None,
118+
_unused_keys: {},
119+
},
120+
)

0 commit comments

Comments
 (0)