Skip to main content

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}