1use alloc::string::String;
24use alloc::vec::Vec;
25use core::fmt;
26
27use crate::rand::{RandomSource, SeededRng, below_u8, simple_seed};
28use crate::uf::State;
29
30const RG_MAX_LEN: usize = 14;
31const SP_BODY_LEN: u8 = 9;
32const SP_FORMATTED_LEN: u8 = 12;
33const SP_BASE_LEN: usize = 8;
34const SP_WEIGHTS: [u32; SP_BASE_LEN] = [9, 8, 7, 6, 5, 4, 3, 2];
35const SP_FORMATTED_DIGIT_POS: [usize; 9] = [0, 1, 3, 4, 5, 7, 8, 9, 11];
36
37const STRUCTURAL_MIN_LEN: u8 = 5;
38const STRUCTURAL_MAX_LEN: u8 = 14;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum RgError {
42 InvalidLength,
43 InvalidCharacter,
44 InvalidFormat,
45 InvalidCheckDigit,
46 UnsupportedUfForGeneration,
47}
48
49impl fmt::Display for RgError {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str(match self {
52 Self::InvalidLength => "RG length is outside the accepted range for this UF",
53 Self::InvalidCharacter => "RG contains invalid characters",
54 Self::InvalidFormat => "RG format does not match the canonical mask for this UF",
55 Self::InvalidCheckDigit => "RG check digit is invalid",
56 Self::UnsupportedUfForGeneration => {
57 "RG generation is not supported for this UF (no verified algorithm)"
58 }
59 })
60 }
61}
62
63impl core::error::Error for RgError {}
64
65#[derive(Clone, Copy)]
67struct UfSpec {
68 body_len: Option<u8>,
69 has_check_digit: bool,
70 allow_x_terminator: bool,
71 separators: &'static [(u8, char)],
72 formatted_len: Option<u8>,
73}
74
75const STRUCTURAL_DEFAULT: UfSpec = UfSpec {
76 body_len: None,
77 has_check_digit: false,
78 allow_x_terminator: false,
79 separators: &[],
80 formatted_len: None,
81};
82
83const SP_SPEC: UfSpec = UfSpec {
84 body_len: Some(SP_BODY_LEN),
85 has_check_digit: true,
86 allow_x_terminator: true,
87 separators: &[(2, '.'), (5, '.'), (8, '-')],
88 formatted_len: Some(SP_FORMATTED_LEN),
89};
90
91const fn uf_spec(uf: State) -> UfSpec {
92 match uf {
93 State::SP => SP_SPEC,
94 _ => STRUCTURAL_DEFAULT,
95 }
96}
97
98#[derive(Clone, Copy, PartialEq, Eq, Hash)]
101pub struct Rg {
102 bytes: [u8; RG_MAX_LEN],
103 len: u8,
104 uf: State,
105}
106
107impl Rg {
108 pub fn as_str(&self) -> &str {
110 unsafe { core::str::from_utf8_unchecked(&self.bytes[..self.len as usize]) }
112 }
113
114 pub fn uf(&self) -> State {
116 self.uf
117 }
118
119 pub fn formatted(&self) -> String {
122 format_with_spec(self.as_str(), uf_spec(self.uf)).unwrap_or_else(|| self.as_str().into())
123 }
124
125 pub fn masked(&self) -> String {
130 let s = self.as_str();
131 let spec = uf_spec(self.uf);
132 if spec.has_check_digit && s.len() == SP_BODY_LEN as usize {
133 let mut out = String::with_capacity(SP_FORMATTED_LEN as usize);
134 out.push_str(&s[..2]);
135 out.push('.');
136 out.push_str("***");
137 out.push('.');
138 out.push_str("***");
139 out.push('-');
140 out.push('*');
141 out
142 } else {
143 let mut out = String::with_capacity(s.len());
144 for (i, _) in s.chars().enumerate() {
145 if i < 2 {
146 out.push(s.as_bytes()[i] as char);
147 } else {
148 out.push('*');
149 }
150 }
151 out
152 }
153 }
154
155 pub fn body(&self) -> &str {
160 let spec = uf_spec(self.uf);
161 if spec.has_check_digit && self.len as usize == SP_BODY_LEN as usize {
162 unsafe { core::str::from_utf8_unchecked(&self.bytes[..SP_BASE_LEN]) }
164 } else {
165 self.as_str()
166 }
167 }
168
169 pub fn check_digit(&self) -> Option<u8> {
172 let spec = uf_spec(self.uf);
173 if !spec.has_check_digit {
174 return None;
175 }
176 let last = self.bytes[self.len as usize - 1];
177 if last == b'X' {
178 Some(10)
179 } else {
180 Some(last - b'0')
181 }
182 }
183}
184
185impl AsRef<str> for Rg {
186 fn as_ref(&self) -> &str {
187 self.as_str()
188 }
189}
190
191impl fmt::Display for Rg {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.write_str(&self.formatted())
194 }
195}
196
197impl fmt::Debug for Rg {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "Rg({}, {})", self.uf.abbreviation(), self.formatted())
200 }
201}
202
203pub fn normalize(rg: &str, uf: State) -> String {
206 let spec = uf_spec(uf);
207 let mut out = String::with_capacity(rg.len());
208 for c in rg.chars() {
209 if c.is_ascii_digit() {
210 out.push(c);
211 } else if spec.allow_x_terminator && (c == 'X' || c == 'x') {
212 out.push('X');
213 }
214 }
215 out
216}
217
218pub fn remove_symbols(rg: &str, uf: State) -> String {
220 normalize(rg, uf)
221}
222
223pub fn is_valid_lenient(rg: &str, uf: State) -> bool {
226 let spec = uf_spec(uf);
227 let raw = normalize(rg, uf);
228 if !validate_body_length(&raw, spec) {
229 return false;
230 }
231 if !validate_charset(&raw, spec) {
232 return false;
233 }
234 if spec.has_check_digit {
235 return sp_check_digit_ok(&raw);
236 }
237 true
238}
239
240pub fn is_valid(rg: &str, uf: State) -> bool {
244 is_valid_lenient(rg, uf)
245}
246
247pub fn is_valid_strict(rg: &str, uf: State) -> Result<(), RgError> {
250 parse_strict(rg, uf).map(|_| ())
251}
252
253pub fn format_rg(rg: &str, uf: State) -> Option<String> {
256 let spec = uf_spec(uf);
257 let raw = remove_symbols(rg, uf);
258 if !validate_body_length(&raw, spec) {
259 return None;
260 }
261 Some(format_with_spec(&raw, spec).unwrap_or(raw))
262}
263
264pub fn compute_check_digit(base: &str, uf: State) -> Option<u8> {
267 if !matches!(uf, State::SP) {
268 return None;
269 }
270 let raw: Vec<u8> = base.bytes().filter(u8::is_ascii_digit).collect();
271 if raw.len() != SP_BASE_LEN {
272 return None;
273 }
274 Some(sp_check_digit(&raw))
275}
276
277pub fn parse_strict(raw: &str, uf: State) -> Result<Rg, RgError> {
279 let spec = uf_spec(uf);
280 let bytes = raw.as_bytes();
281
282 let body = if let Some(len) = spec.formatted_len.filter(|&l| bytes.len() == l as usize) {
283 let _ = len;
284 parse_formatted_sp(bytes)?
285 } else if let Some(body_len) = spec.body_len {
286 if bytes.len() != body_len as usize {
287 return Err(RgError::InvalidLength);
288 }
289 parse_unformatted(bytes, spec)?
290 } else {
291 if bytes.len() < STRUCTURAL_MIN_LEN as usize || bytes.len() > STRUCTURAL_MAX_LEN as usize {
292 return Err(RgError::InvalidLength);
293 }
294 parse_unformatted(bytes, spec)?
295 };
296
297 if spec.has_check_digit && !sp_check_digit_ok_bytes(&body) {
298 return Err(RgError::InvalidCheckDigit);
299 }
300
301 Ok(Rg::from_body(&body, uf))
302}
303
304pub fn generate(uf: State) -> Result<Rg, RgError> {
309 let mut rng = SeededRng::new(simple_seed());
310 generate_with_rng(&mut rng, uf)
311}
312
313pub fn generate_with_seed(seed: u64, uf: State) -> Result<Rg, RgError> {
318 let mut rng = SeededRng::new(seed);
319 generate_with_rng(&mut rng, uf)
320}
321
322pub fn generate_with_rng<R: RandomSource + ?Sized>(rng: &mut R, uf: State) -> Result<Rg, RgError> {
327 if !matches!(uf, State::SP) {
328 return Err(RgError::UnsupportedUfForGeneration);
329 }
330 let mut digits = [0u8; SP_BASE_LEN];
331 for d in &mut digits {
332 *d = below_u8(rng, 10);
333 }
334 let mut body = [0u8; 9];
335 for (i, &d) in digits.iter().enumerate() {
336 body[i] = d + b'0';
337 }
338 let dv = sp_check_digit(&digits);
339 body[8] = if dv == 10 { b'X' } else { b'0' + dv };
340 Ok(Rg::from_body(&body, State::SP))
341}
342
343impl Rg {
344 fn from_body(body: &[u8], uf: State) -> Self {
345 let mut bytes = [0u8; RG_MAX_LEN];
346 bytes[..body.len()].copy_from_slice(body);
347 Self {
348 bytes,
349 len: u8::try_from(body.len()).expect("RG body length must fit in u8"),
350 uf,
351 }
352 }
353}
354
355fn validate_body_length(raw: &str, spec: UfSpec) -> bool {
356 if let Some(n) = spec.body_len {
357 raw.len() == n as usize
358 } else {
359 let n = raw.len();
360 n >= STRUCTURAL_MIN_LEN as usize && n <= STRUCTURAL_MAX_LEN as usize
361 }
362}
363
364fn validate_charset(raw: &str, spec: UfSpec) -> bool {
365 let bytes = raw.as_bytes();
366 if bytes.is_empty() {
367 return false;
368 }
369 if spec.allow_x_terminator {
370 let (last_idx, rest) = (bytes.len() - 1, &bytes[..bytes.len() - 1]);
371 if !rest.iter().all(u8::is_ascii_digit) {
372 return false;
373 }
374 let last = bytes[last_idx];
375 last.is_ascii_digit() || last == b'X'
376 } else {
377 bytes.iter().all(u8::is_ascii_digit)
378 }
379}
380
381fn parse_unformatted(bytes: &[u8], spec: UfSpec) -> Result<Vec<u8>, RgError> {
382 if !validate_charset(
383 unsafe { core::str::from_utf8_unchecked(bytes) },
385 spec,
386 ) {
387 return Err(RgError::InvalidCharacter);
388 }
389 Ok(bytes.to_vec())
390}
391
392fn parse_formatted_sp(bytes: &[u8]) -> Result<Vec<u8>, RgError> {
393 if bytes[2] != b'.' || bytes[6] != b'.' || bytes[10] != b'-' {
395 return Err(RgError::InvalidFormat);
396 }
397 let mut out = Vec::with_capacity(SP_BODY_LEN as usize);
398 for (i, &idx) in SP_FORMATTED_DIGIT_POS.iter().enumerate() {
399 let b = bytes[idx];
400 let last = i == SP_FORMATTED_DIGIT_POS.len() - 1;
401 if b.is_ascii_digit() || (last && b == b'X') {
402 out.push(b);
403 } else if last && b == b'x' {
404 out.push(b'X');
405 } else {
406 return Err(RgError::InvalidCharacter);
407 }
408 }
409 Ok(out)
410}
411
412fn format_with_spec(body: &str, spec: UfSpec) -> Option<String> {
413 if spec.separators.is_empty() {
414 return None;
415 }
416 let body_len = spec.body_len? as usize;
417 if body.len() != body_len {
418 return None;
419 }
420 let total = body_len + spec.separators.len();
421 let mut out = String::with_capacity(total);
422 let mut sep_iter = spec.separators.iter().peekable();
423 for (i, ch) in body.chars().enumerate() {
424 while let Some(&&(pos, sep_ch)) = sep_iter.peek() {
425 if pos as usize == i && i != 0 {
426 out.push(sep_ch);
427 sep_iter.next();
428 } else {
429 break;
430 }
431 }
432 out.push(ch);
433 }
434 Some(out)
435}
436
437fn sp_check_digit(digits: &[u8]) -> u8 {
438 let sum: u32 = digits
439 .iter()
440 .zip(SP_WEIGHTS.iter())
441 .map(|(&d, &w)| u32::from(d) * w)
442 .sum();
443 (sum % 11) as u8
444}
445
446fn sp_check_digit_ok(body: &str) -> bool {
447 sp_check_digit_ok_bytes(body.as_bytes())
448}
449
450fn sp_check_digit_ok_bytes(bytes: &[u8]) -> bool {
451 if bytes.len() != SP_BODY_LEN as usize {
452 return false;
453 }
454 let mut digits = [0u8; SP_BASE_LEN];
455 for (i, &b) in bytes[..SP_BASE_LEN].iter().enumerate() {
456 if !b.is_ascii_digit() {
457 return false;
458 }
459 digits[i] = b - b'0';
460 }
461 let expected = sp_check_digit(&digits);
462 let last = bytes[SP_BASE_LEN];
463 let actual = if last == b'X' {
464 10
465 } else if last.is_ascii_digit() {
466 last - b'0'
467 } else {
468 return false;
469 };
470 actual == expected
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use alloc::string::ToString;
477
478 #[test]
479 fn real_rg_29465327_2() {
480 assert!(is_valid("294653272", State::SP));
482 assert!(is_valid("29.465.327-2", State::SP));
483 let parsed = parse_strict("294653272", State::SP).unwrap();
484 assert_eq!(parsed.as_str(), "294653272");
485 assert_eq!(parsed.check_digit(), Some(2));
486 assert_eq!(parsed.formatted(), "29.465.327-2");
487 let parsed_fmt = parse_strict("29.465.327-2", State::SP).unwrap();
488 assert_eq!(parsed_fmt, parsed);
489 }
490
491 #[test]
492 fn real_rg_39406714_9() {
493 assert!(is_valid("394067149", State::SP));
495 assert!(is_valid("39.406.714-9", State::SP));
496 let parsed = parse_strict("394067149", State::SP).unwrap();
497 assert_eq!(parsed.as_str(), "394067149");
498 assert_eq!(parsed.check_digit(), Some(9));
499 assert_eq!(parsed.formatted(), "39.406.714-9");
500 let parsed_fmt = parse_strict("39.406.714-9", State::SP).unwrap();
501 assert_eq!(parsed_fmt, parsed);
502 }
503
504 #[test]
505 fn compute_check_digit_real_rgs() {
506 assert_eq!(compute_check_digit("29465327", State::SP), Some(2));
507 assert_eq!(compute_check_digit("39406714", State::SP), Some(9));
508 }
509
510 #[test]
511 fn format_real_rgs() {
512 assert_eq!(
513 format_rg("294653272", State::SP),
514 Some("29.465.327-2".into())
515 );
516 assert_eq!(
517 format_rg("394067149", State::SP),
518 Some("39.406.714-9".into())
519 );
520 }
521
522 #[test]
523 fn sp_check_digit_known_values() {
524 assert_eq!(sp_check_digit(&[1, 2, 3, 4, 5, 6, 7, 8]), 2);
526 assert_eq!(sp_check_digit(&[4, 4, 4, 4, 4, 4, 4, 4]), 0);
528 assert_eq!(sp_check_digit(&[1, 1, 1, 1, 1, 1, 1, 1]), 0);
530 assert_eq!(sp_check_digit(&[6, 0, 0, 0, 0, 0, 0, 0]), 10);
532 }
533
534 #[test]
535 fn is_valid_sp_accepts_valid() {
536 assert!(is_valid("123456782", State::SP));
537 assert!(is_valid("12.345.678-2", State::SP));
538 }
539
540 #[test]
541 fn is_valid_sp_rejects_wrong_dv() {
542 assert!(!is_valid("123456789", State::SP));
543 assert!(!is_valid("12.345.678-9", State::SP));
544 assert!(!is_valid("294653271", State::SP));
545 }
546
547 #[test]
548 fn is_valid_sp_x_terminator() {
549 assert!(is_valid("60000000X", State::SP));
550 assert!(is_valid("60.000.000-X", State::SP));
551 assert!(is_valid("60.000.000-x", State::SP));
552 }
553
554 #[test]
555 fn is_valid_sp_lenient_strips_garbage() {
556 let s = "123456782";
557 let garbage = alloc::format!("{}!{}@{}#{}", &s[0..2], &s[2..5], &s[5..8], &s[8..9]);
558 assert!(is_valid(&garbage, State::SP));
559 }
560
561 #[test]
562 fn is_valid_sp_rejects_wrong_length() {
563 assert!(!is_valid("", State::SP));
564 assert!(!is_valid("12345", State::SP));
565 assert!(!is_valid("1234567890", State::SP));
566 }
567
568 #[test]
569 fn structural_other_uf_accepts_any_digits_in_range() {
570 assert!(is_valid("12345", State::RJ));
571 assert!(is_valid("1234567890", State::MG));
572 assert!(is_valid("12345678901234", State::PR));
573 }
574
575 #[test]
576 fn structural_other_uf_rejects_too_short_or_long() {
577 assert!(!is_valid("1234", State::RJ));
578 assert!(!is_valid("123456789012345", State::RJ));
579 }
580
581 #[test]
582 fn structural_other_uf_rejects_letters() {
583 assert!(!is_valid("abcdef", State::RJ));
584 assert!(!is_valid("", State::RJ));
585 }
586
587 #[test]
588 fn strict_sp_accepts_unformatted() {
589 assert!(is_valid_strict("123456782", State::SP).is_ok());
590 assert!(is_valid_strict("294653272", State::SP).is_ok());
591 }
592
593 #[test]
594 fn strict_sp_accepts_formatted() {
595 assert!(is_valid_strict("12.345.678-2", State::SP).is_ok());
596 assert!(is_valid_strict("29.465.327-2", State::SP).is_ok());
597 }
598
599 #[test]
600 fn strict_sp_rejects_misplaced_separators() {
601 assert_eq!(
602 is_valid_strict("123.45.678-2", State::SP),
603 Err(RgError::InvalidFormat)
604 );
605 assert_eq!(
606 is_valid_strict("12.345.6782-", State::SP),
607 Err(RgError::InvalidFormat)
608 );
609 }
610
611 #[test]
612 fn strict_sp_rejects_garbage() {
613 assert_eq!(
614 is_valid_strict("12.345.678!2", State::SP),
615 Err(RgError::InvalidFormat)
616 );
617 }
618
619 #[test]
620 fn strict_sp_rejects_bad_dv() {
621 assert_eq!(
622 is_valid_strict("123456789", State::SP),
623 Err(RgError::InvalidCheckDigit)
624 );
625 }
626
627 #[test]
628 fn strict_sp_rejects_x_in_middle() {
629 assert_eq!(
630 is_valid_strict("1234X6782", State::SP),
631 Err(RgError::InvalidCharacter)
632 );
633 }
634
635 #[test]
636 fn strict_other_uf_accepts_digits_only() {
637 assert!(is_valid_strict("1234567", State::RJ).is_ok());
638 }
639
640 #[test]
641 fn strict_other_uf_rejects_separators() {
642 assert!(is_valid_strict("12.345.67", State::RJ).is_err());
643 }
644
645 #[test]
646 fn parse_sp_roundtrip() {
647 let parsed = parse_strict("123456782", State::SP).unwrap();
648 assert_eq!(parsed.as_str(), "123456782");
649 let parsed_fmt = parse_strict("12.345.678-2", State::SP).unwrap();
650 assert_eq!(parsed_fmt, parsed);
651 }
652
653 #[test]
654 fn parse_sp_x_terminator() {
655 let parsed = parse_strict("60.000.000-X", State::SP).unwrap();
656 assert_eq!(parsed.as_str(), "60000000X");
657 assert_eq!(parsed.check_digit(), Some(10));
658 }
659
660 #[test]
661 fn parse_other_uf_returns_digits() {
662 let parsed = parse_strict("1234567", State::RJ).unwrap();
663 assert_eq!(parsed.as_str(), "1234567");
664 assert_eq!(parsed.uf(), State::RJ);
665 assert_eq!(parsed.check_digit(), None);
666 }
667
668 #[test]
669 fn format_sp_inserts_separators() {
670 assert_eq!(
671 format_rg("123456782", State::SP),
672 Some("12.345.678-2".into())
673 );
674 assert_eq!(
675 format_rg("60000000X", State::SP),
676 Some("60.000.000-X".into())
677 );
678 }
679
680 #[test]
681 fn format_sp_passes_through_already_formatted() {
682 assert_eq!(
683 format_rg("12.345.678-2", State::SP),
684 Some("12.345.678-2".into())
685 );
686 }
687
688 #[test]
689 fn format_other_uf_returns_digits_unchanged() {
690 assert_eq!(format_rg("1234567", State::RJ), Some("1234567".into()));
691 }
692
693 #[test]
694 fn format_returns_none_on_bad_length() {
695 assert_eq!(format_rg("12", State::SP), None);
696 assert_eq!(format_rg("12", State::RJ), None);
697 }
698
699 #[test]
700 fn remove_symbols_sp_keeps_x() {
701 assert_eq!(remove_symbols("60.000.000-X", State::SP), "60000000X");
702 assert_eq!(remove_symbols("60.000.000-x", State::SP), "60000000X");
703 }
704
705 #[test]
706 fn remove_symbols_other_uf_drops_letters() {
707 assert_eq!(remove_symbols("12.345-67", State::RJ), "1234567");
708 assert_eq!(remove_symbols("X1234567", State::RJ), "1234567");
709 }
710
711 #[test]
712 fn compute_check_digit_sp() {
713 assert_eq!(compute_check_digit("12345678", State::SP), Some(2));
714 assert_eq!(compute_check_digit("44444444", State::SP), Some(0));
715 assert_eq!(compute_check_digit("60000000", State::SP), Some(10));
716 }
717
718 #[test]
719 fn compute_check_digit_rejects_other_uf() {
720 assert_eq!(compute_check_digit("1234567", State::RJ), None);
721 }
722
723 #[test]
724 fn compute_check_digit_rejects_bad_length() {
725 assert_eq!(compute_check_digit("1234567", State::SP), None);
726 assert_eq!(compute_check_digit("123456789", State::SP), None);
727 }
728
729 #[test]
730 fn generate_produces_valid() {
731 for _ in 0..100 {
732 let rg = generate(State::SP).unwrap();
733 assert!(is_valid(rg.as_str(), State::SP));
734 let parsed = parse_strict(rg.as_str(), State::SP).unwrap();
735 assert_eq!(parsed, rg);
736 }
737 }
738
739 #[test]
740 fn generate_format_roundtrip() {
741 for _ in 0..100 {
742 let rg = generate(State::SP).unwrap();
743 let formatted = rg.formatted();
744 let parsed = parse_strict(&formatted, State::SP).unwrap();
745 assert_eq!(parsed, rg);
746 }
747 }
748
749 #[test]
750 fn seeded_generation_accepts_zero_and_is_deterministic() {
751 let first = generate_with_seed(0, State::SP).unwrap();
752 let second = generate_with_seed(0, State::SP).unwrap();
753 assert_eq!(first, second);
754 assert!(is_valid(first.as_str(), State::SP));
755 }
756
757 #[test]
758 fn generate_ok_others_err() {
759 assert!(generate(State::SP).is_ok());
760 assert_eq!(
761 generate(State::RJ),
762 Err(RgError::UnsupportedUfForGeneration)
763 );
764 }
765
766 #[test]
767 fn masked_sp_real_rgs() {
768 let rg = parse_strict("294653272", State::SP).unwrap();
769 assert_eq!(rg.masked(), "29.***.***-*");
770 let rg = parse_strict("60000000X", State::SP).unwrap();
771 assert_eq!(rg.masked(), "60.***.***-*");
772 }
773
774 #[test]
775 fn masked_other_uf() {
776 let rg = parse_strict("1234567", State::RJ).unwrap();
777 assert_eq!(rg.masked(), "12*****");
778 }
779
780 #[test]
781 fn masked_generated_sp() {
782 let rg = generate(State::SP).unwrap();
783 let m = rg.masked();
784 assert!(m.starts_with(&rg.as_str()[..2]));
785 assert_eq!(m, alloc::format!("{}.***.***-*", &rg.as_str()[..2]));
786 }
787
788 #[test]
789 fn body_sp_real_rgs() {
790 let rg = parse_strict("294653272", State::SP).unwrap();
791 assert_eq!(rg.body(), "29465327");
792 let rg = parse_strict("60000000X", State::SP).unwrap();
793 assert_eq!(rg.body(), "60000000");
794 }
795
796 #[test]
797 fn body_other_uf_returns_full() {
798 let rg = parse_strict("1234567", State::RJ).unwrap();
799 assert_eq!(rg.body(), "1234567");
800 }
801
802 #[test]
803 fn rg_is_copy() {
804 let rg = generate(State::SP).unwrap();
805 let copy = rg;
806 assert_eq!(rg, copy);
807 }
808
809 #[test]
810 fn rg_as_ref_str() {
811 let rg = generate(State::SP).unwrap();
812 let r: &str = rg.as_ref();
813 assert_eq!(r, rg.as_str());
814 }
815
816 #[test]
817 fn debug_format_includes_uf() {
818 let rg = parse_strict("294653272", State::SP).unwrap();
819 let dbg = alloc::format!("{rg:?}");
820 assert!(dbg.starts_with("Rg(SP, "));
821 assert!(dbg.ends_with(')'));
822 }
823
824 #[test]
825 fn display_uses_formatted() {
826 let rg = parse_strict("294653272", State::SP).unwrap();
827 assert_eq!(rg.to_string(), "29.465.327-2");
828 }
829
830 #[test]
831 fn other_uf_display_passes_through() {
832 let rg = parse_strict("1234567", State::RJ).unwrap();
833 assert_eq!(rg.to_string(), "1234567");
834 }
835}