sqry_core/schema/
change.rs1use serde::{Deserialize, Serialize};
6use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30#[derive(Default)]
31pub enum ChangeKind {
32 Added,
34
35 Removed,
37
38 #[default]
40 Modified,
41
42 Renamed,
44
45 SignatureChanged,
47}
48
49impl ChangeKind {
50 #[must_use]
52 pub const fn all() -> &'static [Self] {
53 &[
54 Self::Added,
55 Self::Removed,
56 Self::Modified,
57 Self::Renamed,
58 Self::SignatureChanged,
59 ]
60 }
61
62 #[must_use]
64 pub const fn as_str(self) -> &'static str {
65 match self {
66 Self::Added => "added",
67 Self::Removed => "removed",
68 Self::Modified => "modified",
69 Self::Renamed => "renamed",
70 Self::SignatureChanged => "signature_changed",
71 }
72 }
73
74 #[must_use]
79 pub fn parse(s: &str) -> Option<Self> {
80 match s.to_lowercase().replace('-', "_").as_str() {
81 "added" | "new" => Some(Self::Added),
82 "removed" | "deleted" => Some(Self::Removed),
83 "modified" | "changed" => Some(Self::Modified),
84 "renamed" => Some(Self::Renamed),
85 "signature_changed" | "signaturechanged" => Some(Self::SignatureChanged),
86 _ => None,
87 }
88 }
89
90 #[must_use]
92 pub const fn is_structural(self) -> bool {
93 matches!(self, Self::Added | Self::Removed)
94 }
95
96 #[must_use]
98 pub const fn is_content_change(self) -> bool {
99 matches!(
100 self,
101 Self::Modified | Self::Renamed | Self::SignatureChanged
102 )
103 }
104
105 #[must_use]
107 pub const fn affects_api(self) -> bool {
108 matches!(
109 self,
110 Self::Added | Self::Removed | Self::Renamed | Self::SignatureChanged
111 )
112 }
113}
114
115impl fmt::Display for ChangeKind {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.write_str(self.as_str())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_as_str() {
127 assert_eq!(ChangeKind::Added.as_str(), "added");
128 assert_eq!(ChangeKind::Removed.as_str(), "removed");
129 assert_eq!(ChangeKind::Modified.as_str(), "modified");
130 assert_eq!(ChangeKind::Renamed.as_str(), "renamed");
131 assert_eq!(ChangeKind::SignatureChanged.as_str(), "signature_changed");
132 }
133
134 #[test]
135 fn test_parse() {
136 assert_eq!(ChangeKind::parse("added"), Some(ChangeKind::Added));
137 assert_eq!(ChangeKind::parse("REMOVED"), Some(ChangeKind::Removed));
138 assert_eq!(ChangeKind::parse("new"), Some(ChangeKind::Added));
139 assert_eq!(ChangeKind::parse("deleted"), Some(ChangeKind::Removed));
140 assert_eq!(
141 ChangeKind::parse("signature_changed"),
142 Some(ChangeKind::SignatureChanged)
143 );
144 assert_eq!(ChangeKind::parse("unknown"), None);
145 }
146
147 #[test]
148 fn test_display() {
149 assert_eq!(format!("{}", ChangeKind::Added), "added");
150 assert_eq!(
151 format!("{}", ChangeKind::SignatureChanged),
152 "signature_changed"
153 );
154 }
155
156 #[test]
157 fn test_serde_roundtrip() {
158 for kind in ChangeKind::all() {
159 let json = serde_json::to_string(kind).unwrap();
160 let deserialized: ChangeKind = serde_json::from_str(&json).unwrap();
161 assert_eq!(*kind, deserialized);
162 }
163 }
164
165 #[test]
166 fn test_classification() {
167 assert!(ChangeKind::Added.is_structural());
168 assert!(ChangeKind::Removed.is_structural());
169 assert!(!ChangeKind::Modified.is_structural());
170
171 assert!(ChangeKind::Modified.is_content_change());
172 assert!(ChangeKind::Renamed.is_content_change());
173 assert!(!ChangeKind::Added.is_content_change());
174
175 assert!(ChangeKind::Added.affects_api());
176 assert!(ChangeKind::Removed.affects_api());
177 assert!(ChangeKind::Renamed.affects_api());
178 assert!(ChangeKind::SignatureChanged.affects_api());
179 assert!(!ChangeKind::Modified.affects_api());
180 }
181}