1use proptest::{option, prelude::*, strategy};
21use rsonpath_syntax::{
22 builder::SliceBuilder, num::JsonInt, str::JsonString, JsonPathQuery, LogicalExpr, Segment, Selector, Selectors,
23};
24use std::fmt::Debug;
25
26pub struct ArbitraryJsonPathQuery {
31 pub string: String,
33 pub parsed: JsonPathQuery,
35}
36
37impl Debug for ArbitraryJsonPathQuery {
38 #[inline]
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("ArbitraryJsonPathQuery")
41 .field("string", &self.string)
42 .field("parsed", &self.parsed)
43 .field("string_raw", &self.string.as_bytes())
44 .finish()
45 }
46}
47
48#[derive(Debug)]
50pub struct ArbitraryJsonPathQueryParams {
51 pub recursive_depth: u32,
57 pub desired_size: u32,
63 pub max_segments: usize,
66 pub min_selectors: usize,
70 pub max_selectors: usize,
74 pub only_rsonpath_supported_subset: bool,
78}
79
80impl ArbitraryJsonPathQuery {
81 #[inline]
82 #[must_use]
83 pub fn new(string: String, parsed: JsonPathQuery) -> Self {
84 Self { string, parsed }
85 }
86}
87
88impl Default for ArbitraryJsonPathQueryParams {
89 #[inline]
90 fn default() -> Self {
91 Self {
92 only_rsonpath_supported_subset: false,
93 recursive_depth: 3,
94 desired_size: 10,
95 max_segments: 10,
96 min_selectors: 1,
97 max_selectors: 5,
98 }
99 }
100}
101
102impl proptest::arbitrary::Arbitrary for ArbitraryJsonPathQuery {
103 type Parameters = ArbitraryJsonPathQueryParams;
104 type Strategy = BoxedStrategy<Self>;
105
106 #[inline]
107 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
108 assert!(args.min_selectors > 0, "cannot generate a query with no selectors");
109 assert!(
110 args.max_selectors >= args.min_selectors,
111 "[min_selectors, max_selectors] must be non-empty"
112 );
113
114 if args.only_rsonpath_supported_subset {
115 rsonpath_valid_query(&args).prop_map(|x| Self::new(x.0, x.1)).boxed()
116 } else {
117 any_valid_query(&args).prop_map(|x| Self::new(x.0, x.1)).boxed()
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
127enum PropSegment {
128 ShortChildWildcard,
130 ShortChildName(JsonString),
132 ShortDescendantWildcard,
134 ShortDescendantName(JsonString),
136 BracketedChild(Vec<PropSelector>),
138 BracketedDescendant(Vec<PropSelector>),
140}
141
142#[derive(Debug, Clone)]
143enum PropSelector {
144 Wildcard,
145 Name(JsonString),
146 Index(JsonInt),
147 Slice(Option<JsonInt>, Option<JsonInt>, Option<JsonInt>),
148 Filter(LogicalExpr),
149}
150
151fn any_valid_query(props: &ArbitraryJsonPathQueryParams) -> impl Strategy<Value = (String, JsonPathQuery)> {
152 let ArbitraryJsonPathQueryParams {
153 min_selectors,
154 max_selectors,
155 max_segments,
156 recursive_depth,
157 desired_size,
158 ..
159 } = *props;
160
161 prop::collection::vec(any_segment(None, min_selectors, max_selectors), 0..max_segments)
162 .prop_map(map_prop_segments)
163 .prop_recursive(recursive_depth, desired_size, 5, move |query_strategy| {
164 prop::collection::vec(
165 any_segment(Some(query_strategy), min_selectors, max_selectors),
166 0..max_segments,
167 )
168 .prop_map(map_prop_segments)
169 })
170}
171
172fn rsonpath_valid_query(props: &ArbitraryJsonPathQueryParams) -> impl Strategy<Value = (String, JsonPathQuery)> {
173 let ArbitraryJsonPathQueryParams { max_segments, .. } = *props;
174 prop::collection::vec(rsonpath_valid_segment(), 0..max_segments).prop_map(map_prop_segments)
175}
176
177fn map_prop_segments(segments: Vec<(String, PropSegment)>) -> (String, JsonPathQuery) {
178 let mut s = "$".to_string();
179 let mut v = vec![];
180
181 for (segment_s, segment) in segments {
182 s.push_str(&segment_s);
183 match segment {
184 PropSegment::ShortChildWildcard => v.push(Segment::Child(Selectors::one(Selector::Wildcard))),
185 PropSegment::ShortChildName(n) => v.push(Segment::Child(Selectors::one(Selector::Name(n)))),
186 PropSegment::ShortDescendantWildcard => v.push(Segment::Descendant(Selectors::one(Selector::Wildcard))),
187 PropSegment::ShortDescendantName(n) => v.push(Segment::Descendant(Selectors::one(Selector::Name(n)))),
188 PropSegment::BracketedChild(ss) => v.push(Segment::Child(Selectors::many(
189 ss.into_iter().map(map_prop_selector).collect(),
190 ))),
191 PropSegment::BracketedDescendant(ss) => v.push(Segment::Descendant(Selectors::many(
192 ss.into_iter().map(map_prop_selector).collect(),
193 ))),
194 }
195 }
196
197 (s, JsonPathQuery::from_iter(v))
198}
199
200fn map_prop_selector(s: PropSelector) -> Selector {
201 match s {
202 PropSelector::Wildcard => Selector::Wildcard,
203 PropSelector::Name(n) => Selector::Name(n),
204 PropSelector::Index(i) => Selector::Index(i.into()),
205 PropSelector::Slice(start, end, step) => Selector::Slice({
206 let mut builder = SliceBuilder::new();
207 if let Some(start) = start {
208 builder.with_start(start);
209 }
210 if let Some(step) = step {
211 builder.with_step(step);
212 }
213 if let Some(end) = end {
214 builder.with_end(end);
215 }
216 builder.into()
217 }),
218 PropSelector::Filter(logical) => Selector::Filter(logical),
219 }
220}
221
222fn any_segment(
223 recursive_query_strategy: Option<BoxedStrategy<(String, JsonPathQuery)>>,
224 min_selectors: usize,
225 max_selectors: usize,
226) -> impl Strategy<Value = (String, PropSegment)> {
227 return prop_oneof![
228 strategy::Just((".*".to_string(), PropSegment::ShortChildWildcard)),
229 strategy::Just(("..*".to_string(), PropSegment::ShortDescendantWildcard)),
230 any_short_name().prop_map(|name| (format!(".{name}"), PropSegment::ShortChildName(JsonString::new(&name)))),
231 any_short_name().prop_map(|name| (
232 format!("..{name}"),
233 PropSegment::ShortDescendantName(JsonString::new(&name))
234 )),
235 prop::collection::vec(
236 any_selector(recursive_query_strategy.clone()),
237 min_selectors..max_selectors
238 )
239 .prop_map(|reprs| {
240 let mut s = "[".to_string();
241 let v = collect_reprs(reprs, &mut s);
242 s.push(']');
243 (s, PropSegment::BracketedChild(v))
244 }),
245 prop::collection::vec(any_selector(recursive_query_strategy), min_selectors..max_selectors).prop_map(|reprs| {
246 let mut s = "..[".to_string();
247 let v = collect_reprs(reprs, &mut s);
248 s.push(']');
249 (s, PropSegment::BracketedDescendant(v))
250 }),
251 ];
252
253 fn collect_reprs(reprs: Vec<(String, PropSelector)>, s: &mut String) -> Vec<PropSelector> {
254 let mut result = Vec::with_capacity(reprs.len());
255 let mut first = true;
256 for (repr_s, prop_selector) in reprs {
257 if !first {
258 s.push(',');
259 }
260 first = false;
261 s.push_str(&repr_s);
262 result.push(prop_selector);
263 }
264 result
265 }
266}
267
268fn rsonpath_valid_segment() -> impl Strategy<Value = (String, PropSegment)> {
269 prop_oneof![
270 strategy::Just((".*".to_string(), PropSegment::ShortChildWildcard)),
271 strategy::Just(("..*".to_string(), PropSegment::ShortDescendantWildcard)),
272 any_short_name().prop_map(|name| (format!(".{name}"), PropSegment::ShortChildName(JsonString::new(&name)))),
273 any_short_name().prop_map(|name| (
274 format!("..{name}"),
275 PropSegment::ShortDescendantName(JsonString::new(&name))
276 )),
277 rsonpath_valid_selector().prop_map(|repr| {
278 let mut s = "[".to_string();
279 s.push_str(&repr.0);
280 s.push(']');
281 (s, PropSegment::BracketedChild(vec![repr.1]))
282 }),
283 rsonpath_valid_selector().prop_map(|repr| {
284 let mut s = "..[".to_string();
285 s.push_str(&repr.0);
286 s.push(']');
287 (s, PropSegment::BracketedDescendant(vec![repr.1]))
288 }),
289 ]
290}
291
292fn any_selector(
293 recursive_query_strategy: Option<BoxedStrategy<(String, JsonPathQuery)>>,
294) -> impl Strategy<Value = (String, PropSelector)> {
295 prop_oneof![
296 strategy::Just(("*".to_string(), PropSelector::Wildcard)),
297 strings::any_json_string().prop_map(|(raw, s)| (raw, PropSelector::Name(s))),
298 any_json_int().prop_map(|(raw, i)| (raw, PropSelector::Index(i))),
299 any_slice().prop_map(|(raw, a, b, c)| (raw, PropSelector::Slice(a, b, c))),
300 filters::any_logical_expr(recursive_query_strategy)
301 .prop_map(|(raw, expr)| (format!("?{raw}"), PropSelector::Filter(expr)))
302 ]
303}
304
305fn rsonpath_valid_selector() -> impl Strategy<Value = (String, PropSelector)> {
306 prop_oneof![
307 strategy::Just(("*".to_string(), PropSelector::Wildcard)),
308 strings::any_json_string().prop_map(|(raw, s)| (raw, PropSelector::Name(s))),
309 rsonpath_valid_json_int().prop_map(|(raw, i)| (raw, PropSelector::Index(i))),
310 rsonpath_valid_slice().prop_map(|(raw, a, b, c)| (raw, PropSelector::Slice(a, b, c))),
311 ]
312}
313
314fn any_json_int() -> impl Strategy<Value = (String, JsonInt)> {
315 (-((1_i64 << 53) + 1)..((1_i64 << 53) - 1)).prop_map(|i| (i.to_string(), JsonInt::try_from(i).unwrap()))
316}
317
318fn rsonpath_valid_json_int() -> impl Strategy<Value = (String, JsonInt)> {
319 (0..((1_i64 << 53) - 1)).prop_map(|i| (i.to_string(), JsonInt::try_from(i).unwrap()))
320}
321
322fn any_slice() -> impl Strategy<Value = (String, Option<JsonInt>, Option<JsonInt>, Option<JsonInt>)> {
323 (
324 option::of(any_json_int()),
325 option::of(any_json_int()),
326 option::of(any_json_int()),
327 )
328 .prop_map(|(a, b, c)| {
329 let mut s = String::new();
330 let a = a.map(|(a_s, a_i)| {
331 s.push_str(&a_s);
332 a_i
333 });
334 s.push(':');
335 let b = b.map(|(b_s, b_i)| {
336 s.push_str(&b_s);
337 b_i
338 });
339 s.push(':');
340 let c = c.map(|(c_s, c_i)| {
341 s.push_str(&c_s);
342 c_i
343 });
344 (s, a, b, c)
345 })
346}
347
348fn rsonpath_valid_slice() -> impl Strategy<Value = (String, Option<JsonInt>, Option<JsonInt>, Option<JsonInt>)> {
349 (
350 option::of(rsonpath_valid_json_int()),
351 option::of(rsonpath_valid_json_int()),
352 option::of(rsonpath_valid_json_int()),
353 )
354 .prop_map(|(a, b, c)| {
355 let mut s = String::new();
356 let a = a.map(|(a_s, a_i)| {
357 s.push_str(&a_s);
358 a_i
359 });
360 s.push(':');
361 let b = b.map(|(b_s, b_i)| {
362 s.push_str(&b_s);
363 b_i
364 });
365 s.push(':');
366 let c = c.map(|(c_s, c_i)| {
367 s.push_str(&c_s);
368 c_i
369 });
370 (s, a, b, c)
371 })
372}
373
374fn any_short_name() -> impl Strategy<Value = String> {
375 r"([A-Za-z]|_|[^\u0000-\u007F])([A-Za-z0-9]|_|[^\u0000-\u007F])*"
376}
377
378mod strings {
379 use proptest::{prelude::*, sample::SizeRange};
380 use rsonpath_syntax::str::JsonString;
381
382 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
383 enum JsonStringToken {
384 EncodeNormally(char),
385 ForceUnicodeEscape(char),
386 }
387
388 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
389 enum JsonStringTokenEncodingMode {
390 SingleQuoted,
391 DoubleQuoted,
392 }
393
394 impl JsonStringToken {
395 fn raw(self) -> char {
396 match self {
397 Self::EncodeNormally(x) | Self::ForceUnicodeEscape(x) => x,
398 }
399 }
400
401 fn encode(self, mode: JsonStringTokenEncodingMode) -> String {
402 return match self {
403 Self::EncodeNormally('\u{0008}') => r"\b".to_owned(),
404 Self::EncodeNormally('\t') => r"\t".to_owned(),
405 Self::EncodeNormally('\n') => r"\n".to_owned(),
406 Self::EncodeNormally('\u{000C}') => r"\f".to_owned(),
407 Self::EncodeNormally('\r') => r"\r".to_owned(),
408 Self::EncodeNormally('"') => match mode {
409 JsonStringTokenEncodingMode::DoubleQuoted => r#"\""#.to_owned(),
410 JsonStringTokenEncodingMode::SingleQuoted => r#"""#.to_owned(),
411 },
412 Self::EncodeNormally('\'') => match mode {
413 JsonStringTokenEncodingMode::DoubleQuoted => "'".to_owned(),
414 JsonStringTokenEncodingMode::SingleQuoted => r"\'".to_owned(),
415 },
416 Self::EncodeNormally('/') => r"\/".to_owned(),
417 Self::EncodeNormally('\\') => r"\\".to_owned(),
418 Self::EncodeNormally(c @ ..='\u{001F}') | Self::ForceUnicodeEscape(c) => encode_unicode_escape(c),
419 Self::EncodeNormally(c) => c.to_string(),
420 };
421
422 fn encode_unicode_escape(c: char) -> String {
423 let mut buf = [0; 2];
424 let enc = c.encode_utf16(&mut buf);
425 let mut res = String::new();
426 for x in enc {
427 res += &format!("\\u{x:0>4x}");
428 }
429 res
430 }
431 }
432 }
433
434 pub(super) fn any_json_string() -> impl Strategy<Value = (String, JsonString)> {
435 prop_oneof![
436 Just(JsonStringTokenEncodingMode::SingleQuoted),
437 Just(JsonStringTokenEncodingMode::DoubleQuoted)
438 ]
439 .prop_flat_map(|mode| {
440 prop::collection::vec(
441 (prop::char::any(), prop::bool::ANY).prop_map(|(c, b)| {
442 if b {
443 JsonStringToken::EncodeNormally(c)
444 } else {
445 JsonStringToken::ForceUnicodeEscape(c)
446 }
447 }),
448 SizeRange::default(),
449 )
450 .prop_map(move |v| {
451 let q = match mode {
452 JsonStringTokenEncodingMode::SingleQuoted => '\'',
453 JsonStringTokenEncodingMode::DoubleQuoted => '"',
454 };
455 let mut s = String::new();
456 let mut l = String::new();
457 for x in v {
458 s += &x.encode(mode);
459 l.push(x.raw());
460 }
461 (format!("{q}{s}{q}"), JsonString::new(&l))
462 })
463 })
464 }
465}
466
467mod filters {
468 use proptest::{num, prelude::*, strategy};
469 use rsonpath_syntax::{
470 num::{JsonFloat, JsonNumber},
471 str::JsonString,
472 Comparable, ComparisonExpr, ComparisonOp, JsonPathQuery, Literal, LogicalExpr, SingularJsonPathQuery,
473 SingularSegment, TestExpr,
474 };
475
476 pub(super) fn any_logical_expr(
477 test_query_strategy: Option<BoxedStrategy<(String, JsonPathQuery)>>,
478 ) -> impl Strategy<Value = (String, LogicalExpr)> {
479 any_atomic_logical_expr(test_query_strategy).prop_recursive(3, 10, 2, |inner| {
480 prop_oneof![
481 (inner.clone(), proptest::bool::ANY).prop_map(|((s, f), force_paren)| (
482 match f {
483 LogicalExpr::Test(_) if !force_paren => format!("!{s}"),
484 _ => format!("!({s})"),
485 },
486 LogicalExpr::Not(Box::new(f))
487 )),
488 (inner.clone(), inner.clone(), proptest::bool::ANY, proptest::bool::ANY).prop_map(
489 |((lhs_s, lhs_e), (rhs_s, rhs_e), force_left_paren, force_right_paren)| {
490 let put_left_paren = force_left_paren || matches!(lhs_e, LogicalExpr::Or(_, _));
491 let put_right_paren =
492 force_right_paren || matches!(rhs_e, LogicalExpr::Or(_, _) | LogicalExpr::And(_, _));
493 let s = match (put_left_paren, put_right_paren) {
494 (true, true) => format!("({lhs_s})&&({rhs_s})"),
495 (true, false) => format!("({lhs_s})&&{rhs_s}"),
496 (false, true) => format!("{lhs_s}&&({rhs_s})"),
497 (false, false) => format!("{lhs_s}&&{rhs_s}"),
498 };
499 (s, LogicalExpr::And(Box::new(lhs_e), Box::new(rhs_e)))
500 }
501 ),
502 (inner.clone(), inner.clone(), proptest::bool::ANY, proptest::bool::ANY).prop_map(
503 |((lhs_s, lhs_e), (rhs_s, rhs_e), force_left_paren, force_right_paren)| {
504 let put_left_paren = force_left_paren || matches!(lhs_e, LogicalExpr::Or(_, _));
505 let put_right_paren = force_right_paren;
506 let s = match (put_left_paren, put_right_paren) {
507 (true, true) => format!("({lhs_s})||({rhs_s})"),
508 (true, false) => format!("({lhs_s})||{rhs_s}"),
509 (false, true) => format!("{lhs_s}||({rhs_s})"),
510 (false, false) => format!("{lhs_s}||{rhs_s}"),
511 };
512 (s, LogicalExpr::Or(Box::new(lhs_e), Box::new(rhs_e)))
513 }
514 )
515 ]
516 })
517 }
518
519 fn any_atomic_logical_expr(
520 test_query_strategy: Option<BoxedStrategy<(String, JsonPathQuery)>>,
521 ) -> impl Strategy<Value = (String, LogicalExpr)> {
522 if let Some(test_query_strategy) = test_query_strategy {
523 prop_oneof![
524 any_test(test_query_strategy).prop_map(|(s, t)| (s, LogicalExpr::Test(t))),
525 any_comparison().prop_map(|(s, c)| (s, LogicalExpr::Comparison(c))),
526 ]
527 .boxed()
528 } else {
529 any_comparison()
530 .prop_map(|(s, c)| (s, LogicalExpr::Comparison(c)))
531 .boxed()
532 }
533 }
534
535 fn any_test(
536 test_query_strategy: BoxedStrategy<(String, JsonPathQuery)>,
537 ) -> impl Strategy<Value = (String, TestExpr)> {
538 (proptest::bool::ANY, test_query_strategy).prop_map(|(relative, (mut s, q))| {
539 if relative {
540 assert_eq!(
541 s.as_bytes()[0],
542 b'$',
543 "test_query_strategy should always generate root-based queries"
544 );
545 s.replace_range(0..1, "@");
546 (s, TestExpr::Relative(q))
547 } else {
548 (s, TestExpr::Absolute(q))
549 }
550 })
551 }
552
553 fn any_comparison() -> impl Strategy<Value = (String, ComparisonExpr)> {
554 (any_comparable(), any_comparison_op(), any_comparable()).prop_map(
555 |((lhs_s, lhs_e), (op_s, op_e), (rhs_s, rhs_e))| {
556 (
557 format!("{lhs_s}{op_s}{rhs_s}"),
558 ComparisonExpr::from_parts(lhs_e, op_e, rhs_e),
559 )
560 },
561 )
562 }
563
564 fn any_comparable() -> impl Strategy<Value = (String, Comparable)> {
565 prop_oneof![
566 any_literal().prop_map(|(s, l)| (s, Comparable::Literal(l))),
567 (proptest::bool::ANY, any_singular_query()).prop_map(|(relative, (mut s, q))| {
568 if relative {
569 assert_eq!(
570 s.as_bytes()[0],
571 b'$',
572 "test_query_strategy should always generate root-based queries"
573 );
574 s.replace_range(0..1, "@");
575 (s, Comparable::RelativeSingularQuery(q))
576 } else {
577 (s, Comparable::AbsoluteSingularQuery(q))
578 }
579 })
580 ]
581 }
582
583 prop_compose! {
584 fn any_singular_query()(segments in prop::collection::vec(any_singular_segment(), 0..10)) -> (String, SingularJsonPathQuery) {
585 let mut s = "$".to_string();
586 let mut v = vec![];
587
588 for (segment_s, segment) in segments {
589 s.push_str(&segment_s);
590 v.push(segment);
591 }
592
593 (s, SingularJsonPathQuery::from_iter(v))
594 }
595 }
596
597 fn any_singular_segment() -> impl Strategy<Value = (String, SingularSegment)> {
598 prop_oneof![
599 super::any_json_int().prop_map(|(s, i)| (format!("[{s}]"), SingularSegment::Index(i.into()))),
600 super::any_short_name().prop_map(|n| (format!(".{n}"), SingularSegment::Name(JsonString::new(&n)))),
601 super::strings::any_json_string().prop_map(|(s, n)| (format!("[{s}]"), SingularSegment::Name(n))),
602 ]
603 }
604
605 fn any_literal() -> impl Strategy<Value = (String, Literal)> {
606 prop_oneof![
607 strategy::Just(("null".to_string(), Literal::Null)),
608 proptest::bool::ANY.prop_map(|b| (b.to_string(), Literal::Bool(b))),
609 any_json_number().prop_map(|(s, n)| (s, Literal::Number(n))),
610 super::strings::any_json_string().prop_map(|(raw, s)| (raw, Literal::String(s)))
611 ]
612 }
613
614 fn any_json_number() -> impl Strategy<Value = (String, JsonNumber)> {
615 prop_oneof![
616 super::any_json_int().prop_map(|(s, i)| (s, JsonNumber::Int(i))),
617 any_json_float().prop_map(|(s, f)| (s, JsonNumber::Float(f))),
618 ]
619 .prop_map(|(x, n)| (x, n.normalize()))
620 }
621
622 fn any_json_float() -> impl Strategy<Value = (String, JsonFloat)> {
623 return prop_oneof![
628 any_float().prop_map(|f| (f.to_string(), JsonFloat::try_from(f).unwrap())),
629 any_float()
630 .prop_flat_map(|f| arbitrary_exp_repr(f).prop_map(move |s| (s, JsonFloat::try_from(f).unwrap()))),
631 ];
632
633 fn any_float() -> impl Strategy<Value = f64> {
634 prop_oneof![num::f64::NORMAL, num::f64::NORMAL.prop_map(f64::trunc)]
635 }
636
637 fn arbitrary_exp_repr(f: f64) -> impl Strategy<Value = String> {
638 let s = f.to_string();
639 let fp_pos: isize = s.find('.').unwrap_or(s.len()).try_into().unwrap();
640 let num_digits = if fp_pos == s.len() as isize {
641 s.len()
642 } else {
643 s.len() - 1
644 } - if f.is_sign_negative() {
645 1
647 } else {
648 0
649 };
650 (-1024..=1024_isize, proptest::bool::ANY, proptest::bool::ANY).prop_map(
651 move |(exp, force_sign, uppercase_e)| {
652 let new_pos = fp_pos - exp;
653 let mut res = String::new();
654 if f.is_sign_negative() {
655 res.push('-');
656 }
657 let mut orig_digits = s.chars().filter(|c| *c != '.');
658
659 if new_pos <= 0 {
668 res.push_str("0.");
670 for _ in 0..(-new_pos) {
671 res.push('0');
672 }
673 for orig_digit in orig_digits {
674 res.push(orig_digit);
675 }
676 } else if new_pos < num_digits as isize {
677 let mut pos = 0;
679 let mut pushed_non_zero = false;
680 loop {
681 if pos == new_pos {
682 if !pushed_non_zero {
683 res.push('0');
684 }
685 pushed_non_zero = true;
686 res.push('.');
687 } else {
688 let Some(orig_digit) = orig_digits.next() else { break };
689 if orig_digit == '0' {
690 if pushed_non_zero {
691 res.push(orig_digit);
692 }
693 } else {
694 pushed_non_zero = true;
695 res.push(orig_digit);
696 }
697 }
698 pos += 1;
699 }
700 } else if f == 0.0 {
701 res.push('0');
704 } else {
705 let skip_zeroes = orig_digits.skip_while(|x| *x == '0');
709 for orig_digit in skip_zeroes {
710 res.push(orig_digit);
711 }
712 for _ in 0..(new_pos - num_digits as isize) {
713 res.push('0');
714 }
715 }
716
717 res.push(if uppercase_e { 'E' } else { 'e' });
718
719 if exp > 0 {
720 if force_sign {
721 res.push('+');
722 }
723 res.push_str(&exp.to_string());
724 } else {
725 res.push_str(&exp.to_string());
726 }
727
728 res
729 },
730 )
731 }
732 }
733
734 fn any_comparison_op() -> impl Strategy<Value = (String, ComparisonOp)> {
735 prop_oneof![
736 strategy::Just(("==".to_string(), ComparisonOp::EqualTo)),
737 strategy::Just(("!=".to_string(), ComparisonOp::NotEqualTo)),
738 strategy::Just(("<".to_string(), ComparisonOp::LessThan)),
739 strategy::Just((">".to_string(), ComparisonOp::GreaterThan)),
740 strategy::Just(("<=".to_string(), ComparisonOp::LesserOrEqualTo)),
741 strategy::Just((">=".to_string(), ComparisonOp::GreaterOrEqualTo)),
742 ]
743 }
744}