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