1use std::fmt;
8
9use azul_css::{AzString, StringVec, impl_option, impl_option_inner};
10
11#[derive(Debug, Clone, PartialEq, PartialOrd)]
14#[repr(C, u8)]
15pub enum FmtValue {
16 Bool(bool),
17 Uchar(u8),
18 Schar(i8),
19 Ushort(u16),
20 Sshort(i16),
21 Uint(u32),
22 Sint(i32),
23 Ulong(u64),
24 Slong(i64),
25 Isize(isize),
26 Usize(usize),
27 Float(f32),
28 Double(f64),
29 Str(AzString),
30 StrVec(StringVec),
31}
32
33impl strfmt::DisplayStr for FmtValue {
34 fn display_str(&self, f: &mut strfmt::Formatter<'_, '_>) -> strfmt::Result<()> {
35 use strfmt::DisplayStr;
36 match self {
37 Self::Bool(v) => format!("{v}").display_str(f),
38 Self::Uchar(v) => v.display_str(f),
39 Self::Schar(v) => v.display_str(f),
40 Self::Ushort(v) => v.display_str(f),
41 Self::Sshort(v) => v.display_str(f),
42 Self::Uint(v) => v.display_str(f),
43 Self::Sint(v) => v.display_str(f),
44 Self::Ulong(v) => v.display_str(f),
45 Self::Slong(v) => v.display_str(f),
46 Self::Isize(v) => v.display_str(f),
47 Self::Usize(v) => v.display_str(f),
48 Self::Float(v) => v.display_str(f),
49 Self::Double(v) => v.display_str(f),
50 Self::Str(v) => v.as_str().display_str(f),
51 Self::StrVec(sv) => {
52 "[".display_str(f)?;
53 for (i, s) in sv.as_ref().iter().enumerate() {
54 if i != 0 {
55 ", ".display_str(f)?;
56 }
57 s.as_str().display_str(f)?;
58 }
59 "]".display_str(f)?;
60 Ok(())
61 }
62 }
63 }
64}
65
66impl fmt::Display for FmtValue {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::Bool(v) => v.fmt(f),
70 Self::Uchar(v) => v.fmt(f),
71 Self::Schar(v) => v.fmt(f),
72 Self::Ushort(v) => v.fmt(f),
73 Self::Sshort(v) => v.fmt(f),
74 Self::Uint(v) => v.fmt(f),
75 Self::Sint(v) => v.fmt(f),
76 Self::Ulong(v) => v.fmt(f),
77 Self::Slong(v) => v.fmt(f),
78 Self::Isize(v) => v.fmt(f),
79 Self::Usize(v) => v.fmt(f),
80 Self::Float(v) => v.fmt(f),
81 Self::Double(v) => v.fmt(f),
82 Self::Str(v) => v.as_str().fmt(f),
83 Self::StrVec(sv) => {
84 use std::fmt::Debug;
85 let vec: Vec<&str> = sv.as_ref().iter().map(AzString::as_str).collect();
86 vec.fmt(f)
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, PartialOrd)]
94#[repr(C)]
95pub struct FmtArg {
96 pub key: AzString,
97 pub value: FmtValue,
98}
99
100azul_css::impl_option!(FmtArg, OptionFmtArg, copy = false, [Debug, Clone, PartialEq, PartialOrd]);
101azul_css::impl_vec!(FmtArg, FmtArgVec, FmtArgVecDestructor, FmtArgVecDestructorType, FmtArgVecSlice, OptionFmtArg);
102azul_css::impl_vec_clone!(FmtArg, FmtArgVec, FmtArgVecDestructor);
103azul_css::impl_vec_debug!(FmtArg, FmtArgVec);
104azul_css::impl_vec_partialeq!(FmtArg, FmtArgVec);
105azul_css::impl_vec_partialord!(FmtArg, FmtArgVec);
106
107#[allow(clippy::needless_pass_by_value)]
111#[must_use] pub fn fmt_string(format: AzString, args: FmtArgVec) -> String {
112 use strfmt::Format;
113 let format_map = args
114 .iter()
115 .map(|a| (a.key.clone().into_library_owned_string(), a.value.clone()))
116 .collect();
117 match format.as_str().format(&format_map) {
118 Ok(o) => o,
119 Err(e) => format!("{e}"),
120 }
121}
122
123#[cfg(test)]
124mod autotest_generated {
125 use super::*;
126
127 fn az(s: &str) -> AzString {
132 AzString::from(s)
133 }
134
135 fn strvec(items: &[&str]) -> StringVec {
136 StringVec::from_vec(items.iter().map(|s| az(s)).collect())
137 }
138
139 fn run(format: &str, args: &[(&str, FmtValue)]) -> String {
144 let v: Vec<FmtArg> = args
145 .iter()
146 .map(|(k, value)| FmtArg {
147 key: az(k),
148 value: value.clone(),
149 })
150 .collect();
151 fmt_string(az(format), FmtArgVec::from_vec(v))
152 }
153
154 fn is_err_str(s: &str) -> bool {
157 s.starts_with("Invalid(") || s.starts_with("KeyError(") || s.starts_with("TypeError(")
158 }
159
160 fn scalar_variants() -> Vec<FmtValue> {
162 vec![
163 FmtValue::Bool(true),
164 FmtValue::Bool(false),
165 FmtValue::Uchar(0),
166 FmtValue::Uchar(u8::MAX),
167 FmtValue::Schar(i8::MIN),
168 FmtValue::Schar(i8::MAX),
169 FmtValue::Ushort(0),
170 FmtValue::Ushort(u16::MAX),
171 FmtValue::Sshort(i16::MIN),
172 FmtValue::Sshort(i16::MAX),
173 FmtValue::Uint(0),
174 FmtValue::Uint(u32::MAX),
175 FmtValue::Sint(i32::MIN),
176 FmtValue::Sint(i32::MAX),
177 FmtValue::Ulong(0),
178 FmtValue::Ulong(u64::MAX),
179 FmtValue::Slong(i64::MIN),
180 FmtValue::Slong(i64::MAX),
181 FmtValue::Isize(isize::MIN),
182 FmtValue::Isize(isize::MAX),
183 FmtValue::Usize(0),
184 FmtValue::Usize(usize::MAX),
185 FmtValue::Float(0.0),
186 FmtValue::Float(f32::MIN),
187 FmtValue::Float(f32::MAX),
188 FmtValue::Float(f32::NAN),
189 FmtValue::Float(f32::INFINITY),
190 FmtValue::Float(f32::NEG_INFINITY),
191 FmtValue::Float(f32::EPSILON),
192 FmtValue::Double(0.0),
193 FmtValue::Double(f64::MIN),
194 FmtValue::Double(f64::MAX),
195 FmtValue::Double(f64::NAN),
196 FmtValue::Double(f64::INFINITY),
197 FmtValue::Double(f64::NEG_INFINITY),
198 FmtValue::Double(f64::MIN_POSITIVE),
199 FmtValue::Str(AzString::default()),
200 FmtValue::Str(az("plain")),
201 ]
202 }
203
204 #[test]
209 fn substitutes_a_single_placeholder() {
210 assert_eq!(
211 run("Hello {name}!", &[("name", FmtValue::Str(az("World")))]),
212 "Hello World!"
213 );
214 }
215
216 #[test]
217 fn empty_format_and_empty_args_yield_empty_output() {
218 assert_eq!(fmt_string(AzString::default(), FmtArgVec::new()), "");
219 assert_eq!(fmt_string(az("plain text"), FmtArgVec::new()), "plain text");
220 }
221
222 #[test]
223 fn args_without_a_matching_placeholder_are_ignored() {
224 assert_eq!(
225 run(
226 "{a}",
227 &[("a", FmtValue::Sint(1)), ("unused", FmtValue::Sint(2))]
228 ),
229 "1"
230 );
231 }
232
233 #[test]
234 fn the_same_key_can_be_substituted_repeatedly() {
235 assert_eq!(run("{k}-{k}-{k}", &[("k", FmtValue::Uint(7))]), "7-7-7");
236 }
237
238 #[test]
239 fn duplicate_keys_resolve_to_the_last_arg() {
240 assert_eq!(
243 run("{k}", &[("k", FmtValue::Sint(1)), ("k", FmtValue::Sint(2))]),
244 "2"
245 );
246 }
247
248 #[test]
249 fn substituted_values_are_not_re_scanned_for_placeholders() {
250 assert_eq!(
253 run(
254 "{a}",
255 &[
256 ("a", FmtValue::Str(az("{b}"))),
257 ("b", FmtValue::Str(az("PWNED"))),
258 ]
259 ),
260 "{b}"
261 );
262 }
263
264 #[test]
269 fn missing_key_returns_a_key_error_string() {
270 let out = run("{missing}", &[]);
271 assert!(out.contains("Invalid key: missing"), "{out}");
272 assert!(is_err_str(&out), "{out}");
273 }
274
275 #[test]
276 fn unclosed_brace_returns_an_error_string() {
277 for bad in ["{", "{name", "abc {name"] {
278 let out = run(bad, &[("name", FmtValue::Sint(1))]);
279 assert!(out.contains("Expected '}'"), "{bad:?} -> {out}");
280 }
281 }
282
283 #[test]
284 fn lone_closing_brace_returns_an_error_string() {
285 for bad in ["}", "a}b", "trailing}"] {
286 let out = run(bad, &[]);
287 assert!(out.contains("Single '}'"), "{bad:?} -> {out}");
288 }
289 }
290
291 #[test]
292 fn nested_opening_brace_returns_an_error_string() {
293 let out = run("{a{b}", &[("a", FmtValue::Sint(1))]);
294 assert!(out.contains("extra {"), "{out}");
295 }
296
297 #[test]
298 fn empty_placeholder_is_rejected_even_when_an_empty_key_exists() {
299 let out = run("{}", &[("", FmtValue::Sint(1))]);
301 assert!(out.contains("must specify identifier"), "{out}");
302 }
303
304 #[test]
305 fn a_colon_in_a_key_makes_that_key_unaddressable() {
306 let out = run("{a:b}", &[("a:b", FmtValue::Sint(1))]);
309 assert!(out.contains("Invalid key: a"), "{out}");
310 }
311
312 #[test]
313 fn escaped_braces_are_emitted_literally() {
314 assert_eq!(run("{{literal}}", &[]), "{literal}");
315 assert_eq!(run("{{{{", &[]), "{{");
316 assert_eq!(
317 run("{{{k}}}", &[("k", FmtValue::Str(az("v")))]),
318 "{v}",
319 "escaped braces around a real placeholder"
320 );
321 }
322
323 #[test]
328 fn every_scalar_variant_round_trips_through_its_display_impl() {
329 for v in scalar_variants() {
332 let out = run("{v}", &[("v", v.clone())]);
333 assert_eq!(out, v.to_string(), "mismatch for {v:?}");
334 assert!(!is_err_str(&out), "unexpected error for {v:?}: {out}");
335 }
336 }
337
338 #[test]
339 fn integer_limits_match_the_native_rendering() {
340 assert_eq!(
341 run("{v}", &[("v", FmtValue::Slong(i64::MIN))]),
342 i64::MIN.to_string()
343 );
344 assert_eq!(
345 run("{v}", &[("v", FmtValue::Ulong(u64::MAX))]),
346 u64::MAX.to_string()
347 );
348 assert_eq!(
349 run("{v}", &[("v", FmtValue::Usize(usize::MAX))]),
350 usize::MAX.to_string()
351 );
352 assert_eq!(
353 run("{v}", &[("v", FmtValue::Isize(isize::MIN))]),
354 isize::MIN.to_string()
355 );
356 assert_eq!(run("{v}", &[("v", FmtValue::Schar(i8::MIN))]), "-128");
357 }
358
359 #[test]
360 fn non_finite_floats_render_without_panicking() {
361 assert_eq!(run("{v}", &[("v", FmtValue::Float(f32::NAN))]), "NaN");
362 assert_eq!(run("{v}", &[("v", FmtValue::Float(f32::INFINITY))]), "inf");
363 assert_eq!(
364 run("{v}", &[("v", FmtValue::Double(f64::NEG_INFINITY))]),
365 "-inf"
366 );
367 assert_eq!(run("{v:.2}", &[("v", FmtValue::Double(f64::NAN))]), "NaN");
369 assert_eq!(
370 run("{v:.5}", &[("v", FmtValue::Double(f64::INFINITY))]),
371 "inf"
372 );
373 }
374
375 #[test]
376 fn float_extremes_match_the_native_rendering() {
377 assert_eq!(
378 run("{v}", &[("v", FmtValue::Double(f64::MAX))]),
379 f64::MAX.to_string()
380 );
381 assert_eq!(
382 run("{v}", &[("v", FmtValue::Double(f64::MIN_POSITIVE))]),
383 f64::MIN_POSITIVE.to_string()
384 );
385 assert_eq!(
386 run("{v}", &[("v", FmtValue::Float(f32::MIN))]),
387 f32::MIN.to_string()
388 );
389 }
390
391 #[test]
392 fn radix_format_codes_match_the_native_rendering() {
393 assert_eq!(run("{v:x}", &[("v", FmtValue::Uint(255))]), "ff");
394 assert_eq!(run("{v:X}", &[("v", FmtValue::Uint(255))]), "FF");
395 assert_eq!(run("{v:#x}", &[("v", FmtValue::Uint(255))]), "0xff");
396 assert_eq!(run("{v:b}", &[("v", FmtValue::Uchar(5))]), "101");
397 assert_eq!(run("{v:o}", &[("v", FmtValue::Uint(8))]), "10");
398 assert_eq!(
400 run("{v:b}", &[("v", FmtValue::Sint(i32::MIN))]),
401 format!("{:b}", i32::MIN)
402 );
403 assert_eq!(
404 run("{v:x}", &[("v", FmtValue::Usize(usize::MAX))]),
405 format!("{:x}", usize::MAX)
406 );
407 }
408
409 #[test]
410 fn explicit_sign_is_honoured_for_numbers() {
411 assert_eq!(run("{v:+}", &[("v", FmtValue::Sint(5))]), "+5");
412 assert_eq!(run("{v:+}", &[("v", FmtValue::Sint(-5))]), "-5");
413 assert_eq!(run("{v:+}", &[("v", FmtValue::Sint(0))]), "+0");
414 }
415
416 #[test]
417 fn exponent_and_precision_codes_match_the_native_rendering() {
418 assert_eq!(
419 run("{v:e}", &[("v", FmtValue::Double(1234.0))]),
420 format!("{:e}", 1234.0_f64)
421 );
422 assert_eq!(
423 run("{v:.3}", &[("v", FmtValue::Double(1.234_56))]),
424 format!("{:.3}", 1.234_56_f64)
425 );
426 }
427
428 #[test]
433 fn precision_on_an_integer_is_a_type_error() {
434 let out = run("{v:.2}", &[("v", FmtValue::Sint(5))]);
435 assert!(out.contains("precision not allowed for integers"), "{out}");
436 }
437
438 #[test]
439 fn a_string_valued_arg_rejects_numeric_format_codes() {
440 for spec in ["{v:x}", "{v:b}", "{v:e}", "{v:#}", "{v:+}"] {
441 let out = run(spec, &[("v", FmtValue::Str(az("hi")))]);
442 assert!(is_err_str(&out), "{spec} unexpectedly succeeded: {out}");
443 }
444 }
445
446 #[test]
447 fn a_float_valued_arg_rejects_integer_format_codes() {
448 let out = run("{v:x}", &[("v", FmtValue::Double(1.0))]);
449 assert!(out.contains("Unknown format code"), "{out}");
450 let out = run("{v:#}", &[("v", FmtValue::Double(1.0))]);
451 assert!(out.contains("Alternate form"), "{out}");
452 }
453
454 #[test]
455 fn an_unknown_type_specifier_is_reported() {
456 let out = run("{v:z}", &[("v", FmtValue::Sint(1))]);
457 assert!(out.contains("Invalid type specifier"), "{out}");
458 }
459
460 #[test]
461 fn unsupported_specs_degrade_to_an_error_string() {
462 let zero_pad = run("{v:08}", &[("v", FmtValue::Sint(42))]);
465 assert!(is_err_str(&zero_pad), "{zero_pad}");
466 let thousands = run("{v:,}", &[("v", FmtValue::Sint(1000))]);
467 assert!(thousands.contains("not yet supported"), "{thousands}");
468 }
469
470 #[test]
471 fn an_out_of_range_width_or_precision_is_rejected_not_overflowed() {
472 let w = run("{v:>99999999999999999999}", &[("v", FmtValue::Sint(1))]);
474 assert!(w.contains("overflow error when parsing width"), "{w}");
475 let p = run(
476 "{v:.99999999999999999999}",
477 &[("v", FmtValue::Str(az("abc")))],
478 );
479 assert!(p.contains("overflow error when parsing precision"), "{p}");
480 let d = run("{v:.}", &[("v", FmtValue::Str(az("abc")))]);
482 assert!(d.contains("missing precision"), "{d}");
483 }
484
485 #[test]
490 fn keys_values_and_literals_may_be_non_ascii() {
491 assert_eq!(
492 run("こんにちは、{名前}!🦀", &[("名前", FmtValue::Str(az("世界")))]),
493 "こんにちは、世界!🦀"
494 );
495 }
496
497 #[test]
498 fn width_and_precision_count_chars_not_bytes() {
499 assert_eq!(
501 run("{v:.2}", &[("v", FmtValue::Str(az("日本語")))]),
502 "日本",
503 "precision truncates on a char boundary"
504 );
505 assert_eq!(
506 run("{v:>5}", &[("v", FmtValue::Str(az("日本語")))]),
507 " 日本語",
508 "width pads by chars, not bytes"
509 );
510 }
511
512 #[test]
513 fn a_multibyte_fill_char_pads_correctly() {
514 assert_eq!(run("{v:🦀>4}", &[("v", FmtValue::Str(az("ab")))]), "🦀🦀ab");
515 }
516
517 #[test]
518 fn control_and_nul_characters_pass_through_untouched() {
519 let weird = "a\0b\tc\nd\u{7f}e";
520 assert_eq!(run(weird, &[]), weird);
521 }
522
523 #[test]
528 fn string_alignment_and_truncation_behave_as_specified() {
529 assert_eq!(run("{v:>8}", &[("v", FmtValue::Str(az("ab")))]), " ab");
530 assert_eq!(run("{v:<4}|", &[("v", FmtValue::Str(az("ab")))]), "ab |");
531 assert_eq!(run("{v:^6}", &[("v", FmtValue::Str(az("ab")))]), " ab ");
532 assert_eq!(run("{v:.3}", &[("v", FmtValue::Str(az("abcdefg")))]), "abc");
533 assert_eq!(
534 run("{v:.0}", &[("v", FmtValue::Str(az("abc")))]),
535 "",
536 "zero precision erases the value"
537 );
538 assert_eq!(
539 run("{v:.9}", &[("v", FmtValue::Str(az("abc")))]),
540 "abc",
541 "precision longer than the value does not overrun"
542 );
543 assert_eq!(
544 run("{v:>2}", &[("v", FmtValue::Str(az("abcdef")))]),
545 "abcdef",
546 "width smaller than the value never truncates"
547 );
548 }
549
550 #[test]
555 fn strvec_renders_as_a_bracketed_comma_list() {
556 assert_eq!(run("{v}", &[("v", FmtValue::StrVec(strvec(&[])))]), "[]");
557 assert_eq!(run("{v}", &[("v", FmtValue::StrVec(strvec(&["a"])))]), "[a]");
558 assert_eq!(
559 run("{v}", &[("v", FmtValue::StrVec(strvec(&["a", "b", "c"])))]),
560 "[a, b, c]"
561 );
562 assert_eq!(
563 run("{v}", &[("v", FmtValue::StrVec(strvec(&["", ""])))]),
564 "[, ]",
565 "empty members still get a separator"
566 );
567 }
568
569 #[test]
570 fn strvec_applies_the_width_spec_to_every_fragment() {
571 let out = run("{v:>10}", &[("v", FmtValue::StrVec(strvec(&["a"])))]);
575 assert_eq!(out, " [ a ]");
576 assert_eq!(out.chars().count(), 30);
577 }
578
579 #[test]
584 fn many_placeholders_do_not_blow_up() {
585 let format: String = "{k}".repeat(500);
586 let out = run(&format, &[("k", FmtValue::Uchar(9))]);
587 assert_eq!(out, "9".repeat(500));
588 }
589
590 #[test]
591 fn a_very_long_value_is_substituted_whole() {
592 let big = "x".repeat(50_000);
593 let out = run("{v}", &[("v", FmtValue::Str(AzString::from(big.clone())))]);
594 assert_eq!(out.len(), 50_000);
595 assert_eq!(out, big);
596 }
597
598 #[test]
599 fn a_long_literal_format_string_is_returned_verbatim() {
600 let big = "y".repeat(100_000);
601 assert_eq!(fmt_string(AzString::from(big.clone()), FmtArgVec::new()), big);
602 }
603
604 #[test]
609 fn display_never_panics_on_any_variant_or_edge_value() {
610 for v in scalar_variants() {
611 let _ = v.to_string();
612 }
613 let _ = FmtValue::StrVec(strvec(&[])).to_string();
614 let _ = FmtValue::StrVec(strvec(&["a", "", "🦀"])).to_string();
615 }
616
617 #[test]
618 fn display_renders_each_variant_as_its_inner_value() {
619 assert_eq!(FmtValue::Bool(true).to_string(), "true");
620 assert_eq!(FmtValue::Bool(false).to_string(), "false");
621 assert_eq!(FmtValue::Uchar(u8::MAX).to_string(), "255");
622 assert_eq!(FmtValue::Schar(i8::MIN).to_string(), "-128");
623 assert_eq!(FmtValue::Sshort(i16::MIN).to_string(), "-32768");
624 assert_eq!(FmtValue::Ulong(u64::MAX).to_string(), u64::MAX.to_string());
625 assert_eq!(FmtValue::Slong(i64::MIN).to_string(), i64::MIN.to_string());
626 assert_eq!(FmtValue::Usize(usize::MAX).to_string(), usize::MAX.to_string());
627 assert_eq!(FmtValue::Isize(isize::MIN).to_string(), isize::MIN.to_string());
628 assert_eq!(FmtValue::Float(f32::NAN).to_string(), "NaN");
629 assert_eq!(FmtValue::Double(f64::INFINITY).to_string(), "inf");
630 assert_eq!(FmtValue::Double(f64::NEG_INFINITY).to_string(), "-inf");
631 }
632
633 #[test]
634 fn display_of_a_string_is_unquoted_but_a_strvec_is_debug_quoted() {
635 assert_eq!(FmtValue::Str(az("a\"b")).to_string(), "a\"b");
638 assert_eq!(FmtValue::Str(AzString::default()).to_string(), "");
639 assert_eq!(FmtValue::StrVec(strvec(&[])).to_string(), "[]");
640 assert_eq!(
641 FmtValue::StrVec(strvec(&["a", "b"])).to_string(),
642 "[\"a\", \"b\"]"
643 );
644 }
645
646 #[test]
647 fn display_of_a_strvec_diverges_from_the_strfmt_rendering() {
648 let v = FmtValue::StrVec(strvec(&["a", "b"]));
650 assert_ne!(run("{v}", &[("v", v.clone())]), v.to_string());
651 assert_eq!(run("{v}", &[("v", v)]), "[a, b]");
652 }
653
654 #[test]
655 fn display_forwards_width_precision_and_fill_flags() {
656 assert_eq!(format!("{:>6}", FmtValue::Uint(42)), " 42");
657 assert_eq!(format!("{:<6}|", FmtValue::Uint(42)), "42 |");
658 assert_eq!(format!("{:0>6}", FmtValue::Uint(42)), "000042");
659 assert_eq!(format!("{:>6}", FmtValue::Str(az("ab"))), " ab");
660 assert_eq!(
661 format!("{:.2}", FmtValue::Double(2.0 / 3.0)),
662 format!("{:.2}", 2.0_f64 / 3.0)
663 );
664 }
665
666 #[test]
667 fn debug_is_variant_tagged() {
668 assert_eq!(format!("{:?}", FmtValue::Uint(1)), "Uint(1)");
669 assert_eq!(format!("{:?}", FmtValue::Bool(false)), "Bool(false)");
670 }
671
672 #[test]
677 fn nan_breaks_reflexive_equality_and_has_no_ordering() {
678 let nan = FmtValue::Double(f64::NAN);
679 assert_ne!(nan, nan.clone(), "NaN must not compare equal to itself");
680 assert_eq!(nan.partial_cmp(&nan.clone()), None);
681 assert_eq!(
682 FmtValue::Float(f32::NAN).partial_cmp(&FmtValue::Float(1.0)),
683 None
684 );
685 }
686
687 #[test]
688 fn equality_and_ordering_discriminate_between_variants() {
689 assert_eq!(FmtValue::Uint(1), FmtValue::Uint(1));
690 assert_ne!(
691 FmtValue::Uint(1),
692 FmtValue::Sint(1),
693 "same numeric value, different variant"
694 );
695 assert!(FmtValue::Bool(true) < FmtValue::Uchar(0));
697 assert!(FmtValue::Uchar(1) > FmtValue::Uchar(0));
698 assert!(FmtValue::Double(f64::NEG_INFINITY) < FmtValue::Double(f64::MIN));
699 }
700
701 #[test]
702 fn cloning_an_arg_vec_preserves_equality_and_survives_a_double_drop() {
703 let args = FmtArgVec::from_vec(vec![
704 FmtArg {
705 key: az("a"),
706 value: FmtValue::Str(az("one")),
707 },
708 FmtArg {
709 key: az("b"),
710 value: FmtValue::StrVec(strvec(&["x", "y"])),
711 },
712 ]);
713 let copy = args.clone();
714 assert_eq!(args, copy);
715 assert_eq!(
716 fmt_string(az("{a}{b}"), args),
717 fmt_string(az("{a}{b}"), copy.clone()),
718 "a clone must format identically to its source"
719 );
720 drop(copy); }
722}