-
Notifications
You must be signed in to change notification settings - Fork 27
/
validator.ts
68 lines (59 loc) · 2.37 KB
/
validator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { FileHandle, readFile } from 'fs/promises'
import { pipeline } from 'stream/promises'
export interface Validator {
/**
* Validate the download result. It should throw `ValidationError` if validation failed.
*
* @param destination The result file
* @param url The url where the file downloaded from
*/
validate(destination: string, url: string): Promise<void>
}
export class ChecksumValidator implements Validator {
constructor(protected checksum?: ChecksumValidatorOptions) { }
async validate(destination: string, url: string): Promise<void> {
if (this.checksum) {
const hash = createHash(this.checksum.algorithm)
await pipeline(createReadStream(destination), hash)
const actual = hash.digest('hex')
const expect = this.checksum.hash
if (actual !== expect) {
throw new ChecksumNotMatchError(this.checksum.algorithm, this.checksum.hash, actual, destination, url)
}
}
}
}
export function isValidator(options?: Validator | ChecksumValidatorOptions): options is Validator {
if (!options) { return false }
return 'validate' in options && typeof options.validate === 'function'
}
export function resolveValidator(options?: ChecksumValidatorOptions | Validator): Validator {
if (isValidator(options)) { return options }
if (options) {
return new ChecksumValidator({ hash: options.hash, algorithm: options.algorithm })
}
return { validate() { return Promise.resolve() } }
}
export interface ChecksumValidatorOptions {
algorithm: string
hash: string
}
export class JsonValidator implements Validator {
async validate(destination: string, url: string): Promise<void> {
const content = await readFile(destination, 'utf-8')
JSON.parse(content)
}
}
export class ValidationError extends Error {
constructor(error: string, message?: string) {
super(message)
this.name = error
}
}
export class ChecksumNotMatchError extends ValidationError {
constructor(readonly algorithm: string, readonly expect: string, readonly actual: string, readonly file: string, readonly source?: string) {
super('ChecksumNotMatchError', source ? `File ${file} (${source}) ${algorithm} checksum not match. Expect: ${expect}. Actual: ${actual}.` : `File ${file} ${algorithm} checksum not match. Expect: ${expect}. Actual: ${actual}.`)
}
}