1use crate::base::cmp::CanonicalOrd;
6use crate::base::iana::Rtype;
7use crate::base::name::{FlattenInto, ParsedName, ToName};
8use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
9use crate::base::scan::Scanner;
10use crate::base::wire::{Composer, ParseError};
11use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
12use core::cmp::Ordering;
13use core::fmt;
14use octseq::octets::{Octets, OctetsFrom, OctetsInto};
15use octseq::parse::Parser;
16
17#[derive(Clone, Debug, Hash)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Minfo<N> {
33 rmailbx: N,
34 emailbx: N,
35}
36
37impl Minfo<()> {
38 pub(crate) const RTYPE: Rtype = Rtype::MINFO;
40}
41
42impl<N> Minfo<N> {
43 pub fn new(rmailbx: N, emailbx: N) -> Self {
45 Minfo { rmailbx, emailbx }
46 }
47
48 pub fn rmailbx(&self) -> &N {
54 &self.rmailbx
55 }
56
57 pub fn emailbx(&self) -> &N {
64 &self.emailbx
65 }
66
67 pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<N>>(
68 self,
69 ) -> Result<Minfo<Target>, Target::Error> {
70 Ok(Minfo::new(
71 self.rmailbx.try_octets_into()?,
72 self.emailbx.try_octets_into()?,
73 ))
74 }
75
76 pub(in crate::rdata) fn flatten<TargetName>(
77 self,
78 ) -> Result<Minfo<TargetName>, N::AppendError>
79 where
80 N: FlattenInto<TargetName>,
81 {
82 Ok(Minfo::new(
83 self.rmailbx.try_flatten_into()?,
84 self.emailbx.try_flatten_into()?,
85 ))
86 }
87
88 pub fn scan<S: Scanner<Name = N>>(
89 scanner: &mut S,
90 ) -> Result<Self, S::Error> {
91 Ok(Self::new(scanner.scan_name()?, scanner.scan_name()?))
92 }
93}
94
95impl<Octs> Minfo<ParsedName<Octs>> {
96 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
97 parser: &mut Parser<'a, Src>,
98 ) -> Result<Self, ParseError> {
99 Ok(Self::new(
100 ParsedName::parse(parser)?,
101 ParsedName::parse(parser)?,
102 ))
103 }
104}
105
106impl<Name, SrcName> OctetsFrom<Minfo<SrcName>> for Minfo<Name>
109where
110 Name: OctetsFrom<SrcName>,
111{
112 type Error = Name::Error;
113
114 fn try_octets_from(source: Minfo<SrcName>) -> Result<Self, Self::Error> {
115 Ok(Minfo::new(
116 Name::try_octets_from(source.rmailbx)?,
117 Name::try_octets_from(source.emailbx)?,
118 ))
119 }
120}
121
122impl<Name, TName> FlattenInto<Minfo<TName>> for Minfo<Name>
123where
124 Name: FlattenInto<TName>,
125{
126 type AppendError = Name::AppendError;
127
128 fn try_flatten_into(self) -> Result<Minfo<TName>, Name::AppendError> {
129 self.flatten()
130 }
131}
132
133impl<N, NN> PartialEq<Minfo<NN>> for Minfo<N>
136where
137 N: ToName,
138 NN: ToName,
139{
140 fn eq(&self, other: &Minfo<NN>) -> bool {
141 self.rmailbx.name_eq(&other.rmailbx)
142 && self.emailbx.name_eq(&other.emailbx)
143 }
144}
145
146impl<N: ToName> Eq for Minfo<N> {}
147
148impl<N, NN> PartialOrd<Minfo<NN>> for Minfo<N>
151where
152 N: ToName,
153 NN: ToName,
154{
155 fn partial_cmp(&self, other: &Minfo<NN>) -> Option<Ordering> {
156 match self.rmailbx.name_cmp(&other.rmailbx) {
157 Ordering::Equal => {}
158 other => return Some(other),
159 }
160 Some(self.emailbx.name_cmp(&other.emailbx))
161 }
162}
163
164impl<N: ToName> Ord for Minfo<N> {
165 fn cmp(&self, other: &Self) -> Ordering {
166 match self.rmailbx.name_cmp(&other.rmailbx) {
167 Ordering::Equal => {}
168 other => return other,
169 }
170 self.emailbx.name_cmp(&other.emailbx)
171 }
172}
173
174impl<N: ToName, NN: ToName> CanonicalOrd<Minfo<NN>> for Minfo<N> {
175 fn canonical_cmp(&self, other: &Minfo<NN>) -> Ordering {
176 match self.rmailbx.lowercase_composed_cmp(&other.rmailbx) {
177 Ordering::Equal => {}
178 other => return other,
179 }
180 self.emailbx.lowercase_composed_cmp(&other.emailbx)
181 }
182}
183
184impl<N> RecordData for Minfo<N> {
187 fn rtype(&self) -> Rtype {
188 Minfo::RTYPE
189 }
190}
191
192impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
193 for Minfo<ParsedName<Octs::Range<'a>>>
194{
195 fn parse_rdata(
196 rtype: Rtype,
197 parser: &mut Parser<'a, Octs>,
198 ) -> Result<Option<Self>, ParseError> {
199 if rtype == Minfo::RTYPE {
200 Self::parse(parser).map(Some)
201 } else {
202 Ok(None)
203 }
204 }
205}
206
207impl<Name: ToName> ComposeRecordData for Minfo<Name> {
208 fn rdlen(&self, compress: bool) -> Option<u16> {
209 if compress {
210 None
211 } else {
212 Some(self.rmailbx.compose_len() + self.emailbx.compose_len())
213 }
214 }
215
216 fn compose_rdata<Target: Composer + ?Sized>(
217 &self,
218 target: &mut Target,
219 ) -> Result<(), Target::AppendError> {
220 if target.can_compress() {
221 target.append_compressed_name(&self.rmailbx)?;
222 target.append_compressed_name(&self.emailbx)
223 } else {
224 self.rmailbx.compose(target)?;
225 self.emailbx.compose(target)
226 }
227 }
228
229 fn compose_canonical_rdata<Target: Composer + ?Sized>(
230 &self,
231 target: &mut Target,
232 ) -> Result<(), Target::AppendError> {
233 self.rmailbx.compose_canonical(target)?;
234 self.emailbx.compose_canonical(target)
235 }
236}
237
238impl<N: fmt::Display> fmt::Display for Minfo<N> {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "{}. {}.", self.rmailbx, self.emailbx)
243 }
244}
245
246impl<N: ToName> ZonefileFmt for Minfo<N> {
249 fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
250 p.block(|p| {
251 p.write_token(self.rmailbx.fmt_with_dot())?;
252 p.write_comment("responsible mailbox")?;
253 p.write_token(self.emailbx.fmt_with_dot())?;
254 p.write_comment("error mailbox")
255 })
256 }
257}
258
259#[cfg(test)]
262#[cfg(all(feature = "std", feature = "bytes"))]
263mod test {
264 use super::*;
265 use crate::base::name::Name;
266 use crate::base::rdata::test::{
267 test_compose_parse, test_rdlen, test_scan,
268 };
269 use core::str::FromStr;
270 use std::vec::Vec;
271
272 #[test]
273 #[allow(clippy::redundant_closure)] fn minfo_compose_parse_scan() {
275 let rdata = Minfo::<Name<Vec<u8>>>::new(
276 Name::from_str("r.example.com").unwrap(),
277 Name::from_str("e.example.com").unwrap(),
278 );
279 test_rdlen(&rdata);
280 test_compose_parse(&rdata, |parser| Minfo::parse(parser));
281 test_scan(&["r.example.com", "e.example.com"], Minfo::scan, &rdata);
282 }
283
284 #[test]
285 fn minfo_octets_into() {
286 let minfo: Minfo<Name<Vec<u8>>> = Minfo::new(
287 "a.example".parse().unwrap(),
288 "b.example".parse().unwrap(),
289 );
290 let minfo_bytes: Minfo<Name<bytes::Bytes>> =
291 minfo.clone().octets_into();
292 assert_eq!(minfo.rmailbx(), minfo_bytes.rmailbx());
293 assert_eq!(minfo.emailbx(), minfo_bytes.emailbx());
294 }
295}