1use crate::base::charstr::CharStr;
6#[cfg(feature = "serde")]
7use crate::base::charstr::DeserializeCharStrSeed;
8use crate::base::cmp::CanonicalOrd;
9use crate::base::iana::Rtype;
10use crate::base::rdata::{
11 ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
12};
13use crate::base::scan::Scanner;
14#[cfg(feature = "serde")]
15use crate::base::scan::Symbol;
16use crate::base::wire::{Composer, FormError, ParseError};
17use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
18#[cfg(feature = "bytes")]
19use bytes::BytesMut;
20use core::cmp::Ordering;
21use core::convert::{Infallible, TryFrom};
22use core::{fmt, hash, mem, str};
23use octseq::builder::{
24 infallible, EmptyBuilder, FreezeBuilder, FromBuilder, OctetsBuilder,
25 ShortBuf,
26};
27use octseq::octets::{Octets, OctetsFrom, OctetsInto};
28use octseq::parse::Parser;
29#[cfg(feature = "serde")]
30use octseq::serde::{DeserializeOctets, SerializeOctets};
31
32#[derive(Clone)]
84#[repr(transparent)]
85pub struct Txt<Octs: ?Sized>(Octs);
86
87impl Txt<()> {
88 pub(crate) const RTYPE: Rtype = Rtype::TXT;
90}
91
92impl<Octs: FromBuilder> Txt<Octs> {
93 pub fn build_from_slice(text: &[u8]) -> Result<Self, TxtAppendError>
102 where
103 <Octs as FromBuilder>::Builder:
104 EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
105 {
106 let mut builder = TxtBuilder::<Octs::Builder>::new();
107 builder.append_slice(text)?;
108 builder.finish()
109 }
110}
111
112impl<Octs> Txt<Octs> {
113 pub fn from_octets(octets: Octs) -> Result<Self, TxtError>
123 where
124 Octs: AsRef<[u8]>,
125 {
126 Txt::check_slice(octets.as_ref())?;
127 Ok(unsafe { Txt::from_octets_unchecked(octets) })
128 }
129
130 unsafe fn from_octets_unchecked(octets: Octs) -> Self {
137 Txt(octets)
138 }
139}
140
141impl Txt<[u8]> {
142 pub fn from_slice(slice: &[u8]) -> Result<&Self, TxtError> {
147 Txt::check_slice(slice)?;
148 Ok(unsafe { Txt::from_slice_unchecked(slice) })
149 }
150
151 unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
158 mem::transmute(slice)
160 }
161
162 fn check_slice(mut slice: &[u8]) -> Result<(), TxtError> {
164 if slice.is_empty() {
165 return Err(TxtError(TxtErrorInner::Empty));
166 }
167 LongRecordData::check_len(slice.len())?;
168 while let Some(&len) = slice.first() {
169 let len = usize::from(len);
170 if slice.len() <= len {
171 return Err(TxtError(TxtErrorInner::ShortInput));
172 }
173 slice = &slice[len + 1..];
174 }
175 Ok(())
176 }
177}
178
179impl<Octs> Txt<Octs> {
180 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
182 parser: &mut Parser<'a, Src>,
183 ) -> Result<Self, ParseError>
184 where
185 Octs: AsRef<[u8]>,
186 {
187 let len = parser.remaining();
188 let text = parser.parse_octets(len)?;
189 let mut tmp = Parser::from_ref(text.as_ref());
190 while tmp.remaining() != 0 {
191 CharStr::skip(&mut tmp)?
192 }
193 Ok(Txt(text))
194 }
195
196 pub fn scan<S: Scanner<Octets = Octs>>(
198 scanner: &mut S,
199 ) -> Result<Self, S::Error> {
200 scanner.scan_charstr_entry().map(Txt)
201 }
202}
203
204impl<Octs: AsRef<[u8]> + ?Sized> Txt<Octs> {
205 pub fn iter(&self) -> TxtIter<'_> {
209 TxtIter(self.iter_charstrs())
210 }
211
212 pub fn iter_charstrs(&self) -> TxtCharStrIter<'_> {
216 TxtCharStrIter(Parser::from_ref(self.0.as_ref()))
217 }
218
219 pub fn as_flat_slice(&self) -> Option<&[u8]> {
221 if usize::from(self.0.as_ref()[0]) == self.0.as_ref().len() - 1 {
222 Some(&self.0.as_ref()[1..])
223 } else {
224 None
225 }
226 }
227
228 #[allow(clippy::len_without_is_empty)]
237 pub fn len(&self) -> usize {
238 self.0.as_ref().len()
239 }
240
241 pub fn try_text<T: FromBuilder>(
250 &self,
251 ) -> Result<T, <<T as FromBuilder>::Builder as OctetsBuilder>::AppendError>
252 where
253 <T as FromBuilder>::Builder: EmptyBuilder,
254 {
255 let mut res = T::Builder::with_capacity(self.len());
258 for item in self.iter() {
259 res.append_slice(item)?;
260 }
261 Ok(res.freeze())
262 }
263
264 pub fn text<T: FromBuilder>(&self) -> T
274 where
275 <T as FromBuilder>::Builder: EmptyBuilder,
276 <<T as FromBuilder>::Builder as OctetsBuilder>::AppendError:
277 Into<Infallible>,
278 {
279 infallible(self.try_text())
280 }
281}
282
283impl<SrcOcts> Txt<SrcOcts> {
284 pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<SrcOcts>>(
288 self,
289 ) -> Result<Txt<Target>, Target::Error> {
290 Ok(Txt(self.0.try_octets_into()?))
291 }
292
293 pub(in crate::rdata) fn flatten<Octs: OctetsFrom<SrcOcts>>(
297 self,
298 ) -> Result<Txt<Octs>, Octs::Error> {
299 self.convert_octets()
300 }
301}
302
303impl<Octs, SrcOcts> OctetsFrom<Txt<SrcOcts>> for Txt<Octs>
306where
307 Octs: OctetsFrom<SrcOcts>,
308{
309 type Error = Octs::Error;
310
311 fn try_octets_from(source: Txt<SrcOcts>) -> Result<Self, Self::Error> {
312 Octs::try_octets_from(source.0).map(Self)
313 }
314}
315
316impl<'a, Octs: AsRef<[u8]>> IntoIterator for &'a Txt<Octs> {
319 type Item = &'a [u8];
320 type IntoIter = TxtIter<'a>;
321
322 fn into_iter(self) -> Self::IntoIter {
323 self.iter()
324 }
325}
326
327impl<Octs, Other> PartialEq<Txt<Other>> for Txt<Octs>
330where
331 Octs: AsRef<[u8]>,
332 Other: AsRef<[u8]>,
333{
334 fn eq(&self, other: &Txt<Other>) -> bool {
335 self.0.as_ref().eq(other.0.as_ref())
336 }
337}
338
339impl<Octs: AsRef<[u8]>> Eq for Txt<Octs> {}
340
341impl<Octs, Other> PartialOrd<Txt<Other>> for Txt<Octs>
344where
345 Octs: AsRef<[u8]>,
346 Other: AsRef<[u8]>,
347{
348 fn partial_cmp(&self, other: &Txt<Other>) -> Option<Ordering> {
349 self.0.as_ref().partial_cmp(other.0.as_ref())
350 }
351}
352
353impl<Octs, Other> CanonicalOrd<Txt<Other>> for Txt<Octs>
354where
355 Octs: AsRef<[u8]>,
356 Other: AsRef<[u8]>,
357{
358 fn canonical_cmp(&self, other: &Txt<Other>) -> Ordering {
359 self.0.as_ref().cmp(other.0.as_ref())
360 }
361}
362
363impl<Octs: AsRef<[u8]>> Ord for Txt<Octs> {
364 fn cmp(&self, other: &Self) -> Ordering {
365 self.0.as_ref().cmp(other.0.as_ref())
366 }
367}
368
369impl<Octs: AsRef<[u8]>> hash::Hash for Txt<Octs> {
372 fn hash<H: hash::Hasher>(&self, state: &mut H) {
373 self.0.as_ref().hash(state)
374 }
375}
376
377impl<Octs> RecordData for Txt<Octs> {
380 fn rtype(&self) -> Rtype {
381 Txt::RTYPE
382 }
383}
384
385impl<'a, Octs> ParseRecordData<'a, Octs> for Txt<Octs::Range<'a>>
386where
387 Octs: Octets + ?Sized,
388{
389 fn parse_rdata(
390 rtype: Rtype,
391 parser: &mut Parser<'a, Octs>,
392 ) -> Result<Option<Self>, ParseError> {
393 if rtype == Txt::RTYPE {
394 Self::parse(parser).map(Some)
395 } else {
396 Ok(None)
397 }
398 }
399}
400
401impl<Octs: AsRef<[u8]>> ComposeRecordData for Txt<Octs> {
402 fn rdlen(&self, _compress: bool) -> Option<u16> {
403 Some(u16::try_from(self.0.as_ref().len()).expect("long TXT rdata"))
404 }
405
406 fn compose_rdata<Target: Composer + ?Sized>(
407 &self,
408 target: &mut Target,
409 ) -> Result<(), Target::AppendError> {
410 target.append_slice(self.0.as_ref())
411 }
412
413 fn compose_canonical_rdata<Target: Composer + ?Sized>(
414 &self,
415 target: &mut Target,
416 ) -> Result<(), Target::AppendError> {
417 self.compose_rdata(target)
418 }
419}
420
421impl<Octs: AsRef<[u8]>> fmt::Display for Txt<Octs> {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 let mut first = true;
426 for slice in self.iter_charstrs() {
427 if !first {
428 f.write_str(" ")?;
429 } else {
430 first = false;
431 }
432 write!(f, "{}", slice.display_quoted())?;
433 }
434 Ok(())
435 }
436}
437
438impl<Octs: AsRef<[u8]>> fmt::Debug for Txt<Octs> {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 f.write_str("Txt(")?;
443 fmt::Display::fmt(self, f)?;
444 f.write_str(")")
445 }
446}
447
448impl<Octs> ZonefileFmt for Txt<Octs>
451where
452 Octs: AsRef<[u8]>,
453{
454 fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
455 p.block(|p| {
456 for slice in self.iter_charstrs() {
457 p.write_token(slice.display_quoted())?;
458 }
459 Ok(())
460 })
461 }
462}
463
464#[cfg(feature = "serde")]
467impl<Octs> serde::Serialize for Txt<Octs>
468where
469 Octs: AsRef<[u8]> + SerializeOctets,
470{
471 fn serialize<S: serde::Serializer>(
472 &self,
473 serializer: S,
474 ) -> Result<S::Ok, S::Error> {
475 use serde::ser::SerializeSeq;
476
477 struct TxtSeq<'a, Octs>(&'a Txt<Octs>);
478
479 impl<Octs> serde::Serialize for TxtSeq<'_, Octs>
480 where
481 Octs: AsRef<[u8]> + SerializeOctets,
482 {
483 fn serialize<S: serde::Serializer>(
484 &self,
485 serializer: S,
486 ) -> Result<S::Ok, S::Error> {
487 let mut serializer = serializer.serialize_seq(None)?;
488 for item in self.0.iter_charstrs() {
489 serializer.serialize_element(item)?;
490 }
491 serializer.end()
492 }
493 }
494
495 if serializer.is_human_readable() {
496 serializer.serialize_newtype_struct("Txt", &TxtSeq(self))
497 } else {
498 serializer.serialize_newtype_struct(
499 "Txt",
500 &self.0.as_serialized_octets(),
501 )
502 }
503 }
504}
505
506#[cfg(feature = "serde")]
507impl<'de, Octs> serde::Deserialize<'de> for Txt<Octs>
508where
509 Octs: FromBuilder + DeserializeOctets<'de>,
510 <Octs as FromBuilder>::Builder: EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
511{
512 fn deserialize<D: serde::Deserializer<'de>>(
513 deserializer: D,
514 ) -> Result<Self, D::Error> {
515 use core::marker::PhantomData;
516
517 struct NewtypeVisitor<T>(PhantomData<T>);
518
519 impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
520 where
521 Octs: FromBuilder + DeserializeOctets<'de>,
522 <Octs as FromBuilder>::Builder:
523 OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
524 {
525 type Value = Txt<Octs>;
526
527 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 f.write_str("TXT record data")
529 }
530
531 fn visit_newtype_struct<D: serde::Deserializer<'de>>(
532 self,
533 deserializer: D,
534 ) -> Result<Self::Value, D::Error> {
535 if deserializer.is_human_readable() {
536 deserializer.deserialize_seq(ReadableVisitor(PhantomData))
537 } else {
538 Octs::deserialize_with_visitor(
539 deserializer,
540 CompactVisitor(Octs::visitor()),
541 )
542 }
543 }
544 }
545
546 struct ReadableVisitor<Octs>(PhantomData<Octs>);
547
548 impl<'de, Octs> serde::de::Visitor<'de> for ReadableVisitor<Octs>
549 where
550 Octs: FromBuilder,
551 <Octs as FromBuilder>::Builder:
552 OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
553 {
554 type Value = Txt<Octs>;
555
556 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 f.write_str("TXT record data")
558 }
559
560 fn visit_str<E: serde::de::Error>(
561 self,
562 v: &str,
563 ) -> Result<Self::Value, E> {
564 let mut builder =
567 TxtBuilder::<<Octs as FromBuilder>::Builder>::new();
568 let mut chars = v.chars();
569 while let Some(ch) =
570 Symbol::from_chars(&mut chars).map_err(E::custom)?
571 {
572 builder
573 .append_u8(ch.into_octet().map_err(E::custom)?)
574 .map_err(E::custom)?;
575 }
576 builder.finish().map_err(E::custom)
577 }
578
579 fn visit_seq<A: serde::de::SeqAccess<'de>>(
580 self,
581 mut seq: A,
582 ) -> Result<Self::Value, A::Error> {
583 let mut builder = <Octs as FromBuilder>::Builder::empty();
584 while seq
585 .next_element_seed(DeserializeCharStrSeed::new(
586 &mut builder,
587 ))?
588 .is_some()
589 {
590 LongRecordData::check_len(builder.as_ref().len())
591 .map_err(serde::de::Error::custom)?;
592 }
593 if builder.as_ref().is_empty() {
594 builder
595 .append_slice(b"\0")
596 .map_err(|_| serde::de::Error::custom(ShortBuf))?;
597 }
598 Ok(Txt(builder.freeze()))
599 }
600 }
601
602 struct CompactVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
603
604 impl<'de, Octs> serde::de::Visitor<'de> for CompactVisitor<'de, Octs>
605 where
606 Octs: FromBuilder + DeserializeOctets<'de>,
607 <Octs as FromBuilder>::Builder:
608 OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
609 {
610 type Value = Txt<Octs>;
611
612 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613 f.write_str("TXT record data")
614 }
615
616 fn visit_borrowed_bytes<E: serde::de::Error>(
617 self,
618 value: &'de [u8],
619 ) -> Result<Self::Value, E> {
620 self.0.visit_borrowed_bytes(value).and_then(|octets| {
621 Txt::from_octets(octets).map_err(E::custom)
622 })
623 }
624
625 #[cfg(feature = "std")]
626 fn visit_byte_buf<E: serde::de::Error>(
627 self,
628 value: std::vec::Vec<u8>,
629 ) -> Result<Self::Value, E> {
630 self.0.visit_byte_buf(value).and_then(|octets| {
631 Txt::from_octets(octets).map_err(E::custom)
632 })
633 }
634 }
635
636 deserializer
637 .deserialize_newtype_struct("Txt", NewtypeVisitor(PhantomData))
638 }
639}
640
641#[derive(Clone)]
645pub struct TxtCharStrIter<'a>(Parser<'a, [u8]>);
646
647impl<'a> Iterator for TxtCharStrIter<'a> {
648 type Item = &'a CharStr<[u8]>;
649
650 fn next(&mut self) -> Option<Self::Item> {
651 if self.0.remaining() == 0 {
652 None
653 } else {
654 Some(CharStr::parse_slice(&mut self.0).unwrap())
655 }
656 }
657}
658
659#[derive(Clone)]
663pub struct TxtIter<'a>(TxtCharStrIter<'a>);
664
665impl<'a> Iterator for TxtIter<'a> {
666 type Item = &'a [u8];
667
668 fn next(&mut self) -> Option<Self::Item> {
669 self.0.next().map(CharStr::as_slice)
670 }
671}
672
673#[derive(Clone, Debug)]
680pub struct TxtBuilder<Builder> {
681 builder: Builder,
683
684 start: Option<usize>,
688}
689
690impl<Builder: OctetsBuilder + EmptyBuilder> TxtBuilder<Builder> {
691 #[must_use]
693 pub fn new() -> Self {
694 TxtBuilder {
695 builder: Builder::empty(),
696 start: None,
697 }
698 }
699}
700
701#[cfg(feature = "bytes")]
702impl TxtBuilder<BytesMut> {
703 pub fn new_bytes() -> Self {
705 Self::new()
706 }
707}
708
709impl<Builder: OctetsBuilder + AsRef<[u8]> + AsMut<[u8]>> TxtBuilder<Builder> {
710 fn builder_append_slice(
715 &mut self,
716 slice: &[u8],
717 ) -> Result<(), TxtAppendError> {
718 LongRecordData::check_append_len(
719 self.builder.as_ref().len(),
720 slice.len(),
721 )?;
722 self.builder.append_slice(slice)?;
723 Ok(())
724 }
725
726 pub fn append_slice(
743 &mut self,
744 mut slice: &[u8],
745 ) -> Result<(), TxtAppendError> {
746 if let Some(start) = self.start {
747 let left = 255 - (self.builder.as_ref().len() - (start + 1));
748 if slice.len() < left {
749 self.builder_append_slice(slice)?;
750 return Ok(());
751 }
752 let (append, left) = slice.split_at(left);
753 self.builder_append_slice(append)?;
754 self.builder.as_mut()[start] = 255;
755 slice = left;
756 }
757 for chunk in slice.chunks(255) {
758 self.start = if chunk.len() == 255 {
760 None
761 } else {
762 Some(self.builder.as_ref().len())
763 };
764 self.builder_append_slice(&[chunk.len() as u8])?;
765 self.builder_append_slice(chunk)?;
766 }
767 Ok(())
768 }
769
770 pub fn append_u8(&mut self, ch: u8) -> Result<(), TxtAppendError> {
775 self.append_slice(&[ch])
776 }
777
778 pub fn append_charstr<Octs: AsRef<[u8]> + ?Sized>(
790 &mut self,
791 s: &CharStr<Octs>,
792 ) -> Result<(), TxtAppendError> {
793 self.close_charstr();
794 LongRecordData::check_append_len(
795 self.builder.as_ref().len(),
796 usize::from(s.compose_len()),
797 )?;
798 s.compose(&mut self.builder)?;
799 Ok(())
800 }
801
802 pub fn close_charstr(&mut self) {
807 if let Some(start) = self.start {
808 let last_slice_len = self.builder.as_ref().len() - (start + 1);
809 self.builder.as_mut()[start] = last_slice_len as u8;
810 self.start = None;
811 }
812 }
813
814 pub fn finish(mut self) -> Result<Txt<Builder::Octets>, TxtAppendError>
820 where
821 Builder: FreezeBuilder,
822 {
823 self.close_charstr();
824 if self.builder.as_ref().is_empty() {
825 self.builder.append_slice(b"\0")?;
826 }
827 Ok(Txt(self.builder.freeze()))
828 }
829}
830
831impl<Builder: OctetsBuilder + EmptyBuilder> Default for TxtBuilder<Builder> {
832 fn default() -> Self {
833 Self::new()
834 }
835}
836
837#[derive(Clone, Copy, Debug)]
843pub struct TxtError(TxtErrorInner);
844
845#[derive(Clone, Copy, Debug)]
846enum TxtErrorInner {
847 Empty,
848 Long(LongRecordData),
849 ShortInput,
850}
851
852impl TxtError {
853 #[must_use]
854 pub fn as_str(self) -> &'static str {
855 match self.0 {
856 TxtErrorInner::Empty => "empty TXT record",
857 TxtErrorInner::Long(err) => err.as_str(),
858 TxtErrorInner::ShortInput => "short input",
859 }
860 }
861}
862
863impl From<LongRecordData> for TxtError {
864 fn from(err: LongRecordData) -> TxtError {
865 TxtError(TxtErrorInner::Long(err))
866 }
867}
868
869impl From<TxtError> for FormError {
870 fn from(err: TxtError) -> FormError {
871 FormError::new(err.as_str())
872 }
873}
874
875impl fmt::Display for TxtError {
876 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877 f.write_str(self.as_str())
878 }
879}
880
881#[derive(Clone, Copy, Debug)]
885pub enum TxtAppendError {
886 LongRecordData,
888
889 ShortBuf,
891}
892
893impl TxtAppendError {
894 #[must_use]
896 pub fn as_str(self) -> &'static str {
897 match self {
898 TxtAppendError::LongRecordData => "record data too long",
899 TxtAppendError::ShortBuf => "buffer size exceeded",
900 }
901 }
902}
903
904impl From<LongRecordData> for TxtAppendError {
905 fn from(_: LongRecordData) -> TxtAppendError {
906 TxtAppendError::LongRecordData
907 }
908}
909
910impl<T: Into<ShortBuf>> From<T> for TxtAppendError {
911 fn from(_: T) -> TxtAppendError {
912 TxtAppendError::ShortBuf
913 }
914}
915
916impl fmt::Display for TxtAppendError {
917 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918 f.write_str(self.as_str())
919 }
920}
921
922#[cfg(test)]
925#[cfg(all(feature = "std", feature = "bytes"))]
926mod test {
927 use super::*;
928 use crate::base::rdata::test::{
929 test_compose_parse, test_rdlen, test_scan,
930 };
931 use std::vec::Vec;
932
933 #[test]
934 #[allow(clippy::redundant_closure)] fn txt_compose_parse_scan() {
936 let rdata = Txt::from_octets(b"\x03foo\x03bar".as_ref()).unwrap();
937 test_rdlen(&rdata);
938 test_compose_parse(&rdata, |parser| Txt::parse(parser));
939 test_scan(&["foo", "bar"], Txt::scan, &rdata);
940 }
941
942 #[test]
943 fn txt_from_slice() {
944 assert!(Txt::from_octets(b"").is_err());
945
946 let short = b"01234";
947 let txt: Txt<Vec<u8>> = Txt::build_from_slice(short).unwrap();
948 assert_eq!(Some(&short[..]), txt.as_flat_slice());
949 assert_eq!(short.to_vec(), txt.text::<Vec<u8>>());
950
951 let full = short.repeat(51);
953 let txt: Txt<Vec<u8>> = Txt::build_from_slice(&full).unwrap();
954 assert_eq!(Some(&full[..]), txt.as_flat_slice());
955 assert_eq!(full.to_vec(), txt.text::<Vec<u8>>());
956
957 let long = short.repeat(52);
959 let txt: Txt<Vec<u8>> = Txt::build_from_slice(&long).unwrap();
960 assert_eq!(None, txt.as_flat_slice());
961 assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
962
963 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
965 for chunk in long.chunks(9) {
966 builder.append_slice(chunk).unwrap();
967 }
968 let txt = builder.finish().unwrap();
969 assert_eq!(None, txt.as_flat_slice());
970 assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
971
972 let builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
974 let txt = builder.finish().unwrap();
975 assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
976
977 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
979 builder.append_slice(b"").unwrap();
980 let txt = builder.finish().unwrap();
981 assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
982
983 let mut parser = Parser::from_static(b"\x01");
985 assert!(Txt::parse(&mut parser).is_err());
986
987 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
989 assert!(builder
990 .append_slice(&b"\x00".repeat(u16::MAX as usize))
991 .is_err());
992
993 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
995 assert!(builder
996 .append_slice(&b"\x00".repeat(u16::MAX as usize - 512))
997 .is_ok());
998 assert!(builder.append_slice(&b"\x00".repeat(512)).is_err());
999 }
1000
1001 #[test]
1002 fn txt_canonical_compare() {
1003 let data = [
1004 "mailru-verification: 14505c6eb222c847",
1005 "yandex-verification: 6059b187e78de544",
1006 "v=spf1 include:_spf.protonmail.ch ~all",
1007 "swisssign-check=CF0JHMTlTDNoES3rrknIRggocffSwqmzMb9X8YbjzK",
1008 "google-site-\
1009 verification=aq9zJnp3H3bNE0Y4D4rH5I5Dhj8VMaLYx0uQ7Rozfgg",
1010 "ahrefs-site-verification_\
1011 4bdac6bbaa81e0d591d7c0f3ef238905c0521b69bf3d74e64d3775bc\
1012 b2743afd",
1013 "brave-ledger-verification=\
1014 66a7f27fb99949cc0c564ab98efcc58ea1bac3e97eb557c782ab2d44b\
1015 49aefd7",
1016 ];
1017
1018 let records = data
1019 .iter()
1020 .map(|e| {
1021 let mut builder = TxtBuilder::<Vec<u8>>::new();
1022 builder.append_slice(e.as_bytes()).unwrap();
1023 builder.finish().unwrap()
1024 })
1025 .collect::<Vec<_>>();
1026
1027 let mut sorted = records.clone();
1030 sorted.sort_by(|a, b| a.canonical_cmp(b));
1031
1032 for (a, b) in records.iter().zip(sorted.iter()) {
1033 assert_eq!(a, b);
1034 }
1035 }
1036
1037 #[test]
1038 fn txt_strings_eq() {
1039 let records = [["foo", "bar"], ["foob", "ar"], ["foo", "bar"]];
1040
1041 let records = records
1042 .iter()
1043 .map(|strings| {
1044 let mut builder = TxtBuilder::<Vec<u8>>::new();
1045 for string in strings {
1046 builder
1047 .append_charstr(
1048 CharStr::from_slice(string.as_bytes()).unwrap(),
1049 )
1050 .unwrap();
1051 }
1052 builder.finish().unwrap()
1053 })
1054 .collect::<Vec<_>>();
1055
1056 assert_ne!(records[0], records[1]);
1057 assert_eq!(records[0], records[2]);
1058 }
1059
1060 #[cfg(all(feature = "serde", feature = "std"))]
1061 #[test]
1062 fn txt_ser_de() {
1063 use serde_test::{assert_tokens, Configure, Token};
1064
1065 let txt = Txt::from_octets(Vec::from(b"\x03foo".as_ref())).unwrap();
1066 assert_tokens(
1067 &txt.clone().compact(),
1068 &[
1069 Token::NewtypeStruct { name: "Txt" },
1070 Token::ByteBuf(b"\x03foo"),
1071 ],
1072 );
1073 assert_tokens(
1074 &txt.readable(),
1075 &[
1076 Token::NewtypeStruct { name: "Txt" },
1077 Token::Seq { len: None },
1078 Token::NewtypeStruct { name: "CharStr" },
1079 Token::BorrowedStr("foo"),
1080 Token::SeqEnd,
1081 ],
1082 );
1083
1084 let txt = Txt::from_octets(Vec::from(b"\x03foo\x04\\bar".as_ref()))
1085 .unwrap();
1086 assert_tokens(
1087 &txt.clone().compact(),
1088 &[
1089 Token::NewtypeStruct { name: "Txt" },
1090 Token::ByteBuf(b"\x03foo\x04\\bar"),
1091 ],
1092 );
1093 assert_tokens(
1094 &txt.readable(),
1095 &[
1096 Token::NewtypeStruct { name: "Txt" },
1097 Token::Seq { len: None },
1098 Token::NewtypeStruct { name: "CharStr" },
1099 Token::BorrowedStr("foo"),
1100 Token::NewtypeStruct { name: "CharStr" },
1101 Token::BorrowedStr("\\\\bar"),
1102 Token::SeqEnd,
1103 ],
1104 );
1105 }
1106
1107 #[cfg(all(feature = "serde", feature = "std"))]
1108 #[test]
1109 fn txt_de_str() {
1110 use serde_test::{assert_de_tokens, Configure, Token};
1111
1112 assert_de_tokens(
1113 &Txt::from_octets(Vec::from(b"\x03foo".as_ref()))
1114 .unwrap()
1115 .readable(),
1116 &[
1117 Token::NewtypeStruct { name: "Txt" },
1118 Token::BorrowedStr("foo"),
1119 ],
1120 );
1121 }
1122
1123 #[test]
1124 fn txt_display() {
1125 fn cmp(input: &[u8], output: &str) {
1126 assert_eq!(
1127 format!("{}", Txt::from_octets(input).unwrap()),
1128 output
1129 );
1130 }
1131
1132 cmp(b"\x03foo", "\"foo\"");
1133 cmp(b"\x03foo\x03bar", "\"foo\" \"bar\"");
1134 cmp(b"\x03fo\"\x04bar ", "\"fo\\\"\" \"bar \"");
1135 }
1138}