Skip to main content

weaveffi_model/
errors.rs

1//! The shared error **naming policy**.
2//!
3//! The error *model* (which domains exist, which codes they carry, which
4//! module owns them) lives in the model as
5//! [`ErrorBinding`](crate::model::ErrorBinding). This module holds only the
6//! idiomatic naming rules every backend applies to those names, centralized so
7//! no target drifts into `KEY_NOT_FOUNDError` (raw SCREAMING_SNAKE with a
8//! naive `Error` suffix) while another emits `keyNotFound`.
9//!
10//! Backends pick the suffix that matches their ecosystem (`Error` or
11//! `Exception`) and case-convert each code's name through the helpers below.
12
13use heck::ToUpperCamelCase;
14
15/// PascalCase form of a raw error code name, with no suffix.
16/// `KEY_NOT_FOUND` -> `KeyNotFound`. Use for languages whose error variants
17/// are nested types/cases (Kotlin sealed subclasses, etc.) rather than
18/// standalone `*Error` classes.
19pub fn pascal(raw: &str) -> String {
20    raw.to_upper_camel_case()
21}
22
23/// PascalCase + exactly one `suffix`, avoiding doubled or SCREAMING suffixes.
24/// `("KEY_NOT_FOUND", "Error")` -> `KeyNotFoundError`;
25/// `("AlreadyError", "Error")` -> `AlreadyError`.
26pub fn type_name(raw: &str, suffix: &str) -> String {
27    let pascal = raw.to_upper_camel_case();
28    if pascal.ends_with(suffix) {
29        pascal
30    } else {
31        format!("{pascal}{suffix}")
32    }
33}
34
35/// Exception-branded type name for an error domain, for targets whose
36/// idiomatic errors are exceptions rather than `*Error` types.
37/// A trailing `Error` stem is replaced instead of stacked:
38/// `KvError` -> `KvException`; `Failure` -> `FailureException`; a bare
39/// `Error` -> `Exception`.
40pub fn exception_type_name(raw: &str) -> String {
41    let pascal = raw.to_upper_camel_case();
42    let stem = pascal.strip_suffix("Error").unwrap_or(&pascal);
43    type_name(stem, "Exception")
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn type_name_avoids_screaming_and_doubling() {
52        assert_eq!(type_name("KEY_NOT_FOUND", "Error"), "KeyNotFoundError");
53        assert_eq!(
54            type_name("KEY_NOT_FOUND", "Exception"),
55            "KeyNotFoundException"
56        );
57        assert_eq!(type_name("AlreadyError", "Error"), "AlreadyError");
58        assert_eq!(type_name("invalid_input", "Error"), "InvalidInputError");
59    }
60
61    #[test]
62    fn exception_type_name_replaces_error_stem() {
63        assert_eq!(exception_type_name("KvError"), "KvException");
64        assert_eq!(exception_type_name("ContactsError"), "ContactsException");
65        assert_eq!(exception_type_name("Failure"), "FailureException");
66        assert_eq!(exception_type_name("KvException"), "KvException");
67        assert_eq!(exception_type_name("Error"), "Exception");
68    }
69
70    #[test]
71    fn pascal_is_suffix_free() {
72        assert_eq!(pascal("KEY_NOT_FOUND"), "KeyNotFound");
73    }
74}