Skip to content

Commit 80a9cf2

Browse files
author
Ian Walter
committed
Fixing lint
1 parent fe52551 commit 80a9cf2

File tree

8 files changed

+443
-4
lines changed

8 files changed

+443
-4
lines changed

packages/whip-blog/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,3 @@ import prisma from '@generates/whip-prisma'
33
export default function blogPlugin (app, opts) {
44
if (!app.prisma) prisma(app, opts.prisma)
55
}
6-
7-
blogPlugin.getSession

packages/whip-check/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { default as SchemaValidator } from './lib/SchemaValidator.js'
2+
export * from './lib/validators.js'
3+
export * from './lib/modifiers.js'
4+
export { default as validate } from './lib/middleware/validate.js'
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import decamelize from 'decamelize'
2+
import { createLogger } from '@generates/logger'
3+
import { has } from '@generates/dotter'
4+
import { isEmpty } from './validators.js'
5+
6+
const logger = createLogger({ level: 'info', namespace: 'nrg.validation' })
7+
8+
const defaults = { failFast: 0 }
9+
const pipe = (...fns) => val => fns.reduce((acc, fn) => fn(acc), val)
10+
const toValidators = (acc, [key, option]) =>
11+
option?.validate && key !== 'canBeEmpty' ? acc.concat([option]) : acc
12+
13+
export default class SchemaValidator {
14+
constructor (schema, options) {
15+
this.schema = schema
16+
this.fields = {}
17+
18+
// Merge the given options with the defaults.
19+
this.options = Object.assign({}, defaults, options)
20+
21+
// Convert the fields in the schema definition to objects that can be used
22+
// to validate data.
23+
for (const [field, options] of Object.entries(schema)) {
24+
const defaultName = decamelize(field, { separator: ' ' })
25+
this.fields[field] = {
26+
...options,
27+
name: options.name && !options.validate ? options.name : defaultName,
28+
validators: Object.entries(options).reduce(toValidators, []),
29+
modifiers: Object.values(options).filter(o => o?.modify)
30+
}
31+
32+
// Intended for nested SchemaValidators.
33+
if (options.validate) {
34+
this.fields[field].validators.push(options)
35+
if (options.constructor?.name === 'SchemaValidator') {
36+
this.fields[field].isSchemaValidator = true
37+
}
38+
}
39+
}
40+
}
41+
42+
handleFailure (ctx, key, field) {
43+
// Log validation failure.
44+
if (ctx.validations[key].isEmpty) {
45+
logger.debug(`Required field ${key} is empty`)
46+
} else if (ctx.validations[key].err) {
47+
logger.warn('Error during validation', ctx.validations[key].err)
48+
} else {
49+
logger.debug('Validation failure', ctx.validations[key])
50+
}
51+
52+
// Determine validation failure message and add it to feedback.
53+
let message = ctx.validations[key].message
54+
if (!message && field.message) {
55+
if (typeof field.message === 'function') {
56+
message = field.message(ctx, key, field)
57+
} else {
58+
message = field.message
59+
}
60+
} else if (!message) {
61+
message = `A valid ${field.name} is required.`
62+
}
63+
if (ctx.feedback[key]) {
64+
ctx.feedback[key].push(message)
65+
} else {
66+
ctx.feedback[key] = [message]
67+
}
68+
69+
// Add any other feedback within the validation object to feedback for the
70+
// field.
71+
const { feedback } = ctx.validations[key]
72+
if (feedback) {
73+
if (Array.isArray(feedback)) {
74+
ctx.feedback[key] = ctx.feedback[key].concat(feedback)
75+
} else {
76+
ctx.feedback[key].push(feedback)
77+
}
78+
}
79+
}
80+
81+
async validate (input, state) {
82+
const ctx = {
83+
options: this.options,
84+
validations: {},
85+
feedback: {},
86+
data: {},
87+
input,
88+
state,
89+
failureCount: 0,
90+
get isValid () {
91+
return !this.failureCount
92+
}
93+
}
94+
95+
for (const [key, field] of Object.entries(this.fields)) {
96+
const { canBeEmpty } = field
97+
98+
// Add the input to the data map so that the subset of data can be used
99+
// later.
100+
if (has(input, key)) ctx.data[key] = pipe(...field.modifiers)(input[key])
101+
102+
const vInput = ctx.data[key]
103+
const vState = state && state[key]
104+
if (canBeEmpty) {
105+
// If the field can be empty, skip other validations if the canBeEmpty
106+
// validation is valid.
107+
ctx.validations[key] = await canBeEmpty.validate(vInput, vState, ctx)
108+
if (ctx.validations[key].isValid) continue
109+
}
110+
111+
if (!canBeEmpty && isEmpty(vInput)) {
112+
// If the field can't be empty and is empty, mark it as invalid and skip
113+
// validations.
114+
ctx.validations[key] = { isValid: false, isEmpty: true }
115+
} else {
116+
// Perform the validation(s).
117+
for (const validator of field.validators) {
118+
try {
119+
ctx.validations[key] = await validator.validate(vInput, vState, ctx)
120+
if (field.isSchemaValidator) {
121+
ctx.data[key] = ctx.validations[key].data
122+
}
123+
} catch (err) {
124+
ctx.validations[key] = { isValid: false, err }
125+
}
126+
if (!ctx.validations[key].isValid) break
127+
}
128+
}
129+
130+
// Perform validation failure steps if the validation fails.
131+
if (ctx.validations[key] && ctx.validations[key].isValid === false) {
132+
ctx.failureCount++
133+
this.handleFailure(ctx, key, field)
134+
if (ctx.options.failFast && ctx.options.failFast === ctx.failureCount) {
135+
break
136+
}
137+
}
138+
}
139+
140+
return ctx
141+
}
142+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ValidationError } from '@generates/whip'
2+
3+
export default function validate (opts) {
4+
if (!opts?.validator) {
5+
throw new Error('Missing validator option for validate middleware')
6+
}
7+
8+
return async function validateMiddleware (req, res, next) {
9+
const logger = req.logger.ns('whip.data')
10+
logger.debug(opts.validator, { body: req.body })
11+
12+
const validator = req.opts.validators[opts.validator]
13+
const validation = await validator.validate(req.body)
14+
if (validation.isValid) {
15+
req.state.validation = validation
16+
next()
17+
} else {
18+
throw new ValidationError(validation)
19+
}
20+
}
21+
}

packages/whip-check/lib/modifiers.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export function trim (data) {
2+
return data && trim.modify(data)
3+
}
4+
trim.modify = function modfiy (data) {
5+
return data.trim()
6+
}
7+
8+
export function lowercase (data) {
9+
return data && lowercase.modify(data)
10+
}
11+
lowercase.modify = function modify (data) {
12+
return data.toLowerCase()
13+
}
14+
15+
export function toUrl (input) {
16+
return input && toUrl.modify(input)
17+
}
18+
toUrl.modify = function modify (input) {
19+
let url = input
20+
try {
21+
url = new URL(input).href
22+
} catch (err) {
23+
// Ignore error.
24+
}
25+
return url
26+
}

0 commit comments

Comments
 (0)