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 }
430 else {
431 first = false;
432 }
433 write!(f, "{}", slice.display_quoted())?;
434 }
435 Ok(())
436 }
437}
438
439impl<Octs: AsRef<[u8]>> fmt::Debug for Txt<Octs> {
442 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
443 f.write_str("Txt(")?;
444 fmt::Display::fmt(self, f)?;
445 f.write_str(")")
446 }
447}
448
449impl<Octs> ZonefileFmt for Txt<Octs>
452where
453 Octs: AsRef<[u8]>,
454{
455 fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
456 p.block(|p| {
457 for slice in self.iter_charstrs() {
458 p.write_token(slice.display_quoted())?;
459 }
460 Ok(())
461 })
462 }
463}
464
465#[cfg(feature = "serde")]
468impl<Octs> serde::Serialize for Txt<Octs>
469where
470 Octs: AsRef<[u8]> + SerializeOctets,
471{
472 fn serialize<S: serde::Serializer>(
473 &self,
474 serializer: S,
475 ) -> Result<S::Ok, S::Error> {
476 use serde::ser::SerializeSeq;
477
478 struct TxtSeq<'a, Octs>(&'a Txt<Octs>);
479
480 impl<Octs> serde::Serialize for TxtSeq<'_, Octs>
481 where
482 Octs: AsRef<[u8]> + SerializeOctets,
483 {
484 fn serialize<S: serde::Serializer>(
485 &self,
486 serializer: S,
487 ) -> Result<S::Ok, S::Error> {
488 let mut serializer = serializer.serialize_seq(None)?;
489 for item in self.0.iter_charstrs() {
490 serializer.serialize_element(item)?;
491 }
492 serializer.end()
493 }
494 }
495
496 if serializer.is_human_readable() {
497 serializer.serialize_newtype_struct("Txt", &TxtSeq(self))
498 }
499 else {
500 serializer.serialize_newtype_struct(
501 "Txt",
502 &self.0.as_serialized_octets(),
503 )
504 }
505 }
506}
507
508#[cfg(feature = "serde")]
509impl<'de, Octs> serde::Deserialize<'de> for Txt<Octs>
510where
511 Octs: FromBuilder + DeserializeOctets<'de>,
512 <Octs as FromBuilder>::Builder: EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
513{
514 fn deserialize<D: serde::Deserializer<'de>>(
515 deserializer: D,
516 ) -> Result<Self, D::Error> {
517 use core::marker::PhantomData;
518
519 struct NewtypeVisitor<T>(PhantomData<T>);
520
521 impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
522 where
523 Octs: FromBuilder + DeserializeOctets<'de>,
524 <Octs as FromBuilder>::Builder:
525 OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
526 {
527 type Value = Txt<Octs>;
528
529 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
530 f.write_str("TXT record data")
531 }
532
533 fn visit_newtype_struct<D: serde::Deserializer<'de>>(
534 self,
535 deserializer: D,
536 ) -> Result<Self::Value, D::Error> {
537 if deserializer.is_human_readable() {
538 deserializer.deserialize_seq(ReadableVisitor(PhantomData))
539 } else {
540 Octs::deserialize_with_visitor(
541 deserializer,
542 CompactVisitor(Octs::visitor()),
543 )
544 }
545 }
546 }
547
548 struct ReadableVisitor<Octs>(PhantomData<Octs>);
549
550 impl<'de, Octs> serde::de::Visitor<'de> for ReadableVisitor<Octs>
551 where
552 Octs: FromBuilder,
553 <Octs as FromBuilder>::Builder:
554 OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
555 {
556 type Value = Txt<Octs>;
557
558 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
559 f.write_str("TXT record data")
560 }
561
562 fn visit_str<E: serde::de::Error>(
563 self,
564 v: &str,
565 ) -> Result<Self::Value, E> {
566 let mut builder =
569 TxtBuilder::<<Octs as FromBuilder>::Builder>::new();
570 let mut chars = v.chars();
571 while let Some(ch) =
572 Symbol::from_chars(&mut chars).map_err(E::custom)?
573 {
574 builder
575 .append_u8(ch.into_octet().map_err(E::custom)?)
576 .map_err(E::custom)?;
577 }
578 builder.finish().map_err(E::custom)
579 }
580
581 fn visit_seq<A: serde::de::SeqAccess<'de>>(
582 self,
583 mut seq: A,
584 ) -> Result<Self::Value, A::Error> {
585 let mut builder = <Octs as FromBuilder>::Builder::empty();
586 while seq.next_element_seed(
587 DeserializeCharStrSeed::new(&mut builder)
588 )?.is_some() {
589 LongRecordData::check_len(
590 builder.as_ref().len()
591 ).map_err(serde::de::Error::custom)?;
592 }
593 if builder.as_ref().is_empty() {
594 builder.append_slice(b"\0").map_err(|_| {
595 serde::de::Error::custom(ShortBuf)
596 })?;
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.deserialize_newtype_struct(
637 "Txt", NewtypeVisitor(PhantomData)
638 )
639 }
640}
641
642#[derive(Clone)]
646pub struct TxtCharStrIter<'a>(Parser<'a, [u8]>);
647
648impl<'a> Iterator for TxtCharStrIter<'a> {
649 type Item = &'a CharStr<[u8]>;
650
651 fn next(&mut self) -> Option<Self::Item> {
652 if self.0.remaining() == 0 {
653 None
654 } else {
655 Some(CharStr::parse_slice(&mut self.0).unwrap())
656 }
657 }
658}
659
660#[derive(Clone)]
664pub struct TxtIter<'a>(TxtCharStrIter<'a>);
665
666impl<'a> Iterator for TxtIter<'a> {
667 type Item = &'a [u8];
668
669 fn next(&mut self) -> Option<Self::Item> {
670 self.0.next().map(CharStr::as_slice)
671 }
672}
673
674#[derive(Clone, Debug)]
681pub struct TxtBuilder<Builder> {
682 builder: Builder,
684
685 start: Option<usize>,
689}
690
691impl<Builder: OctetsBuilder + EmptyBuilder> TxtBuilder<Builder> {
692 #[must_use]
694 pub fn new() -> Self {
695 TxtBuilder {
696 builder: Builder::empty(),
697 start: None,
698 }
699 }
700}
701
702#[cfg(feature = "bytes")]
703impl TxtBuilder<BytesMut> {
704 pub fn new_bytes() -> Self {
706 Self::new()
707 }
708}
709
710impl<Builder: OctetsBuilder + AsRef<[u8]> + AsMut<[u8]>> TxtBuilder<Builder> {
711 fn builder_append_slice(
716 &mut self, slice: &[u8]
717 ) -> Result<(), TxtAppendError> {
718 LongRecordData::check_append_len(
719 self.builder.as_ref().len(), slice.len()
720 )?;
721 self.builder.append_slice(slice)?;
722 Ok(())
723 }
724
725 pub fn append_slice(
742 &mut self, mut slice: &[u8]
743 ) -> Result<(), TxtAppendError> {
744 if let Some(start) = self.start {
745 let left = 255 - (self.builder.as_ref().len() - (start + 1));
746 if slice.len() < left {
747 self.builder_append_slice(slice)?;
748 return Ok(());
749 }
750 let (append, left) = slice.split_at(left);
751 self.builder_append_slice(append)?;
752 self.builder.as_mut()[start] = 255;
753 slice = left;
754 }
755 for chunk in slice.chunks(255) {
756 self.start = if chunk.len() == 255 {
758 None
759 } else {
760 Some(self.builder.as_ref().len())
761 };
762 self.builder_append_slice(&[chunk.len() as u8])?;
763 self.builder_append_slice(chunk)?;
764 }
765 Ok(())
766 }
767
768 pub fn append_u8(&mut self, ch: u8) -> Result<(), TxtAppendError> {
773 self.append_slice(&[ch])
774 }
775
776 pub fn append_charstr<Octs: AsRef<[u8]> + ?Sized>(
788 &mut self, s: &CharStr<Octs>
789 ) -> Result<(), TxtAppendError> {
790 self.close_charstr();
791 LongRecordData::check_append_len(
792 self.builder.as_ref().len(),
793 usize::from(s.compose_len())
794 )?;
795 s.compose(&mut self.builder)?;
796 Ok(())
797 }
798
799 pub fn close_charstr(&mut self) {
804 if let Some(start) = self.start {
805 let last_slice_len = self.builder.as_ref().len() - (start + 1);
806 self.builder.as_mut()[start] = last_slice_len as u8;
807 self.start = None;
808 }
809 }
810
811 pub fn finish(mut self) -> Result<Txt<Builder::Octets>, TxtAppendError>
817 where
818 Builder: FreezeBuilder,
819 {
820 self.close_charstr();
821 if self.builder.as_ref().is_empty() {
822 self.builder.append_slice(b"\0")?;
823 }
824 Ok(Txt(self.builder.freeze()))
825 }
826}
827
828impl<Builder: OctetsBuilder + EmptyBuilder> Default for TxtBuilder<Builder> {
829 fn default() -> Self {
830 Self::new()
831 }
832}
833
834#[derive(Clone, Copy, Debug)]
840pub struct TxtError(TxtErrorInner);
841
842#[derive(Clone, Copy, Debug)]
843enum TxtErrorInner {
844 Empty,
845 Long(LongRecordData),
846 ShortInput,
847}
848
849impl TxtError {
850 #[must_use]
851 pub fn as_str(self) -> &'static str {
852 match self.0 {
853 TxtErrorInner::Empty => "empty TXT record",
854 TxtErrorInner::Long(err) => err.as_str(),
855 TxtErrorInner::ShortInput => "short input",
856 }
857 }
858}
859
860impl From<LongRecordData> for TxtError {
861 fn from(err: LongRecordData) -> TxtError {
862 TxtError(TxtErrorInner::Long(err))
863 }
864}
865
866impl From<TxtError> for FormError {
867 fn from(err: TxtError) -> FormError {
868 FormError::new(err.as_str())
869 }
870}
871
872impl fmt::Display for TxtError {
873 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
874 f.write_str(self.as_str())
875 }
876}
877
878#[derive(Clone, Copy, Debug)]
882pub enum TxtAppendError {
883 LongRecordData,
885
886 ShortBuf
888}
889
890impl TxtAppendError {
891 #[must_use]
893 pub fn as_str(self) -> &'static str {
894 match self {
895 TxtAppendError::LongRecordData => "record data too long",
896 TxtAppendError::ShortBuf => "buffer size exceeded"
897 }
898 }
899}
900
901impl From<LongRecordData> for TxtAppendError {
902 fn from(_: LongRecordData) -> TxtAppendError {
903 TxtAppendError::LongRecordData
904 }
905}
906
907impl<T: Into<ShortBuf>> From<T> for TxtAppendError {
908 fn from(_: T) -> TxtAppendError {
909 TxtAppendError::ShortBuf
910 }
911}
912
913impl fmt::Display for TxtAppendError {
914 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
915 f.write_str(self.as_str())
916 }
917}
918
919#[cfg(test)]
922#[cfg(all(feature = "std", feature = "bytes"))]
923mod test {
924 use super::*;
925 use crate::base::rdata::test::{
926 test_compose_parse, test_rdlen, test_scan,
927 };
928 use std::vec::Vec;
929
930
931
932 #[test]
933 #[allow(clippy::redundant_closure)] fn txt_compose_parse_scan() {
935 let rdata = Txt::from_octets(b"\x03foo\x03bar".as_ref()).unwrap();
936 test_rdlen(&rdata);
937 test_compose_parse(&rdata, |parser| Txt::parse(parser));
938 test_scan(&["foo", "bar"], Txt::scan, &rdata);
939 }
940
941 #[test]
942 fn txt_from_slice() {
943 assert!(Txt::from_octets(b"").is_err());
944
945 let short = b"01234";
946 let txt: Txt<Vec<u8>> = Txt::build_from_slice(short).unwrap();
947 assert_eq!(Some(&short[..]), txt.as_flat_slice());
948 assert_eq!(short.to_vec(), txt.text::<Vec<u8>>());
949
950 let full = short.repeat(51);
952 let txt: Txt<Vec<u8>> = Txt::build_from_slice(&full).unwrap();
953 assert_eq!(Some(&full[..]), txt.as_flat_slice());
954 assert_eq!(full.to_vec(), txt.text::<Vec<u8>>());
955
956 let long = short.repeat(52);
958 let txt: Txt<Vec<u8>> = Txt::build_from_slice(&long).unwrap();
959 assert_eq!(None, txt.as_flat_slice());
960 assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
961
962 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
964 for chunk in long.chunks(9) {
965 builder.append_slice(chunk).unwrap();
966 }
967 let txt = builder.finish().unwrap();
968 assert_eq!(None, txt.as_flat_slice());
969 assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
970
971 let builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
973 let txt = builder.finish().unwrap();
974 assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
975
976 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
978 builder.append_slice(b"").unwrap();
979 let txt = builder.finish().unwrap();
980 assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
981
982 let mut parser = Parser::from_static(b"\x01");
984 assert!(Txt::parse(&mut parser).is_err());
985
986 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
988 assert!(builder
989 .append_slice(&b"\x00".repeat(u16::MAX as usize))
990 .is_err());
991
992 let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
994 assert!(builder
995 .append_slice(&b"\x00".repeat(u16::MAX as usize - 512))
996 .is_ok());
997 assert!(builder.append_slice(&b"\x00".repeat(512)).is_err());
998 }
999
1000 #[test]
1001 fn txt_canonical_compare() {
1002 let data = [
1003 "mailru-verification: 14505c6eb222c847",
1004 "yandex-verification: 6059b187e78de544",
1005 "v=spf1 include:_spf.protonmail.ch ~all",
1006 "swisssign-check=CF0JHMTlTDNoES3rrknIRggocffSwqmzMb9X8YbjzK",
1007 "google-site-\
1008 verification=aq9zJnp3H3bNE0Y4D4rH5I5Dhj8VMaLYx0uQ7Rozfgg",
1009 "ahrefs-site-verification_\
1010 4bdac6bbaa81e0d591d7c0f3ef238905c0521b69bf3d74e64d3775bc\
1011 b2743afd",
1012 "brave-ledger-verification=\
1013 66a7f27fb99949cc0c564ab98efcc58ea1bac3e97eb557c782ab2d44b\
1014 49aefd7",
1015 ];
1016
1017 let records = data
1018 .iter()
1019 .map(|e| {
1020 let mut builder = TxtBuilder::<Vec<u8>>::new();
1021 builder.append_slice(e.as_bytes()).unwrap();
1022 builder.finish().unwrap()
1023 })
1024 .collect::<Vec<_>>();
1025
1026 let mut sorted = records.clone();
1029 sorted.sort_by(|a, b| a.canonical_cmp(b));
1030
1031 for (a, b) in records.iter().zip(sorted.iter()) {
1032 assert_eq!(a, b);
1033 }
1034 }
1035
1036 #[test]
1037 fn txt_strings_eq() {
1038 let records = [["foo", "bar"], ["foob", "ar"], ["foo", "bar"]];
1039
1040 let records = records
1041 .iter()
1042 .map(|strings| {
1043 let mut builder = TxtBuilder::<Vec<u8>>::new();
1044 for string in strings {
1045 builder
1046 .append_charstr(
1047 CharStr::from_slice(string.as_bytes()).unwrap(),
1048 )
1049 .unwrap();
1050 }
1051 builder.finish().unwrap()
1052 })
1053 .collect::<Vec<_>>();
1054
1055 assert_ne!(records[0], records[1]);
1056 assert_eq!(records[0], records[2]);
1057 }
1058
1059 #[cfg(all(feature = "serde", feature = "std"))]
1060 #[test]
1061 fn txt_ser_de() {
1062 use serde_test::{assert_tokens, Configure, Token};
1063
1064 let txt = Txt::from_octets(Vec::from(b"\x03foo".as_ref())).unwrap();
1065 assert_tokens(
1066 &txt.clone().compact(),
1067 &[
1068 Token::NewtypeStruct { name: "Txt" },
1069 Token::ByteBuf(b"\x03foo"),
1070 ],
1071 );
1072 assert_tokens(
1073 &txt.readable(),
1074 &[
1075 Token::NewtypeStruct { name: "Txt" },
1076 Token::Seq { len: None },
1077 Token::NewtypeStruct { name: "CharStr" },
1078 Token::BorrowedStr("foo"),
1079 Token::SeqEnd,
1080 ],
1081 );
1082
1083 let txt = Txt::from_octets(
1084 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}
1139