Skip to main content

wowlab_tidy/languages/rust/rules/api/
public_error_enum.rs

1use ra_ap_syntax::ast::{self, HasName, HasVisibility, VisibilityKind};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "pub error enum",
9        code: "pub enum ParseError { Eof, Syntax }",
10        pass: false,
11    },
12    Example {
13        label: "pub error kind enum",
14        code: "pub enum IoErrorKind { NotFound, Denied }",
15        pass: false,
16    },
17    Example {
18        label: "private kind enum",
19        code: "enum ErrorKind { Io, Protocol }",
20        pass: true,
21    },
22    Example {
23        label: "pub(crate) error enum",
24        code: "pub(crate) enum ParseError { Eof }",
25        pass: true,
26    },
27    Example {
28        label: "pub enum without error suffix",
29        code: "pub enum Mode { Fast, Slow }",
30        pass: true,
31    },
32    Example {
33        label: "pub error struct",
34        code: "pub struct ParseError { line: usize }",
35        pass: true,
36    },
37    Example {
38        label: "pub error enum in test module",
39        code: "#[cfg(test)]\nmod tests {\n    pub enum ParseError { Eof }\n}",
40        pass: true,
41    },
42];
43
44crate::ast_rule!(
45    public_error_enum,
46    "Flag `pub enum` named `*Error`/`*ErrorKind` — expose a situation-specific error struct with a private kind enum instead.",
47    "A public error enum exposes every failure mode as breaking API surface; a struct wrapping a private kind enum keeps internal failure modes non-breaking.",
48    Medium,
49);
50
51fn check_public_error_enum(ctx: &AstCtx<'_>) -> Vec<Violation> {
52    let public_enums = ctx
53        .nodes::<ast::Enum>()
54        .filter(|item| !ctx.is_in_test(item))
55        .filter(|item| {
56            item.visibility()
57                .is_some_and(|vis| matches!(vis.kind(), VisibilityKind::Pub))
58        });
59
60    public_enums
61        .filter_map(|item| {
62            let name = item.name()?;
63            let text = name.text().to_string();
64
65            (text.ends_with("Error") || text.ends_with("ErrorKind")).then(|| {
66                ctx.violation(
67                    &name,
68                    format!(
69                        "public enum `{text}` — expose a situation-specific error struct with a private kind enum"
70                    ),
71                )
72            })
73        })
74        .collect()
75}
76
77crate::tidy_ast_test!(check_public_error_enum, {
78    crate::example_tests!(EXAMPLES, check_public_error_enum);
79});