Class: CurpMx::Validator
- Inherits:
-
Object
- Object
- CurpMx::Validator
- Defined in:
- lib/curp_mx/validator.rb
Overview
Validates a CURP's format and a few data points in it.
Hot-path notes: the format check uses String#match? (no MatchData allocation) and fields are read by fixed offset. Lookups go through frozen Sets, not Array scans. See spec + benchmarks.
Constant Summary collapse
- FORMAT =
Format only — no captures. Fields are sliced by offset afterwards. 0-3 name initials 4-5 year 6-7 month 8-9 day 10 sex 11-12 state 13-15 consonants 16 homoclave 17 check digit
Position 16 (homoclave/century): 0-9 for births before 2000, A-J for 2000 onward, per the Instructivo Normativo (DOF 18-10-2021). Sex accepts X (non-binary CURPs issued since 2023) beyond the H/M in that same text.
/\A[A-Z]{4}\d{2}[0-1]\d[0-3]\d[HMX][A-Z]{2}[^AEIOU]{3}[0-9A-J]\d\z/.freeze
- STATES_RENAPO =
Entity codes for positions 12-13, from Anexo 03 "Catálogo de Entidades Federativas para la conformación de la CURP" of the Instructivo Normativo (DOF 18-10-2021). 32 entities (Mexico City is DF; there is no CX) plus NE for people born abroad.
%w[AS BC BS CC CL CM CS CH DF DG GT GR HG JC MC MN MS NT NL OC PL QT QR SP SL SR TC TS TL VZ YN ZS NE].freeze
- NAME_ISSUES =
Problematic name initials (RENAPO substitutes the 2nd letter with X when the first four letters spell one of these). Full catalog of 82 words from Anexo 01 "Catálogo de palabras altisonantes" of the Instructivo Normativo (DOF 18-10-2021).
%w[BACA BAKA BUEI BUEY CACA CACO CAGA CAGO CAKA CAKO COGE COGI COJA COJE COJI COJO COLA CULO FALO FETO GETA GUEI GUEY JETA JOTO KACA KACO KAGA KAGO KAKA KAKO KOGE KOGI KOJA KOJE KOJI KOJO KOLA KULO LILO LOCA LOCO LOKA LOKO MAME MAMO MEAR MEAS MEON MIAR MION MOCO MOKO MULA MULO NACA NACO ORIN PEDA PEDO PENE PIPI PITO POPO PUTA PUTO QULO RATA ROBA ROBE ROBO RUIN SENO TETA VACA VAGA VAGO VAKA VUEI VUEY WUEI WUEY].freeze
- STATES =
O(1) lookup copies of the constants above (the public constants stay as readable frozen Arrays).
STATES_RENAPO.to_set.freeze
- WORDS =
NAME_ISSUES.to_set.freeze
Instance Attribute Summary collapse
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#raw_input ⇒ Object
readonly
Returns the value of attribute raw_input.
Class Method Summary collapse
-
.check_digit(str) ⇒ Object
Computes the RENAPO check digit (0-9) from the first 17 characters.
- .valid?(curp) ⇒ Boolean
Instance Method Summary collapse
-
#initialize(curp) ⇒ Validator
constructor
A new instance of Validator.
- #valid? ⇒ Boolean
- #validate ⇒ Object
Constructor Details
#initialize(curp) ⇒ Validator
Returns a new instance of Validator.
76 77 78 79 80 81 |
# File 'lib/curp_mx/validator.rb', line 76 def initialize(curp) @raw_input = curp.is_a?(String) ? curp.upcase : curp @errors = {} validate end |
Instance Attribute Details
#errors ⇒ Object (readonly)
Returns the value of attribute errors.
13 14 15 |
# File 'lib/curp_mx/validator.rb', line 13 def errors @errors end |
#raw_input ⇒ Object (readonly)
Returns the value of attribute raw_input.
13 14 15 |
# File 'lib/curp_mx/validator.rb', line 13 def raw_input @raw_input end |
Class Method Details
.check_digit(str) ⇒ Object
Computes the RENAPO check digit (0-9) from the first 17 characters.
Works on raw bytes: RENAPO's value table is contiguous in ASCII — '0'-'9' = 48-57 (=> 0-9), 'A'-'N' = 65-78 (=> 10-23), 'O'-'Z' = 79-90 (=> 25-36). The unused value 24 is Ñ's slot in the table; Ñ never appears in a CURP (RENAPO substitutes it), so we only need its offset (the -54 shift from 'O' on), not the character itself. Returns nil for input shorter than 17 bytes.
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/curp_mx/validator.rb', line 60 def self.check_digit(str) return nil if str.bytesize < 17 sum = 0 17.times do |i| b = str.getbyte(i) value = b < 58 ? b - 48 : (b < 79 ? b - 55 : b - 54) sum += value * (18 - i) end (10 - sum % 10) % 10 end |
.valid?(curp) ⇒ Boolean
72 73 74 |
# File 'lib/curp_mx/validator.rb', line 72 def self.valid?(curp) new(curp).valid? end |
Instance Method Details
#valid? ⇒ Boolean
83 84 85 |
# File 'lib/curp_mx/validator.rb', line 83 def valid? @errors.empty? end |
#validate ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/curp_mx/validator.rb', line 87 def validate unless @raw_input.is_a?(String) && FORMAT.match?(@raw_input) add_error(:format, 'Invalid format') return false end validate_state validate_name_initials validate_birth_date validate_date_exists validate_check_digit end |