Class: CurpMx::Validator

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#errorsObject (readonly)

Returns the value of attribute errors.



13
14
15
# File 'lib/curp_mx/validator.rb', line 13

def errors
  @errors
end

#raw_inputObject (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

Returns:

  • (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

Returns:

  • (Boolean)


83
84
85
# File 'lib/curp_mx/validator.rb', line 83

def valid?
  @errors.empty?
end

#validateObject



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