1#![warn(
48 clippy::all,
49 nonstandard_style,
50 future_incompatible,
51 missing_debug_implementations
52)]
53#![deny(missing_docs)]
54#![forbid(unsafe_code)]
55
56use std::{
57 cmp::{Ordering, Reverse},
58 collections::BTreeMap,
59 fmt::{self, Display},
60 str::FromStr,
61};
62
63use headers_core::{Error as HeaderError, Header, HeaderName, HeaderValue};
64use mediatype::{names, MediaType, MediaTypeBuf, ReadParams};
65
66#[derive(Debug)]
74pub struct Accept(Vec<MediaTypeBuf>);
75
76impl Accept {
77 pub fn media_types(&self) -> impl Iterator<Item = &MediaTypeBuf> {
85 self.0.iter()
86 }
87
88 pub fn negotiate<'a, 'mt: 'a, Available>(
132 &self,
133 available: Available,
134 ) -> Option<&'a MediaType<'mt>>
135 where
136 Available: IntoIterator<Item = &'a MediaType<'mt>>,
137 {
138 struct BestMediaType<'a, 'mt: 'a> {
139 quality: QValue,
140 parsed_priority: usize,
141 given_priority: usize,
142 media_type: &'a MediaType<'mt>,
143 }
144
145 available
146 .into_iter()
147 .enumerate()
148 .filter_map(|(given_priority, available_type)| {
149 if let Some(matched_range) = self
150 .0
151 .iter()
152 .enumerate()
153 .find(|(_, available_range)| MediaRange(available_range) == *available_type)
154 {
155 let quality = Self::parse_q_value(matched_range.1);
156 if quality.is_zero() {
157 return None;
158 }
159 Some(BestMediaType {
160 quality,
161 parsed_priority: matched_range.0,
162 given_priority,
163 media_type: available_type,
164 })
165 } else {
166 None
167 }
168 })
169 .max_by_key(|x| (x.quality, Reverse((x.parsed_priority, x.given_priority))))
170 .map(|best| best.media_type)
171 }
172
173 fn parse(mut s: &str) -> Result<Self, HeaderError> {
174 let mut media_types = Vec::new();
175
176 while !s.is_empty() {
180 if let Some(index) = s.find(|c: char| !is_ows(c)) {
182 s = &s[index..];
183 } else {
184 break;
185 }
186
187 let mut end = 0;
188 let mut quoted = false;
189 let mut escaped = false;
190 for c in s.chars() {
191 if escaped {
192 escaped = false;
193 } else {
194 match c {
195 '"' => quoted = !quoted,
196 '\\' if quoted => escaped = true,
197 ',' if !quoted => break,
198 _ => (),
199 }
200 }
201 end += c.len_utf8();
202 }
203
204 match MediaTypeBuf::from_str(s[..end].trim()) {
206 Ok(mt) => media_types.push(mt),
207 Err(_) => return Err(HeaderError::invalid()),
208 }
209
210 s = s[end..].trim_start_matches(',');
212 }
213
214 media_types.sort_by_key(|x| {
216 let spec = Self::parse_specificity(x);
217 let q = Self::parse_q_value(x);
218 Reverse((spec, q))
219 });
220
221 Ok(Self(media_types))
222 }
223
224 fn parse_q_value(media_type: &MediaTypeBuf) -> QValue {
225 media_type
226 .get_param(names::Q)
227 .and_then(|v| v.as_str().parse().ok())
228 .unwrap_or_default()
229 }
230
231 fn parse_specificity(media_type: &MediaTypeBuf) -> usize {
232 let type_specificity = if media_type.ty() != names::_STAR {
233 1
234 } else {
235 0
236 };
237 let subtype_specificity = if media_type.subty() != names::_STAR {
238 1
239 } else {
240 0
241 };
242
243 let parameter_count = media_type
244 .params()
245 .filter(|&(name, _)| name != names::Q)
246 .count();
247
248 type_specificity + subtype_specificity + parameter_count
249 }
250}
251
252impl Header for Accept {
254 fn name() -> &'static HeaderName {
255 &http::header::ACCEPT
256 }
257
258 fn decode<'i, I>(values: &mut I) -> Result<Self, HeaderError>
259 where
260 I: Iterator<Item = &'i HeaderValue>,
261 {
262 let mut values_iter = values.map(|v| v.to_str().map_err(|_| HeaderError::invalid()));
263 let mut value_str = String::from(values_iter.next().ok_or(HeaderError::invalid())??);
265 for v in values_iter {
266 value_str.push(',');
267 value_str.push_str(v?);
268 }
269 Self::parse(&value_str)
270 }
271
272 fn encode<E>(&self, values: &mut E)
273 where
274 E: Extend<HeaderValue>,
275 {
276 let value = HeaderValue::from_str(&self.to_string())
277 .expect("Header value should only contain visible ASCII characters (32-127)");
278 values.extend(std::iter::once(value));
279 }
280}
281
282impl FromStr for Accept {
283 type Err = HeaderError;
284
285 fn from_str(s: &str) -> Result<Self, Self::Err> {
286 Self::parse(s).map_err(|_| HeaderError::invalid())
287 }
288}
289
290impl TryFrom<&HeaderValue> for Accept {
291 type Error = HeaderError;
292
293 fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
294 let s = value.to_str().map_err(|_| HeaderError::invalid())?;
295 s.parse().map_err(|_| HeaderError::invalid())
296 }
297}
298
299impl Display for Accept {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let media_types = self
302 .0
303 .iter()
304 .map(|mt| mt.to_string())
305 .collect::<Vec<_>>()
306 .join(", ");
307 write!(f, "{media_types}")
308 }
309}
310
311impl<'a> FromIterator<MediaType<'a>> for Accept {
312 fn from_iter<T: IntoIterator<Item = MediaType<'a>>>(iter: T) -> Self {
313 iter.into_iter().map(MediaTypeBuf::from).collect()
314 }
315}
316
317impl FromIterator<MediaTypeBuf> for Accept {
318 fn from_iter<T: IntoIterator<Item = MediaTypeBuf>>(iter: T) -> Self {
319 Self(iter.into_iter().collect())
320 }
321}
322
323const fn is_ows(c: char) -> bool {
327 c == ' ' || c == '\t'
328}
329
330struct MediaRange<'a>(&'a MediaTypeBuf);
331
332impl PartialEq<MediaType<'_>> for MediaRange<'_> {
333 fn eq(&self, other: &MediaType<'_>) -> bool {
334 let (type_match, subtype_match, suffix_match) = (
335 self.0.ty() == other.ty,
336 self.0.subty() == other.subty,
337 self.0.suffix() == other.suffix,
338 );
339
340 let wildcard_type = self.0.ty() == names::_STAR;
341 let wildcard_subtype = self.0.subty() == names::_STAR && type_match;
342
343 let exact_match =
344 type_match && subtype_match && suffix_match && self.0.params().count() == 0;
345
346 let params_match = type_match && subtype_match && suffix_match && {
347 let self_params = self
348 .0
349 .params()
350 .filter(|&(name, _)| name != names::Q)
351 .collect::<BTreeMap<_, _>>();
352
353 let other_params = other
354 .params()
355 .filter(|&(name, _)| name != names::Q)
356 .collect::<BTreeMap<_, _>>();
357
358 self_params == other_params
359 };
360
361 wildcard_type || wildcard_subtype || exact_match || params_match
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366struct QValue(
367 u16,
369);
370
371impl Default for QValue {
372 fn default() -> Self {
373 QValue(1000)
374 }
375}
376
377impl QValue {
378 pub fn is_zero(&self) -> bool {
380 self.0 == 0
381 }
382}
383
384impl FromStr for QValue {
385 type Err = HeaderError;
386
387 fn from_str(s: &str) -> Result<Self, Self::Err> {
388 fn parse_fractional(digits: &[u8]) -> Result<u16, HeaderError> {
391 digits
392 .iter()
393 .try_fold(0u16, |acc, &c| {
394 if c.is_ascii_digit() {
395 Some(acc * 10 + (c - b'0') as u16)
396 } else {
397 None
398 }
399 })
400 .map(|num| match digits.len() {
401 1 => num * 100,
402 2 => num * 10,
403 _ => num,
404 })
405 .ok_or_else(HeaderError::invalid)
406 }
407
408 match s.as_bytes() {
409 b"0" => Ok(QValue(0)),
410 b"1" => Ok(QValue(1000)),
411 [b'1', b'.', zeros @ ..] if zeros.len() <= 3 && zeros.iter().all(|d| *d == b'0') => {
412 Ok(QValue(1000))
413 }
414 [b'0', b'.', fractional @ ..] if fractional.len() <= 3 => {
415 parse_fractional(fractional).map(QValue)
416 }
417 _ => Err(HeaderError::invalid()),
418 }
419 }
420}
421
422impl Ord for QValue {
423 fn cmp(&self, other: &Self) -> Ordering {
424 self.0.cmp(&other.0)
425 }
426}
427
428impl PartialOrd for QValue {
429 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
430 Some(self.cmp(other))
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn reordering() {
440 let accept = Accept::from_str("audio/*; q=0.2, audio/basic").unwrap();
441 let mut media_types = accept.media_types();
442 assert_eq!(
443 media_types.next(),
444 Some(&MediaTypeBuf::from_str("audio/basic").unwrap())
445 );
446 assert_eq!(
447 media_types.next(),
448 Some(&MediaTypeBuf::from_str("audio/*; q=0.2").unwrap())
449 );
450 assert_eq!(media_types.next(), None);
451 }
452
453 #[test]
454 fn reordering_elaborate() {
455 let accept =
456 Accept::from_str("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c").unwrap();
457 let mut media_types = accept.media_types();
458 assert_eq!(
459 media_types.next(),
460 Some(&MediaTypeBuf::from_str("text/html").unwrap())
461 );
462 assert_eq!(
463 media_types.next(),
464 Some(&MediaTypeBuf::from_str("text/x-c").unwrap())
465 );
466 assert_eq!(
467 media_types.next(),
468 Some(&MediaTypeBuf::from_str("text/x-dvi; q=0.8").unwrap())
469 );
470 assert_eq!(
471 media_types.next(),
472 Some(&MediaTypeBuf::from_str("text/plain; q=0.5").unwrap())
473 );
474 assert_eq!(media_types.next(), None);
475 }
476
477 #[test]
478 fn preserve_ordering() {
479 let accept = Accept::from_str("x/y, a/b").unwrap();
480 let mut media_types = accept.media_types();
481 assert_eq!(
482 media_types.next(),
483 Some(&MediaTypeBuf::from_str("x/y").unwrap())
484 );
485 assert_eq!(
486 media_types.next(),
487 Some(&MediaTypeBuf::from_str("a/b").unwrap())
488 );
489 assert_eq!(media_types.next(), None);
490 }
491
492 #[test]
493 fn params() {
494 let accept =
495 Accept::from_str("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
496 .unwrap();
497 let mut media_types = accept.media_types();
498 assert_eq!(
499 media_types.next(),
500 Some(&MediaTypeBuf::from_str("text/html").unwrap())
501 );
502 assert_eq!(
503 media_types.next(),
504 Some(&MediaTypeBuf::from_str("application/xhtml+xml").unwrap())
505 );
506 assert_eq!(
507 media_types.next(),
508 Some(&MediaTypeBuf::from_str("application/xml;q=0.9").unwrap())
509 );
510 assert_eq!(
511 media_types.next(),
512 Some(&MediaTypeBuf::from_str("*/*;q=0.8").unwrap())
513 );
514 assert_eq!(media_types.next(), None);
515 }
516
517 #[test]
518 fn quoted_params() {
519 let accept = Accept::from_str(
520 "text/html; message=\"Hello, world!\", application/xhtml+xml; message=\"Hello, \
521 world?\"",
522 )
523 .unwrap();
524 let mut media_types = accept.media_types();
525 assert_eq!(
526 media_types.next(),
527 Some(&MediaTypeBuf::from_str("text/html; message=\"Hello, world!\"").unwrap())
528 );
529 assert_eq!(
530 media_types.next(),
531 Some(
532 &MediaTypeBuf::from_str("application/xhtml+xml; message=\"Hello, world?\"")
533 .unwrap()
534 )
535 );
536 assert_eq!(media_types.next(), None);
537 }
538
539 #[test]
540 fn more_specifics() {
541 let accept = Accept::from_str("text/*, text/plain, text/plain;format=flowed, */*").unwrap();
542 let mut media_types = accept.media_types();
543 assert_eq!(
544 media_types.next(),
545 Some(&MediaTypeBuf::from_str("text/plain;format=flowed").unwrap())
546 );
547 assert_eq!(
548 media_types.next(),
549 Some(&MediaTypeBuf::from_str("text/plain").unwrap())
550 );
551 assert_eq!(
552 media_types.next(),
553 Some(&MediaTypeBuf::from_str("text/*").unwrap())
554 );
555 assert_eq!(
556 media_types.next(),
557 Some(&MediaTypeBuf::from_str("*/*").unwrap())
558 );
559 assert_eq!(media_types.next(), None);
560 }
561
562 #[test]
563 fn variable_quality_more_specifics() {
564 let accept = Accept::from_str(
565 "text/*;q=0.3, text/plain;q=0.7, text/csv;q=0, text/plain;format=flowed, \
566 text/plain;format=fixed;q=0.4, */*;q=0.5",
567 )
568 .unwrap();
569 let mut media_types = accept.media_types();
570 assert_eq!(
571 media_types.next(),
572 Some(&MediaTypeBuf::from_str("text/plain;format=flowed").unwrap())
573 );
574 assert_eq!(
575 media_types.next(),
576 Some(&MediaTypeBuf::from_str("text/plain;format=fixed;q=0.4").unwrap())
577 );
578 assert_eq!(
579 media_types.next(),
580 Some(&MediaTypeBuf::from_str("text/plain;q=0.7").unwrap())
581 );
582 assert_eq!(
583 media_types.next(),
584 Some(&MediaTypeBuf::from_str("text/csv;q=0").unwrap())
585 );
586 assert_eq!(
587 media_types.next(),
588 Some(&MediaTypeBuf::from_str("text/*;q=0.3").unwrap())
589 );
590 assert_eq!(
591 media_types.next(),
592 Some(&MediaTypeBuf::from_str("*/*;q=0.5").unwrap())
593 );
594 assert_eq!(media_types.next(), None);
595 }
596
597 #[test]
598 fn negotiate() {
599 let accept = Accept::from_str(
600 "text/html, application/xhtml+xml, application/xml;q=0.9, text/*;q=0.7, text/csv;q=0",
601 )
602 .unwrap();
603
604 assert_eq!(
606 accept
607 .negotiate(&vec![
608 MediaType::parse("text/html").unwrap(),
609 MediaType::parse("application/json").unwrap()
610 ])
611 .unwrap(),
612 &MediaType::parse("text/html").unwrap()
613 );
614 assert_eq!(
616 accept
617 .negotiate(&vec![
618 MediaType::parse("application/xhtml+xml").unwrap(),
619 MediaType::parse("text/html").unwrap()
620 ])
621 .unwrap(),
622 &MediaType::parse("text/html").unwrap()
623 );
624 assert_eq!(
626 accept
627 .negotiate(&vec![
628 MediaType::parse("text/plain").unwrap(),
629 MediaType::parse("image/gif").unwrap()
630 ])
631 .unwrap(),
632 &MediaType::parse("text/plain").unwrap()
633 );
634 assert_eq!(
636 accept
637 .negotiate(&vec![
638 MediaType::parse("image/gif").unwrap(),
639 MediaType::parse("text/plain").unwrap(),
640 MediaType::parse("text/troff").unwrap(),
641 ])
642 .unwrap(),
643 &MediaType::parse("text/plain").unwrap()
644 );
645 assert_eq!(
647 accept.negotiate(&vec![
648 MediaType::parse("image/gif").unwrap(),
649 MediaType::parse("image/png").unwrap()
650 ]),
651 None
652 );
653 assert_eq!(
655 accept.negotiate(&vec![
656 MediaType::parse("image/gif").unwrap(),
657 MediaType::parse("text/csv").unwrap()
658 ]),
659 None
660 );
661 }
662
663 #[test]
664 fn negotiate_with_full_wildcard() {
665 let accept =
666 Accept::from_str("text/html, text/*;q=0.7, */*;q=0.1, text/csv;q=0.0").unwrap();
667
668 assert_eq!(
670 accept
671 .negotiate(&vec![
672 MediaType::parse("text/html").unwrap(),
673 MediaType::parse("application/json").unwrap()
674 ])
675 .unwrap(),
676 &MediaType::parse("text/html").unwrap()
677 );
678 assert_eq!(
680 accept
681 .negotiate(&vec![
682 MediaType::parse("text/plain").unwrap(),
683 MediaType::parse("image/gif").unwrap()
684 ])
685 .unwrap(),
686 &MediaType::parse("text/plain").unwrap()
687 );
688 assert_eq!(
690 accept
691 .negotiate(&vec![
692 MediaType::parse("text/javascript").unwrap(),
693 MediaType::parse("text/plain").unwrap()
694 ])
695 .unwrap(),
696 &MediaType::parse("text/javascript").unwrap()
697 );
698 assert_eq!(
700 accept
701 .negotiate(&vec![
702 MediaType::parse("image/gif").unwrap(),
703 MediaType::parse("image/png").unwrap()
704 ])
705 .unwrap(),
706 &MediaType::parse("image/gif").unwrap()
707 );
708 assert_eq!(
710 accept
711 .negotiate(&vec![
712 MediaType::parse("text/csv").unwrap(),
713 MediaType::parse("text/javascript").unwrap()
714 ])
715 .unwrap(),
716 &MediaType::parse("text/javascript").unwrap()
717 );
718 }
719
720 #[test]
721 fn negotiate_diabolically() {
722 let accept = Accept::from_str(
723 "text/*;q=0.3, text/csv;q=0.2, text/plain;q=0.7, text/plain;format=rot13;q=0.7, \
724 text/plain;format=flowed, text/plain;format=fixed;q=0.4, */*;q=0.5",
725 )
726 .unwrap();
727
728 assert_eq!(
730 accept
731 .negotiate(&vec![
732 MediaType::parse("text/html").unwrap(),
733 MediaType::parse("text/plain").unwrap()
734 ])
735 .unwrap(),
736 &MediaType::parse("text/plain").unwrap()
737 );
738 assert_eq!(
740 accept
741 .negotiate(&vec![
742 MediaType::parse("text/plain").unwrap(),
743 MediaType::parse("text/plain;format=rot13").unwrap(),
744 ])
745 .unwrap(),
746 &MediaType::parse("text/plain;format=rot13").unwrap()
747 );
748 assert_eq!(
750 accept
751 .negotiate(&vec![
752 MediaType::parse("text/plain").unwrap(),
753 MediaType::parse("text/plain;format=fixed").unwrap()
754 ])
755 .unwrap(),
756 &MediaType::parse("text/plain").unwrap()
757 );
758 assert_eq!(
761 accept
762 .negotiate(&vec![
763 MediaType::parse("text/html").unwrap(),
764 MediaType::parse("image/gif").unwrap()
765 ])
766 .unwrap(),
767 &MediaType::parse("image/gif").unwrap()
768 );
769 }
770
771 #[test]
772 fn try_from_header_value() {
773 let header_value = &HeaderValue::from_static("audio/*; q=0.2, audio/basic");
774 let accept: Accept = header_value.try_into().unwrap();
775
776 let mut media_types = accept.media_types();
777 assert_eq!(
778 media_types.next(),
779 Some(&MediaTypeBuf::from_str("audio/basic").unwrap())
780 );
781 assert_eq!(
782 media_types.next(),
783 Some(&MediaTypeBuf::from_str("audio/*; q=0.2").unwrap())
784 );
785 assert_eq!(media_types.next(), None);
786 }
787
788 #[test]
789 fn decode() {
790 let mut empty_iter = [].iter();
791 assert!(
792 Accept::decode(&mut empty_iter).is_err(),
793 "providing no headers results in an error"
794 );
795
796 let header_value_1 = HeaderValue::from_static("audio/*; q=0.2");
797 let header_value_2 = HeaderValue::from_static("audio/basic");
798 let header_value_combined = HeaderValue::from_static("audio/*; q=0.2, audio/basic");
799 let combined_accept_try_into: Accept = (&header_value_combined).try_into().unwrap();
800
801 let combined_accept_decode =
803 Accept::decode(&mut [&header_value_combined].into_iter()).unwrap();
804 let mut combined_iter_decode = combined_accept_decode.media_types();
805 let mut combined_iter_try_into = combined_accept_try_into.media_types();
806
807 for (m1, m2) in core::iter::zip(&mut combined_iter_decode, &mut combined_iter_try_into) {
808 assert_eq!(m1, m2, "same media type through `decode` and `try_into`");
809 }
810 assert_eq!(combined_iter_decode.next(), None);
811 assert_eq!(combined_iter_try_into.next(), None);
812
813 let separate_accept_decode =
815 Accept::decode(&mut [&header_value_1, &header_value_2].into_iter()).unwrap();
816 let mut separate_iter_decode = separate_accept_decode.media_types();
817 let mut separate_iter_try_into = combined_accept_try_into.media_types();
818
819 for (m1, m2) in core::iter::zip(&mut separate_iter_decode, &mut separate_iter_try_into) {
820 assert_eq!(m1, m2, "same media type through `decode` and `try_into`");
821 }
822 assert_eq!(separate_iter_decode.next(), None);
823 assert_eq!(separate_iter_try_into.next(), None);
824 }
825
826 #[test]
827 fn mixed_lifetime_from_iter() {
828 #[allow(unused)]
830 fn best<'a>(available: &'a [MediaType<'static>]) -> Option<&'a MediaType<'static>> {
831 let accept = Accept::from_str("*/*").unwrap();
832 accept.negotiate(available.iter())
833 }
834 }
835
836 #[test]
837 fn from_iterator() {
838 let accept = Accept::from_iter([
840 MediaType::parse("text/html").unwrap(),
841 MediaType::parse("image/gif").unwrap(),
842 ]);
843
844 assert_eq!(
845 accept.media_types().collect::<Vec<_>>(),
846 vec![
847 MediaType::parse("text/html").unwrap(),
848 MediaType::parse("image/gif").unwrap(),
849 ]
850 );
851
852 let accept = Accept::from_iter([
854 MediaTypeBuf::from_str("text/html").unwrap(),
855 MediaTypeBuf::from_str("image/gif").unwrap(),
856 ]);
857
858 assert_eq!(
859 accept.media_types().collect::<Vec<_>>(),
860 vec![
861 MediaType::parse("text/html").unwrap(),
862 MediaType::parse("image/gif").unwrap(),
863 ]
864 );
865 }
866
867 #[test]
868 fn test_qvalue_parsing_one() {
869 assert_eq!(QValue(1000), "1".parse().unwrap());
870 assert_eq!(QValue(1000), "1.".parse().unwrap());
871 assert_eq!(QValue(1000), "1.0".parse().unwrap());
872 assert_eq!(QValue(1000), "1.00".parse().unwrap());
873 assert_eq!(QValue(1000), "1.000".parse().unwrap());
874 }
875
876 #[test]
877 fn test_qvalue_parsing_partial() {
878 assert_eq!(QValue(0), "0".parse().unwrap());
879 assert_eq!(QValue(0), "0.".parse().unwrap());
880 assert_eq!(QValue(0), "0.0".parse().unwrap());
881 assert_eq!(QValue(0), "0.00".parse().unwrap());
882 assert_eq!(QValue(0), "0.000".parse().unwrap());
883 assert_eq!(QValue(100), "0.1".parse().unwrap());
884 assert_eq!(QValue(120), "0.12".parse().unwrap());
885 assert_eq!(QValue(123), "0.123".parse().unwrap());
886 assert_eq!(QValue(23), "0.023".parse().unwrap());
887 assert_eq!(QValue(3), "0.003".parse().unwrap());
888 }
889
890 #[test]
891 fn qvalue_parsing_invalid() {
892 assert!("0.0000".parse::<QValue>().is_err());
893 assert!("0.1.".parse::<QValue>().is_err());
894 assert!("0.12.".parse::<QValue>().is_err());
895 assert!("0.123.".parse::<QValue>().is_err());
896 assert!("0.1234".parse::<QValue>().is_err());
897 assert!("1.123".parse::<QValue>().is_err());
898 assert!("1.1234".parse::<QValue>().is_err());
899 assert!("1.12345".parse::<QValue>().is_err());
900 assert!("2.0".parse::<QValue>().is_err());
901 assert!("-0.0".parse::<QValue>().is_err());
902 assert!("1.0000".parse::<QValue>().is_err());
903 }
904
905 #[test]
906 fn qvalue_ordering() {
907 assert!(QValue(1000) > QValue(0));
908 assert!(QValue(1000) > QValue(100));
909 assert!(QValue(100) > QValue(0));
910 assert!(QValue(120) > QValue(100));
911 assert!(QValue(123) > QValue(120));
912 assert!(QValue(23) < QValue(100));
913 assert!(QValue(3) < QValue(23));
914 }
915
916 #[test]
917 fn qvalue_default() {
918 let q: QValue = Default::default();
919 assert_eq!(q, QValue(1000));
920 }
921
922 #[test]
923 fn qvalue_is_zero() {
924 assert!("0.".parse::<QValue>().unwrap().is_zero());
925 }
926}