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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy