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}