use_friend_registry/
lib.rs1#![doc = include_str!("../README.md")]
2
3use std::{borrow::Borrow, collections::BTreeMap};
6
7use use_friend::{FigureKind, Friend, IdentityKind, TechnologyKind};
8use use_friend_fixture::FriendFixtures;
9
10#[derive(Clone, Debug, Default)]
12pub struct FriendRegistry {
13 friends: Vec<Friend>,
14}
15
16impl FriendRegistry {
17 #[must_use]
19 pub fn new<I>(friends: I) -> Self
20 where
21 I: IntoIterator,
22 I::Item: Borrow<Friend>,
23 {
24 Self {
25 friends: friends.into_iter().map(|friend| *friend.borrow()).collect(),
26 }
27 }
28
29 #[must_use]
31 pub fn from_static(friends: &'static [Friend]) -> Self {
32 Self::new(friends)
33 }
34
35 #[must_use]
37 pub fn all() -> Self {
38 Self::from_static(FriendFixtures::all())
39 }
40
41 #[must_use]
43 pub fn len(&self) -> usize {
44 self.friends.len()
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.friends.is_empty()
51 }
52
53 pub fn iter(&self) -> impl Iterator<Item = &Friend> {
55 self.friends.iter()
56 }
57
58 #[must_use]
60 pub fn by_ecosystem(&self, ecosystem: &str) -> Vec<&Friend> {
61 self.iter()
62 .filter(|friend| friend.matches_ecosystem(ecosystem))
63 .collect()
64 }
65
66 #[must_use]
68 pub fn by_tag(&self, tag: &str) -> Vec<&Friend> {
69 self.iter().filter(|friend| friend.has_tag(tag)).collect()
70 }
71
72 #[must_use]
74 pub fn by_technology_kind(&self, kind: TechnologyKind) -> Vec<&Friend> {
75 self.iter()
76 .filter(|friend| friend.technology_kind == kind)
77 .collect()
78 }
79
80 #[must_use]
82 pub fn by_identity_kind(&self, kind: IdentityKind) -> Vec<&Friend> {
83 self.iter()
84 .filter(|friend| friend.identity_kind == kind)
85 .collect()
86 }
87
88 #[must_use]
90 pub fn by_figure_kind(&self, kind: FigureKind) -> Vec<&Friend> {
91 self.iter()
92 .filter(|friend| friend.figure_kind == kind)
93 .collect()
94 }
95
96 #[must_use]
98 pub fn find_by_id(&self, id: &str) -> Option<&Friend> {
99 let normalized = id.trim();
100
101 if normalized.is_empty() {
102 return None;
103 }
104
105 self.iter().find(|friend| friend.id == normalized)
106 }
107
108 #[must_use]
110 pub fn count_by_technology_kind(&self) -> BTreeMap<TechnologyKind, usize> {
111 let mut counts = BTreeMap::new();
112
113 for friend in &self.friends {
114 *counts.entry(friend.technology_kind).or_default() += 1;
115 }
116
117 counts
118 }
119
120 #[must_use]
122 pub fn count_by_identity_kind(&self) -> BTreeMap<IdentityKind, usize> {
123 let mut counts = BTreeMap::new();
124
125 for friend in &self.friends {
126 *counts.entry(friend.identity_kind).or_default() += 1;
127 }
128
129 counts
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::FriendRegistry;
136 use use_friend::{FigureKind, IdentityKind, TechnologyKind};
137 use use_friend_fixture::FriendFixtures;
138
139 #[test]
140 fn builds_from_static_slice_with_new() {
141 let registry = FriendRegistry::new(FriendFixtures::small());
142 let rust_friends = registry.by_ecosystem(" rust ");
143
144 assert_eq!(registry.len(), 6);
145 assert_eq!(rust_friends.len(), 1);
146 assert_eq!(rust_friends[0].name, "Ferris");
147 assert!(!registry.is_empty());
148 }
149
150 #[test]
151 fn filters_by_tag_and_kinds() {
152 let registry = FriendRegistry::all();
153
154 assert_eq!(registry.by_tag("container")[0].ecosystem, "Docker");
155 assert_eq!(
156 registry
157 .by_technology_kind(TechnologyKind::ProgrammingLanguage)
158 .len(),
159 12
160 );
161 assert_eq!(
162 registry.by_identity_kind(IdentityKind::LogoCharacter).len(),
163 8
164 );
165 assert_eq!(registry.by_figure_kind(FigureKind::Creature).len(), 7);
166 }
167
168 #[test]
169 fn filters_new_zig_database_and_language_records() {
170 let registry = FriendRegistry::all();
171 let zig_names: Vec<_> = registry
172 .by_ecosystem("Zig")
173 .into_iter()
174 .map(|friend| friend.name)
175 .collect();
176 let database_names: Vec<_> = registry
177 .by_tag("database")
178 .into_iter()
179 .map(|friend| friend.name)
180 .collect();
181 let language_ecosystems: Vec<_> = registry
182 .by_technology_kind(TechnologyKind::ProgrammingLanguage)
183 .into_iter()
184 .map(|friend| friend.ecosystem)
185 .collect();
186
187 assert!(zig_names.contains(&"Zero the Ziguana"));
188 assert!(zig_names.contains(&"Ziggy the Ziguana"));
189 assert!(zig_names.contains(&"Carmen the Allocgator"));
190 assert!(database_names.contains(&"Sakila"));
191 assert!(database_names.contains(&"MariaDB Sea Lion"));
192
193 for ecosystem in ["Java", "PHP", "Zig", "Raku", "Perl", "Crystal"] {
194 assert!(language_ecosystems.contains(&ecosystem));
195 }
196 }
197
198 #[test]
199 fn finds_by_stable_identifier() {
200 let registry = FriendRegistry::all();
201
202 assert_eq!(
203 registry
204 .find_by_id(" github-octocat ")
205 .map(|friend| friend.name),
206 Some("Octocat")
207 );
208 assert_eq!(registry.find_by_id(""), None);
209 assert_eq!(registry.find_by_id("missing"), None);
210 }
211
212 #[test]
213 fn counts_with_deterministic_maps() {
214 let registry = FriendRegistry::all();
215 let technology_counts = registry.count_by_technology_kind();
216 let identity_counts = registry.count_by_identity_kind();
217
218 assert_eq!(
219 technology_counts.get(&TechnologyKind::ProgrammingLanguage),
220 Some(&12)
221 );
222 assert_eq!(
223 technology_counts.get(&TechnologyKind::OperatingSystem),
224 Some(&7)
225 );
226 assert_eq!(identity_counts.get(&IdentityKind::Mascot), Some(&20));
227 assert_eq!(identity_counts.get(&IdentityKind::LogoSymbol), Some(&4));
228 }
229}