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