1use crate::cedula;
2use crate::ValidationError;
3
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum RucType {
7 NaturalPerson,
8 JuridicalEntity,
9 PublicEntity,
10}
11
12pub fn ruc_type(input: &str) -> Option<RucType> {
31 let input = input.trim();
32 if input.len() != 13 || !input.chars().all(|c| c.is_ascii_digit()) {
33 return None;
34 }
35 match input.chars().nth(2).unwrap().to_digit(10).unwrap() {
36 0..=5 => Some(RucType::NaturalPerson),
37 6 => Some(RucType::PublicEntity),
38 9 => Some(RucType::JuridicalEntity),
39 _ => None,
40 }
41}
42
43pub fn validate(input: &str) -> Result<(), ValidationError> {
75 let input = input.trim();
76
77 if input.len() != 13 {
78 return Err(ValidationError::InvalidLength);
79 }
80
81 if !input.chars().all(|c| c.is_ascii_digit()) {
82 return Err(ValidationError::InvalidFormat);
83 }
84
85 let third_digit = input.chars().nth(2).unwrap().to_digit(10).unwrap();
86
87 match third_digit {
88 0..=5 => validate_natural(input),
89 6 => validate_public(input),
90 7..=8 => Err(ValidationError::InvalidFormat),
91 9 => validate_juridical(input),
92 _ => Err(ValidationError::InvalidFormat),
93 }
94}
95
96fn validate_natural(input: &str) -> Result<(), ValidationError> {
97 let cedula = &input[..10];
98 cedula::validate(cedula)?;
99
100 let establishment: u32 = input[10..13].parse().unwrap();
101 if establishment == 0 || establishment > 999 {
102 return Err(ValidationError::InvalidFormat);
103 }
104
105 Ok(())
106}
107
108fn validate_juridical(input: &str) -> Result<(), ValidationError> {
109 let province: u32 = input[..2].parse().unwrap();
110 if province == 0 || province > 24 {
111 return Err(ValidationError::InvalidProvinceCode);
112 }
113
114 let check_digit_pos9 = input.chars().nth(9).unwrap().to_digit(10).unwrap();
115
116 let weights = [4, 3, 2, 7, 6, 5, 4, 3, 2];
117 let mut sum = 0u32;
118
119 for (i, digit) in input.chars().take(9).enumerate() {
120 let digit = digit.to_digit(10).unwrap();
121 sum += digit * weights[i];
122 }
123
124 let computed_check = if sum.is_multiple_of(11) {
125 0
126 } else {
127 11 - (sum % 11)
128 };
129
130 if computed_check == 10 {
131 return Err(ValidationError::InvalidCheckDigit);
132 }
133
134 if computed_check != check_digit_pos9 {
135 return Err(ValidationError::InvalidCheckDigit);
136 }
137
138 let establishment: u32 = input[9..13].parse().unwrap();
139 if establishment == 0 || establishment > 9999 {
140 return Err(ValidationError::InvalidFormat);
141 }
142
143 Ok(())
144}
145
146fn validate_public(input: &str) -> Result<(), ValidationError> {
147 let province: u32 = input[..2].parse().unwrap();
148 if province == 0 || province > 24 {
149 return Err(ValidationError::InvalidProvinceCode);
150 }
151
152 let check_digit = input.chars().nth(8).unwrap().to_digit(10).unwrap();
153
154 let weights = [3, 2, 7, 6, 5, 4, 3, 2];
155 let mut sum = 0u32;
156
157 for (i, digit) in input.chars().take(8).enumerate() {
158 let digit = digit.to_digit(10).unwrap();
159 sum += digit * weights[i];
160 }
161
162 let remainder = if sum.is_multiple_of(11) {
163 0
164 } else {
165 11 - (sum % 11)
166 };
167
168 if remainder != check_digit {
169 return Err(ValidationError::InvalidCheckDigit);
170 }
171
172 let establishment: u32 = input[9..13].parse().unwrap();
173 if establishment == 0 || establishment > 9999 {
174 return Err(ValidationError::InvalidFormat);
175 }
176
177 Ok(())
178}
179
180pub fn is_valid(input: &str) -> bool {
195 validate(input).is_ok()
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn natural_ruc() {
204 assert!(validate("1713175071001").is_ok());
205 }
206
207 #[test]
208 fn juridical_ruc() {
209 assert!(validate("1790085783001").is_ok());
210 }
211
212 #[test]
213 fn public_ruc() {
214 assert!(validate("1760001550001").is_ok());
215 }
216
217 #[test]
218 fn invalid_establishment() {
219 assert_eq!(
220 validate("1713175071000"),
221 Err(ValidationError::InvalidFormat)
222 );
223 }
224
225 #[test]
226 fn bad_check_digit() {
227 assert_eq!(
228 validate("1790085782001"),
229 Err(ValidationError::InvalidCheckDigit)
230 );
231 }
232}