Skip to main content

icydb_utils/case/
mod.rs

1//! Module: case
2//! Responsibility: shared text case conversion vocabulary and helpers.
3//! Does not own: identifier authority, schema naming policy, or external crate behavior.
4//! Boundary: wraps local and external case conversion behind one workspace API.
5
6mod constant;
7mod snake;
8mod title;
9
10use std::fmt::{self, Display};
11
12use convert_case as cc;
13
14pub use snake::to_snake_case;
15
16///
17/// Case
18///
19/// Supported case conversion targets shared across schema, derive, and runtime
20/// surfaces.
21///
22
23#[derive(Clone, Copy, Debug)]
24pub enum Case {
25    Camel,
26    Constant,
27    Kebab,
28    Lower,
29    Sentence,
30    Snake,
31    Title,
32    Upper,
33    UpperCamel,
34    UpperSnake,
35    UpperKebab,
36}
37
38impl Display for Case {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        let label = match self {
41            Self::Camel => "Camel",
42            Self::Constant => "Constant",
43            Self::Kebab => "Kebab",
44            Self::Lower => "Lower",
45            Self::Sentence => "Sentence",
46            Self::Snake => "Snake",
47            Self::Title => "Title",
48            Self::Upper => "Upper",
49            Self::UpperCamel => "UpperCamel",
50            Self::UpperSnake => "UpperSnake",
51            Self::UpperKebab => "UpperKebab",
52        };
53
54        f.write_str(label)
55    }
56}
57
58///
59/// Casing
60///
61/// Shared string case conversion surface retained locally so workspace crates
62/// do not depend on `canic-utils` for text casing.
63///
64
65pub trait Casing<T: std::fmt::Display> {
66    /// Convert the receiver into the requested case form.
67    fn to_case(&self, case: Case) -> String;
68
69    /// Return whether the receiver is already in the requested case form.
70    fn is_case(&self, case: Case) -> bool;
71}
72
73impl<T: std::fmt::Display> Casing<T> for T
74where
75    String: PartialEq<T>,
76{
77    fn to_case(&self, case: Case) -> String {
78        let s = &self.to_string();
79
80        match case {
81            Case::Lower => s.to_lowercase(),
82            Case::Upper => s.to_uppercase(),
83            Case::Title => title::to_title_case(s),
84            Case::Snake => snake::to_snake_case(s),
85            Case::UpperSnake => snake::to_snake_case(s).to_uppercase(),
86            Case::Constant => constant::to_constant_case(s),
87            Case::Camel => cc::Casing::to_case(s, cc::Case::Camel),
88            Case::Kebab => cc::Casing::to_case(s, cc::Case::Kebab),
89            Case::Sentence => cc::Casing::to_case(s, cc::Case::Sentence),
90            Case::UpperCamel => cc::Casing::to_case(s, cc::Case::UpperCamel),
91            Case::UpperKebab => cc::Casing::to_case(s, cc::Case::Kebab).to_uppercase(),
92        }
93    }
94
95    fn is_case(&self, case: Case) -> bool {
96        &self.to_case(case) == self
97    }
98}