1use alloc::string::String;
2use core::fmt::{self, Write};
3use core::num;
4
5use castaway::{match_type, LifetimeFree};
6
7use super::repr::{IntoRepr, Repr};
8use crate::{CompactString, ToCompactStringError, UnwrapWithMsg};
9
10pub trait ToCompactString {
17 #[inline]
38 #[track_caller]
39 fn to_compact_string(&self) -> CompactString {
40 self.try_to_compact_string().unwrap_with_msg()
41 }
42
43 fn try_to_compact_string(&self) -> Result<CompactString, ToCompactStringError>;
49}
50
51unsafe impl LifetimeFree for CompactString {}
57unsafe impl LifetimeFree for Repr {}
58
59impl<T: fmt::Display> ToCompactString for T {
77 #[inline]
78 fn try_to_compact_string(&self) -> Result<CompactString, ToCompactStringError> {
79 let repr = match_type!(self, {
80 &u8 as s => s.into_repr()?,
81 &i8 as s => s.into_repr()?,
82 &u16 as s => s.into_repr()?,
83 &i16 as s => s.into_repr()?,
84 &u32 as s => s.into_repr()?,
85 &i32 as s => s.into_repr()?,
86 &u64 as s => s.into_repr()?,
87 &i64 as s => s.into_repr()?,
88 &u128 as s => s.into_repr()?,
89 &i128 as s => s.into_repr()?,
90 &usize as s => s.into_repr()?,
91 &isize as s => s.into_repr()?,
92 &f32 as s => s.into_repr()?,
93 &f64 as s => s.into_repr()?,
94 &bool as s => s.into_repr()?,
95 &char as s => s.into_repr()?,
96 &String as s => Repr::new(s)?,
97 &CompactString as s => Repr::new(s)?,
98 &num::NonZeroU8 as s => s.into_repr()?,
99 &num::NonZeroI8 as s => s.into_repr()?,
100 &num::NonZeroU16 as s => s.into_repr()?,
101 &num::NonZeroI16 as s => s.into_repr()?,
102 &num::NonZeroU32 as s => s.into_repr()?,
103 &num::NonZeroI32 as s => s.into_repr()?,
104 &num::NonZeroU64 as s => s.into_repr()?,
105 &num::NonZeroI64 as s => s.into_repr()?,
106 &num::NonZeroUsize as s => s.into_repr()?,
107 &num::NonZeroIsize as s => s.into_repr()?,
108 &num::NonZeroU128 as s => s.into_repr()?,
109 &num::NonZeroI128 as s => s.into_repr()?,
110 s => {
111 let mut c = CompactString::const_new("");
112 write!(c, "{}", s)?;
113 return Ok(c);
114 }
115 });
116
117 Ok(CompactString(repr))
118 }
119}
120
121pub trait CompactStringExt {
143 fn concat_compact(self) -> CompactString;
155
156 fn join_compact<S: AsRef<str>>(self, separator: S) -> CompactString;
169}
170
171impl<I, C> CompactStringExt for C
172where
173 I: AsRef<str>,
174 C: IntoIterator<Item = I>,
175{
176 fn concat_compact(self) -> CompactString {
177 self.into_iter()
178 .fold(CompactString::const_new(""), |mut s, item| {
179 s.push_str(item.as_ref());
180 s
181 })
182 }
183
184 fn join_compact<S: AsRef<str>>(self, separator: S) -> CompactString {
185 let mut compact_string = CompactString::const_new("");
186
187 let mut iter = self.into_iter().peekable();
188 let sep = separator.as_ref();
189
190 while let Some(item) = iter.next() {
191 compact_string.push_str(item.as_ref());
192 if iter.peek().is_some() {
193 compact_string.push_str(sep);
194 }
195 }
196
197 compact_string
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use alloc::string::{String, ToString};
204 use alloc::vec::Vec;
205 use core::num;
206
207 use proptest::prelude::*;
208 use test_strategy::proptest;
209
210 use super::{CompactStringExt, ToCompactString};
211 use crate::CompactString;
212
213 #[test]
214 fn test_join() {
215 let slice = ["hello", "world"];
216 let c1 = (&slice).join_compact(" ");
217 let c2 = slice.join_compact(" ");
218 assert_eq!(c1, "hello world");
219 assert_eq!(c2, "hello world");
220
221 let vector = vec!["🍎", "🍊", "🍌"];
222 let c = vector.join_compact(",");
223 assert_eq!(c, "🍎,🍊,🍌");
224
225 let owned_strings = ["foo".to_string(), "bar".to_string()];
226 let c = owned_strings.join_compact("/");
227 assert_eq!(c, "foo/bar");
228 }
229
230 #[test]
231 fn test_join_iter() {
232 let values = ["foo", "bar", "baz"];
233 let compact = values.iter().map(|x| x.to_string()).join_compact("/");
234 assert_eq!(compact, "foo/bar/baz");
235
236 let compact = values.into_iter().map(|x| x.trim()).join_compact("-");
237 assert_eq!(compact, "foo-bar-baz");
238
239 let compact = values
240 .into_iter()
241 .map(CompactString::from)
242 .join_compact("...");
243 assert_eq!(compact, "foo...bar...baz");
244 }
245
246 #[proptest]
247 #[cfg_attr(miri, ignore)]
248 fn proptest_join(items: Vec<String>, separator: String) {
249 let c: CompactString = items.iter().join_compact(&separator);
250 let s: String = items.join(&separator);
251 assert_eq!(c, s);
252 }
253
254 #[test]
255 fn test_concat() {
256 let items = vec!["hello", "world"];
257 let c = items.join_compact(" ");
258 assert_eq!(c, "hello world");
259
260 let vector = vec!["🍎", "🍊", "🍌"];
261 let c = vector.concat_compact();
262 assert_eq!(c, "🍎🍊🍌");
263 }
264
265 #[proptest]
266 #[cfg_attr(miri, ignore)]
267 fn proptest_concat(items: Vec<String>) {
268 let c: CompactString = items.iter().concat_compact();
269 let s: String = items.concat();
270 assert_eq!(c, s);
271 }
272
273 #[proptest]
274 #[cfg_attr(miri, ignore)]
275 fn proptest_to_compact_string_u8(val: u8) {
276 let compact = val.to_compact_string();
277 prop_assert_eq!(compact.as_str(), val.to_string());
278 }
279
280 #[proptest]
281 #[cfg_attr(miri, ignore)]
282 fn proptest_to_compact_string_i8(val: i8) {
283 let compact = val.to_compact_string();
284 prop_assert_eq!(compact.as_str(), val.to_string());
285 }
286
287 #[proptest]
288 #[cfg_attr(miri, ignore)]
289 fn proptest_to_compact_string_u16(val: u16) {
290 let compact = val.to_compact_string();
291 prop_assert_eq!(compact.as_str(), val.to_string());
292 }
293
294 #[proptest]
295 #[cfg_attr(miri, ignore)]
296 fn proptest_to_compact_string_i16(val: i16) {
297 let compact = val.to_compact_string();
298 prop_assert_eq!(compact.as_str(), val.to_string());
299 }
300 #[proptest]
301 #[cfg_attr(miri, ignore)]
302 fn proptest_to_compact_string_u32(val: u32) {
303 let compact = val.to_compact_string();
304 prop_assert_eq!(compact.as_str(), val.to_string());
305 }
306 #[proptest]
307 #[cfg_attr(miri, ignore)]
308 fn proptest_to_compact_string_i32(val: i32) {
309 let compact = val.to_compact_string();
310 prop_assert_eq!(compact.as_str(), val.to_string());
311 }
312 #[proptest]
313 #[cfg_attr(miri, ignore)]
314 fn proptest_to_compact_string_u64(val: u64) {
315 let compact = val.to_compact_string();
316 prop_assert_eq!(compact.as_str(), val.to_string());
317 }
318 #[proptest]
319 #[cfg_attr(miri, ignore)]
320 fn proptest_to_compact_string_i64(val: i64) {
321 let compact = val.to_compact_string();
322 prop_assert_eq!(compact.as_str(), val.to_string());
323 }
324 #[proptest]
325 #[cfg_attr(miri, ignore)]
326 fn proptest_to_compact_string_usize(val: usize) {
327 let compact = val.to_compact_string();
328 prop_assert_eq!(compact.as_str(), val.to_string());
329 }
330 #[proptest]
331 #[cfg_attr(miri, ignore)]
332 fn proptest_to_compact_string_isize(val: isize) {
333 let compact = val.to_compact_string();
334 prop_assert_eq!(compact.as_str(), val.to_string());
335 }
336 #[proptest]
337 #[cfg_attr(miri, ignore)]
338 fn proptest_to_compact_string_u128(val: u128) {
339 let compact = val.to_compact_string();
340 prop_assert_eq!(compact.as_str(), val.to_string());
341 }
342 #[proptest]
343 #[cfg_attr(miri, ignore)]
344 fn proptest_to_compact_string_i128(val: i128) {
345 let compact = val.to_compact_string();
346 prop_assert_eq!(compact.as_str(), val.to_string());
347 }
348
349 #[proptest]
350 #[cfg_attr(miri, ignore)]
351 fn proptest_to_compact_string_non_zero_u8(
352 #[strategy((1..=u8::MAX).prop_map(|x| unsafe { num::NonZeroU8::new_unchecked(x)} ))]
353 val: num::NonZeroU8,
354 ) {
355 let compact = val.to_compact_string();
356 prop_assert_eq!(compact.as_str(), val.to_string());
357 }
358
359 #[proptest]
360 #[cfg_attr(miri, ignore)]
361 fn proptest_to_compact_string_non_zero_u16(
362 #[strategy((1..=u16::MAX).prop_map(|x| unsafe { num::NonZeroU16::new_unchecked(x)} ))]
363 val: num::NonZeroU16,
364 ) {
365 let compact = val.to_compact_string();
366 prop_assert_eq!(compact.as_str(), val.to_string());
367 }
368
369 #[proptest]
370 #[cfg_attr(miri, ignore)]
371 fn proptest_to_compact_string_non_zero_u32(
372 #[strategy((1..=u32::MAX).prop_map(|x| unsafe { num::NonZeroU32::new_unchecked(x)} ))]
373 val: num::NonZeroU32,
374 ) {
375 let compact = val.to_compact_string();
376 prop_assert_eq!(compact.as_str(), val.to_string());
377 }
378
379 #[proptest]
380 #[cfg_attr(miri, ignore)]
381 fn proptest_to_compact_string_non_zero_u64(
382 #[strategy((1..=u64::MAX).prop_map(|x| unsafe { num::NonZeroU64::new_unchecked(x)} ))]
383 val: num::NonZeroU64,
384 ) {
385 let compact = val.to_compact_string();
386 prop_assert_eq!(compact.as_str(), val.to_string());
387 }
388
389 #[proptest]
390 #[cfg_attr(miri, ignore)]
391 fn proptest_to_compact_string_non_zero_u128(
392 #[strategy((1..=u128::MAX).prop_map(|x| unsafe { num::NonZeroU128::new_unchecked(x)} ))]
393 val: num::NonZeroU128,
394 ) {
395 let compact = val.to_compact_string();
396 prop_assert_eq!(compact.as_str(), val.to_string());
397 }
398
399 #[proptest]
400 #[cfg_attr(miri, ignore)]
401 fn proptest_to_compact_string_non_zero_usize(
402 #[strategy((1..=usize::MAX).prop_map(|x| unsafe { num::NonZeroUsize::new_unchecked(x)} ))]
403 val: num::NonZeroUsize,
404 ) {
405 let compact = val.to_compact_string();
406 prop_assert_eq!(compact.as_str(), val.to_string());
407 }
408
409 #[proptest]
410 #[cfg_attr(miri, ignore)]
411 fn proptest_to_compact_string_non_zero_i8(
412 #[strategy((1..=u8::MAX).prop_map(|x| unsafe { num::NonZeroI8::new_unchecked(x as i8)} ))]
413 val: num::NonZeroI8,
414 ) {
415 let compact = val.to_compact_string();
416 prop_assert_eq!(compact.as_str(), val.to_string());
417 }
418
419 #[proptest]
420 #[cfg_attr(miri, ignore)]
421 fn proptest_to_compact_string_non_zero_i16(
422 #[strategy((1..=u16::MAX).prop_map(|x| unsafe { num::NonZeroI16::new_unchecked(x as i16)} ))]
423 val: num::NonZeroI16,
424 ) {
425 let compact = val.to_compact_string();
426 prop_assert_eq!(compact.as_str(), val.to_string());
427 }
428
429 #[proptest]
430 #[cfg_attr(miri, ignore)]
431 fn proptest_to_compact_string_non_zero_i32(
432 #[strategy((1..=u32::MAX).prop_map(|x| unsafe { num::NonZeroI32::new_unchecked(x as i32)} ))]
433 val: num::NonZeroI32,
434 ) {
435 let compact = val.to_compact_string();
436 prop_assert_eq!(compact.as_str(), val.to_string());
437 }
438
439 #[proptest]
440 #[cfg_attr(miri, ignore)]
441 fn proptest_to_compact_string_non_zero_i64(
442 #[strategy((1..=u64::MAX).prop_map(|x| unsafe { num::NonZeroI64::new_unchecked(x as i64)} ))]
443 val: num::NonZeroI64,
444 ) {
445 let compact = val.to_compact_string();
446 prop_assert_eq!(compact.as_str(), val.to_string());
447 }
448
449 #[proptest]
450 #[cfg_attr(miri, ignore)]
451 fn proptest_to_compact_string_non_zero_i128(
452 #[strategy((1..=u128::MAX).prop_map(|x| unsafe { num::NonZeroI128::new_unchecked(x as i128)} ))]
453 val: num::NonZeroI128,
454 ) {
455 let compact = val.to_compact_string();
456 prop_assert_eq!(compact.as_str(), val.to_string());
457 }
458
459 #[proptest]
460 #[cfg_attr(miri, ignore)]
461 fn proptest_to_compact_string_non_zero_isize(
462 #[strategy((1..=usize::MAX).prop_map(|x| unsafe { num::NonZeroIsize::new_unchecked(x as isize)} ))]
463 val: num::NonZeroIsize,
464 ) {
465 let compact = val.to_compact_string();
466 prop_assert_eq!(compact.as_str(), val.to_string());
467 }
468}