Skip to content

Commit

Permalink
refactor: simplify isUenValid logic (#2156)
Browse files Browse the repository at this point in the history
* refactor: simplify isUenValid logic

* fix: use stricter regex matching
  • Loading branch information
mantariksh committed Jun 17, 2021
1 parent ff100aa commit b0f8233
Showing 1 changed file with 100 additions and 135 deletions.
235 changes: 100 additions & 135 deletions src/shared/util/uen-validation.ts
Original file line number Diff line number Diff line change
@@ -1,164 +1,129 @@
/**
* Validates whether a provided string value adheres to the UIN/FIN format
* as provided on the Singapore Government's National Registration Identity Card.
* @param value The value to be validated
* Validate entity-type indicators, as per
* https://www.uen.gov.sg/ueninternet/faces/pages/admin/aboutUEN.jspx
*/
export const isUenValid = (value: string): boolean => {
return validateUEN(value)
}
const VALID_ENTITY_TYPE_INDICATORS = new Set([
'LP',
'LL',
'FC',
'PF',
'RF',
'MQ',
'MM',
'NB',
'CC',
'CS',
'MB',
'FM',
'GS',
'GA',
'GB',
'DP',
'CP',
'NR',
'CM',
'CD',
'MD',
'HS',
'VH',
'CH',
'MH',
'CL',
'XL',
'CX',
'RP',
'TU',
'TC',
'FB',
'FN',
'PA',
'PB',
'SS',
'MC',
'SM',
])

/**
* validates UEN of businesses in Singapore
* https://www.uen.gov.sg/ueninternet/faces/pages/admin/aboutUEN.jspx
* https://gist.github.com/mervintankw/90d5660c6ab03a83ddf77fa8199a0e52
* @param {string} uen
* @returns {boolean}
* Helper to check whether a string is numeric
* @param s String
* @returns True if string is numeric
*/
function validateUEN(uen: string) {
const entityTypeIndicator = [
'LP',
'LL',
'FC',
'PF',
'RF',
'MQ',
'MM',
'NB',
'CC',
'CS',
'MB',
'FM',
'GS',
'GA',
'GB',
'DP',
'CP',
'NR',
'CM',
'CD',
'MD',
'HS',
'VH',
'CH',
'MH',
'CL',
'XL',
'CX',
'RP',
'TU',
'TC',
'FB',
'FN',
'PA',
'PB',
'SS',
'MC',
'SM',
]
const isNumeric = (s: string): boolean => !!s.match(/^[0-9]+$/)

// check that uen is not empty
if (!uen || String(uen) === '') {
return false
}
/**
* Helper to check whether a string is alphabetic
* @param s string
* @returns True if string is alphabetic
*/
const isAlphabetic = (s: string): boolean => !!s.match(/^[a-zA-Z]+$/)

/**
* Validates whether a provided string value adheres to the UIN/FIN format
* as provided on the Singapore Government's National Registration Identity Card.
* @param value The value to be validated
*/
export const isUenValid = (uen: string): boolean => {
// allow lowercase strings
uen = uen.toUpperCase()

// check if uen is 9 or 10 digits
if (uen.length < 9 || uen.length > 10) {
return false
}

uen = uen.toUpperCase()
const uenStrArray: any[] = uen.split('')

// (A) Businesses registered with ACRA
if (uenStrArray.length === 9) {
if (uen.length === 9) {
// check that last character is a letter
if (!isNaN(uenStrArray[uenStrArray.length - 1])) {
const lastChar = uen[uen.length - 1]
if (!isAlphabetic(lastChar)) {
return false
}

for (let i = 0; i < uenStrArray.length - 1; i++) {
// check that first 8 letters are all numbers
if (isNaN(uenStrArray[i])) {
return false
}
// check that first 8 letters are all numbers
const first8Chars = uen.slice(0, 8)
if (!isNumeric(first8Chars)) {
return false
}

// (A) Businesses registered with ACRA (SUCCESS)
return true
} else if (uenStrArray.length === 10) {
// check that last character is a letter
if (!isNaN(uenStrArray[uenStrArray.length - 1])) {
return false
}

// (B) Local companies registered with ACRA
if (
!isNaN(uenStrArray[0]) &&
!isNaN(uenStrArray[1]) &&
!isNaN(uenStrArray[2]) &&
!isNaN(uenStrArray[3])
) {
// check that 5th to 9th letters are all numbers
if (
!isNaN(uenStrArray[4]) &&
!isNaN(uenStrArray[5]) &&
!isNaN(uenStrArray[6]) &&
!isNaN(uenStrArray[7]) &&
!isNaN(uenStrArray[8])
) {
// (B) Local companies registered with ACRA (SUCCESS)
return true
} else {
return false
}
}
// (C) All other entities which will be issued new UEN
else {
// check that 1st letter is either T or S or R
if (
uenStrArray[0] !== 'T' &&
uenStrArray[0] !== 'S' &&
uenStrArray[0] !== 'R'
) {
return false
}
}

// check that 2nd and 3rd letters are numbers only
if (isNaN(uenStrArray[1]) || isNaN(uenStrArray[2])) {
return false
}
// Length is 10
// check that last character is a letter
const lastChar = uen[uen.length - 1]
if (!isAlphabetic(lastChar)) {
return false
}

// check that 4th letter is an alphabet
if (!isNaN(uenStrArray[3])) {
return false
}
// (B) Local companies registered with ACRA
const first4Chars = uen.slice(0, 4)
if (isNumeric(first4Chars)) {
// if first 4 are digits then next 5 must be digits too
const next5Chars = uen.slice(4, 9)
return isNumeric(next5Chars)
}

// check entity-type indicator
let entityTypeMatch = false
const entityType = String(uenStrArray[3]) + String(uenStrArray[4])
for (let i = 0; i < entityTypeIndicator.length; i++) {
if (String(entityTypeIndicator[i]) === String(entityType)) {
entityTypeMatch = true
}
}
if (!entityTypeMatch) {
return false
}
// (C) All other entities which will be issued new UEN
// check that 1st letter is either T or S or R
const firstChar = uen[0]
if (!['T', 'S', 'R'].includes(firstChar)) {
return false
}

// check that 6th to 9th letters are numbers only
if (
isNaN(uenStrArray[5]) ||
isNaN(uenStrArray[6]) ||
isNaN(uenStrArray[7]) ||
isNaN(uenStrArray[8])
) {
return false
}
// check that 2nd and 3rd letters are numbers only
const chars2And3 = uen.slice(1, 3)
if (!isNumeric(chars2And3)) {
return false
}

// (C) All other entities which will be issued new UEN (SUCCESS)
return true
}
// check entity-type indicator
const entityTypeIndicator = uen.slice(3, 5)
if (!VALID_ENTITY_TYPE_INDICATORS.has(entityTypeIndicator)) {
return false
}

return false
// check that 6th to 9th letters are numbers only
const chars5To8 = uen.slice(5, 9)
return isNumeric(chars5To8)
}

0 comments on commit b0f8233

Please sign in to comment.