1use bitflags::bitflags;
6use chrono::naive::NaiveDateTime;
7use std::collections::HashSet;
8use std::fmt;
9use std::hash::{Hash, Hasher};
10use std::str::FromStr;
11use xml::attribute::OwnedAttribute;
12use xml::name::OwnedName;
13use xml::reader::{ParserConfig, XmlEvent};
14use xml::writer::{EmitterConfig, XmlEvent as XmlWriteEvent};
15use xml::EventWriter;
16
17use crate::error::Error;
18use crate::{Builder, Parser};
19
20const FILE_ELEM: &str = "file";
22const FOLDER_ELEM: &str = "folder";
23const PARENT_FOLDER_ELEM: &str = "parent-folder";
24const FOLDER_LISTING_ELEM: &str = "folder-listing";
25
26const VERSION_ATTR: &str = "version";
27const NAME_ATTR: &str = "name";
28const SIZE_ATTR: &str = "size";
29const MODIFIED_ATTR: &str = "modified";
30const CREATED_ATTR: &str = "created";
31const ACCESSED_ATTR: &str = "accessed";
32const USER_PERM_ATTR: &str = "user-perm";
33const GROUP_PERM_ATTR: &str = "group-perm";
34const OTHER_PERM_ATTR: &str = "other-perm";
35const OWNER_ATTR: &str = "owner";
36const GROUP_ATTR: &str = "group";
37const TYPE_ATTR: &str = "type";
38const XML_LANG_ATTR: &str = "xml:lang";
39
40bitflags! {
41 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
42 pub struct Permission : u8 {
43 const READ = 0x1;
44 const WRITE = 0x2;
45 const DELETE = 0x4;
46 }
47}
48
49impl fmt::Display for Permission {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 if self.contains(Self::READ) {
52 write!(f, "R")?;
53 }
54 if self.contains(Self::WRITE) {
55 write!(f, "W")?;
56 }
57 if self.contains(Self::DELETE) {
58 write!(f, "D")?;
59 }
60 Ok(())
61 }
62}
63
64impl FromStr for Permission {
65 type Err = Error;
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 let mut perm = Permission::empty();
68 s.chars().try_for_each(|c| {
69 match c {
70 'R' => perm = perm | Permission::READ,
71 'W' => perm = perm | Permission::WRITE,
72 'D' => perm = perm | Permission::DELETE,
73 _ => return Err(Error::InvalidData(s.to_string())),
74 };
75 Ok(())
76 })?;
77 Ok(perm)
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub struct FormattedDateTimeObj(NaiveDateTime, String);
84
85impl FormattedDateTimeObj {
86 const ISO_8601_UTC_TIME_FORMAT: &'static str = "%Y%m%dT%H%M%SZ";
91 const ISO_8601_TIME_FORMAT: &'static str = "%Y%m%dT%H%M%S";
92
93 fn new(dt: NaiveDateTime) -> Self {
94 Self(dt, Self::ISO_8601_TIME_FORMAT.to_string())
95 }
96 fn new_utc(dt: NaiveDateTime) -> Self {
97 Self(dt, Self::ISO_8601_UTC_TIME_FORMAT.to_string())
98 }
99
100 fn parse_datetime(dt: String) -> Result<Self, Error> {
101 let Ok(datetime) =
102 NaiveDateTime::parse_from_str(dt.as_str(), Self::ISO_8601_UTC_TIME_FORMAT)
103 else {
104 return NaiveDateTime::parse_from_str(dt.as_str(), Self::ISO_8601_TIME_FORMAT)
105 .map(|t| Self::new(t))
106 .map_err(|_| Error::InvalidData(dt));
107 };
108
109 Ok(Self::new_utc(datetime))
111 }
112}
113
114impl fmt::Display for FormattedDateTimeObj {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", self.0.format(self.1.as_str()))
117 }
118}
119
120#[derive(Clone, Debug)]
121pub enum FolderListingAttribute {
122 Name(String),
123 Size(String),
124 Modified(FormattedDateTimeObj),
125 Created(FormattedDateTimeObj),
126 Accessed(FormattedDateTimeObj),
127 UserPerm(Permission),
128 GroupPerm(Permission),
129 OtherPerm(Permission),
130 Owner(String),
131 Group(String),
132 XmlLang(String),
133 Type(String),
134}
135
136impl Hash for FolderListingAttribute {
137 fn hash<H: Hasher>(&self, state: &mut H) {
138 self.xml_attribute_name().hash(state);
139 }
140}
141
142impl PartialEq for FolderListingAttribute {
143 fn eq(&self, other: &FolderListingAttribute) -> bool {
144 self.xml_attribute_name() == other.xml_attribute_name()
145 }
146}
147
148impl Eq for FolderListingAttribute {}
149
150pub type Attributes = HashSet<FolderListingAttribute>;
151
152impl FolderListingAttribute {
153 fn try_from_xml_attribute(a: &OwnedAttribute) -> Result<Self, Error> {
154 let v = a.value.clone();
155 Ok(match a.name.local_name.as_str() {
157 NAME_ATTR => Self::Name(v),
158 SIZE_ATTR => Self::Size(v),
159 MODIFIED_ATTR => Self::Modified(FormattedDateTimeObj::parse_datetime(v)?),
160 CREATED_ATTR => Self::Created(FormattedDateTimeObj::parse_datetime(v)?),
161 ACCESSED_ATTR => Self::Accessed(FormattedDateTimeObj::parse_datetime(v)?),
162 USER_PERM_ATTR => Self::UserPerm(str::parse(v.as_str())?),
163 GROUP_PERM_ATTR => Self::GroupPerm(str::parse(v.as_str())?),
164 OTHER_PERM_ATTR => Self::OtherPerm(str::parse(v.as_str())?),
165 OWNER_ATTR => Self::Owner(v),
166 GROUP_ATTR => Self::Group(v),
167 XML_LANG_ATTR => Self::XmlLang(v),
168 TYPE_ATTR => Self::Type(v),
169 _ => return Err(Error::InvalidData(format!("{a:?}"))),
170 })
171 }
172
173 const fn xml_attribute_name(&self) -> &'static str {
174 match self {
175 Self::Name(_) => NAME_ATTR,
176 Self::Size(_) => SIZE_ATTR,
177 Self::Modified(_) => MODIFIED_ATTR,
178 Self::Created(_) => CREATED_ATTR,
179 Self::Accessed(_) => ACCESSED_ATTR,
180 Self::UserPerm(_) => USER_PERM_ATTR,
181 Self::GroupPerm(_) => GROUP_PERM_ATTR,
182 Self::OtherPerm(_) => OTHER_PERM_ATTR,
183 Self::Owner(_) => OWNER_ATTR,
184 Self::Group(_) => GROUP_ATTR,
185 Self::XmlLang(_) => XML_LANG_ATTR,
186 Self::Type(_) => TYPE_ATTR,
187 }
188 }
189
190 fn xml_attribute_value(&self) -> String {
191 match self {
192 Self::Name(v)
193 | Self::Size(v)
194 | Self::Owner(v)
195 | Self::Group(v)
196 | Self::XmlLang(v)
197 | Self::Type(v) => v.clone(),
198 Self::Modified(dt) | Self::Created(dt) | Self::Accessed(dt) => dt.to_string(),
199 Self::UserPerm(p) | Self::GroupPerm(p) | Self::OtherPerm(p) => p.to_string(),
200 }
201 }
202}
203
204#[derive(Clone, Debug, PartialEq)]
206pub struct File {
207 data: Option<String>,
208 attributes: Attributes,
209}
210
211impl File {
212 fn write<W: std::io::prelude::Write>(&self, writer: &mut EventWriter<W>) -> Result<(), Error> {
213 let mut builder = XmlWriteEvent::start_element(FILE_ELEM);
215 let attrs: Vec<(&str, String)> = self
216 .attributes
217 .iter()
218 .map(|a| (a.xml_attribute_name(), a.xml_attribute_value()))
219 .collect();
220 for a in &attrs {
221 builder = builder.attr(a.0, &a.1);
222 }
223 writer.write(builder)?;
224
225 if let Some(data) = &self.data {
226 writer.write(data.as_str())?;
227 }
228
229 writer.write(XmlWriteEvent::end_element()).map_err(Into::into)
231 }
232
233 fn validate(&self) -> Result<(), Error> {
234 if !self.attributes.contains(&FolderListingAttribute::Name("".to_string())) {
236 return Err(Error::MissingData(NAME_ATTR.to_string()));
237 }
238 Ok(())
239 }
240}
241
242impl TryFrom<XmlEvent> for File {
243 type Error = Error;
244 fn try_from(src: XmlEvent) -> Result<Self, Error> {
245 let XmlEvent::StartElement { ref name, ref attributes, .. } = src else {
246 return Err(Error::InvalidData(format!("{:?}", src)));
247 };
248 if name.local_name.as_str() != FILE_ELEM {
249 return Err(Error::InvalidData(name.local_name.clone()));
250 }
251 let mut attrs = HashSet::new();
252 for a in attributes {
253 if !attrs.insert(FolderListingAttribute::try_from_xml_attribute(a)?) {
254 return Err(Error::InvalidData(format!("duplicate \"{}\"", a.name.local_name)));
255 }
256 }
257
258 let file = File { data: None, attributes: attrs };
259 file.validate()?;
260 Ok(file)
261 }
262}
263
264#[derive(Clone, Debug, PartialEq)]
266pub struct Folder {
267 data: Option<String>,
268 attributes: Attributes,
269}
270
271impl Folder {
272 fn write<W: std::io::prelude::Write>(&self, writer: &mut EventWriter<W>) -> Result<(), Error> {
273 let mut builder = XmlWriteEvent::start_element(FOLDER_ELEM);
275 let attrs: Vec<(&str, String)> = self
276 .attributes
277 .iter()
278 .map(|a| (a.xml_attribute_name(), a.xml_attribute_value()))
279 .collect();
280 for a in &attrs {
281 builder = builder.attr(a.0, a.1.as_str());
282 }
283 writer.write(builder)?;
284
285 if let Some(data) = &self.data {
286 writer.write(data.as_str())?;
287 }
288
289 Ok(writer.write(XmlWriteEvent::end_element())?)
291 }
292
293 fn validate(&self) -> Result<(), Error> {
294 if !self.attributes.contains(&FolderListingAttribute::Name("".to_string())) {
296 return Err(Error::MissingData(NAME_ATTR.to_string()));
297 }
298 if self.attributes.contains(&FolderListingAttribute::Type("".to_string())) {
300 return Err(Error::InvalidData(TYPE_ATTR.to_string()));
301 }
302 Ok(())
303 }
304}
305
306impl TryFrom<XmlEvent> for Folder {
307 type Error = Error;
308 fn try_from(src: XmlEvent) -> Result<Self, Error> {
309 let XmlEvent::StartElement { ref name, ref attributes, .. } = src else {
310 return Err(Error::InvalidData(format!("{:?}", src)));
311 };
312 if name.local_name.as_str() != FOLDER_ELEM {
313 return Err(Error::InvalidData(name.local_name.clone()));
314 }
315 let mut attrs = HashSet::new();
316 attributes.iter().try_for_each(|a| {
317 if !attrs.insert(FolderListingAttribute::try_from_xml_attribute(a)?) {
318 return Err(Error::InvalidData(format!("duplicate \"{}\"", a.name.local_name)));
319 }
320 Ok(())
321 })?;
322
323 let folder = Folder { data: None, attributes: attrs };
324 folder.validate()?;
325 Ok(folder)
326 }
327}
328
329enum ParsedXmlEvent {
330 DocumentStart,
331 FolderListingElement,
332 ParentFolderElement,
333 FolderElement(Folder),
334 FileElement(File),
335}
336
337#[derive(Clone, Debug, PartialEq)]
341pub struct FolderListing {
342 parent_folder: bool,
344 files: Vec<File>,
345 folders: Vec<Folder>,
346}
347
348impl FolderListing {
349 const DEFAULT_VERSION: &'static str = "1.0";
350
351 fn new_empty() -> Self {
352 Self { parent_folder: false, files: Vec::new(), folders: Vec::new() }
353 }
354
355 fn validate_folder_listing_element(element: XmlEvent) -> Result<(), Error> {
358 let XmlEvent::StartElement { ref name, ref attributes, .. } = element else {
359 return Err(Error::InvalidData(format!("{:?}", element)));
360 };
361
362 if name.local_name != FOLDER_LISTING_ELEM {
363 return Err(Error::InvalidData(name.local_name.clone()));
364 }
365 let default_version: OwnedAttribute = OwnedAttribute::new(
366 OwnedName { local_name: VERSION_ATTR.to_string(), namespace: None, prefix: None },
367 FolderListing::DEFAULT_VERSION,
368 );
369 let version = &attributes
371 .iter()
372 .find(|a| a.name.local_name == VERSION_ATTR)
373 .unwrap_or(&default_version)
374 .value;
375 if version != FolderListing::DEFAULT_VERSION {
376 return Err(Error::UnsupportedVersion);
377 }
378 Ok(())
379 }
380}
381
382impl Parser for FolderListing {
383 type Error = Error;
384
385 fn parse<R: std::io::prelude::Read>(buf: R) -> Result<Self, Self::Error> {
387 let mut reader = ParserConfig::new()
388 .ignore_comments(true)
389 .whitespace_to_characters(true)
390 .cdata_to_characters(true)
391 .trim_whitespace(true)
392 .create_reader(buf);
393 let mut prev = Vec::new();
394
395 match reader.next() {
397 Ok(XmlEvent::StartDocument { .. }) => {
398 prev.push(ParsedXmlEvent::DocumentStart);
399 }
400 Ok(element) => return Err(Error::InvalidData(format!("{:?}", element))),
401 Err(e) => return Err(Error::ReadXml(e)),
402 };
403
404 let xml_event = reader.next()?;
406 let _ = Self::validate_folder_listing_element(xml_event)?;
407
408 prev.push(ParsedXmlEvent::FolderListingElement);
409 let mut folder_listing = Self::new_empty();
410
411 let mut finished_document = false;
413 let mut finished_folder_listing = false;
414 while !finished_document {
415 let e = reader.next()?;
417 let invalid_elem_err = Err(Error::InvalidData(format!("{:?}", e)));
418 match e {
419 XmlEvent::StartElement { ref name, .. } => {
420 match name.local_name.as_str() {
421 PARENT_FOLDER_ELEM => prev.push(ParsedXmlEvent::ParentFolderElement),
422 FOLDER_ELEM => prev.push(ParsedXmlEvent::FolderElement(e.try_into()?)),
423 FILE_ELEM => prev.push(ParsedXmlEvent::FileElement(e.try_into()?)),
424 other_value => return Err(Error::InvalidData(other_value.to_string())),
425 };
426 }
427 XmlEvent::EndElement { ref name } => {
428 let Some(parsed_elem) = prev.pop() else {
429 return invalid_elem_err;
430 };
431 match name.local_name.as_str() {
432 FOLDER_LISTING_ELEM => {
433 let ParsedXmlEvent::FolderListingElement = parsed_elem else {
434 return Err(Error::MissingData(format!(
435 "closing {FOLDER_LISTING_ELEM}"
436 )));
437 };
438 finished_folder_listing = true;
439 }
440 PARENT_FOLDER_ELEM => {
441 let ParsedXmlEvent::ParentFolderElement = parsed_elem else {
442 return Err(Error::MissingData(format!(
443 "closing {PARENT_FOLDER_ELEM}"
444 )));
445 };
446 folder_listing.parent_folder = true;
447 }
448 FOLDER_ELEM => {
449 let ParsedXmlEvent::FolderElement(f) = parsed_elem else {
450 return Err(Error::MissingData(format!("closing {FOLDER_ELEM}")));
451 };
452 folder_listing.folders.push(f);
453 }
454 FILE_ELEM => {
455 let ParsedXmlEvent::FileElement(f) = parsed_elem else {
456 return Err(Error::MissingData(format!("closing {FILE_ELEM}")));
457 };
458 folder_listing.files.push(f);
459 }
460 _ => return invalid_elem_err,
461 };
462 }
463 XmlEvent::Characters(data) => {
464 let err = Err(Error::InvalidData(data.clone()));
465 if let Some(mut event) = prev.pop() {
466 match &mut event {
467 ParsedXmlEvent::FolderElement(ref mut f) => f.data = Some(data),
468 ParsedXmlEvent::FileElement(ref mut f) => f.data = Some(data),
469 _ => return err,
470 };
471 prev.push(event);
472 } else {
473 return err;
474 }
475 }
476 XmlEvent::EndDocument => {
477 if !finished_folder_listing {
478 return Err(Error::MissingData(format!("closing {FOLDER_LISTING_ELEM}")));
479 }
480 finished_document = true;
481 }
482 _ => return invalid_elem_err,
483 }
484 }
485 Ok(folder_listing)
486 }
487}
488
489impl Builder for FolderListing {
490 type Error = Error;
491
492 fn mime_type(&self) -> String {
494 "application/xml".to_string()
495 }
496
497 fn build<W: std::io::Write>(&self, buf: W) -> Result<(), Self::Error> {
499 let mut w = EmitterConfig::new()
500 .write_document_declaration(true)
501 .perform_indent(true)
502 .create_writer(buf);
503
504 let folder_listing = XmlWriteEvent::start_element(FOLDER_LISTING_ELEM)
506 .attr(VERSION_ATTR, Self::DEFAULT_VERSION);
507 w.write(folder_listing)?;
508
509 if self.parent_folder {
510 w.write(XmlWriteEvent::start_element(PARENT_FOLDER_ELEM))?;
511 w.write(XmlWriteEvent::end_element())?;
512 }
513 self.folders.iter().try_for_each(|f| f.write(&mut w))?;
514 self.files.iter().try_for_each(|f| f.write(&mut w))?;
515
516 w.write(XmlWriteEvent::end_element())?;
518 Ok(())
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use chrono::{NaiveDate, NaiveTime};
526
527 use std::fs;
528 use std::io::Cursor;
529
530 #[fuchsia::test]
531 fn parse_empty_folder_listing_success() {
532 const EMPTY_FOLDER_LISTING_TEST_FILE: &str = "/pkg/data/sample_folder_listing_1.xml";
533 let bytes = fs::read(EMPTY_FOLDER_LISTING_TEST_FILE).expect("should be ok");
534 let folder_listing = FolderListing::parse(Cursor::new(bytes)).expect("should be ok");
535 assert_eq!(folder_listing, FolderListing::new_empty());
536 }
537
538 #[fuchsia::test]
539 fn parse_simple_folder_listing_success() {
540 const FOLDER_LISTING_TEST_FILE: &str = "/pkg/data/sample_folder_listing_2.xml";
541 let bytes = fs::read(FOLDER_LISTING_TEST_FILE).expect("should be ok");
542 let folder_listing = FolderListing::parse(Cursor::new(bytes)).expect("should be ok");
543 assert_eq!(
544 folder_listing,
545 FolderListing {
546 parent_folder: true,
547 files: vec![
548 File {
549 data: Some("Jumar Handling Guide".to_string()),
550 attributes: HashSet::from([
551 FolderListingAttribute::Name("Jumar.txt".to_string()),
552 FolderListingAttribute::Size("6672".to_string()),
553 ]),
554 },
555 File {
556 data: Some("OBEX Specification v1.0".to_string()),
557 attributes: HashSet::from([
558 FolderListingAttribute::Name("Obex.doc".to_string()),
559 FolderListingAttribute::Type("application/msword".to_string()),
560 ]),
561 },
562 ],
563 folders: vec![
564 Folder {
565 data: None,
566 attributes: HashSet::from([FolderListingAttribute::Name(
567 "System".to_string()
568 ),])
569 },
570 Folder {
571 data: None,
572 attributes: HashSet::from([FolderListingAttribute::Name(
573 "IR Inbox".to_string()
574 ),])
575 },
576 ],
577 }
578 );
579 }
580
581 #[fuchsia::test]
582 fn parse_detailed_folder_listing_success() {
583 const DETAILED_FOLDER_LISTING_TEST_FILE: &str = "/pkg/data/sample_folder_listing_3.xml";
584 let bytes = fs::read(DETAILED_FOLDER_LISTING_TEST_FILE).expect("should be ok");
585 let folder_listing = FolderListing::parse(Cursor::new(bytes)).expect("should be ok");
586 assert_eq!(
587 folder_listing,
588 FolderListing {
589 parent_folder: true,
590 files: vec![
591 File {
592 data: None,
593 attributes: HashSet::from([
594 FolderListingAttribute::Name("Jumar.txt".to_string()),
595 FolderListingAttribute::Created(FormattedDateTimeObj(
596 NaiveDateTime::new(
597 NaiveDate::from_ymd_opt(1997, 12, 09).unwrap(),
598 NaiveTime::from_hms_opt(09, 03, 00).unwrap(),
599 ),
600 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
601 )),
602 FolderListingAttribute::Size("6672".to_string()),
603 FolderListingAttribute::Modified(FormattedDateTimeObj(
604 NaiveDateTime::new(
605 NaiveDate::from_ymd_opt(1997, 12, 22).unwrap(),
606 NaiveTime::from_hms_opt(16, 41, 00).unwrap(),
607 ),
608 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
609 )),
610 FolderListingAttribute::UserPerm(Permission::READ | Permission::WRITE),
611 ]),
612 },
613 File {
614 data: None,
615 attributes: HashSet::from([
616 FolderListingAttribute::Name("Obex.doc".to_string()),
617 FolderListingAttribute::Created(FormattedDateTimeObj(
618 NaiveDateTime::new(
619 NaiveDate::from_ymd_opt(1997, 01, 22).unwrap(),
620 NaiveTime::from_hms_opt(10, 23, 00).unwrap(),
621 ),
622 FormattedDateTimeObj::ISO_8601_UTC_TIME_FORMAT.to_string(),
623 )),
624 FolderListingAttribute::Size("41042".to_string()),
625 FolderListingAttribute::Type("application/msword".to_string()),
626 FolderListingAttribute::Modified(FormattedDateTimeObj(
627 NaiveDateTime::new(
628 NaiveDate::from_ymd_opt(1997, 01, 22).unwrap(),
629 NaiveTime::from_hms_opt(10, 23, 00).unwrap(),
630 ),
631 FormattedDateTimeObj::ISO_8601_UTC_TIME_FORMAT.to_string(),
632 )),
633 ]),
634 },
635 ],
636 folders: vec![
637 Folder {
638 data: None,
639 attributes: HashSet::from([
640 FolderListingAttribute::Name("System".to_string()),
641 FolderListingAttribute::Created(FormattedDateTimeObj(
642 NaiveDateTime::new(
643 NaiveDate::from_ymd_opt(1996, 11, 03).unwrap(),
644 NaiveTime::from_hms_opt(14, 15, 00).unwrap(),
645 ),
646 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
647 )),
648 ]),
649 },
650 Folder {
651 data: None,
652 attributes: HashSet::from([
653 FolderListingAttribute::Name("IR Inbox".to_string()),
654 FolderListingAttribute::Created(FormattedDateTimeObj(
655 NaiveDateTime::new(
656 NaiveDate::from_ymd_opt(1995, 03, 30).unwrap(),
657 NaiveTime::from_hms_opt(10, 50, 00).unwrap(),
658 ),
659 FormattedDateTimeObj::ISO_8601_UTC_TIME_FORMAT.to_string(),
660 )),
661 ]),
662 },
663 ],
664 }
665 );
666 }
667
668 #[fuchsia::test]
669 fn parse_folder_listing_fail() {
670 let bad_sample_xml_files = vec![
671 "/pkg/data/bad_sample.xml",
672 "/pkg/data/bad_sample_folder_listing_1.xml",
673 "/pkg/data/bad_sample_folder_listing_2.xml",
674 "/pkg/data/bad_sample_folder_listing_3.xml",
675 "/pkg/data/bad_sample_folder_listing_4.xml",
676 "/pkg/data/bad_sample_folder_listing_5.xml",
677 "/pkg/data/bad_sample_folder_listing_6.xml",
678 "/pkg/data/bad_sample_folder_listing_7.xml",
679 "/pkg/data/bad_sample_folder_listing_8.xml",
680 ];
681
682 bad_sample_xml_files.iter().for_each(|f| {
683 let bytes = fs::read(f).expect("should be ok");
684 let _ = FolderListing::parse(Cursor::new(bytes)).expect_err("should have failed");
685 });
686 }
687
688 #[fuchsia::test]
689 fn build_empty_folder_listing_success() {
690 let empty_folder_listing = FolderListing::new_empty();
692 let mut buf = Vec::new();
693 assert_eq!(empty_folder_listing.mime_type(), "application/xml");
694 empty_folder_listing.build(&mut buf).expect("should have succeeded");
695 assert_eq!(
696 empty_folder_listing,
697 FolderListing::parse(Cursor::new(buf)).expect("should be valid xml")
698 );
699 }
700
701 #[fuchsia::test]
702 fn build_simple_folder_listing_success() {
703 let folder_listing = FolderListing {
704 parent_folder: true,
705 files: vec![File {
706 data: Some("Jumar Handling Guide".to_string()),
707 attributes: HashSet::from([
708 FolderListingAttribute::Name("Jumar.txt".to_string()),
709 FolderListingAttribute::Size("6672".to_string()),
710 ]),
711 }],
712 folders: vec![Folder {
713 data: None,
714 attributes: HashSet::from([FolderListingAttribute::Name("System".to_string())]),
715 }],
716 };
717 let mut buf = Vec::new();
718 folder_listing.build(&mut buf).expect("should have succeeded");
719 assert_eq!(
720 folder_listing,
721 FolderListing::parse(Cursor::new(buf)).expect("should be valid xml")
722 );
723 }
724
725 #[fuchsia::test]
726 fn build_detailed_folder_listing_success() {
727 let detailed_folder_listing = FolderListing {
728 parent_folder: true,
729 files: vec![File {
730 data: None,
731 attributes: HashSet::from([
732 FolderListingAttribute::Name("Jumar.txt".to_string()),
733 FolderListingAttribute::Created(FormattedDateTimeObj(
734 NaiveDateTime::new(
735 NaiveDate::from_ymd_opt(1997, 12, 09).unwrap(),
736 NaiveTime::from_hms_opt(09, 03, 00).unwrap(),
737 ),
738 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
739 )),
740 FolderListingAttribute::Size("6672".to_string()),
741 FolderListingAttribute::Modified(FormattedDateTimeObj(
742 NaiveDateTime::new(
743 NaiveDate::from_ymd_opt(1997, 12, 22).unwrap(),
744 NaiveTime::from_hms_opt(16, 41, 00).unwrap(),
745 ),
746 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
747 )),
748 FolderListingAttribute::UserPerm(Permission::READ | Permission::WRITE),
749 ]),
750 }],
751 folders: vec![Folder {
752 data: None,
753 attributes: HashSet::from([
754 FolderListingAttribute::Name("System".to_string()),
755 FolderListingAttribute::Created(FormattedDateTimeObj(
756 NaiveDateTime::new(
757 NaiveDate::from_ymd_opt(1996, 11, 03).unwrap(),
758 NaiveTime::from_hms_opt(14, 15, 00).unwrap(),
759 ),
760 FormattedDateTimeObj::ISO_8601_TIME_FORMAT.to_string(),
761 )),
762 ]),
763 }],
764 };
765 let mut buf = Vec::new();
766 assert_eq!(detailed_folder_listing.mime_type(), "application/xml");
767 detailed_folder_listing.build(&mut buf).expect("should have succeeded");
768
769 assert_eq!(
770 detailed_folder_listing,
771 FolderListing::parse(Cursor::new(buf)).expect("should be valid xml")
772 );
773 }
774}