use crate::cedula;
use crate::ValidationError;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RucType {
NaturalPerson,
JuridicalEntity,
PublicEntity,
}
pub fn ruc_type(input: &str) -> Option<RucType> {
let input = input.trim();
if input.len() != 13 || !input.chars().all(|c| c.is_ascii_digit()) {
return None;
}
match input.chars().nth(2).unwrap().to_digit(10).unwrap() {
0..=5 => Some(RucType::NaturalPerson),
6 => Some(RucType::PublicEntity),
9 => Some(RucType::JuridicalEntity),
_ => None,
}
}
pub fn validate(input: &str) -> Result<(), ValidationError> {
let input = input.trim();
if input.len() != 13 {
return Err(ValidationError::InvalidLength);
}
if !input.chars().all(|c| c.is_ascii_digit()) {
return Err(ValidationError::InvalidFormat);
}
let third_digit = input.chars().nth(2).unwrap().to_digit(10).unwrap();
match third_digit {
0..=5 => validate_natural(input),
6 => validate_public(input),
7..=8 => Err(ValidationError::InvalidFormat),
9 => validate_juridical(input),
_ => Err(ValidationError::InvalidFormat),
}
}
fn validate_natural(input: &str) -> Result<(), ValidationError> {
let cedula = &input[..10];
cedula::validate(cedula)?;
let establishment: u32 = input[10..13].parse().unwrap();
if establishment == 0 || establishment > 999 {
return Err(ValidationError::InvalidFormat);
}
Ok(())
}
fn validate_juridical(input: &str) -> Result<(), ValidationError> {
let province: u32 = input[..2].parse().unwrap();
if province == 0 || province > 24 {
return Err(ValidationError::InvalidProvinceCode);
}
let check_digit_pos9 = input.chars().nth(9).unwrap().to_digit(10).unwrap();
let weights = [4, 3, 2, 7, 6, 5, 4, 3, 2];
let mut sum = 0u32;
for (i, digit) in input.chars().take(9).enumerate() {
let digit = digit.to_digit(10).unwrap();
sum += digit * weights[i];
}
let computed_check = if sum.is_multiple_of(11) {
0
} else {
11 - (sum % 11)
};
if computed_check == 10 {
return Err(ValidationError::InvalidCheckDigit);
}
if computed_check != check_digit_pos9 {
return Err(ValidationError::InvalidCheckDigit);
}
let establishment: u32 = input[9..13].parse().unwrap();
if establishment == 0 || establishment > 9999 {
return Err(ValidationError::InvalidFormat);
}
Ok(())
}
fn validate_public(input: &str) -> Result<(), ValidationError> {
let province: u32 = input[..2].parse().unwrap();
if province == 0 || province > 24 {
return Err(ValidationError::InvalidProvinceCode);
}
let check_digit = input.chars().nth(8).unwrap().to_digit(10).unwrap();
let weights = [3, 2, 7, 6, 5, 4, 3, 2];
let mut sum = 0u32;
for (i, digit) in input.chars().take(8).enumerate() {
let digit = digit.to_digit(10).unwrap();
sum += digit * weights[i];
}
let remainder = if sum.is_multiple_of(11) {
0
} else {
11 - (sum % 11)
};
if remainder != check_digit {
return Err(ValidationError::InvalidCheckDigit);
}
let establishment: u32 = input[9..13].parse().unwrap();
if establishment == 0 || establishment > 9999 {
return Err(ValidationError::InvalidFormat);
}
Ok(())
}
pub fn is_valid(input: &str) -> bool {
validate(input).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn natural_ruc() {
assert!(validate("1713175071001").is_ok());
}
#[test]
fn juridical_ruc() {
assert!(validate("1790085783001").is_ok());
}
#[test]
fn public_ruc() {
assert!(validate("1760001550001").is_ok());
}
#[test]
fn invalid_establishment() {
assert_eq!(
validate("1713175071000"),
Err(ValidationError::InvalidFormat)
);
}
#[test]
fn bad_check_digit() {
assert_eq!(
validate("1790085782001"),
Err(ValidationError::InvalidCheckDigit)
);
}
}