csv/string_record.rs
1use std::{
2 fmt, io,
3 iter::FromIterator,
4 ops::{self, Range},
5 result, str,
6};
7
8use serde::de::Deserialize;
9
10use crate::{
11 byte_record::{ByteRecord, ByteRecordIter, Position},
12 deserializer::deserialize_string_record,
13 error::{Error, ErrorKind, FromUtf8Error, Result},
14 reader::Reader,
15};
16
17/// A single CSV record stored as valid UTF-8 bytes.
18///
19/// A string record permits reading or writing CSV rows that are valid UTF-8.
20/// If string records are used to read CSV data that is not valid UTF-8, then
21/// the CSV reader will return an invalid UTF-8 error. If you do need to read
22/// possibly invalid UTF-8 data, then you should prefer using a
23/// [`ByteRecord`](struct.ByteRecord.html),
24/// since it makes no assumptions about UTF-8.
25///
26/// If you are using the Serde (de)serialization APIs, then you probably never
27/// need to interact with a `ByteRecord` or a `StringRecord`. However, there
28/// are some circumstances in which you might need to use a raw record type
29/// while still using Serde. For example, if you need to deserialize possibly
30/// invalid UTF-8 fields, then you'll need to first read your record into a
31/// `ByteRecord`, and then use `ByteRecord::deserialize` to run Serde. Another
32/// reason for using the raw record deserialization APIs is if you're using
33/// Serde to read into borrowed data such as a `&'a str` or a `&'a [u8]`.
34///
35/// Two `StringRecord`s are compared on the basis of their field data. Any
36/// position information associated with the records is ignored.
37#[derive(Clone, Eq)]
38pub struct StringRecord(ByteRecord);
39
40impl PartialEq for StringRecord {
41 fn eq(&self, other: &StringRecord) -> bool {
42 self.0.iter_eq(&other.0)
43 }
44}
45
46impl<T: AsRef<[u8]>> PartialEq<Vec<T>> for StringRecord {
47 fn eq(&self, other: &Vec<T>) -> bool {
48 self.0.iter_eq(other)
49 }
50}
51
52impl<'a, T: AsRef<[u8]>> PartialEq<Vec<T>> for &'a StringRecord {
53 fn eq(&self, other: &Vec<T>) -> bool {
54 self.0.iter_eq(other)
55 }
56}
57
58impl<T: AsRef<[u8]>> PartialEq<[T]> for StringRecord {
59 fn eq(&self, other: &[T]) -> bool {
60 self.0.iter_eq(other)
61 }
62}
63
64impl<'a, T: AsRef<[u8]>> PartialEq<[T]> for &'a StringRecord {
65 fn eq(&self, other: &[T]) -> bool {
66 self.0.iter_eq(other)
67 }
68}
69
70impl fmt::Debug for StringRecord {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 let fields: Vec<&str> = self.iter().collect();
73 write!(f, "StringRecord({:?})", fields)
74 }
75}
76
77impl Default for StringRecord {
78 #[inline]
79 fn default() -> StringRecord {
80 StringRecord::new()
81 }
82}
83
84impl StringRecord {
85 /// Create a new empty `StringRecord`.
86 ///
87 /// Note that you may find the `StringRecord::from` constructor more
88 /// convenient, which is provided by an impl on the `From` trait.
89 ///
90 /// # Example: create an empty record
91 ///
92 /// ```
93 /// use csv::StringRecord;
94 ///
95 /// let record = StringRecord::new();
96 /// assert_eq!(record.len(), 0);
97 /// ```
98 ///
99 /// # Example: initialize a record from a `Vec`
100 ///
101 /// ```
102 /// use csv::StringRecord;
103 ///
104 /// let record = StringRecord::from(vec!["a", "b", "c"]);
105 /// assert_eq!(record.len(), 3);
106 /// ```
107 #[inline]
108 pub fn new() -> StringRecord {
109 StringRecord(ByteRecord::new())
110 }
111
112 /// Create a new empty `StringRecord` with the given capacity.
113 ///
114 /// `buffer` refers to the capacity of the buffer used to store the
115 /// actual row contents. `fields` refers to the number of fields one
116 /// might expect to store.
117 #[inline]
118 pub fn with_capacity(buffer: usize, fields: usize) -> StringRecord {
119 StringRecord(ByteRecord::with_capacity(buffer, fields))
120 }
121
122 /// Create a new `StringRecord` from a `ByteRecord`.
123 ///
124 /// Note that this does UTF-8 validation. If the given `ByteRecord` does
125 /// not contain valid UTF-8, then this returns an error. The error includes
126 /// the UTF-8 error and the original `ByteRecord`.
127 ///
128 /// # Example: valid UTF-8
129 ///
130 /// ```
131 /// use std::error::Error;
132 /// use csv::{ByteRecord, StringRecord};
133 ///
134 /// # fn main() { example().unwrap(); }
135 /// fn example() -> Result<(), Box<dyn Error>> {
136 /// let byte_record = ByteRecord::from(vec!["a", "b", "c"]);
137 /// let str_record = StringRecord::from_byte_record(byte_record)?;
138 /// assert_eq!(str_record.len(), 3);
139 /// Ok(())
140 /// }
141 /// ```
142 ///
143 /// # Example: invalid UTF-8
144 ///
145 /// ```
146 /// use csv::{ByteRecord, StringRecord};
147 ///
148 /// let byte_record = ByteRecord::from(vec![
149 /// &b"quux"[..], &b"foo\xFFbar"[..], &b"c"[..],
150 /// ]);
151 /// let err = StringRecord::from_byte_record(byte_record).unwrap_err();
152 /// assert_eq!(err.utf8_error().field(), 1);
153 /// assert_eq!(err.utf8_error().valid_up_to(), 3);
154 /// ```
155 #[inline]
156 pub fn from_byte_record(
157 record: ByteRecord,
158 ) -> result::Result<StringRecord, FromUtf8Error> {
159 match record.validate() {
160 Ok(()) => Ok(StringRecord(record)),
161 Err(err) => Err(FromUtf8Error::new(record, err)),
162 }
163 }
164
165 /// Lossily create a new `StringRecord` from a `ByteRecord`.
166 ///
167 /// This is like `StringRecord::from_byte_record`, except all invalid UTF-8
168 /// sequences are replaced with the `U+FFFD REPLACEMENT CHARACTER`, which
169 /// looks like this: �.
170 ///
171 /// # Example: valid UTF-8
172 ///
173 /// ```
174 /// use csv::{ByteRecord, StringRecord};
175 ///
176 /// let byte_record = ByteRecord::from(vec!["a", "b", "c"]);
177 /// let str_record = StringRecord::from_byte_record_lossy(byte_record);
178 /// assert_eq!(str_record.len(), 3);
179 /// ```
180 ///
181 /// # Example: invalid UTF-8
182 ///
183 /// ```
184 /// use csv::{ByteRecord, StringRecord};
185 ///
186 /// let byte_record = ByteRecord::from(vec![
187 /// &b"quux"[..], &b"foo\xFFbar"[..], &b"c"[..],
188 /// ]);
189 /// let str_record = StringRecord::from_byte_record_lossy(byte_record);
190 /// assert_eq!(&str_record[0], "quux");
191 /// assert_eq!(&str_record[1], "foo�bar");
192 /// assert_eq!(&str_record[2], "c");
193 /// ```
194 #[inline]
195 pub fn from_byte_record_lossy(record: ByteRecord) -> StringRecord {
196 // If the record is valid UTF-8, then take the easy path.
197 if let Ok(()) = record.validate() {
198 return StringRecord(record);
199 }
200 // TODO: We can be faster here. Not sure if it's worth it.
201 let mut str_record =
202 StringRecord::with_capacity(record.as_slice().len(), record.len());
203 for field in &record {
204 str_record.push_field(&String::from_utf8_lossy(field));
205 }
206 str_record
207 }
208
209 /// Deserialize this record.
210 ///
211 /// The `D` type parameter refers to the type that this record should be
212 /// deserialized into. The `'de` lifetime refers to the lifetime of the
213 /// `StringRecord`. The `'de` lifetime permits deserializing into structs
214 /// that borrow field data from this record.
215 ///
216 /// An optional `headers` parameter permits deserializing into a struct
217 /// based on its field names (corresponding to header values) rather than
218 /// the order in which the fields are defined.
219 ///
220 /// # Example: without headers
221 ///
222 /// This shows how to deserialize a single row into a struct based on the
223 /// order in which fields occur. This example also shows how to borrow
224 /// fields from the `StringRecord`, which results in zero allocation
225 /// deserialization.
226 ///
227 /// ```
228 /// use std::error::Error;
229 ///
230 /// use csv::StringRecord;
231 ///
232 /// #[derive(serde::Deserialize)]
233 /// struct Row<'a> {
234 /// city: &'a str,
235 /// country: &'a str,
236 /// population: u64,
237 /// }
238 ///
239 /// # fn main() { example().unwrap() }
240 /// fn example() -> Result<(), Box<dyn Error>> {
241 /// let record = StringRecord::from(vec![
242 /// "Boston", "United States", "4628910",
243 /// ]);
244 ///
245 /// let row: Row = record.deserialize(None)?;
246 /// assert_eq!(row.city, "Boston");
247 /// assert_eq!(row.country, "United States");
248 /// assert_eq!(row.population, 4628910);
249 /// Ok(())
250 /// }
251 /// ```
252 ///
253 /// # Example: with headers
254 ///
255 /// This example is like the previous one, but shows how to deserialize
256 /// into a struct based on the struct's field names. For this to work,
257 /// you must provide a header row.
258 ///
259 /// This example also shows that you can deserialize into owned data
260 /// types (e.g., `String`) instead of borrowed data types (e.g., `&str`).
261 ///
262 /// ```
263 /// use std::error::Error;
264 ///
265 /// use csv::StringRecord;
266 ///
267 /// #[derive(serde::Deserialize)]
268 /// struct Row {
269 /// city: String,
270 /// country: String,
271 /// population: u64,
272 /// }
273 ///
274 /// # fn main() { example().unwrap() }
275 /// fn example() -> Result<(), Box<dyn Error>> {
276 /// // Notice that the fields are not in the same order
277 /// // as the fields in the struct!
278 /// let header = StringRecord::from(vec![
279 /// "country", "city", "population",
280 /// ]);
281 /// let record = StringRecord::from(vec![
282 /// "United States", "Boston", "4628910",
283 /// ]);
284 ///
285 /// let row: Row = record.deserialize(Some(&header))?;
286 /// assert_eq!(row.city, "Boston");
287 /// assert_eq!(row.country, "United States");
288 /// assert_eq!(row.population, 4628910);
289 /// Ok(())
290 /// }
291 /// ```
292 pub fn deserialize<'de, D: Deserialize<'de>>(
293 &'de self,
294 headers: Option<&'de StringRecord>,
295 ) -> Result<D> {
296 deserialize_string_record(self, headers)
297 }
298
299 /// Returns an iterator over all fields in this record.
300 ///
301 /// # Example
302 ///
303 /// This example shows how to iterate over each field in a `StringRecord`.
304 ///
305 /// ```
306 /// use csv::StringRecord;
307 ///
308 /// let record = StringRecord::from(vec!["a", "b", "c"]);
309 /// for field in record.iter() {
310 /// assert!(field == "a" || field == "b" || field == "c");
311 /// }
312 /// ```
313 #[inline]
314 pub fn iter(&self) -> StringRecordIter {
315 self.into_iter()
316 }
317
318 /// Return the field at index `i`.
319 ///
320 /// If no field at index `i` exists, then this returns `None`.
321 ///
322 /// # Example
323 ///
324 /// ```
325 /// use csv::StringRecord;
326 ///
327 /// let record = StringRecord::from(vec!["a", "b", "c"]);
328 /// assert_eq!(record.get(1), Some("b"));
329 /// assert_eq!(record.get(3), None);
330 /// ```
331 #[inline]
332 pub fn get(&self, i: usize) -> Option<&str> {
333 self.0.get(i).map(|bytes| {
334 debug_assert!(str::from_utf8(bytes).is_ok());
335 // This is safe because we guarantee that all string records
336 // have a valid UTF-8 buffer. It's also safe because we
337 // individually check each field for valid UTF-8.
338 unsafe { str::from_utf8_unchecked(bytes) }
339 })
340 }
341
342 /// Returns true if and only if this record is empty.
343 ///
344 /// # Example
345 ///
346 /// ```
347 /// use csv::StringRecord;
348 ///
349 /// assert!(StringRecord::new().is_empty());
350 /// ```
351 #[inline]
352 pub fn is_empty(&self) -> bool {
353 self.len() == 0
354 }
355
356 /// Returns the number of fields in this record.
357 ///
358 /// # Example
359 ///
360 /// ```
361 /// use csv::StringRecord;
362 ///
363 /// let record = StringRecord::from(vec!["a", "b", "c"]);
364 /// assert_eq!(record.len(), 3);
365 /// ```
366 #[inline]
367 pub fn len(&self) -> usize {
368 self.0.len()
369 }
370
371 /// Truncate this record to `n` fields.
372 ///
373 /// If `n` is greater than the number of fields in this record, then this
374 /// has no effect.
375 ///
376 /// # Example
377 ///
378 /// ```
379 /// use csv::StringRecord;
380 ///
381 /// let mut record = StringRecord::from(vec!["a", "b", "c"]);
382 /// assert_eq!(record.len(), 3);
383 /// record.truncate(1);
384 /// assert_eq!(record.len(), 1);
385 /// assert_eq!(record, vec!["a"]);
386 /// ```
387 #[inline]
388 pub fn truncate(&mut self, n: usize) {
389 self.0.truncate(n);
390 }
391
392 /// Clear this record so that it has zero fields.
393 ///
394 /// Note that it is not necessary to clear the record to reuse it with
395 /// the CSV reader.
396 ///
397 /// # Example
398 ///
399 /// ```
400 /// use csv::StringRecord;
401 ///
402 /// let mut record = StringRecord::from(vec!["a", "b", "c"]);
403 /// assert_eq!(record.len(), 3);
404 /// record.clear();
405 /// assert_eq!(record.len(), 0);
406 /// ```
407 #[inline]
408 pub fn clear(&mut self) {
409 self.0.clear();
410 }
411
412 /// Trim the fields of this record so that leading and trailing whitespace
413 /// is removed.
414 ///
415 /// This method uses the Unicode definition of whitespace.
416 ///
417 /// # Example
418 ///
419 /// ```
420 /// use csv::StringRecord;
421 ///
422 /// let mut record = StringRecord::from(vec![
423 /// " ", "\u{3000}\tfoo ", "bar ", "b a z",
424 /// ]);
425 /// record.trim();
426 /// assert_eq!(record, vec!["", "foo", "bar", "b a z"]);
427 /// ```
428 pub fn trim(&mut self) {
429 let length = self.len();
430 if length == 0 {
431 return;
432 }
433 // TODO: We could likely do this in place, but for now, we allocate.
434 let mut trimmed =
435 StringRecord::with_capacity(self.as_slice().len(), self.len());
436 trimmed.set_position(self.position().cloned());
437 for field in &*self {
438 trimmed.push_field(field.trim());
439 }
440 *self = trimmed;
441 }
442
443 /// Add a new field to this record.
444 ///
445 /// # Example
446 ///
447 /// ```
448 /// use csv::StringRecord;
449 ///
450 /// let mut record = StringRecord::new();
451 /// record.push_field("foo");
452 /// assert_eq!(&record[0], "foo");
453 /// ```
454 #[inline]
455 pub fn push_field(&mut self, field: &str) {
456 self.0.push_field(field.as_bytes());
457 }
458
459 /// Return the position of this record, if available.
460 ///
461 /// # Example
462 ///
463 /// ```
464 /// use std::error::Error;
465 /// use csv::{StringRecord, ReaderBuilder};
466 ///
467 /// # fn main() { example().unwrap(); }
468 /// fn example() -> Result<(), Box<dyn Error>> {
469 /// let mut record = StringRecord::new();
470 /// let mut rdr = ReaderBuilder::new()
471 /// .has_headers(false)
472 /// .from_reader("a,b,c\nx,y,z".as_bytes());
473 ///
474 /// assert!(rdr.read_record(&mut record)?);
475 /// {
476 /// let pos = record.position().expect("a record position");
477 /// assert_eq!(pos.byte(), 0);
478 /// assert_eq!(pos.line(), 1);
479 /// assert_eq!(pos.record(), 0);
480 /// }
481 ///
482 /// assert!(rdr.read_record(&mut record)?);
483 /// {
484 /// let pos = record.position().expect("a record position");
485 /// assert_eq!(pos.byte(), 6);
486 /// assert_eq!(pos.line(), 2);
487 /// assert_eq!(pos.record(), 1);
488 /// }
489 ///
490 /// // Finish the CSV reader for good measure.
491 /// assert!(!rdr.read_record(&mut record)?);
492 /// Ok(())
493 /// }
494 /// ```
495 #[inline]
496 pub fn position(&self) -> Option<&Position> {
497 self.0.position()
498 }
499
500 /// Set the position of this record.
501 ///
502 /// # Example
503 ///
504 /// ```
505 /// use csv::{StringRecord, Position};
506 ///
507 /// let mut record = StringRecord::from(vec!["a", "b", "c"]);
508 /// let mut pos = Position::new();
509 /// pos.set_byte(100);
510 /// pos.set_line(4);
511 /// pos.set_record(2);
512 ///
513 /// record.set_position(Some(pos.clone()));
514 /// assert_eq!(record.position(), Some(&pos));
515 /// ```
516 #[inline]
517 pub fn set_position(&mut self, pos: Option<Position>) {
518 self.0.set_position(pos);
519 }
520
521 /// Return the start and end position of a field in this record.
522 ///
523 /// If no such field exists at the given index, then return `None`.
524 ///
525 /// The range returned can be used with the slice returned by `as_slice`.
526 /// Namely, the range returned is guaranteed to start and end at valid
527 /// UTF-8 sequence boundaries.
528 ///
529 /// # Example
530 ///
531 /// ```
532 /// use csv::StringRecord;
533 ///
534 /// let record = StringRecord::from(vec!["foo", "quux", "z"]);
535 /// let range = record.range(1).expect("a record range");
536 /// assert_eq!(&record.as_slice()[range], "quux");
537 /// ```
538 #[inline]
539 pub fn range(&self, i: usize) -> Option<Range<usize>> {
540 self.0.range(i)
541 }
542
543 /// Return the entire row as a single string slice. The slice returned
544 /// stores all fields contiguously. The boundaries of each field can be
545 /// determined via the `range` method.
546 ///
547 /// # Example
548 ///
549 /// ```
550 /// use csv::StringRecord;
551 ///
552 /// let record = StringRecord::from(vec!["foo", "quux", "z"]);
553 /// assert_eq!(record.as_slice(), "fooquuxz");
554 /// ```
555 #[inline]
556 pub fn as_slice(&self) -> &str {
557 debug_assert!(str::from_utf8(self.0.as_slice()).is_ok());
558 // This is safe because we guarantee that each field is valid UTF-8.
559 // If each field is valid UTF-8, then the entire buffer (up to the end
560 // of the last field) must also be valid UTF-8.
561 unsafe { str::from_utf8_unchecked(self.0.as_slice()) }
562 }
563
564 /// Return a reference to this record's raw
565 /// [`ByteRecord`](struct.ByteRecord.html).
566 ///
567 /// # Example
568 ///
569 /// ```
570 /// use csv::StringRecord;
571 ///
572 /// let str_record = StringRecord::from(vec!["a", "b", "c"]);
573 /// let byte_record = str_record.as_byte_record();
574 /// assert_eq!(&byte_record[2], b"c");
575 /// ```
576 #[inline]
577 pub fn as_byte_record(&self) -> &ByteRecord {
578 &self.0
579 }
580
581 /// Convert this `StringRecord` into a
582 /// [`ByteRecord`](struct.ByteRecord.html).
583 ///
584 /// # Example
585 ///
586 /// ```
587 /// use csv::StringRecord;
588 ///
589 /// let str_record = StringRecord::from(vec!["a", "b", "c"]);
590 /// let byte_record = str_record.into_byte_record();
591 /// assert_eq!(&byte_record[2], b"c");
592 /// ```
593 ///
594 /// Note that this can also be achieved using the `From` impl:
595 ///
596 /// ```
597 /// use csv::{ByteRecord, StringRecord};
598 ///
599 /// // Using ByteRecord::from...
600 /// let str_record = StringRecord::from(vec!["a", "b", "c"]);
601 /// assert_eq!(ByteRecord::from(str_record).len(), 3);
602 ///
603 /// // Using StringRecord::into...
604 /// let str_record = StringRecord::from(vec!["a", "b", "c"]);
605 /// let byte_record: ByteRecord = str_record.into();
606 /// assert_eq!(byte_record.len(), 3);
607 /// ```
608 #[inline]
609 pub fn into_byte_record(self) -> ByteRecord {
610 self.0
611 }
612
613 /// Clone this record, but only copy `fields` up to the end of bounds. This
614 /// is useful when one wants to copy a record, but not necessarily any
615 /// excess capacity in that record.
616 #[inline]
617 pub(crate) fn clone_truncated(&self) -> StringRecord {
618 StringRecord(self.0.clone_truncated())
619 }
620
621 /// A safe function for reading CSV data into a `StringRecord`.
622 ///
623 /// This relies on the internal representation of `StringRecord`.
624 #[inline(always)]
625 pub(crate) fn read<R: io::Read>(
626 &mut self,
627 rdr: &mut Reader<R>,
628 ) -> Result<bool> {
629 // SAFETY: This code is critical to upholding the safety of other code
630 // blocks in this module. Namely, after calling `read_byte_record`,
631 // it is possible for `record` to contain invalid UTF-8. We check for
632 // this in the `validate` method, and if it does have invalid UTF-8, we
633 // clear the record. (It is bad for `record` to contain invalid UTF-8
634 // because other accessor methods, like `get`, assume that every field
635 // is valid UTF-8.)
636 let pos = rdr.position().clone();
637 let read_res = rdr.read_byte_record(&mut self.0);
638 let utf8_res = match self.0.validate() {
639 Ok(()) => Ok(()),
640 Err(err) => {
641 // If this record isn't valid UTF-8, then completely wipe it.
642 self.0.clear();
643 Err(err)
644 }
645 };
646 match (read_res, utf8_res) {
647 (Err(err), _) => Err(err),
648 (Ok(_), Err(err)) => {
649 Err(Error::new(ErrorKind::Utf8 { pos: Some(pos), err }))
650 }
651 (Ok(eof), Ok(())) => Ok(eof),
652 }
653 }
654}
655
656impl ops::Index<usize> for StringRecord {
657 type Output = str;
658 #[inline]
659 fn index(&self, i: usize) -> &str {
660 self.get(i).unwrap()
661 }
662}
663
664impl<T: AsRef<str>> From<Vec<T>> for StringRecord {
665 #[inline]
666 fn from(xs: Vec<T>) -> StringRecord {
667 StringRecord::from_iter(xs.into_iter())
668 }
669}
670
671impl<'a, T: AsRef<str>> From<&'a [T]> for StringRecord {
672 #[inline]
673 fn from(xs: &'a [T]) -> StringRecord {
674 StringRecord::from_iter(xs)
675 }
676}
677
678impl<T: AsRef<str>> FromIterator<T> for StringRecord {
679 #[inline]
680 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> StringRecord {
681 let mut record = StringRecord::new();
682 record.extend(iter);
683 record
684 }
685}
686
687impl<T: AsRef<str>> Extend<T> for StringRecord {
688 #[inline]
689 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
690 for x in iter {
691 self.push_field(x.as_ref());
692 }
693 }
694}
695
696impl<'a> IntoIterator for &'a StringRecord {
697 type IntoIter = StringRecordIter<'a>;
698 type Item = &'a str;
699
700 #[inline]
701 fn into_iter(self) -> StringRecordIter<'a> {
702 StringRecordIter(self.0.iter())
703 }
704}
705
706/// An iterator over the fields in a string record.
707///
708/// The `'r` lifetime variable refers to the lifetime of the `StringRecord`
709/// that is being iterated over.
710#[derive(Clone)]
711pub struct StringRecordIter<'r>(ByteRecordIter<'r>);
712
713impl<'r> Iterator for StringRecordIter<'r> {
714 type Item = &'r str;
715
716 #[inline]
717 fn next(&mut self) -> Option<&'r str> {
718 self.0.next().map(|bytes| {
719 debug_assert!(str::from_utf8(bytes).is_ok());
720 // See StringRecord::get for safety argument.
721 unsafe { str::from_utf8_unchecked(bytes) }
722 })
723 }
724
725 #[inline]
726 fn size_hint(&self) -> (usize, Option<usize>) {
727 self.0.size_hint()
728 }
729
730 #[inline]
731 fn count(self) -> usize {
732 self.0.len()
733 }
734}
735
736impl<'r> DoubleEndedIterator for StringRecordIter<'r> {
737 #[inline]
738 fn next_back(&mut self) -> Option<&'r str> {
739 self.0.next_back().map(|bytes| {
740 debug_assert!(str::from_utf8(bytes).is_ok());
741 // See StringRecord::get for safety argument.
742 unsafe { str::from_utf8_unchecked(bytes) }
743 })
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use crate::string_record::StringRecord;
750
751 #[test]
752 fn trim_front() {
753 let mut rec = StringRecord::from(vec![" abc"]);
754 rec.trim();
755 assert_eq!(rec.get(0), Some("abc"));
756
757 let mut rec = StringRecord::from(vec![" abc", " xyz"]);
758 rec.trim();
759 assert_eq!(rec.get(0), Some("abc"));
760 assert_eq!(rec.get(1), Some("xyz"));
761 }
762
763 #[test]
764 fn trim_back() {
765 let mut rec = StringRecord::from(vec!["abc "]);
766 rec.trim();
767 assert_eq!(rec.get(0), Some("abc"));
768
769 let mut rec = StringRecord::from(vec!["abc ", "xyz "]);
770 rec.trim();
771 assert_eq!(rec.get(0), Some("abc"));
772 assert_eq!(rec.get(1), Some("xyz"));
773 }
774
775 #[test]
776 fn trim_both() {
777 let mut rec = StringRecord::from(vec![" abc "]);
778 rec.trim();
779 assert_eq!(rec.get(0), Some("abc"));
780
781 let mut rec = StringRecord::from(vec![" abc ", " xyz "]);
782 rec.trim();
783 assert_eq!(rec.get(0), Some("abc"));
784 assert_eq!(rec.get(1), Some("xyz"));
785 }
786
787 #[test]
788 fn trim_does_not_panic_on_empty_records_1() {
789 let mut rec = StringRecord::from(vec![""]);
790 rec.trim();
791 assert_eq!(rec.get(0), Some(""));
792 }
793
794 #[test]
795 fn trim_does_not_panic_on_empty_records_2() {
796 let mut rec = StringRecord::from(vec!["", ""]);
797 rec.trim();
798 assert_eq!(rec.get(0), Some(""));
799 assert_eq!(rec.get(1), Some(""));
800 }
801
802 #[test]
803 fn trim_does_not_panic_on_empty_records_3() {
804 let mut rec = StringRecord::new();
805 rec.trim();
806 assert_eq!(rec.as_slice().len(), 0);
807 }
808
809 #[test]
810 fn trim_whitespace_only() {
811 let mut rec = StringRecord::from(vec![
812 "\u{0009}\u{000A}\u{000B}\u{000C}\u{000D}\u{0020}\u{0085}\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}",
813 ]);
814 rec.trim();
815 assert_eq!(rec.get(0), Some(""));
816 }
817
818 // Check that record equality respects field boundaries.
819 //
820 // Regression test for #138.
821 #[test]
822 fn eq_field_boundaries() {
823 let test1 = StringRecord::from(vec!["12", "34"]);
824 let test2 = StringRecord::from(vec!["123", "4"]);
825
826 assert_ne!(test1, test2);
827 }
828
829 // Check that record equality respects number of fields.
830 //
831 // Regression test for #138.
832 #[test]
833 fn eq_record_len() {
834 let test1 = StringRecord::from(vec!["12", "34", "56"]);
835 let test2 = StringRecord::from(vec!["12", "34"]);
836 assert_ne!(test1, test2);
837 }
838}