Skip to main content

c_ares/dns/
typed.rs

1//! Strongly-typed views of DNS resource records.
2//!
3//! [`DnsRr`] exposes a generic, untyped API where field access is keyed by
4//! [`DnsRrKey`] and the caller must pick the correct datatype-specific
5//! getter (`get_addr`, `get_str`, `get_u16`, …). This module layers a set
6//! of strongly-typed borrowed-view wrappers over `DnsRr`, one per concrete
7//! record type, with idiomatic getters that hide the key/datatype machinery.
8//!
9//! # Example
10//!
11//! ```no_run
12//! use c_ares::{DnsRecord, DnsParseFlags, DnsSection, TypedRr};
13//!
14//! # fn handle(wire: &[u8]) -> c_ares::Result<()> {
15//! let rec = DnsRecord::parse(wire, DnsParseFlags::empty())?;
16//! for rr in rec.rrs(DnsSection::Answer) {
17//!     match rr.as_typed() {
18//!         TypedRr::A(a) => println!("{}: A {}", a.name(), a.addr()),
19//!         TypedRr::Mx(mx) => {
20//!             println!("{}: MX {} {}", mx.name(), mx.preference(), mx.exchange());
21//!         }
22//!         _ => {}
23//!     }
24//! }
25//! # Ok(())
26//! # }
27//! ```
28//!
29//! # Field optionality
30//!
31//! Typed accessors return bare values, never `Option`.
32//!
33//! - **Numeric scalars** (`u8`, `u16`, `u32`) return zero when unset.
34//! - **`Ipv4Addr` / `Ipv6Addr`** return `0.0.0.0` / `::` when unset.
35//! - **`&str` / `&[u8]`** return `""` / `&[]` when unset.
36//! - **Array-shaped fields** (TXT entries, OPT/SVCB/HTTPS parameters)
37//!   are exposed as iterators with companion `*_count` methods; an empty
38//!   array is its own valid representation.
39//!
40//! # Discrimination
41//!
42//! Each wrapper has a corresponding `as_*` discriminator inherent method
43//! on [`DnsRr`] (e.g. [`DnsRr::as_a`]) that returns `Some(wrapper)` iff
44//! the record's [`rr_type`](DnsRr::rr_type) matches. The
45//! [`DnsRr::as_typed`] method dispatches on the type once and returns a
46//! [`TypedRr`] enum carrying the typed view directly.
47
48use std::fmt;
49use std::net::{Ipv4Addr, Ipv6Addr};
50
51use super::dns_opt::{OptParseError, OptValue, parse_opt_value};
52use super::enums::{DnsCls, DnsRecordType, DnsRrKey};
53use super::rr::DnsRr;
54
55/// Generates the four common accessors (`as_dns_rr`, `name`, `dns_class`,
56/// `ttl`) that every typed record wrapper exposes, plus an associated
57/// constructor `pub(super) fn new(rr: &'a DnsRr) -> Self`.
58macro_rules! common_accessors {
59    ($struct:ident) => {
60        /// Wraps a generic [`DnsRr`] without checking its type.
61        pub(super) fn new(rr: &'a DnsRr) -> Self {
62            Self(rr)
63        }
64
65        /// Returns the underlying generic [`DnsRr`].
66        ///
67        /// Useful for accessing common fields via [`DnsRr`]'s own API,
68        /// for [`Debug`](fmt::Debug) formatting, or for falling back to
69        /// the raw key-based getters.
70        pub fn as_dns_rr(self) -> &'a DnsRr {
71            self.0
72        }
73
74        /// Returns the resource record owner name.
75        pub fn name(self) -> &'a str {
76            self.0.name()
77        }
78
79        /// Returns the resource record DNS class.
80        pub fn dns_class(self) -> DnsCls {
81            self.0.dns_class()
82        }
83
84        /// Returns the resource record TTL in seconds.
85        pub fn ttl(self) -> u32 {
86            self.0.ttl()
87        }
88    };
89}
90
91// =============================================================================
92// Fixed-shape record wrappers
93// =============================================================================
94
95/// Typed view of an [`A`](DnsRecordType::A) record.
96#[derive(Copy, Clone)]
97pub struct ARecord<'a>(&'a DnsRr);
98
99impl<'a> ARecord<'a> {
100    common_accessors!(ARecord);
101
102    /// Returns the IPv4 address ([`A_ADDR`](DnsRrKey::A_ADDR)).
103    ///
104    /// Defaults to `0.0.0.0` for builder records where `set_addr` has
105    /// not been called.
106    pub fn addr(self) -> Ipv4Addr {
107        self.0
108            .get_addr(DnsRrKey::A_ADDR)
109            .unwrap_or(Ipv4Addr::UNSPECIFIED)
110    }
111}
112
113impl fmt::Debug for ARecord<'_> {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.debug_struct("ARecord")
116            .field("name", &self.name())
117            .field("ttl", &self.ttl())
118            .field("addr", &self.addr())
119            .finish()
120    }
121}
122
123/// Typed view of an [`AAAA`](DnsRecordType::AAAA) record.
124#[derive(Copy, Clone)]
125pub struct AaaaRecord<'a>(&'a DnsRr);
126
127impl<'a> AaaaRecord<'a> {
128    common_accessors!(AaaaRecord);
129
130    /// Returns the IPv6 address ([`AAAA_ADDR`](DnsRrKey::AAAA_ADDR)).
131    ///
132    /// Defaults to `::` for builder records where `set_addr6` has not
133    /// been called.
134    pub fn addr(self) -> Ipv6Addr {
135        self.0
136            .get_addr6(DnsRrKey::AAAA_ADDR)
137            .unwrap_or(Ipv6Addr::UNSPECIFIED)
138    }
139}
140
141impl fmt::Debug for AaaaRecord<'_> {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.debug_struct("AaaaRecord")
144            .field("name", &self.name())
145            .field("ttl", &self.ttl())
146            .field("addr", &self.addr())
147            .finish()
148    }
149}
150
151/// Typed view of an [`NS`](DnsRecordType::NS) record.
152#[derive(Copy, Clone)]
153pub struct NsRecord<'a>(&'a DnsRr);
154
155impl<'a> NsRecord<'a> {
156    common_accessors!(NsRecord);
157
158    /// Returns the name server domain name
159    /// ([`NS_NSDNAME`](DnsRrKey::NS_NSDNAME)).
160    pub fn nsdname(self) -> &'a str {
161        self.0.get_str(DnsRrKey::NS_NSDNAME).unwrap_or("")
162    }
163}
164
165impl fmt::Debug for NsRecord<'_> {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        f.debug_struct("NsRecord")
168            .field("name", &self.name())
169            .field("ttl", &self.ttl())
170            .field("nsdname", &self.nsdname())
171            .finish()
172    }
173}
174
175/// Typed view of a [`CNAME`](DnsRecordType::CNAME) record.
176#[derive(Copy, Clone)]
177pub struct CnameRecord<'a>(&'a DnsRr);
178
179impl<'a> CnameRecord<'a> {
180    common_accessors!(CnameRecord);
181
182    /// Returns the canonical name ([`CNAME_CNAME`](DnsRrKey::CNAME_CNAME)).
183    pub fn cname(self) -> &'a str {
184        self.0.get_str(DnsRrKey::CNAME_CNAME).unwrap_or("")
185    }
186}
187
188impl fmt::Debug for CnameRecord<'_> {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        f.debug_struct("CnameRecord")
191            .field("name", &self.name())
192            .field("ttl", &self.ttl())
193            .field("cname", &self.cname())
194            .finish()
195    }
196}
197
198/// Typed view of an [`SOA`](DnsRecordType::SOA) record.
199#[derive(Copy, Clone)]
200pub struct SoaRecord<'a>(&'a DnsRr);
201
202impl<'a> SoaRecord<'a> {
203    common_accessors!(SoaRecord);
204
205    /// Primary nameserver ([`SOA_MNAME`](DnsRrKey::SOA_MNAME)).
206    pub fn mname(self) -> &'a str {
207        self.0.get_str(DnsRrKey::SOA_MNAME).unwrap_or("")
208    }
209
210    /// Responsible mailbox ([`SOA_RNAME`](DnsRrKey::SOA_RNAME)).
211    pub fn rname(self) -> &'a str {
212        self.0.get_str(DnsRrKey::SOA_RNAME).unwrap_or("")
213    }
214
215    /// Serial number ([`SOA_SERIAL`](DnsRrKey::SOA_SERIAL)).
216    pub fn serial(self) -> u32 {
217        self.0.get_u32(DnsRrKey::SOA_SERIAL)
218    }
219
220    /// Refresh interval ([`SOA_REFRESH`](DnsRrKey::SOA_REFRESH)).
221    pub fn refresh(self) -> u32 {
222        self.0.get_u32(DnsRrKey::SOA_REFRESH)
223    }
224
225    /// Retry interval ([`SOA_RETRY`](DnsRrKey::SOA_RETRY)).
226    pub fn retry(self) -> u32 {
227        self.0.get_u32(DnsRrKey::SOA_RETRY)
228    }
229
230    /// Expire limit ([`SOA_EXPIRE`](DnsRrKey::SOA_EXPIRE)).
231    pub fn expire(self) -> u32 {
232        self.0.get_u32(DnsRrKey::SOA_EXPIRE)
233    }
234
235    /// Minimum TTL ([`SOA_MINIMUM`](DnsRrKey::SOA_MINIMUM)).
236    pub fn minimum(self) -> u32 {
237        self.0.get_u32(DnsRrKey::SOA_MINIMUM)
238    }
239}
240
241impl fmt::Debug for SoaRecord<'_> {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("SoaRecord")
244            .field("name", &self.name())
245            .field("ttl", &self.ttl())
246            .field("mname", &self.mname())
247            .field("rname", &self.rname())
248            .field("serial", &self.serial())
249            .field("refresh", &self.refresh())
250            .field("retry", &self.retry())
251            .field("expire", &self.expire())
252            .field("minimum", &self.minimum())
253            .finish()
254    }
255}
256
257/// Typed view of a [`PTR`](DnsRecordType::PTR) record.
258#[derive(Copy, Clone)]
259pub struct PtrRecord<'a>(&'a DnsRr);
260
261impl<'a> PtrRecord<'a> {
262    common_accessors!(PtrRecord);
263
264    /// Returns the pointer domain name ([`PTR_DNAME`](DnsRrKey::PTR_DNAME)).
265    pub fn dname(self) -> &'a str {
266        self.0.get_str(DnsRrKey::PTR_DNAME).unwrap_or("")
267    }
268}
269
270impl fmt::Debug for PtrRecord<'_> {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.debug_struct("PtrRecord")
273            .field("name", &self.name())
274            .field("ttl", &self.ttl())
275            .field("dname", &self.dname())
276            .finish()
277    }
278}
279
280/// Typed view of an [`HINFO`](DnsRecordType::HINFO) record.
281#[derive(Copy, Clone)]
282pub struct HinfoRecord<'a>(&'a DnsRr);
283
284impl<'a> HinfoRecord<'a> {
285    common_accessors!(HinfoRecord);
286
287    /// CPU description ([`HINFO_CPU`](DnsRrKey::HINFO_CPU)).
288    pub fn cpu(self) -> &'a str {
289        self.0.get_str(DnsRrKey::HINFO_CPU).unwrap_or("")
290    }
291
292    /// OS description ([`HINFO_OS`](DnsRrKey::HINFO_OS)).
293    pub fn os(self) -> &'a str {
294        self.0.get_str(DnsRrKey::HINFO_OS).unwrap_or("")
295    }
296}
297
298impl fmt::Debug for HinfoRecord<'_> {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        f.debug_struct("HinfoRecord")
301            .field("name", &self.name())
302            .field("ttl", &self.ttl())
303            .field("cpu", &self.cpu())
304            .field("os", &self.os())
305            .finish()
306    }
307}
308
309/// Typed view of an [`MX`](DnsRecordType::MX) record.
310#[derive(Copy, Clone)]
311pub struct MxRecord<'a>(&'a DnsRr);
312
313impl<'a> MxRecord<'a> {
314    common_accessors!(MxRecord);
315
316    /// Preference ([`MX_PREFERENCE`](DnsRrKey::MX_PREFERENCE)).
317    pub fn preference(self) -> u16 {
318        self.0.get_u16(DnsRrKey::MX_PREFERENCE)
319    }
320
321    /// Exchange domain name ([`MX_EXCHANGE`](DnsRrKey::MX_EXCHANGE)).
322    pub fn exchange(self) -> &'a str {
323        self.0.get_str(DnsRrKey::MX_EXCHANGE).unwrap_or("")
324    }
325}
326
327impl fmt::Debug for MxRecord<'_> {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        f.debug_struct("MxRecord")
330            .field("name", &self.name())
331            .field("ttl", &self.ttl())
332            .field("preference", &self.preference())
333            .field("exchange", &self.exchange())
334            .finish()
335    }
336}
337
338/// Typed view of a [`SIG`](DnsRecordType::SIG) record.
339#[derive(Copy, Clone)]
340pub struct SigRecord<'a>(&'a DnsRr);
341
342impl<'a> SigRecord<'a> {
343    common_accessors!(SigRecord);
344
345    /// Type covered ([`SIG_TYPE_COVERED`](DnsRrKey::SIG_TYPE_COVERED)).
346    pub fn type_covered(self) -> u16 {
347        self.0.get_u16(DnsRrKey::SIG_TYPE_COVERED)
348    }
349
350    /// Algorithm ([`SIG_ALGORITHM`](DnsRrKey::SIG_ALGORITHM)).
351    pub fn algorithm(self) -> u8 {
352        self.0.get_u8(DnsRrKey::SIG_ALGORITHM)
353    }
354
355    /// Number of labels ([`SIG_LABELS`](DnsRrKey::SIG_LABELS)).
356    pub fn labels(self) -> u8 {
357        self.0.get_u8(DnsRrKey::SIG_LABELS)
358    }
359
360    /// Original TTL ([`SIG_ORIGINAL_TTL`](DnsRrKey::SIG_ORIGINAL_TTL)).
361    pub fn original_ttl(self) -> u32 {
362        self.0.get_u32(DnsRrKey::SIG_ORIGINAL_TTL)
363    }
364
365    /// Signature expiration time
366    /// ([`SIG_EXPIRATION`](DnsRrKey::SIG_EXPIRATION)).
367    pub fn expiration(self) -> u32 {
368        self.0.get_u32(DnsRrKey::SIG_EXPIRATION)
369    }
370
371    /// Signature inception time
372    /// ([`SIG_INCEPTION`](DnsRrKey::SIG_INCEPTION)).
373    pub fn inception(self) -> u32 {
374        self.0.get_u32(DnsRrKey::SIG_INCEPTION)
375    }
376
377    /// Key tag ([`SIG_KEY_TAG`](DnsRrKey::SIG_KEY_TAG)).
378    pub fn key_tag(self) -> u16 {
379        self.0.get_u16(DnsRrKey::SIG_KEY_TAG)
380    }
381
382    /// Signer's name ([`SIG_SIGNERS_NAME`](DnsRrKey::SIG_SIGNERS_NAME)).
383    pub fn signers_name(self) -> &'a str {
384        self.0.get_str(DnsRrKey::SIG_SIGNERS_NAME).unwrap_or("")
385    }
386
387    /// Signature data ([`SIG_SIGNATURE`](DnsRrKey::SIG_SIGNATURE)).
388    pub fn signature(self) -> &'a [u8] {
389        self.0.get_bin(DnsRrKey::SIG_SIGNATURE).unwrap_or(&[])
390    }
391}
392
393impl fmt::Debug for SigRecord<'_> {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        f.debug_struct("SigRecord")
396            .field("name", &self.name())
397            .field("ttl", &self.ttl())
398            .field("type_covered", &self.type_covered())
399            .field("algorithm", &self.algorithm())
400            .field("labels", &self.labels())
401            .field("original_ttl", &self.original_ttl())
402            .field("expiration", &self.expiration())
403            .field("inception", &self.inception())
404            .field("key_tag", &self.key_tag())
405            .field("signers_name", &self.signers_name())
406            .field("signature_len", &self.signature().len())
407            .finish()
408    }
409}
410
411/// Typed view of an [`SRV`](DnsRecordType::SRV) record.
412#[derive(Copy, Clone)]
413pub struct SrvRecord<'a>(&'a DnsRr);
414
415impl<'a> SrvRecord<'a> {
416    common_accessors!(SrvRecord);
417
418    /// Priority ([`SRV_PRIORITY`](DnsRrKey::SRV_PRIORITY)).
419    pub fn priority(self) -> u16 {
420        self.0.get_u16(DnsRrKey::SRV_PRIORITY)
421    }
422
423    /// Weight ([`SRV_WEIGHT`](DnsRrKey::SRV_WEIGHT)).
424    pub fn weight(self) -> u16 {
425        self.0.get_u16(DnsRrKey::SRV_WEIGHT)
426    }
427
428    /// Port ([`SRV_PORT`](DnsRrKey::SRV_PORT)).
429    pub fn port(self) -> u16 {
430        self.0.get_u16(DnsRrKey::SRV_PORT)
431    }
432
433    /// Target domain ([`SRV_TARGET`](DnsRrKey::SRV_TARGET)).
434    pub fn target(self) -> &'a str {
435        self.0.get_str(DnsRrKey::SRV_TARGET).unwrap_or("")
436    }
437}
438
439impl fmt::Debug for SrvRecord<'_> {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        f.debug_struct("SrvRecord")
442            .field("name", &self.name())
443            .field("ttl", &self.ttl())
444            .field("priority", &self.priority())
445            .field("weight", &self.weight())
446            .field("port", &self.port())
447            .field("target", &self.target())
448            .finish()
449    }
450}
451
452/// Typed view of a [`NAPTR`](DnsRecordType::NAPTR) record.
453#[derive(Copy, Clone)]
454pub struct NaptrRecord<'a>(&'a DnsRr);
455
456impl<'a> NaptrRecord<'a> {
457    common_accessors!(NaptrRecord);
458
459    /// Order ([`NAPTR_ORDER`](DnsRrKey::NAPTR_ORDER)).
460    pub fn order(self) -> u16 {
461        self.0.get_u16(DnsRrKey::NAPTR_ORDER)
462    }
463
464    /// Preference ([`NAPTR_PREFERENCE`](DnsRrKey::NAPTR_PREFERENCE)).
465    pub fn preference(self) -> u16 {
466        self.0.get_u16(DnsRrKey::NAPTR_PREFERENCE)
467    }
468
469    /// Flags ([`NAPTR_FLAGS`](DnsRrKey::NAPTR_FLAGS)).
470    pub fn flags(self) -> &'a str {
471        self.0.get_str(DnsRrKey::NAPTR_FLAGS).unwrap_or("")
472    }
473
474    /// Services ([`NAPTR_SERVICES`](DnsRrKey::NAPTR_SERVICES)).
475    pub fn services(self) -> &'a str {
476        self.0.get_str(DnsRrKey::NAPTR_SERVICES).unwrap_or("")
477    }
478
479    /// Regular expression ([`NAPTR_REGEXP`](DnsRrKey::NAPTR_REGEXP)).
480    pub fn regexp(self) -> &'a str {
481        self.0.get_str(DnsRrKey::NAPTR_REGEXP).unwrap_or("")
482    }
483
484    /// Replacement domain ([`NAPTR_REPLACEMENT`](DnsRrKey::NAPTR_REPLACEMENT)).
485    pub fn replacement(self) -> &'a str {
486        self.0.get_str(DnsRrKey::NAPTR_REPLACEMENT).unwrap_or("")
487    }
488}
489
490impl fmt::Debug for NaptrRecord<'_> {
491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492        f.debug_struct("NaptrRecord")
493            .field("name", &self.name())
494            .field("ttl", &self.ttl())
495            .field("order", &self.order())
496            .field("preference", &self.preference())
497            .field("flags", &self.flags())
498            .field("services", &self.services())
499            .field("regexp", &self.regexp())
500            .field("replacement", &self.replacement())
501            .finish()
502    }
503}
504
505/// Typed view of a [`TLSA`](DnsRecordType::TLSA) record.
506#[derive(Copy, Clone)]
507pub struct TlsaRecord<'a>(&'a DnsRr);
508
509impl<'a> TlsaRecord<'a> {
510    common_accessors!(TlsaRecord);
511
512    /// Certificate usage ([`TLSA_CERT_USAGE`](DnsRrKey::TLSA_CERT_USAGE)).
513    pub fn cert_usage(self) -> u8 {
514        self.0.get_u8(DnsRrKey::TLSA_CERT_USAGE)
515    }
516
517    /// Selector ([`TLSA_SELECTOR`](DnsRrKey::TLSA_SELECTOR)).
518    pub fn selector(self) -> u8 {
519        self.0.get_u8(DnsRrKey::TLSA_SELECTOR)
520    }
521
522    /// Matching type ([`TLSA_MATCH`](DnsRrKey::TLSA_MATCH)).
523    pub fn matching_type(self) -> u8 {
524        self.0.get_u8(DnsRrKey::TLSA_MATCH)
525    }
526
527    /// Certificate association data ([`TLSA_DATA`](DnsRrKey::TLSA_DATA)).
528    pub fn data(self) -> &'a [u8] {
529        self.0.get_bin(DnsRrKey::TLSA_DATA).unwrap_or(&[])
530    }
531}
532
533impl fmt::Debug for TlsaRecord<'_> {
534    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535        f.debug_struct("TlsaRecord")
536            .field("name", &self.name())
537            .field("ttl", &self.ttl())
538            .field("cert_usage", &self.cert_usage())
539            .field("selector", &self.selector())
540            .field("matching_type", &self.matching_type())
541            .field("data_len", &self.data().len())
542            .finish()
543    }
544}
545
546/// Typed view of a [`URI`](DnsRecordType::URI) record.
547#[derive(Copy, Clone)]
548pub struct UriRecord<'a>(&'a DnsRr);
549
550impl<'a> UriRecord<'a> {
551    common_accessors!(UriRecord);
552
553    /// Priority ([`URI_PRIORITY`](DnsRrKey::URI_PRIORITY)).
554    pub fn priority(self) -> u16 {
555        self.0.get_u16(DnsRrKey::URI_PRIORITY)
556    }
557
558    /// Weight ([`URI_WEIGHT`](DnsRrKey::URI_WEIGHT)).
559    pub fn weight(self) -> u16 {
560        self.0.get_u16(DnsRrKey::URI_WEIGHT)
561    }
562
563    /// Target URI ([`URI_TARGET`](DnsRrKey::URI_TARGET)).
564    pub fn target(self) -> &'a str {
565        self.0.get_str(DnsRrKey::URI_TARGET).unwrap_or("")
566    }
567}
568
569impl fmt::Debug for UriRecord<'_> {
570    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
571        f.debug_struct("UriRecord")
572            .field("name", &self.name())
573            .field("ttl", &self.ttl())
574            .field("priority", &self.priority())
575            .field("weight", &self.weight())
576            .field("target", &self.target())
577            .finish()
578    }
579}
580
581// =============================================================================
582// Variable-shape record wrappers
583// =============================================================================
584
585/// Typed view of a [`TXT`](DnsRecordType::TXT) record.
586///
587/// TXT records carry an array of binary strings. Each entry is a `&[u8]`
588/// because the wire format does not require UTF-8.
589#[derive(Copy, Clone)]
590pub struct TxtRecord<'a>(&'a DnsRr);
591
592impl<'a> TxtRecord<'a> {
593    common_accessors!(TxtRecord);
594
595    /// Returns the number of TXT entries
596    /// ([`TXT_DATA`](DnsRrKey::TXT_DATA)).
597    pub fn entry_count(self) -> usize {
598        self.0.get_abin_count(DnsRrKey::TXT_DATA)
599    }
600
601    /// Returns an iterator over the TXT entries
602    /// ([`TXT_DATA`](DnsRrKey::TXT_DATA)).
603    pub fn entries(self) -> impl Iterator<Item = &'a [u8]> {
604        self.0.abins(DnsRrKey::TXT_DATA)
605    }
606}
607
608impl fmt::Debug for TxtRecord<'_> {
609    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610        f.debug_struct("TxtRecord")
611            .field("name", &self.name())
612            .field("ttl", &self.ttl())
613            .field("entry_count", &self.entry_count())
614            .finish()
615    }
616}
617
618/// Typed view of an [`OPT`](DnsRecordType::OPT) (EDNS0) record.
619///
620/// OPT records repurpose the class and TTL fields for EDNS metadata; the
621/// generic [`DnsRr::dns_class`] and [`DnsRr::ttl`] still return those raw
622/// values, but the typed accessors below interpret them per RFC 6891.
623#[derive(Copy, Clone)]
624pub struct OptRecord<'a>(&'a DnsRr);
625
626impl<'a> OptRecord<'a> {
627    common_accessors!(OptRecord);
628
629    /// Sender's UDP payload size
630    /// ([`OPT_UDP_SIZE`](DnsRrKey::OPT_UDP_SIZE)).
631    pub fn udp_size(self) -> u16 {
632        self.0.get_u16(DnsRrKey::OPT_UDP_SIZE)
633    }
634
635    /// EDNS version ([`OPT_VERSION`](DnsRrKey::OPT_VERSION)).
636    pub fn version(self) -> u8 {
637        self.0.get_u8(DnsRrKey::OPT_VERSION)
638    }
639
640    /// EDNS flags ([`OPT_FLAGS`](DnsRrKey::OPT_FLAGS)).
641    pub fn flags(self) -> u16 {
642        self.0.get_u16(DnsRrKey::OPT_FLAGS)
643    }
644
645    /// Returns the number of EDNS options
646    /// ([`OPT_OPTIONS`](DnsRrKey::OPT_OPTIONS)).
647    pub fn option_count(self) -> usize {
648        self.0.get_opt_count(DnsRrKey::OPT_OPTIONS)
649    }
650
651    /// Returns an iterator over EDNS options
652    /// ([`OPT_OPTIONS`](DnsRrKey::OPT_OPTIONS)) yielding
653    /// `(option_code, decoded_value)` pairs.
654    ///
655    /// The value is decoded according to its registered datatype (see
656    /// [`OptValue`]). For options the linked c-ares does not recognise,
657    /// decoding may fail; use [`raw_options`](Self::raw_options) to
658    /// access the underlying byte slice instead.
659    pub fn options(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
660        self.0
661            .opts(DnsRrKey::OPT_OPTIONS)
662            .map(|(k, v)| (k, parse_opt_value(DnsRrKey::OPT_OPTIONS, k, v)))
663    }
664
665    /// Returns an iterator over EDNS options
666    /// ([`OPT_OPTIONS`](DnsRrKey::OPT_OPTIONS)) yielding
667    /// `(option_code, value_bytes)` pairs without decoding.
668    pub fn raw_options(self) -> impl Iterator<Item = (u16, &'a [u8])> {
669        self.0.opts(DnsRrKey::OPT_OPTIONS)
670    }
671}
672
673impl fmt::Debug for OptRecord<'_> {
674    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675        f.debug_struct("OptRecord")
676            .field("udp_size", &self.udp_size())
677            .field("version", &self.version())
678            .field("flags", &self.flags())
679            .field("option_count", &self.option_count())
680            .finish()
681    }
682}
683
684/// Typed view of an [`SVCB`](DnsRecordType::SVCB) record.
685#[derive(Copy, Clone)]
686pub struct SvcbRecord<'a>(&'a DnsRr);
687
688impl<'a> SvcbRecord<'a> {
689    common_accessors!(SvcbRecord);
690
691    /// SvcPriority ([`SVCB_PRIORITY`](DnsRrKey::SVCB_PRIORITY)).
692    pub fn priority(self) -> u16 {
693        self.0.get_u16(DnsRrKey::SVCB_PRIORITY)
694    }
695
696    /// TargetName ([`SVCB_TARGET`](DnsRrKey::SVCB_TARGET)).
697    pub fn target(self) -> &'a str {
698        self.0.get_str(DnsRrKey::SVCB_TARGET).unwrap_or("")
699    }
700
701    /// Returns the number of SvcParams
702    /// ([`SVCB_PARAMS`](DnsRrKey::SVCB_PARAMS)).
703    pub fn param_count(self) -> usize {
704        self.0.get_opt_count(DnsRrKey::SVCB_PARAMS)
705    }
706
707    /// Returns an iterator over SvcParams
708    /// ([`SVCB_PARAMS`](DnsRrKey::SVCB_PARAMS)) yielding
709    /// `(param_key, decoded_value)` pairs.
710    ///
711    /// The value is decoded according to its registered datatype (see
712    /// [`OptValue`]). For parameters the linked c-ares does not
713    /// recognise, decoding may fail; use [`raw_params`](Self::raw_params)
714    /// to access the underlying byte slice instead.
715    pub fn params(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
716        self.0
717            .opts(DnsRrKey::SVCB_PARAMS)
718            .map(|(k, v)| (k, parse_opt_value(DnsRrKey::SVCB_PARAMS, k, v)))
719    }
720
721    /// Returns an iterator over SvcParams
722    /// ([`SVCB_PARAMS`](DnsRrKey::SVCB_PARAMS)) yielding
723    /// `(param_key, value_bytes)` pairs without decoding.
724    pub fn raw_params(self) -> impl Iterator<Item = (u16, &'a [u8])> {
725        self.0.opts(DnsRrKey::SVCB_PARAMS)
726    }
727}
728
729impl fmt::Debug for SvcbRecord<'_> {
730    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731        f.debug_struct("SvcbRecord")
732            .field("name", &self.name())
733            .field("ttl", &self.ttl())
734            .field("priority", &self.priority())
735            .field("target", &self.target())
736            .field("param_count", &self.param_count())
737            .finish()
738    }
739}
740
741/// Typed view of an [`HTTPS`](DnsRecordType::HTTPS) record.
742#[derive(Copy, Clone)]
743pub struct HttpsRecord<'a>(&'a DnsRr);
744
745impl<'a> HttpsRecord<'a> {
746    common_accessors!(HttpsRecord);
747
748    /// SvcPriority ([`HTTPS_PRIORITY`](DnsRrKey::HTTPS_PRIORITY)).
749    pub fn priority(self) -> u16 {
750        self.0.get_u16(DnsRrKey::HTTPS_PRIORITY)
751    }
752
753    /// TargetName ([`HTTPS_TARGET`](DnsRrKey::HTTPS_TARGET)).
754    pub fn target(self) -> &'a str {
755        self.0.get_str(DnsRrKey::HTTPS_TARGET).unwrap_or("")
756    }
757
758    /// Returns the number of SvcParams
759    /// ([`HTTPS_PARAMS`](DnsRrKey::HTTPS_PARAMS)).
760    pub fn param_count(self) -> usize {
761        self.0.get_opt_count(DnsRrKey::HTTPS_PARAMS)
762    }
763
764    /// Returns an iterator over SvcParams
765    /// ([`HTTPS_PARAMS`](DnsRrKey::HTTPS_PARAMS)) yielding
766    /// `(param_key, decoded_value)` pairs.
767    ///
768    /// The value is decoded according to its registered datatype (see
769    /// [`OptValue`]). For parameters the linked c-ares does not
770    /// recognise, decoding may fail; use [`raw_params`](Self::raw_params)
771    /// to access the underlying byte slice instead.
772    pub fn params(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
773        self.0
774            .opts(DnsRrKey::HTTPS_PARAMS)
775            .map(|(k, v)| (k, parse_opt_value(DnsRrKey::HTTPS_PARAMS, k, v)))
776    }
777
778    /// Returns an iterator over SvcParams
779    /// ([`HTTPS_PARAMS`](DnsRrKey::HTTPS_PARAMS)) yielding
780    /// `(param_key, value_bytes)` pairs without decoding.
781    pub fn raw_params(self) -> impl Iterator<Item = (u16, &'a [u8])> {
782        self.0.opts(DnsRrKey::HTTPS_PARAMS)
783    }
784}
785
786impl fmt::Debug for HttpsRecord<'_> {
787    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788        f.debug_struct("HttpsRecord")
789            .field("name", &self.name())
790            .field("ttl", &self.ttl())
791            .field("priority", &self.priority())
792            .field("target", &self.target())
793            .field("param_count", &self.param_count())
794            .finish()
795    }
796}
797
798/// Typed view of a [`CAA`](DnsRecordType::CAA) record.
799#[derive(Copy, Clone)]
800pub struct CaaRecord<'a>(&'a DnsRr);
801
802impl<'a> CaaRecord<'a> {
803    common_accessors!(CaaRecord);
804
805    /// Raw flags byte ([`CAA_CRITICAL`](DnsRrKey::CAA_CRITICAL)).
806    ///
807    /// RFC 8659 defines only the issuer-critical bit (`0x80`); use
808    /// [`is_critical`](Self::is_critical) for that bit specifically.
809    pub fn flags(self) -> u8 {
810        self.0.get_u8(DnsRrKey::CAA_CRITICAL)
811    }
812
813    /// Returns `true` iff the issuer-critical bit (`0x80`) is set in
814    /// the flags byte.
815    pub fn is_critical(self) -> bool {
816        (self.flags() & 0x80) != 0
817    }
818
819    /// Property tag ([`CAA_TAG`](DnsRrKey::CAA_TAG)), e.g. `"issue"`.
820    pub fn tag(self) -> &'a str {
821        self.0.get_str(DnsRrKey::CAA_TAG).unwrap_or("")
822    }
823
824    /// Property value ([`CAA_VALUE`](DnsRrKey::CAA_VALUE)).
825    ///
826    /// Returned as raw bytes; CAA values are usually printable ASCII but
827    /// the RFC does not require valid UTF-8.
828    pub fn value(self) -> &'a [u8] {
829        self.0.get_bin(DnsRrKey::CAA_VALUE).unwrap_or(&[])
830    }
831}
832
833impl fmt::Debug for CaaRecord<'_> {
834    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835        f.debug_struct("CaaRecord")
836            .field("name", &self.name())
837            .field("ttl", &self.ttl())
838            .field("flags", &self.flags())
839            .field("is_critical", &self.is_critical())
840            .field("tag", &self.tag())
841            .field("value_len", &self.value().len())
842            .finish()
843    }
844}
845
846/// Typed view of a [`RAW_RR`](DnsRecordType::RAW_RR) record.
847///
848/// `RAW_RR` is c-ares's catch-all for record types it does not parse
849/// natively. The original wire-format type code is preserved in
850/// [`raw_type`](Self::raw_type), and the unparsed RDATA in
851/// [`data`](Self::data).
852#[derive(Copy, Clone)]
853pub struct RawRrRecord<'a>(&'a DnsRr);
854
855impl<'a> RawRrRecord<'a> {
856    common_accessors!(RawRrRecord);
857
858    /// Original wire-format RR type code
859    /// ([`RAW_RR_TYPE`](DnsRrKey::RAW_RR_TYPE)).
860    ///
861    /// Distinct from [`DnsRr::rr_type`], which always returns
862    /// [`DnsRecordType::RAW_RR`] for these records.
863    pub fn raw_type(self) -> u16 {
864        self.0.get_u16(DnsRrKey::RAW_RR_TYPE)
865    }
866
867    /// Unparsed RDATA bytes ([`RAW_RR_DATA`](DnsRrKey::RAW_RR_DATA)).
868    ///
869    /// Empty for records with zero-length RDATA. The DNS wire format
870    /// does not distinguish "absent" from "zero-length" RDATA.
871    pub fn data(self) -> &'a [u8] {
872        self.0.get_bin(DnsRrKey::RAW_RR_DATA).unwrap_or(&[])
873    }
874}
875
876impl fmt::Debug for RawRrRecord<'_> {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        f.debug_struct("RawRrRecord")
879            .field("name", &self.name())
880            .field("ttl", &self.ttl())
881            .field("raw_type", &self.raw_type())
882            .field("data_len", &self.data().len())
883            .finish()
884    }
885}
886
887// =============================================================================
888// TypedRr enum + dispatch
889// =============================================================================
890
891/// Match-friendly enumeration of every typed record view.
892///
893/// Returned by [`DnsRr::as_typed`]. Marked `#[non_exhaustive]` so future
894/// record types can be added without breaking exhaustive matches.
895#[derive(Copy, Clone, Debug)]
896#[non_exhaustive]
897pub enum TypedRr<'a> {
898    /// IPv4 address record.
899    A(ARecord<'a>),
900    /// IPv6 address record.
901    Aaaa(AaaaRecord<'a>),
902    /// Authoritative nameserver record.
903    Ns(NsRecord<'a>),
904    /// Canonical name record.
905    Cname(CnameRecord<'a>),
906    /// Start of authority record.
907    Soa(SoaRecord<'a>),
908    /// Domain name pointer record.
909    Ptr(PtrRecord<'a>),
910    /// Host information record.
911    Hinfo(HinfoRecord<'a>),
912    /// Mail exchange record.
913    Mx(MxRecord<'a>),
914    /// Text record.
915    Txt(TxtRecord<'a>),
916    /// SIG (RFC 2535 / 2931) record.
917    Sig(SigRecord<'a>),
918    /// Service location record.
919    Srv(SrvRecord<'a>),
920    /// Naming authority pointer record.
921    Naptr(NaptrRecord<'a>),
922    /// EDNS0 OPT pseudo-record.
923    Opt(OptRecord<'a>),
924    /// DANE TLSA record.
925    Tlsa(TlsaRecord<'a>),
926    /// Service binding record.
927    Svcb(SvcbRecord<'a>),
928    /// HTTPS service binding record.
929    Https(HttpsRecord<'a>),
930    /// URI record.
931    Uri(UriRecord<'a>),
932    /// Certification authority authorization record.
933    Caa(CaaRecord<'a>),
934    /// Raw / unparsed record.
935    RawRr(RawRrRecord<'a>),
936    /// Wildcard request type. Should not appear in responses; carries the
937    /// underlying generic record for completeness.
938    Any(&'a DnsRr),
939    /// Unknown record type. Carries the underlying generic record.
940    Unknown(&'a DnsRr),
941}
942
943// =============================================================================
944// DnsRr discriminator methods
945// =============================================================================
946
947impl DnsRr {
948    /// Returns a typed [`ARecord`] view if this record is of type
949    /// [`A`](DnsRecordType::A).
950    pub fn as_a(&self) -> Option<ARecord<'_>> {
951        (self.rr_type() == DnsRecordType::A).then(|| ARecord::new(self))
952    }
953
954    /// Returns a typed [`AaaaRecord`] view if this record is of type
955    /// [`AAAA`](DnsRecordType::AAAA).
956    pub fn as_aaaa(&self) -> Option<AaaaRecord<'_>> {
957        (self.rr_type() == DnsRecordType::AAAA).then(|| AaaaRecord::new(self))
958    }
959
960    /// Returns a typed [`NsRecord`] view if this record is of type
961    /// [`NS`](DnsRecordType::NS).
962    pub fn as_ns(&self) -> Option<NsRecord<'_>> {
963        (self.rr_type() == DnsRecordType::NS).then(|| NsRecord::new(self))
964    }
965
966    /// Returns a typed [`CnameRecord`] view if this record is of type
967    /// [`CNAME`](DnsRecordType::CNAME).
968    pub fn as_cname(&self) -> Option<CnameRecord<'_>> {
969        (self.rr_type() == DnsRecordType::CNAME).then(|| CnameRecord::new(self))
970    }
971
972    /// Returns a typed [`SoaRecord`] view if this record is of type
973    /// [`SOA`](DnsRecordType::SOA).
974    pub fn as_soa(&self) -> Option<SoaRecord<'_>> {
975        (self.rr_type() == DnsRecordType::SOA).then(|| SoaRecord::new(self))
976    }
977
978    /// Returns a typed [`PtrRecord`] view if this record is of type
979    /// [`PTR`](DnsRecordType::PTR).
980    pub fn as_ptr_rr(&self) -> Option<PtrRecord<'_>> {
981        (self.rr_type() == DnsRecordType::PTR).then(|| PtrRecord::new(self))
982    }
983
984    /// Returns a typed [`HinfoRecord`] view if this record is of type
985    /// [`HINFO`](DnsRecordType::HINFO).
986    pub fn as_hinfo(&self) -> Option<HinfoRecord<'_>> {
987        (self.rr_type() == DnsRecordType::HINFO).then(|| HinfoRecord::new(self))
988    }
989
990    /// Returns a typed [`MxRecord`] view if this record is of type
991    /// [`MX`](DnsRecordType::MX).
992    pub fn as_mx(&self) -> Option<MxRecord<'_>> {
993        (self.rr_type() == DnsRecordType::MX).then(|| MxRecord::new(self))
994    }
995
996    /// Returns a typed [`TxtRecord`] view if this record is of type
997    /// [`TXT`](DnsRecordType::TXT).
998    pub fn as_txt(&self) -> Option<TxtRecord<'_>> {
999        (self.rr_type() == DnsRecordType::TXT).then(|| TxtRecord::new(self))
1000    }
1001
1002    /// Returns a typed [`SigRecord`] view if this record is of type
1003    /// [`SIG`](DnsRecordType::SIG).
1004    pub fn as_sig(&self) -> Option<SigRecord<'_>> {
1005        (self.rr_type() == DnsRecordType::SIG).then(|| SigRecord::new(self))
1006    }
1007
1008    /// Returns a typed [`SrvRecord`] view if this record is of type
1009    /// [`SRV`](DnsRecordType::SRV).
1010    pub fn as_srv(&self) -> Option<SrvRecord<'_>> {
1011        (self.rr_type() == DnsRecordType::SRV).then(|| SrvRecord::new(self))
1012    }
1013
1014    /// Returns a typed [`NaptrRecord`] view if this record is of type
1015    /// [`NAPTR`](DnsRecordType::NAPTR).
1016    pub fn as_naptr(&self) -> Option<NaptrRecord<'_>> {
1017        (self.rr_type() == DnsRecordType::NAPTR).then(|| NaptrRecord::new(self))
1018    }
1019
1020    /// Returns a typed [`OptRecord`] view if this record is of type
1021    /// [`OPT`](DnsRecordType::OPT).
1022    pub fn as_opt(&self) -> Option<OptRecord<'_>> {
1023        (self.rr_type() == DnsRecordType::OPT).then(|| OptRecord::new(self))
1024    }
1025
1026    /// Returns a typed [`TlsaRecord`] view if this record is of type
1027    /// [`TLSA`](DnsRecordType::TLSA).
1028    pub fn as_tlsa(&self) -> Option<TlsaRecord<'_>> {
1029        (self.rr_type() == DnsRecordType::TLSA).then(|| TlsaRecord::new(self))
1030    }
1031
1032    /// Returns a typed [`SvcbRecord`] view if this record is of type
1033    /// [`SVCB`](DnsRecordType::SVCB).
1034    pub fn as_svcb(&self) -> Option<SvcbRecord<'_>> {
1035        (self.rr_type() == DnsRecordType::SVCB).then(|| SvcbRecord::new(self))
1036    }
1037
1038    /// Returns a typed [`HttpsRecord`] view if this record is of type
1039    /// [`HTTPS`](DnsRecordType::HTTPS).
1040    pub fn as_https(&self) -> Option<HttpsRecord<'_>> {
1041        (self.rr_type() == DnsRecordType::HTTPS).then(|| HttpsRecord::new(self))
1042    }
1043
1044    /// Returns a typed [`UriRecord`] view if this record is of type
1045    /// [`URI`](DnsRecordType::URI).
1046    pub fn as_uri(&self) -> Option<UriRecord<'_>> {
1047        (self.rr_type() == DnsRecordType::URI).then(|| UriRecord::new(self))
1048    }
1049
1050    /// Returns a typed [`CaaRecord`] view if this record is of type
1051    /// [`CAA`](DnsRecordType::CAA).
1052    pub fn as_caa(&self) -> Option<CaaRecord<'_>> {
1053        (self.rr_type() == DnsRecordType::CAA).then(|| CaaRecord::new(self))
1054    }
1055
1056    /// Returns a typed [`RawRrRecord`] view if this record is of type
1057    /// [`RAW_RR`](DnsRecordType::RAW_RR).
1058    pub fn as_raw_rr(&self) -> Option<RawRrRecord<'_>> {
1059        (self.rr_type() == DnsRecordType::RAW_RR).then(|| RawRrRecord::new(self))
1060    }
1061
1062    /// Dispatches on [`rr_type`](Self::rr_type) and returns a
1063    /// match-friendly [`TypedRr`] view.
1064    pub fn as_typed(&self) -> TypedRr<'_> {
1065        match self.rr_type() {
1066            DnsRecordType::A => TypedRr::A(ARecord::new(self)),
1067            DnsRecordType::AAAA => TypedRr::Aaaa(AaaaRecord::new(self)),
1068            DnsRecordType::NS => TypedRr::Ns(NsRecord::new(self)),
1069            DnsRecordType::CNAME => TypedRr::Cname(CnameRecord::new(self)),
1070            DnsRecordType::SOA => TypedRr::Soa(SoaRecord::new(self)),
1071            DnsRecordType::PTR => TypedRr::Ptr(PtrRecord::new(self)),
1072            DnsRecordType::HINFO => TypedRr::Hinfo(HinfoRecord::new(self)),
1073            DnsRecordType::MX => TypedRr::Mx(MxRecord::new(self)),
1074            DnsRecordType::TXT => TypedRr::Txt(TxtRecord::new(self)),
1075            DnsRecordType::SIG => TypedRr::Sig(SigRecord::new(self)),
1076            DnsRecordType::SRV => TypedRr::Srv(SrvRecord::new(self)),
1077            DnsRecordType::NAPTR => TypedRr::Naptr(NaptrRecord::new(self)),
1078            DnsRecordType::OPT => TypedRr::Opt(OptRecord::new(self)),
1079            DnsRecordType::TLSA => TypedRr::Tlsa(TlsaRecord::new(self)),
1080            DnsRecordType::SVCB => TypedRr::Svcb(SvcbRecord::new(self)),
1081            DnsRecordType::HTTPS => TypedRr::Https(HttpsRecord::new(self)),
1082            DnsRecordType::URI => TypedRr::Uri(UriRecord::new(self)),
1083            DnsRecordType::CAA => TypedRr::Caa(CaaRecord::new(self)),
1084            DnsRecordType::RAW_RR => TypedRr::RawRr(RawRrRecord::new(self)),
1085            DnsRecordType::ANY => TypedRr::Any(self),
1086            DnsRecordType::UNKNOWN(_) => TypedRr::Unknown(self),
1087        }
1088    }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093    use super::*;
1094    use crate::dns::{DnsFlags, DnsOpcode, DnsParseFlags, DnsRcode, DnsRecord, DnsSection};
1095
1096    fn assert_send<T: Send>() {}
1097    fn assert_sync<T: Sync>() {}
1098    fn assert_copy<T: Copy>() {}
1099
1100    #[test]
1101    fn wrappers_are_send_sync_copy() {
1102        assert_send::<ARecord<'_>>();
1103        assert_sync::<ARecord<'_>>();
1104        assert_copy::<ARecord<'_>>();
1105        assert_send::<TypedRr<'_>>();
1106        assert_sync::<TypedRr<'_>>();
1107        assert_copy::<TypedRr<'_>>();
1108    }
1109
1110    fn make_rec(rtype: DnsRecordType) -> DnsRecord {
1111        let mut rec =
1112            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
1113        // Use A as a generic question type — RAW_RR/OPT/etc. aren't
1114        // valid query types but can still appear as RRs in the answer
1115        // section.
1116        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
1117            .expect("query_add");
1118        rec.rr_add(DnsSection::Answer, "example.com", rtype, DnsCls::IN, 300)
1119            .expect("rr_add");
1120        rec
1121    }
1122
1123    fn first_rr(rec: &DnsRecord) -> &DnsRr {
1124        rec.rr(DnsSection::Answer, 0).expect("rr")
1125    }
1126
1127    // ---- Type-discriminator tests ------------------------------------------
1128
1129    #[test]
1130    fn as_a_matches_only_a() {
1131        let rec = make_rec(DnsRecordType::A);
1132        let rr = first_rr(&rec);
1133        assert!(rr.as_a().is_some());
1134        assert!(rr.as_aaaa().is_none());
1135        assert!(rr.as_mx().is_none());
1136        assert!(rr.as_txt().is_none());
1137    }
1138
1139    #[test]
1140    fn as_typed_dispatches_correctly() {
1141        for &rtype in &[
1142            DnsRecordType::A,
1143            DnsRecordType::AAAA,
1144            DnsRecordType::MX,
1145            DnsRecordType::TXT,
1146            DnsRecordType::CAA,
1147            DnsRecordType::SRV,
1148        ] {
1149            let rec = make_rec(rtype);
1150            let rr = first_rr(&rec);
1151            match (rtype, rr.as_typed()) {
1152                (DnsRecordType::A, TypedRr::A(_))
1153                | (DnsRecordType::AAAA, TypedRr::Aaaa(_))
1154                | (DnsRecordType::MX, TypedRr::Mx(_))
1155                | (DnsRecordType::TXT, TypedRr::Txt(_))
1156                | (DnsRecordType::CAA, TypedRr::Caa(_))
1157                | (DnsRecordType::SRV, TypedRr::Srv(_)) => {}
1158                (rt, other) => panic!("type {rt:?} dispatched to wrong variant: {other:?}"),
1159            }
1160        }
1161    }
1162
1163    // ---- Builder partial-set tests (no panic on unset pointer fields) ------
1164
1165    #[test]
1166    fn builder_partial_fields_have_defaults() {
1167        // A record: c-ares pre-allocates the addr slot at creation.
1168        let rec = make_rec(DnsRecordType::A);
1169        let a = first_rr(&rec).as_a().expect("as_a");
1170        let _ = a.addr();
1171        assert_eq!(a.ttl(), 300);
1172
1173        // MX: preference defaults to 0, exchange to "".
1174        let rec = make_rec(DnsRecordType::MX);
1175        let mx = first_rr(&rec).as_mx().expect("as_mx");
1176        assert_eq!(mx.preference(), 0);
1177        assert_eq!(mx.exchange(), "");
1178
1179        // SOA: numeric and string defaults.
1180        let rec = make_rec(DnsRecordType::SOA);
1181        let soa = first_rr(&rec).as_soa().expect("as_soa");
1182        assert_eq!(soa.mname(), "");
1183        assert_eq!(soa.rname(), "");
1184        assert_eq!(soa.serial(), 0);
1185        assert_eq!(soa.minimum(), 0);
1186
1187        // CAA: flags zero, tag/value empty, not critical.
1188        let rec = make_rec(DnsRecordType::CAA);
1189        let caa = first_rr(&rec).as_caa().expect("as_caa");
1190        assert_eq!(caa.flags(), 0);
1191        assert!(!caa.is_critical());
1192        assert_eq!(caa.tag(), "");
1193        assert_eq!(caa.value(), &[] as &[u8]);
1194
1195        // TXT: empty entries, count zero.
1196        let rec = make_rec(DnsRecordType::TXT);
1197        let txt = first_rr(&rec).as_txt().expect("as_txt");
1198        assert_eq!(txt.entry_count(), 0);
1199        assert_eq!(txt.entries().count(), 0);
1200
1201        // RAW_RR: data empty, raw_type zero.
1202        let rec = make_rec(DnsRecordType::RAW_RR);
1203        let raw = first_rr(&rec).as_raw_rr().expect("as_raw_rr");
1204        assert_eq!(raw.raw_type(), 0);
1205        assert_eq!(raw.data(), &[] as &[u8]);
1206    }
1207
1208    // ---- Builder full-set + typed read-back --------------------------------
1209
1210    #[test]
1211    fn a_full_roundtrip() {
1212        let mut rec = make_rec(DnsRecordType::A);
1213        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
1214        rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(10, 0, 0, 1))
1215            .expect("set");
1216        let a = first_rr(&rec).as_a().expect("as_a");
1217        assert_eq!(a.addr(), Ipv4Addr::new(10, 0, 0, 1));
1218        assert_eq!(a.name(), "example.com");
1219    }
1220
1221    #[test]
1222    fn aaaa_full_roundtrip() {
1223        let mut rec = make_rec(DnsRecordType::AAAA);
1224        let addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
1225        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
1226        rr.set_addr6(DnsRrKey::AAAA_ADDR, addr).expect("set");
1227        let aaaa = first_rr(&rec).as_aaaa().expect("as_aaaa");
1228        assert_eq!(aaaa.addr(), addr);
1229    }
1230
1231    #[test]
1232    fn mx_full_roundtrip() {
1233        let mut rec = make_rec(DnsRecordType::MX);
1234        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
1235        rr.set_u16(DnsRrKey::MX_PREFERENCE, 10).expect("pref");
1236        rr.set_str(DnsRrKey::MX_EXCHANGE, "mail.example.com")
1237            .expect("exch");
1238        let mx = first_rr(&rec).as_mx().expect("as_mx");
1239        assert_eq!(mx.preference(), 10);
1240        assert_eq!(mx.exchange(), "mail.example.com");
1241    }
1242
1243    #[test]
1244    fn caa_critical_bit() {
1245        let mut rec = make_rec(DnsRecordType::CAA);
1246        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
1247        rr.set_u8(DnsRrKey::CAA_CRITICAL, 0x80).expect("crit");
1248        rr.set_str(DnsRrKey::CAA_TAG, "issue").expect("tag");
1249        rr.set_bin(DnsRrKey::CAA_VALUE, b"ca.example.net")
1250            .expect("val");
1251        let caa = first_rr(&rec).as_caa().expect("as_caa");
1252        assert_eq!(caa.flags(), 0x80);
1253        assert!(caa.is_critical());
1254        assert_eq!(caa.tag(), "issue");
1255        assert_eq!(caa.value(), &b"ca.example.net"[..]);
1256    }
1257
1258    #[test]
1259    fn txt_entries_iterator() {
1260        let mut rec = make_rec(DnsRecordType::TXT);
1261        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
1262        rr.add_abin(DnsRrKey::TXT_DATA, b"first").expect("a1");
1263        rr.add_abin(DnsRrKey::TXT_DATA, b"second").expect("a2");
1264        let txt = first_rr(&rec).as_txt().expect("as_txt");
1265        assert_eq!(txt.entry_count(), 2);
1266        let entries: Vec<&[u8]> = txt.entries().collect();
1267        assert_eq!(entries, vec![&b"first"[..], &b"second"[..]]);
1268    }
1269
1270    // ---- Parser-driven (wire round trip) -----------------------------------
1271
1272    fn build_then_parse(rec: &DnsRecord) -> DnsRecord {
1273        let wire = rec.write().expect("write");
1274        DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("parse")
1275    }
1276
1277    #[test]
1278    fn parsed_a_record() {
1279        let mut rec = make_rec(DnsRecordType::A);
1280        rec.rr_mut(DnsSection::Answer, 0)
1281            .unwrap()
1282            .set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(93, 184, 216, 34))
1283            .unwrap();
1284        let parsed = build_then_parse(&rec);
1285        let a = first_rr(&parsed).as_a().expect("as_a");
1286        assert_eq!(a.addr(), Ipv4Addr::new(93, 184, 216, 34));
1287    }
1288
1289    #[test]
1290    fn parsed_soa_record() {
1291        let mut rec = make_rec(DnsRecordType::SOA);
1292        let rr = rec.rr_mut(DnsSection::Answer, 0).unwrap();
1293        rr.set_str(DnsRrKey::SOA_MNAME, "ns1.example.com").unwrap();
1294        rr.set_str(DnsRrKey::SOA_RNAME, "hostmaster.example.com")
1295            .unwrap();
1296        rr.set_u32(DnsRrKey::SOA_SERIAL, 42).unwrap();
1297        rr.set_u32(DnsRrKey::SOA_REFRESH, 3600).unwrap();
1298        rr.set_u32(DnsRrKey::SOA_RETRY, 900).unwrap();
1299        rr.set_u32(DnsRrKey::SOA_EXPIRE, 604800).unwrap();
1300        rr.set_u32(DnsRrKey::SOA_MINIMUM, 60).unwrap();
1301        let parsed = build_then_parse(&rec);
1302        let soa = first_rr(&parsed).as_soa().expect("as_soa");
1303        assert_eq!(soa.mname(), "ns1.example.com");
1304        assert_eq!(soa.rname(), "hostmaster.example.com");
1305        assert_eq!(soa.serial(), 42);
1306        assert_eq!(soa.expire(), 604800);
1307    }
1308
1309    #[test]
1310    fn parsed_txt_record() {
1311        let mut rec = make_rec(DnsRecordType::TXT);
1312        rec.rr_mut(DnsSection::Answer, 0)
1313            .unwrap()
1314            .add_abin(DnsRrKey::TXT_DATA, b"v=spf1 -all")
1315            .unwrap();
1316        let parsed = build_then_parse(&rec);
1317        let txt = first_rr(&parsed).as_txt().expect("as_txt");
1318        assert_eq!(txt.entry_count(), 1);
1319        assert_eq!(txt.entries().next(), Some(&b"v=spf1 -all"[..]));
1320    }
1321
1322    #[test]
1323    fn parsed_mx_record() {
1324        let mut rec = make_rec(DnsRecordType::MX);
1325        let rr = rec.rr_mut(DnsSection::Answer, 0).unwrap();
1326        rr.set_u16(DnsRrKey::MX_PREFERENCE, 5).unwrap();
1327        rr.set_str(DnsRrKey::MX_EXCHANGE, "mx.example.com").unwrap();
1328        let parsed = build_then_parse(&rec);
1329        let mx = first_rr(&parsed).as_mx().expect("as_mx");
1330        assert_eq!(mx.preference(), 5);
1331        assert_eq!(mx.exchange(), "mx.example.com");
1332    }
1333
1334    // ---- Full-coverage sweep: every accessor + Debug + as_* discriminator -
1335
1336    /// Helper that builds a record, lets the caller populate it, then
1337    /// returns it.
1338    fn build<F: FnOnce(&mut DnsRr)>(rtype: DnsRecordType, populate: F) -> DnsRecord {
1339        let mut rec = make_rec(rtype);
1340        populate(rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut"));
1341        rec
1342    }
1343
1344    #[test]
1345    fn ns_full_accessors_and_debug() {
1346        let rec = build(DnsRecordType::NS, |rr| {
1347            rr.set_str(DnsRrKey::NS_NSDNAME, "ns1.example.com").unwrap();
1348        });
1349        let ns = first_rr(&rec).as_ns().expect("as_ns");
1350        assert_eq!(ns.nsdname(), "ns1.example.com");
1351        assert_eq!(ns.name(), "example.com");
1352        assert_eq!(ns.dns_class(), DnsCls::IN);
1353        assert_eq!(ns.ttl(), 300);
1354        let _ = ns.as_dns_rr();
1355        assert!(format!("{ns:?}").contains("NsRecord"));
1356        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Ns(_)));
1357    }
1358
1359    #[test]
1360    fn cname_full_accessors_and_debug() {
1361        let rec = build(DnsRecordType::CNAME, |rr| {
1362            rr.set_str(DnsRrKey::CNAME_CNAME, "alias.example.com")
1363                .unwrap();
1364        });
1365        let c = first_rr(&rec).as_cname().expect("as_cname");
1366        assert_eq!(c.cname(), "alias.example.com");
1367        assert!(format!("{c:?}").contains("CnameRecord"));
1368        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Cname(_)));
1369    }
1370
1371    #[test]
1372    fn ptr_full_accessors_and_debug() {
1373        let rec = build(DnsRecordType::PTR, |rr| {
1374            rr.set_str(DnsRrKey::PTR_DNAME, "host.example.com").unwrap();
1375        });
1376        let p = first_rr(&rec).as_ptr_rr().expect("as_ptr_rr");
1377        assert_eq!(p.dname(), "host.example.com");
1378        assert!(format!("{p:?}").contains("PtrRecord"));
1379        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Ptr(_)));
1380    }
1381
1382    #[test]
1383    fn hinfo_full_accessors_and_debug() {
1384        let rec = build(DnsRecordType::HINFO, |rr| {
1385            rr.set_str(DnsRrKey::HINFO_CPU, "x86_64").unwrap();
1386            rr.set_str(DnsRrKey::HINFO_OS, "linux").unwrap();
1387        });
1388        let h = first_rr(&rec).as_hinfo().expect("as_hinfo");
1389        assert_eq!(h.cpu(), "x86_64");
1390        assert_eq!(h.os(), "linux");
1391        assert!(format!("{h:?}").contains("HinfoRecord"));
1392        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Hinfo(_)));
1393    }
1394
1395    #[test]
1396    fn soa_full_accessors_and_debug() {
1397        let rec = build(DnsRecordType::SOA, |rr| {
1398            rr.set_str(DnsRrKey::SOA_MNAME, "ns.example.com").unwrap();
1399            rr.set_str(DnsRrKey::SOA_RNAME, "hostmaster.example.com")
1400                .unwrap();
1401            rr.set_u32(DnsRrKey::SOA_SERIAL, 1).unwrap();
1402            rr.set_u32(DnsRrKey::SOA_REFRESH, 2).unwrap();
1403            rr.set_u32(DnsRrKey::SOA_RETRY, 3).unwrap();
1404            rr.set_u32(DnsRrKey::SOA_EXPIRE, 4).unwrap();
1405            rr.set_u32(DnsRrKey::SOA_MINIMUM, 5).unwrap();
1406        });
1407        let s = first_rr(&rec).as_soa().expect("as_soa");
1408        assert_eq!(s.mname(), "ns.example.com");
1409        assert_eq!(s.rname(), "hostmaster.example.com");
1410        assert_eq!(s.serial(), 1);
1411        assert_eq!(s.refresh(), 2);
1412        assert_eq!(s.retry(), 3);
1413        assert_eq!(s.expire(), 4);
1414        assert_eq!(s.minimum(), 5);
1415        assert!(format!("{s:?}").contains("SoaRecord"));
1416        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Soa(_)));
1417    }
1418
1419    #[test]
1420    fn sig_full_accessors_and_debug() {
1421        let rec = build(DnsRecordType::SIG, |rr| {
1422            rr.set_u16(DnsRrKey::SIG_TYPE_COVERED, 1).unwrap();
1423            rr.set_u8(DnsRrKey::SIG_ALGORITHM, 8).unwrap();
1424            rr.set_u8(DnsRrKey::SIG_LABELS, 2).unwrap();
1425            rr.set_u32(DnsRrKey::SIG_ORIGINAL_TTL, 3600).unwrap();
1426            rr.set_u32(DnsRrKey::SIG_EXPIRATION, 4_000_000_000).unwrap();
1427            rr.set_u32(DnsRrKey::SIG_INCEPTION, 1_000_000_000).unwrap();
1428            rr.set_u16(DnsRrKey::SIG_KEY_TAG, 12345).unwrap();
1429            rr.set_str(DnsRrKey::SIG_SIGNERS_NAME, "example.com")
1430                .unwrap();
1431            rr.set_bin(DnsRrKey::SIG_SIGNATURE, b"\x01\x02\x03\x04")
1432                .unwrap();
1433        });
1434        let s = first_rr(&rec).as_sig().expect("as_sig");
1435        assert_eq!(s.type_covered(), 1);
1436        assert_eq!(s.algorithm(), 8);
1437        assert_eq!(s.labels(), 2);
1438        assert_eq!(s.original_ttl(), 3600);
1439        assert_eq!(s.expiration(), 4_000_000_000);
1440        assert_eq!(s.inception(), 1_000_000_000);
1441        assert_eq!(s.key_tag(), 12345);
1442        assert_eq!(s.signers_name(), "example.com");
1443        assert_eq!(s.signature(), &b"\x01\x02\x03\x04"[..]);
1444        assert!(format!("{s:?}").contains("SigRecord"));
1445        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Sig(_)));
1446    }
1447
1448    #[test]
1449    fn srv_full_accessors_and_debug() {
1450        let rec = build(DnsRecordType::SRV, |rr| {
1451            rr.set_u16(DnsRrKey::SRV_PRIORITY, 10).unwrap();
1452            rr.set_u16(DnsRrKey::SRV_WEIGHT, 20).unwrap();
1453            rr.set_u16(DnsRrKey::SRV_PORT, 443).unwrap();
1454            rr.set_str(DnsRrKey::SRV_TARGET, "svc.example.com").unwrap();
1455        });
1456        let s = first_rr(&rec).as_srv().expect("as_srv");
1457        assert_eq!(s.priority(), 10);
1458        assert_eq!(s.weight(), 20);
1459        assert_eq!(s.port(), 443);
1460        assert_eq!(s.target(), "svc.example.com");
1461        assert!(format!("{s:?}").contains("SrvRecord"));
1462    }
1463
1464    #[test]
1465    fn naptr_full_accessors_and_debug() {
1466        let rec = build(DnsRecordType::NAPTR, |rr| {
1467            rr.set_u16(DnsRrKey::NAPTR_ORDER, 100).unwrap();
1468            rr.set_u16(DnsRrKey::NAPTR_PREFERENCE, 10).unwrap();
1469            rr.set_str(DnsRrKey::NAPTR_FLAGS, "U").unwrap();
1470            rr.set_str(DnsRrKey::NAPTR_SERVICES, "E2U+sip").unwrap();
1471            rr.set_str(DnsRrKey::NAPTR_REGEXP, "!^.*$!sip:info@example.com!")
1472                .unwrap();
1473            rr.set_str(DnsRrKey::NAPTR_REPLACEMENT, ".").unwrap();
1474        });
1475        let n = first_rr(&rec).as_naptr().expect("as_naptr");
1476        assert_eq!(n.order(), 100);
1477        assert_eq!(n.preference(), 10);
1478        assert_eq!(n.flags(), "U");
1479        assert_eq!(n.services(), "E2U+sip");
1480        assert_eq!(n.regexp(), "!^.*$!sip:info@example.com!");
1481        assert_eq!(n.replacement(), ".");
1482        assert!(format!("{n:?}").contains("NaptrRecord"));
1483        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Naptr(_)));
1484    }
1485
1486    #[test]
1487    fn tlsa_full_accessors_and_debug() {
1488        let rec = build(DnsRecordType::TLSA, |rr| {
1489            rr.set_u8(DnsRrKey::TLSA_CERT_USAGE, 3).unwrap();
1490            rr.set_u8(DnsRrKey::TLSA_SELECTOR, 1).unwrap();
1491            rr.set_u8(DnsRrKey::TLSA_MATCH, 1).unwrap();
1492            rr.set_bin(DnsRrKey::TLSA_DATA, &[0xab; 32]).unwrap();
1493        });
1494        let t = first_rr(&rec).as_tlsa().expect("as_tlsa");
1495        assert_eq!(t.cert_usage(), 3);
1496        assert_eq!(t.selector(), 1);
1497        assert_eq!(t.matching_type(), 1);
1498        assert_eq!(t.data().len(), 32);
1499        assert!(format!("{t:?}").contains("TlsaRecord"));
1500        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Tlsa(_)));
1501    }
1502
1503    #[test]
1504    fn uri_full_accessors_and_debug() {
1505        let rec = build(DnsRecordType::URI, |rr| {
1506            rr.set_u16(DnsRrKey::URI_PRIORITY, 1).unwrap();
1507            rr.set_u16(DnsRrKey::URI_WEIGHT, 2).unwrap();
1508            rr.set_str(DnsRrKey::URI_TARGET, "https://example.com/")
1509                .unwrap();
1510        });
1511        let u = first_rr(&rec).as_uri().expect("as_uri");
1512        assert_eq!(u.priority(), 1);
1513        assert_eq!(u.weight(), 2);
1514        assert_eq!(u.target(), "https://example.com/");
1515        assert!(format!("{u:?}").contains("UriRecord"));
1516        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Uri(_)));
1517    }
1518
1519    #[test]
1520    fn svcb_full_accessors_and_debug() {
1521        let rec = build(DnsRecordType::SVCB, |rr| {
1522            rr.set_u16(DnsRrKey::SVCB_PRIORITY, 1).unwrap();
1523            rr.set_str(DnsRrKey::SVCB_TARGET, "svc.example.com")
1524                .unwrap();
1525            // ALPN option (key 1) with value "h2"
1526            rr.set_opt(DnsRrKey::SVCB_PARAMS, 1, b"\x00\x02h2").unwrap();
1527        });
1528        let s = first_rr(&rec).as_svcb().expect("as_svcb");
1529        assert_eq!(s.priority(), 1);
1530        assert_eq!(s.target(), "svc.example.com");
1531        assert_eq!(s.param_count(), 1);
1532        // raw_params() yields raw bytes
1533        let raw: Vec<(u16, &[u8])> = s.raw_params().collect();
1534        assert_eq!(raw, vec![(1u16, &b"\x00\x02h2"[..])]);
1535        // params() decodes — ALPN (key 1) is a StrList
1536        let parsed: Vec<(u16, OptValue)> =
1537            s.params().map(|(k, v)| (k, v.expect("decode"))).collect();
1538        assert_eq!(parsed.len(), 1);
1539        assert_eq!(parsed[0].0, 1);
1540        assert!(matches!(parsed[0].1, OptValue::StrList(_)));
1541        assert!(format!("{s:?}").contains("SvcbRecord"));
1542        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Svcb(_)));
1543    }
1544
1545    #[test]
1546    fn https_full_accessors_and_debug() {
1547        let rec = build(DnsRecordType::HTTPS, |rr| {
1548            rr.set_u16(DnsRrKey::HTTPS_PRIORITY, 1).unwrap();
1549            rr.set_str(DnsRrKey::HTTPS_TARGET, "svc.example.com")
1550                .unwrap();
1551            rr.set_opt(DnsRrKey::HTTPS_PARAMS, 1, b"\x00\x02h3")
1552                .unwrap();
1553        });
1554        let h = first_rr(&rec).as_https().expect("as_https");
1555        assert_eq!(h.priority(), 1);
1556        assert_eq!(h.target(), "svc.example.com");
1557        assert_eq!(h.param_count(), 1);
1558        assert_eq!(h.raw_params().count(), 1);
1559        assert_eq!(h.params().count(), 1);
1560        assert!(format!("{h:?}").contains("HttpsRecord"));
1561        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Https(_)));
1562    }
1563
1564    #[test]
1565    fn opt_full_accessors_and_debug() {
1566        // OPT may be rejected by rr_add as a pseudo-record. Try it and
1567        // fall back to skipping the test if so.
1568        let mut rec =
1569            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
1570        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
1571            .expect("query_add");
1572        if rec
1573            .rr_add(
1574                DnsSection::Additional,
1575                "",
1576                DnsRecordType::OPT,
1577                DnsCls::IN,
1578                0,
1579            )
1580            .is_err()
1581        {
1582            return;
1583        }
1584        let rr = rec.rr_mut(DnsSection::Additional, 0).expect("rr_mut");
1585        let _ = rr.set_u16(DnsRrKey::OPT_UDP_SIZE, 4096);
1586        let _ = rr.set_u8(DnsRrKey::OPT_VERSION, 0);
1587        let _ = rr.set_u16(DnsRrKey::OPT_FLAGS, 0x8000);
1588        let _ = rr.set_opt(DnsRrKey::OPT_OPTIONS, 10, b"\x01\x02");
1589        let opt_rr = rec
1590            .rr(DnsSection::Additional, 0)
1591            .expect("rr")
1592            .as_opt()
1593            .expect("as_opt");
1594        let _ = opt_rr.udp_size();
1595        let _ = opt_rr.version();
1596        let _ = opt_rr.flags();
1597        let _ = opt_rr.option_count();
1598        let _ = opt_rr.options().count();
1599        let _ = opt_rr.raw_options().count();
1600        let _ = format!("{opt_rr:?}");
1601        assert!(matches!(
1602            rec.rr(DnsSection::Additional, 0).unwrap().as_typed(),
1603            TypedRr::Opt(_)
1604        ));
1605    }
1606
1607    #[test]
1608    fn raw_rr_full_accessors_and_debug() {
1609        let rec = build(DnsRecordType::RAW_RR, |rr| {
1610            let _ = rr.set_u16(DnsRrKey::RAW_RR_TYPE, 99);
1611            let _ = rr.set_bin(DnsRrKey::RAW_RR_DATA, b"hello");
1612        });
1613        let r = first_rr(&rec).as_raw_rr().expect("as_raw_rr");
1614        let _ = r.raw_type();
1615        let _ = r.data();
1616        assert!(format!("{r:?}").contains("RawRrRecord"));
1617        assert!(matches!(first_rr(&rec).as_typed(), TypedRr::RawRr(_)));
1618    }
1619
1620    #[test]
1621    fn debug_impls_for_all_simple_wrappers() {
1622        // Cover Debug for the wrappers whose dedicated tests above don't
1623        // already exercise them on a populated record.
1624        let rec = make_rec(DnsRecordType::A);
1625        assert!(format!("{:?}", first_rr(&rec).as_a().unwrap()).contains("ARecord"));
1626        let rec = make_rec(DnsRecordType::AAAA);
1627        assert!(format!("{:?}", first_rr(&rec).as_aaaa().unwrap()).contains("AaaaRecord"));
1628        let rec = make_rec(DnsRecordType::MX);
1629        assert!(format!("{:?}", first_rr(&rec).as_mx().unwrap()).contains("MxRecord"));
1630        let rec = make_rec(DnsRecordType::TXT);
1631        assert!(format!("{:?}", first_rr(&rec).as_txt().unwrap()).contains("TxtRecord"));
1632        let rec = make_rec(DnsRecordType::CAA);
1633        assert!(format!("{:?}", first_rr(&rec).as_caa().unwrap()).contains("CaaRecord"));
1634        // TypedRr Debug
1635        assert!(format!("{:?}", first_rr(&rec).as_typed()).contains("Caa"));
1636    }
1637
1638    #[test]
1639    fn as_discriminators_return_none_on_mismatch() {
1640        let rec = make_rec(DnsRecordType::A);
1641        let rr = first_rr(&rec);
1642        assert!(rr.as_ns().is_none());
1643        assert!(rr.as_cname().is_none());
1644        assert!(rr.as_soa().is_none());
1645        assert!(rr.as_ptr_rr().is_none());
1646        assert!(rr.as_hinfo().is_none());
1647        assert!(rr.as_sig().is_none());
1648        assert!(rr.as_srv().is_none());
1649        assert!(rr.as_naptr().is_none());
1650        assert!(rr.as_opt().is_none());
1651        assert!(rr.as_tlsa().is_none());
1652        assert!(rr.as_svcb().is_none());
1653        assert!(rr.as_https().is_none());
1654        assert!(rr.as_uri().is_none());
1655        assert!(rr.as_caa().is_none());
1656        assert!(rr.as_raw_rr().is_none());
1657    }
1658}