zalgo_codec_common/zalgo_string/mod.rs
1//! Contains the implementation of [`ZalgoString`] as well as related iterators.
2//!
3//! A `ZalgoString` contains a grapheme cluster that was obtained from [`zalgo_encode`].
4//! It allows for iteration over its characters and bytes in both encoded and decoded form.
5//! It can be decoded in-place and the encoded information in other ZalgoStrings can be pushed
6//! onto it.
7
8mod iterators;
9
10use crate::{decode_byte_pair, fmt, zalgo_encode, EncodeError};
11use core::{ops::Index, slice::SliceIndex};
12pub use iterators::{DecodedBytes, DecodedChars};
13#[cfg(feature = "rkyv")]
14use rkyv::bytecheck::{
15 rancor::{fail, Fallible, Source},
16 CheckBytes, Verify,
17};
18
19use alloc::{borrow::Cow, string::String, vec::Vec};
20
21/// A [`String`] that has been encoded with [`zalgo_encode`].
22/// This struct can be decoded in-place and also allows iteration over its characters and bytes, both in
23/// decoded and encoded form.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[cfg_attr(feature = "serde", serde(try_from = "MaybeZalgoString"))]
27#[cfg_attr(
28 feature = "rkyv",
29 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive, CheckBytes)
30)]
31#[cfg_attr(feature = "rkyv", bytecheck(verify))]
32pub struct ZalgoString(String);
33
34#[cfg(feature = "rkyv")]
35unsafe impl<C> Verify<C> for ZalgoString
36where
37 C: Fallible + ?Sized,
38 C::Error: Source,
39{
40 #[inline]
41 fn verify(&self, _context: &mut C) -> Result<(), C::Error> {
42 if let Err(e) = crate::zalgo_decode(&self.0) {
43 fail!(e);
44 }
45 Ok(())
46 }
47}
48
49#[cfg(feature = "serde")]
50#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
51struct MaybeZalgoString(String);
52
53#[cfg(feature = "serde")]
54impl TryFrom<MaybeZalgoString> for ZalgoString {
55 type Error = crate::DecodeError;
56
57 fn try_from(MaybeZalgoString(encoded_string): MaybeZalgoString) -> Result<Self, Self::Error> {
58 if let Err(e) = crate::zalgo_decode(&encoded_string) {
59 Err(e)
60 } else {
61 Ok(ZalgoString(encoded_string))
62 }
63 }
64}
65
66/// Allocates a `String` that contains only the character "E" and no encoded content.
67impl Default for ZalgoString {
68 #[inline]
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl ZalgoString {
75 /// Creates a new empty [`ZalgoString`].
76 #[inline]
77 pub const fn new() -> Self {
78 Self(String::new())
79 }
80
81 /// Creates a new `ZalgoString` with at least the specified capacity.
82 ///
83 /// If you want the ZalgoString to have capacity for x encoded characters
84 /// you must reserve a capacity of 2x.
85 ///
86 /// # Example
87 ///
88 /// ```
89 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
90 ///
91 /// // Reserve capacity for two encoded characters
92 /// let mut zs = ZalgoString::with_capacity(2*2);
93 ///
94 /// // This ZalgoString would decode into an empty string
95 /// assert_eq!(zs.decoded_len(), 0);
96 ///
97 /// // This allocates,
98 /// let zs2 = ZalgoString::try_from("Hi")?;
99 ///
100 /// // but this does not reallocate `zs`
101 /// let cap = zs.capacity();
102 /// zs.push_zalgo_str(&zs2);
103 /// assert_eq!(zs.capacity(), cap);
104 ///
105 /// # Ok::<(), EncodeError>(())
106 /// ```
107 #[inline]
108 #[must_use = "this associated method return a new `ZalgoString` and does not modify the input"]
109 pub fn with_capacity(capacity: usize) -> Self {
110 Self(String::with_capacity(capacity))
111 }
112
113 // region: character access methods
114
115 /// Returns the *encoded* contents of `self` as a string slice.
116 ///
117 /// # Example
118 ///
119 /// Basic usage
120 /// ```
121 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
122 /// let zs = ZalgoString::try_from("Oh boy!")?;
123 /// assert_eq!(zs.as_str(), "̯͈̀͂͏͙́");
124 /// # Ok::<(), EncodeError>(())
125 /// ```
126 /// Note that `ZalgoString` implements [`PartialEq`] with common string types,
127 /// so the comparison in the above example could also be done directly
128 /// ```
129 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
130 /// # let zs = ZalgoString::try_from("Oh boy!")?;
131 /// assert_eq!(zs, "̯͈̀͂͏͙́");
132 /// # Ok::<(), EncodeError>(())
133 /// ```
134 #[inline]
135 #[must_use = "the method returns a reference and does not modify `self`"]
136 pub fn as_str(&self) -> &str {
137 &self.0
138 }
139
140 /// Returns a subslice of `self`.
141 ///
142 /// Same as [`str::get`].
143 ///
144 /// This is the non-panicking alternative to indexing the `ZalgoString`. Returns [`None`] whenever
145 /// the equivalent indexing operation would panic.
146 ///
147 /// # Example
148 ///
149 /// ```
150 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
151 /// let zs = ZalgoString::try_from("Zalgo")?;
152 /// assert_eq!(zs.get(0..2), Some("\u{33a}"));
153 ///
154 /// // indices not on UTF-8 sequence boundaries
155 /// assert!(zs.get(0..3).is_none());
156 ///
157 /// // out of bounds
158 /// assert!(zs.get(..42).is_none());
159 /// # Ok::<(), EncodeError>(())
160 /// ```
161 #[inline]
162 pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<str>>::Output>
163 where
164 I: SliceIndex<str>,
165 {
166 self.0.get(index)
167 }
168
169 /// Returns an unchecked subslice of `self`.
170 ///
171 /// This is the unchecked alternative to indexing a `ZalgoString`.
172 ///
173 /// # Safety
174 ///
175 /// This function has the same safety requirements as [`str::get_unchecked`]:
176 /// - The starting index must not exceed the ending index;
177 /// - Indexes must be within bounds of the original slice;
178 /// - Indexes must lie on UTF-8 sequence boundaries.
179 ///
180 /// # Example
181 ///
182 /// ```
183 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
184 /// let zs = ZalgoString::try_from("Zalgo")?;
185 /// unsafe {
186 /// assert_eq!(zs.get_unchecked(..2), "\u{33a}");
187 /// }
188 /// # Ok::<(), EncodeError>(())
189 /// ```
190 #[inline]
191 pub unsafe fn get_unchecked<I>(&self, index: I) -> &<I as SliceIndex<str>>::Output
192 where
193 I: SliceIndex<str>,
194 {
195 self.0.get_unchecked(index)
196 }
197
198 /// Returns an iterator over the encoded characters of the `ZalgoString`.
199 ///
200 /// # Example
201 ///
202 /// Iterate through the encoded [`char`]s:
203 /// ```
204 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
205 /// let zs = ZalgoString::try_from("42")?;
206 /// let mut chars = zs.chars();
207 /// assert_eq!(chars.next(), Some('\u{314}'));
208 /// # Ok::<(), EncodeError>(())
209 /// ```
210 #[inline]
211 pub fn chars(&self) -> core::str::Chars<'_> {
212 self.0.chars()
213 }
214
215 /// Returns an iterator over the encoded characters of the `ZalgoString` and their positions.
216 ///
217 /// # Example
218 ///
219 /// Combining characters lie deep in the dark depths of Unicode,
220 /// and may not match with your intuition of what a character is.
221 /// ```
222 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
223 /// let zs = ZalgoString::try_from("Zalgo")?;
224 /// let mut ci = zs.char_indices();
225 /// assert_eq!(ci.next(), Some((0,'\u{33a}')));
226 /// // Note the 2 here, the combining characters take up two bytes.
227 /// assert_eq!(ci.next(), Some((2, '\u{341}')));
228 /// // The final character begins at position 8
229 /// assert_eq!(ci.next_back(), Some((8, '\u{34f}')));
230 /// // even though the length in bytes is 10
231 /// assert_eq!(zs.len(), 10);
232 /// # Ok::<(), EncodeError>(())
233 /// ```
234 #[inline]
235 pub fn char_indices(&self) -> core::str::CharIndices<'_> {
236 self.0.char_indices()
237 }
238
239 /// Returns an iterator over the decoded characters of the `ZalgoString`.
240 ///
241 /// These characters are guaranteed to be valid ASCII.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
247 /// let zs = ZalgoString::try_from("Zlgoa")?;
248 /// let mut decoded_chars = zs.decoded_chars();
249 /// assert_eq!(decoded_chars.next(), Some('Z'));
250 /// assert_eq!(decoded_chars.next_back(), Some('a'));
251 /// assert_eq!(decoded_chars.next(), Some('l'));
252 /// assert_eq!(decoded_chars.next(), Some('g'));
253 /// assert_eq!(decoded_chars.next_back(), Some('o'));
254 /// assert_eq!(decoded_chars.next(), None);
255 /// assert_eq!(decoded_chars.next_back(), None);
256 /// # Ok::<(), EncodeError>(())
257 /// ```
258 #[inline]
259 pub fn decoded_chars(&self) -> DecodedChars<'_> {
260 DecodedChars::new(self)
261 }
262
263 /// Converts `self` into a `String`.
264 ///
265 /// This simply returns the underlying `String` without any cloning or decoding.
266 ///
267 /// # Example
268 ///
269 /// Basic usage
270 /// ```
271 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
272 /// let zs = ZalgoString::try_from("Zalgo\n He comes!")?;
273 /// assert_eq!(zs.into_string(), "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ");
274 /// # Ok::<(), EncodeError>(())
275 /// ```
276 #[inline]
277 #[must_use = "`self` will be dropped if the result is not used"]
278 pub fn into_string(self) -> String {
279 self.0
280 }
281
282 /// Decodes `self` into a `String` in-place.
283 ///
284 /// This method has no effect on the allocated capacity.
285 ///
286 /// # Example
287 ///
288 /// Basic usage
289 /// ```
290 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
291 /// let s = "Zalgo";
292 /// let zs = ZalgoString::try_from(s)?;
293 /// assert_eq!(s, zs.into_decoded_string());
294 /// # Ok::<(), EncodeError>(())
295 /// ```
296 #[must_use = "`self` will be dropped if the result is not used"]
297 pub fn into_decoded_string(self) -> String {
298 // Safety: we know that the starting string was encoded from valid ASCII to begin with
299 // so every decoded byte is a valid utf-8 character.
300 unsafe { String::from_utf8_unchecked(self.into_decoded_bytes()) }
301 }
302
303 // endregion: character access methods
304
305 // region: byte access methods
306
307 /// Returns the encoded contents of `self` as a byte slice.
308 ///
309 /// # Example
310 ///
311 /// Basic usage
312 /// ```
313 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
314 /// let zs = ZalgoString::try_from("Zalgo")?;
315 /// assert_eq!(&zs.as_bytes()[0..4], &[204, 186, 205, 129]);
316 /// # Ok::<(), EncodeError>(())
317 /// ```
318 #[inline]
319 #[must_use = "the method returns a reference and does not modify `self`"]
320 pub fn as_bytes(&self) -> &[u8] {
321 self.0.as_bytes()
322 }
323
324 /// Returns an iterator over the encoded bytes of the `ZalgoString`.
325 ///
326 /// # Example
327 ///
328 /// Basic usage
329 /// ```
330 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
331 /// let zs = ZalgoString::try_from("Bytes")?;
332 /// let mut bytes = zs.bytes();
333 /// assert_eq!(bytes.nth(5), Some(148));
334 /// # Ok::<(), EncodeError>(())
335 /// ```
336 #[inline]
337 pub fn bytes(&self) -> core::str::Bytes<'_> {
338 self.0.bytes()
339 }
340
341 /// Returns an iterator over the decoded bytes of the `ZalgoString`.
342 ///
343 /// These bytes are guaranteed to represent valid ASCII.
344 ///
345 /// # Example
346 ///
347 /// ```
348 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
349 /// let zs = ZalgoString::try_from("Zalgo")?;
350 /// let mut decoded_bytes = zs.decoded_bytes();
351 /// assert_eq!(decoded_bytes.next(), Some(90));
352 /// assert_eq!(decoded_bytes.next_back(), Some(111));
353 /// assert_eq!(decoded_bytes.collect::<Vec<u8>>(), vec![97, 108, 103]);
354 /// # Ok::<(), EncodeError>(())
355 /// ```
356 #[inline]
357 pub fn decoded_bytes(&self) -> DecodedBytes<'_> {
358 DecodedBytes::new(self)
359 }
360
361 /// Converts `self` into a byte vector.
362 ///
363 /// This simply returns the underlying buffer without any cloning or decoding.
364 ///
365 /// # Example
366 ///
367 /// Basic usage
368 /// ```
369 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
370 /// let zs = ZalgoString::try_from("Zalgo")?;
371 /// assert_eq!(zs.into_bytes(), vec![204, 186, 205, 129, 205, 140, 205, 135, 205, 143]);
372 /// # Ok::<(), EncodeError>(())
373 /// ```
374 #[inline]
375 #[must_use = "`self` will be dropped if the result is not used"]
376 pub fn into_bytes(self) -> Vec<u8> {
377 self.0.into_bytes()
378 }
379
380 /// Decodes `self` into a byte vector in-place.
381 ///
382 /// This method has no effect on the allocated capacity.
383 ///
384 /// # Example
385 ///
386 /// Basic usage
387 /// ```
388 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
389 /// let zs = ZalgoString::try_from("Zalgo")?;
390 /// assert_eq!(b"Zalgo".to_vec(), zs.into_decoded_bytes());
391 /// # Ok::<(), EncodeError>(())
392 /// ```
393 #[must_use = "`self` will be dropped if the result is not used"]
394 pub fn into_decoded_bytes(self) -> Vec<u8> {
395 let mut w = 0;
396 let mut bytes = self.into_bytes();
397 for r in (0..bytes.len()).step_by(2) {
398 bytes[w] = decode_byte_pair(bytes[r], bytes[r + 1]);
399 w += 1;
400 }
401 bytes.truncate(w);
402 bytes
403 }
404
405 // endregion: byte access methods
406
407 // region: metadata methods
408
409 /// Returns the length of `self` in bytes.
410 ///
411 /// This length is twice the length of the original `String`.
412 ///
413 /// # Example
414 ///
415 /// Basic usage
416 /// ```
417 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
418 /// let zs = ZalgoString::try_from("Z")?;
419 /// assert_eq!(zs.len(), 2);
420 /// # Ok::<(), EncodeError>(())
421 /// ```
422 #[inline]
423 #[must_use = "the method returns a new value and does not modify `self`"]
424 pub fn len(&self) -> usize {
425 self.0.len()
426 }
427
428 /// Returns the capacity of the underlying encoded string in bytes.
429 ///
430 /// The `ZalgoString` is preallocated to the needed capacity of twice the length
431 /// of the original unencoded `String`.
432 /// However, this size is not guaranteed since the allocator can choose to allocate more space.
433 #[inline]
434 #[must_use = "the method returns a new value and does not modify `self`"]
435 pub fn capacity(&self) -> usize {
436 self.0.capacity()
437 }
438
439 /// Returns the length of the `ZalgoString` in bytes if it were to be decoded.
440 ///
441 /// This is computed without any decoding.
442 ///
443 /// # Example
444 ///
445 /// Basic usage
446 /// ```
447 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
448 /// let s = "Zalgo, He comes!";
449 /// let zs = ZalgoString::try_from(s)?;
450 /// assert_eq!(s.len(), zs.decoded_len());
451 /// # Ok::<(), EncodeError>(())
452 /// ```
453 #[inline]
454 #[must_use = "the method returns a new value and does not modify `self`"]
455 pub fn decoded_len(&self) -> usize {
456 self.len() / 2
457 }
458
459 /// Returns whether the `ZalgoString` is empty.
460 ///
461 /// # Example
462 ///
463 /// Basic usage
464 /// ```
465 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
466 /// let zs = ZalgoString::new();
467 /// assert!(zs.is_empty());
468 ///
469 /// let zs = ZalgoString::try_from("Blargh")?;
470 /// assert!(!zs.is_empty());
471 /// # Ok::<(), EncodeError>(())
472 /// ```
473 #[inline]
474 #[must_use = "the method returns a new value and does not modify `self`"]
475 pub fn is_empty(&self) -> bool {
476 self.len() == 0
477 }
478
479 // endregion: metadata methods
480
481 /// Appends the combining characters of a different `ZalgoString` to the end of `self`.
482 ///
483 /// # Example
484 ///
485 /// ```
486 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
487 /// let (s1, s2) = ("Zalgo", ", He comes!");
488 ///
489 /// let mut zs1 = ZalgoString::try_from(s1)?;
490 /// let zs2 = ZalgoString::try_from(s2)?;
491 ///
492 /// zs1.push_zalgo_str(&zs2);
493 ///
494 /// assert_eq!(zs1.into_decoded_string(), format!("{s1}{s2}"));
495 /// # Ok::<(), EncodeError>(())
496 /// ```
497 #[inline]
498 pub fn push_zalgo_str(&mut self, zalgo_string: &Self) {
499 self.0.push_str(zalgo_string.as_str());
500 }
501
502 /// Encodes the given string and pushes it onto `self`.
503 ///
504 /// This method encodes the input string into an intermediate allocation and then appends
505 /// the combining characters of the result to the end of `self`. The append step can
506 /// also reallocate if the capacity is not large enough.
507 ///
508 /// See [`push_zalgo_str`](ZalgoString::push_zalgo_str) for a method that does not hide the
509 /// intermediate allocation.
510 ///
511 /// # Errors
512 ///
513 /// Returns an error if the given string contains a character that's not a printable ASCII
514 /// or newline character.
515 ///
516 /// # Example
517 ///
518 /// ```
519 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
520 /// let (s1, s2) = ("Zalgo", ", He comes!");
521 ///
522 /// let mut zs = ZalgoString::try_from(s1)?;
523 ///
524 /// zs.encode_and_push_str(s2)?;
525 ///
526 /// assert_eq!(zs.into_decoded_string(), format!("{s1}{s2}"));
527 /// # Ok::<(), EncodeError>(())
528 /// ```
529 pub fn encode_and_push_str(&mut self, string: &str) -> Result<(), EncodeError> {
530 self.push_zalgo_str(&ZalgoString::try_from(string)?);
531 Ok(())
532 }
533
534 // region: capacity manipulation methods
535
536 /// Reserves capacity for at least `additional` bytes more than the current length.
537 ///
538 /// Same as [`String::reserve`].
539 ///
540 /// The allocator may reserve more space to speculatively avoid frequent allocations.
541 /// After calling reserve, capacity will be greater than or equal to `self.len() + additional`.
542 ///
543 /// Does nothing if the capacity is already sufficient.
544 ///
545 /// Keep in mind that an encoded ASCII character takes up two bytes,
546 /// which means that the total length in bytes is always an even number.
547 ///
548 /// # Example
549 ///
550 /// ```
551 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
552 /// let mut zs = ZalgoString::try_from("Zalgo")?;
553 /// let c = zs.capacity();
554 /// zs.reserve(4);
555 /// assert!(zs.capacity() >= c + 4);
556 /// # Ok::<(), EncodeError>(())
557 /// ```
558 #[inline]
559 pub fn reserve(&mut self, additional: usize) {
560 self.0.reserve(additional)
561 }
562
563 /// Reserves capacity for exactly `additional` bytes more than the current length.
564 ///
565 /// Same as [`String::reserve_exact`].
566 ///
567 /// Unlike [`reserve`](ZalgoString::reserve), this will not deliberately over-allocate
568 /// to speculatively avoid frequent allocations.
569 /// After calling `reserve_exact`, capacity will be greater than or equal to `self.len() + additional`.
570 ///
571 /// Does nothing if the capacity is already sufficient.
572 ///
573 /// Keep in mind that an encoded ASCII character takes up two bytes,
574 /// which means that the total length in bytes is always an odd number.
575 ///
576 /// # Example
577 ///
578 /// ```
579 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
580 /// let mut zs = ZalgoString::try_from("Zalgo")?;
581 /// let c = zs.capacity();
582 /// zs.reserve_exact(4);
583 /// assert!(zs.capacity() >= c + 4);
584 /// # Ok::<(), EncodeError>(())
585 /// ```
586 #[inline]
587 pub fn reserve_exact(&mut self, additional: usize) {
588 self.0.reserve_exact(additional)
589 }
590
591 // endregion: capacity manipulation methods
592
593 // region: length manipulation methods
594
595 /// Shortens the `ZalgoString` to the specified length.
596 ///
597 /// A `ZalgoString` always takes up an even number of bytes as all encoded characters take up two bytes.
598 ///
599 /// If `new_len` is larger than its current length, this has no effect.
600 ///
601 /// This method has no effect of the allocated capacity.
602 ///
603 /// # Panics
604 ///
605 /// Panics if `new_len` is odd.
606 ///
607 /// # Examples
608 ///
609 /// ```
610 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
611 /// let mut zs = ZalgoString::try_from("Zalgo")?;
612 /// zs.truncate(4);
613 /// assert_eq!(zs, "\u{33a}\u{341}");
614 /// assert_eq!(zs.into_decoded_string(), "Za");
615 /// # Ok::<(), EncodeError>(())
616 /// ```
617 /// Panics if `new_len` is odd:
618 /// ```should_panic
619 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
620 /// let mut zs = ZalgoString::try_from("Zalgo")?;
621 /// zs.truncate(1);
622 /// # Ok::<(), EncodeError>(())
623 /// ```
624 #[inline]
625 pub fn truncate(&mut self, new_len: usize) {
626 if new_len <= self.len() {
627 assert_eq!(new_len % 2, 0, "the new length must be even");
628 self.0.truncate(new_len)
629 }
630 }
631
632 /// Clears this `ZalgoString`, removing all contents.
633 ///
634 /// This means the ZalgoString will have a length of zero, but it does not affect its capacity.
635 ///
636 /// # Example
637 ///
638 /// ```
639 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
640 /// let mut zs = ZalgoString::try_from("Zalgo")?;
641 /// let cap = zs.capacity();
642 ///
643 /// zs.clear();
644 ///
645 /// assert!(zs.is_empty());
646 /// assert_eq!(zs.capacity(), cap);
647 /// # Ok::<(), EncodeError>(())
648 /// ```
649 pub fn clear(&mut self) {
650 self.0.clear()
651 }
652
653 // endregion: length manipulation methods
654}
655
656impl TryFrom<&str> for ZalgoString {
657 type Error = EncodeError;
658
659 /// Encodes the given string slice and stores the result in a new allocation.
660 ///
661 /// # Errors
662 ///
663 /// Returns an error if the input string contains bytes that don’t correspond to printable ASCII characters or newlines.
664 ///
665 /// # Examples
666 ///
667 /// ```
668 /// # use zalgo_codec_common::{EncodeError, ZalgoString};
669 ///
670 /// assert_eq!(ZalgoString::try_from("Zalgo")?, "̺͇́͌͏");
671 ///
672 /// # Ok::<(), EncodeError>(())
673 /// ```
674 ///
675 /// Can only encode printable ASCII and newlines:
676 ///
677 /// ```
678 /// # use zalgo_codec_common::ZalgoString;
679 /// assert!(ZalgoString::try_from("❤️").is_err());
680 /// assert!(ZalgoString::try_from("\r").is_err());
681 /// ```
682 fn try_from(s: &str) -> Result<Self, Self::Error> {
683 zalgo_encode(s).map(Self)
684 }
685}
686
687// region: Addition impls
688
689/// Implements the `+` operator for concaternating two `ZalgoString`s.
690/// Memorywise it works the same as the `Add` implementation for the normal
691/// `String` type: it consumes the lefthand side, extends its buffer, and
692/// copies the combining characters of the right hand side into it.
693impl core::ops::Add<&ZalgoString> for ZalgoString {
694 type Output = ZalgoString;
695 #[inline]
696 fn add(mut self, rhs: &Self) -> Self::Output {
697 self.push_zalgo_str(rhs);
698 self
699 }
700}
701
702/// Implements the `+=` operator for appending to a `ZalgoString`.
703///
704/// This just calls [`push_zalgo_str`](ZalgoString::push_zalgo_str).
705impl core::ops::AddAssign<&ZalgoString> for ZalgoString {
706 #[inline]
707 fn add_assign(&mut self, rhs: &ZalgoString) {
708 self.push_zalgo_str(rhs);
709 }
710}
711
712// endregion: Addition impls
713
714// region: PartialEq impls
715
716macro_rules! impl_partial_eq {
717 ($($rhs:ty),+) => {
718 $(
719 impl PartialEq<$rhs> for ZalgoString {
720 #[inline]
721 fn eq(&self, other: &$rhs) -> bool {
722 &self.0 == other
723 }
724 }
725
726 impl PartialEq<ZalgoString> for $rhs {
727 #[inline]
728 fn eq(&self, other: &ZalgoString) -> bool {
729 self == &other.0
730 }
731 }
732 )+
733 };
734}
735impl_partial_eq! {String, &str, str, Cow<'_, str>}
736
737// endregion: PartialEq impls
738
739/// Displays the encoded form of the `ZalgoString`.
740impl fmt::Display for ZalgoString {
741 #[inline]
742 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
743 write!(f, "{}", self.0)
744 }
745}
746
747impl<I: SliceIndex<str>> Index<I> for ZalgoString {
748 type Output = I::Output;
749 #[inline]
750 fn index(&self, index: I) -> &Self::Output {
751 self.0.as_str().index(index)
752 }
753}
754
755#[cfg(test)]
756mod test {
757 use super::*;
758 use alloc::{
759 format,
760 string::{String, ToString},
761 };
762
763 #[test]
764 fn check_into_decoded_string() {
765 let s = "Zalgo\n He comes!";
766 let zs: ZalgoString = ZalgoString::try_from(s).unwrap();
767 assert_eq!(zs.into_decoded_string(), s);
768
769 let zs = ZalgoString::new();
770 assert_eq!(zs.into_decoded_string(), String::new());
771 }
772
773 #[test]
774 fn check_string_from_zalgo_string() {
775 let zs = ZalgoString::try_from("Zalgo\n He comes!").unwrap();
776 assert_eq!(zs.to_string(), "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ");
777 assert_eq!(zs.into_string(), "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ");
778
779 let zs = ZalgoString::new();
780 assert_eq!(zs.into_string(), String::new());
781 }
782
783 #[test]
784 fn check_partial_eq() {
785 let enc = "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ";
786 let zs = ZalgoString::try_from("Zalgo\n He comes!").unwrap();
787 assert_eq!(zs, enc);
788 assert_eq!(zs, String::from(enc));
789 assert_eq!(zs, Cow::from(enc));
790 assert_eq!(String::from(enc), zs);
791 assert_eq!(Cow::from(enc), zs);
792 }
793
794 #[test]
795 fn check_push_str() {
796 let s1 = "Zalgo";
797 let s2 = ", He comes";
798 let mut zs = ZalgoString::try_from(s1).unwrap();
799 let zs2 = ZalgoString::try_from(s2).unwrap();
800 zs.push_zalgo_str(&zs2);
801 assert_eq!(zs.clone().into_decoded_string(), format!("{s1}{s2}"));
802 zs += &zs2;
803 assert_eq!(
804 (zs + &zs2).into_decoded_string(),
805 format!("{s1}{s2}{s2}{s2}")
806 );
807 }
808
809 #[test]
810 fn check_as_str() {
811 assert_eq!(
812 ZalgoString::try_from("Hi").unwrap().as_str(),
813 "\u{328}\u{349}"
814 );
815 assert_eq!(ZalgoString::try_from("").unwrap().as_str(), "");
816 }
817
818 #[test]
819 fn check_decoded_chars() {
820 let zs = ZalgoString::try_from("Zalgo").unwrap();
821 assert_eq!("oglaZ", zs.decoded_chars().rev().collect::<String>());
822 }
823
824 #[test]
825 fn test_reserve() {
826 let mut zs = ZalgoString::try_from("Zalgo").unwrap();
827 zs.reserve(5);
828 assert!(zs.capacity() >= 10 + 5);
829 let c = zs.capacity();
830 zs.reserve(1);
831 assert_eq!(zs.capacity(), c);
832 }
833
834 #[test]
835 fn test_reserve_exact() {
836 let mut zs = ZalgoString::try_from("Zalgo").unwrap();
837 zs.reserve_exact(5);
838 assert_eq!(zs.capacity(), 10 + 5);
839 let c = zs.capacity();
840 zs.reserve_exact(1);
841 assert_eq!(zs.capacity(), c);
842 }
843
844 #[test]
845 fn test_truncate() {
846 let mut zs = ZalgoString::try_from("Zalgo").unwrap();
847 zs.truncate(100);
848 assert_eq!(zs, "\u{33a}\u{341}\u{34c}\u{347}\u{34f}");
849 zs.truncate(4);
850 assert_eq!(zs, "\u{33a}\u{341}");
851 assert_eq!(zs.into_decoded_string(), "Za");
852 }
853
854 #[test]
855 #[should_panic]
856 fn test_truncate_panic() {
857 let mut zs = ZalgoString::try_from("Zalgo").unwrap();
858 zs.truncate(1)
859 }
860
861 #[test]
862 fn test_default() {
863 assert_eq!(ZalgoString::try_from("").unwrap(), ZalgoString::default());
864 }
865
866 #[test]
867 fn test_with_capacity() {
868 let mut zs = ZalgoString::with_capacity(10.try_into().unwrap());
869 assert_eq!(zs.capacity(), 10);
870 zs.encode_and_push_str("Hi!").unwrap();
871 assert_eq!(zs.capacity(), 10);
872 zs.encode_and_push_str("I am a dinosaur!").unwrap();
873 assert!(zs.capacity() > 10);
874 }
875
876 #[test]
877 fn test_as_str() {
878 fn test_fn(_: &str) {}
879 let s = "Zalgo";
880 let zs = ZalgoString::try_from(s).unwrap();
881 let encd = zalgo_encode(s).unwrap();
882 test_fn(zs.as_str());
883 assert_eq!(zs.as_str(), encd);
884 }
885
886 #[test]
887 fn test_chars() {
888 let s = "Zalgo";
889 let zs = ZalgoString::try_from(s).unwrap();
890 let encd = zalgo_encode(s).unwrap();
891 for (a, b) in zs.chars().zip(encd.chars()) {
892 assert_eq!(a, b);
893 }
894 assert_eq!(zs.chars().nth(1), Some('\u{341}'));
895 }
896
897 #[test]
898 fn test_char_indices() {
899 let s = "Zalgo";
900 let zs = ZalgoString::try_from(s).unwrap();
901 let encd = zalgo_encode(s).unwrap();
902 for (a, b) in zs.char_indices().zip(encd.char_indices()) {
903 assert_eq!(a, b);
904 }
905 assert_eq!(zs.char_indices().nth(1), Some((2, '\u{341}')));
906 }
907
908 #[test]
909 fn test_as_bytes() {
910 let zs = ZalgoString::try_from("Zalgo").unwrap();
911 assert_eq!(
912 zs.as_bytes(),
913 &[204, 186, 205, 129, 205, 140, 205, 135, 205, 143]
914 );
915 }
916
917 #[test]
918 fn test_bytes() {
919 let zs = ZalgoString::try_from("Zalgo").unwrap();
920 assert_eq!(zs.bytes().next(), Some(204));
921 assert_eq!(zs.bytes().nth(2), Some(205));
922 }
923
924 #[test]
925 fn test_is_empty() {
926 let zs = ZalgoString::try_from("Zalgo").unwrap();
927 assert!(!zs.is_empty());
928 assert!(ZalgoString::default().is_empty());
929 }
930
931 #[test]
932 fn test_encode_and_push_str() {
933 let mut zs = ZalgoString::default();
934 assert!(zs.encode_and_push_str("Zalgo").is_ok());
935 assert!(zs.encode_and_push_str("Å").is_err());
936 assert_eq!(zs.into_decoded_string(), "Zalgo");
937 }
938
939 #[test]
940 fn test_clear() {
941 let mut zs = ZalgoString::try_from("Zalgo").unwrap();
942 let c = zs.capacity();
943 zs.clear();
944 assert_eq!(zs.capacity(), c);
945 assert_eq!(zs.len(), 0);
946 assert_eq!(zs.decoded_len(), 0);
947 assert!(zs.is_empty());
948 assert!(zs.into_decoded_string().is_empty());
949 }
950
951 #[test]
952 fn test_get() {
953 let zs = ZalgoString::try_from("Zalgo").unwrap();
954 assert_eq!(zs.get(0..2), Some("\u{33a}"));
955 assert!(zs.get(0..1).is_none());
956 assert!(zs.get(0..42).is_none());
957 }
958
959 #[test]
960 fn test_get_unchecked() {
961 let zs = ZalgoString::try_from("Zalgo").unwrap();
962 unsafe {
963 assert_eq!(zs.get_unchecked(..2), "\u{33a}");
964 }
965 }
966
967 #[test]
968 fn test_indexing() {
969 let zs = ZalgoString::try_from("Zalgo").unwrap();
970 assert_eq!(&zs[0..2], "\u{33a}");
971 assert_eq!(&zs[..2], "\u{33a}");
972 assert_eq!(&zs[0..=1], "\u{33a}");
973 assert_eq!(&zs[..=1], "\u{33a}");
974 assert_eq!(zs[..], zs);
975 }
976
977 #[test]
978 #[should_panic]
979 fn test_index_panic() {
980 let zs = ZalgoString::try_from("Zalgo").unwrap();
981 let _a = &zs[0..3];
982 }
983
984 #[test]
985 fn test_decoded_bytes() {
986 let zs = ZalgoString::try_from("Zalgo").unwrap();
987 assert_eq!(zs.decoded_bytes().next(), Some(b'Z'));
988 assert_eq!(zs.decoded_bytes().nth(2), Some(b'l'));
989 assert_eq!(zs.decoded_bytes().last(), Some(b'o'));
990 let mut dcb = zs.decoded_bytes();
991 assert_eq!(dcb.next(), Some(b'Z'));
992 let dcb2 = dcb.clone();
993 assert_eq!(dcb.count(), 4);
994 assert_eq!(dcb2.last(), Some(b'o'));
995 }
996
997 #[test]
998 fn test_decoded_chars() {
999 let zs = ZalgoString::try_from("Zalgo").unwrap();
1000 assert_eq!(zs.decoded_chars().next(), Some('Z'));
1001 assert_eq!(zs.decoded_chars().nth(2), Some('l'));
1002 assert_eq!(zs.decoded_chars().last(), Some('o'));
1003 let mut dcc = zs.decoded_chars();
1004 assert_eq!(dcc.next(), Some('Z'));
1005 let dcc2 = dcc.clone();
1006 assert_eq!(dcc.count(), 4);
1007 assert_eq!(dcc2.last(), Some('o'));
1008 }
1009
1010 #[test]
1011 fn test_into_string() {
1012 let zs = ZalgoString::try_from("Hi").unwrap();
1013 assert_eq!(zs.into_string(), "\u{328}\u{349}");
1014 let zs = ZalgoString::try_from("").unwrap();
1015 assert_eq!(zs.into_string(), "");
1016 }
1017
1018 #[cfg(feature = "serde")]
1019 #[test]
1020 fn serde_deserialize_zalgo_string() {
1021 use serde_json::from_str;
1022 let s = "Zalgo";
1023 let zs = ZalgoString::try_from(s).unwrap();
1024 let json = format!(r#""{}""#, zs);
1025 let deserialized: ZalgoString = from_str(&json).unwrap();
1026 assert_eq!(deserialized, zs);
1027 assert!(from_str::<ZalgoString>("Horse").is_err());
1028 }
1029}