Skip to content

Commit cfe1725

Browse files
feat(2020-day-04): optional field-level validation rules
Skip field-level validation with validate(passport, false)
1 parent c139381 commit cfe1725

File tree

3 files changed

+125
-6
lines changed

3 files changed

+125
-6
lines changed

2020/day-04/passports.js

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,99 @@ const requiredFields = [
2828
'cid'
2929
]
3030

31-
const validate = (passport) => {
31+
const rules = {
32+
byr: (byr) => {
33+
// byr (Birth Year) - four digits; at least 1920 and at most 2002
34+
return (
35+
String(byr).length === 4 &&
36+
Number(byr) >= 1920 &&
37+
Number(byr) <= 2002
38+
)
39+
},
40+
iyr: (iyr) => {
41+
// iyr (Issue Year) - four digits; at least 2010 and at most 2020.
42+
return (
43+
String(iyr).length === 4 &&
44+
Number(iyr) >= 2010 &&
45+
Number(iyr) <= 2020
46+
)
47+
},
48+
eyr: (eyr) => {
49+
// eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
50+
return (
51+
String(eyr).length === 4 &&
52+
Number(eyr) >= 2020 &&
53+
Number(eyr) <= 2030
54+
)
55+
},
56+
hgt: (hgt) => {
57+
// hgt (Height) - a number followed by either cm or in:
58+
// If cm, the number must be at least 150 and at most 193.
59+
// If in, the number must be at least 59 and at most 76.
60+
const unit = hgt.slice(hgt.length - 2)
61+
const value = hgt.slice(0, hgt.length - 2)
62+
return (
63+
(
64+
unit === 'cm' &&
65+
value >= 150 &&
66+
value <= 193
67+
) || (
68+
unit === 'in' &&
69+
value >= 59 &&
70+
value <= 76
71+
)
72+
)
73+
},
74+
hcl: (hcl) => {
75+
// hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
76+
const regexp = /^#[0-9a-fA-F]+$/ // hex color
77+
return (
78+
regexp.test(hcl) &&
79+
hcl.length === 7
80+
)
81+
},
82+
ecl: (ecl) => {
83+
// ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
84+
const allowed = ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth']
85+
return allowed.includes(ecl)
86+
},
87+
pid: (pid) => {
88+
// pid (Passport ID) - a nine-digit number, including leading zeroes.
89+
return (
90+
String(pid).length === 9 &&
91+
Number(pid) > 0
92+
)
93+
},
94+
cid: (cid) => {
95+
// cid (Country ID) - ignored, missing or not.
96+
return true
97+
}
98+
}
99+
100+
const validate = (passport, checkFields = true) => {
32101
const fieldsToCheck = JSON.parse(JSON.stringify(requiredFields)) // quick deep copy
33102
// polar records don't have cid, but are otherwise valid
34103
if (getType(passport) === 'polar') {
35104
delete fieldsToCheck.splice([fieldsToCheck.indexOf('cid')], 1)
36105
}
37106
// Check for fields
38107
fieldsToCheck.forEach((field) => {
108+
// Check for required vield
39109
if (!passport[field]) {
40110
throw new Error(`Missing field ${field}`)
41111
}
112+
// Skip field validation when disabled
113+
if (!checkFields) {
114+
return
115+
}
116+
// Validate field against rules
117+
if (
118+
!rules[field](
119+
passport[field]
120+
)
121+
) {
122+
throw new Error(`Invalid field value ${field}:${passport[field]}`)
123+
}
42124
})
43125
return true
44126
}

2020/day-04/passports.test.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ const invalid = [
1818
iyr:2011 ecl:brn hgt:59in`
1919
]
2020

21+
const invalidValues = [
22+
`eyr:1972 cid:100
23+
hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926`,
24+
`iyr:2019
25+
hcl:#602927 eyr:1967 hgt:170cm
26+
ecl:grn pid:012533040 byr:1946`,
27+
`hcl:dab227 iyr:2012,
28+
ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277`,
29+
`hgt:59cm ecl:zzz
30+
eyr:2038 hcl:74454a iyr:2023
31+
pid:3556412378 byr:2007`
32+
]
33+
const validValues = [
34+
`pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980
35+
hcl:#623a2f`,
36+
`eyr:2029 ecl:blu cid:129 byr:1989
37+
iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm`,
38+
`hcl:#888785
39+
hgt:164cm byr:2001 iyr:2015 cid:88
40+
pid:545766238 ecl:hzl
41+
eyr:2022`,
42+
'iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719'
43+
]
44+
2145
describe('--- Day 4: Passport Processing ---', () => {
2246
describe('Part 1', () => {
2347
describe('parseScan()', () => {
@@ -36,16 +60,29 @@ describe('--- Day 4: Passport Processing ---', () => {
3660
})
3761
})
3862
describe('validate()', () => {
39-
it('verifies all required fields in a passport', () => {
63+
it('verifies presences of all required fields in a passport', () => {
4064
valid.forEach((scan, idx) => {
4165
const passport = parseScan(scan)
4266
// Valid when all required fields
43-
expect(validate(passport)).to.equal(true)
67+
expect(validate(passport, false)).to.equal(true)
4468
})
4569
})
4670
it('errors on invalid passports', () => {
4771
const passport = parseScan(invalid[0])
48-
expect(() => validate(passport)).to.throw('Missing field hgt')
72+
expect(() => validate(passport, false)).to.throw('Missing field hgt')
73+
})
74+
it('verifies the vield values agaisnt the type rules', () => {
75+
validValues.forEach((scan, idx) => {
76+
const passport = parseScan(scan)
77+
// Valid when all required fields
78+
expect(validate(passport)).to.equal(true)
79+
})
80+
})
81+
it('errors on invalid passports', () => {
82+
invalidValues.forEach((scan) => {
83+
const passport = parseScan(scan)
84+
expect(() => validate(passport)).to.throw()
85+
})
4986
})
5087
})
5188
})

2020/day-04/solution.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const DEBUG = false;
1+
const DEBUG = true
22
const fs = require('fs')
33
const path = require('path')
44
const split2 = require('split2')
@@ -17,7 +17,7 @@ const part1 = () => {
1717
// Handle the record in the buffer
1818
const passport = parseScan(recordBuffer)
1919
try {
20-
if (validate(passport)) {
20+
if (validate(passport, false)) {
2121
validCount++
2222
}
2323
} catch (e) {

0 commit comments

Comments
 (0)