Skip to main content

sqry_core/schema/
duplicate.rs

1//! Canonical duplicate kind enumeration.
2//!
3//! Defines types of duplicates to detect in code.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Types of duplicates to detect in code.
9///
10/// Used by `find_duplicates` tool to specify which kind of
11/// code duplication to search for.
12///
13/// # Serialization
14///
15/// All variants serialize to lowercase: `"body"`, `"signature"`, etc.
16///
17/// # Examples
18///
19/// ```
20/// use sqry_core::schema::DuplicateKind;
21///
22/// let kind = DuplicateKind::Signature;
23/// assert_eq!(kind.as_str(), "signature");
24///
25/// let parsed = DuplicateKind::parse("struct").unwrap();
26/// assert_eq!(parsed, DuplicateKind::Struct);
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30#[derive(Default)]
31pub enum DuplicateKind {
32    /// Function/method body duplicates.
33    ///
34    /// Finds functions with similar or identical implementations.
35    /// Useful for identifying copy-paste code that could be refactored.
36    #[default]
37    Body,
38
39    /// Function signature duplicates.
40    ///
41    /// Finds functions with identical signatures (name, parameters, return type).
42    /// Useful for identifying potential interface inconsistencies.
43    Signature,
44
45    /// Struct/class definition duplicates.
46    ///
47    /// Finds types with similar field/property structures.
48    /// Useful for identifying redundant type definitions.
49    Struct,
50}
51
52impl DuplicateKind {
53    /// Returns all variants in definition order.
54    #[must_use]
55    pub const fn all() -> &'static [Self] {
56        &[Self::Body, Self::Signature, Self::Struct]
57    }
58
59    /// Returns the canonical string representation.
60    #[must_use]
61    pub const fn as_str(self) -> &'static str {
62        match self {
63            Self::Body => "body",
64            Self::Signature => "signature",
65            Self::Struct => "struct",
66        }
67    }
68
69    /// Parses a string into a `DuplicateKind`.
70    ///
71    /// Returns `None` if the string doesn't match any known kind.
72    /// Case-insensitive.
73    #[must_use]
74    pub fn parse(s: &str) -> Option<Self> {
75        match s.to_lowercase().as_str() {
76            "body" | "implementation" | "impl" => Some(Self::Body),
77            "signature" | "sig" => Some(Self::Signature),
78            "struct" | "class" | "type" => Some(Self::Struct),
79            _ => None,
80        }
81    }
82
83    /// Returns a human-readable description of this duplicate kind.
84    #[must_use]
85    pub const fn description(self) -> &'static str {
86        match self {
87            Self::Body => "function/method body duplicates",
88            Self::Signature => "function signature duplicates",
89            Self::Struct => "struct/class definition duplicates",
90        }
91    }
92}
93
94impl fmt::Display for DuplicateKind {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.write_str(self.as_str())
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_as_str() {
106        assert_eq!(DuplicateKind::Body.as_str(), "body");
107        assert_eq!(DuplicateKind::Signature.as_str(), "signature");
108        assert_eq!(DuplicateKind::Struct.as_str(), "struct");
109    }
110
111    #[test]
112    fn test_parse() {
113        assert_eq!(DuplicateKind::parse("body"), Some(DuplicateKind::Body));
114        assert_eq!(
115            DuplicateKind::parse("SIGNATURE"),
116            Some(DuplicateKind::Signature)
117        );
118        assert_eq!(
119            DuplicateKind::parse("implementation"),
120            Some(DuplicateKind::Body)
121        );
122        assert_eq!(DuplicateKind::parse("class"), Some(DuplicateKind::Struct));
123        assert_eq!(DuplicateKind::parse("unknown"), None);
124    }
125
126    #[test]
127    fn test_display() {
128        assert_eq!(format!("{}", DuplicateKind::Body), "body");
129        assert_eq!(format!("{}", DuplicateKind::Struct), "struct");
130    }
131
132    #[test]
133    fn test_serde_roundtrip() {
134        for kind in DuplicateKind::all() {
135            let json = serde_json::to_string(kind).unwrap();
136            let deserialized: DuplicateKind = serde_json::from_str(&json).unwrap();
137            assert_eq!(*kind, deserialized);
138        }
139    }
140
141    #[test]
142    fn test_default() {
143        assert_eq!(DuplicateKind::default(), DuplicateKind::Body);
144    }
145
146    #[test]
147    fn test_description() {
148        assert!(DuplicateKind::Body.description().contains("body"));
149        assert!(DuplicateKind::Signature.description().contains("signature"));
150        assert!(DuplicateKind::Struct.description().contains("struct"));
151    }
152}