Skip to content

Commit ded2a35

Browse files
committed
fix: fix "keys" option
fixes #477
1 parent d0f9290 commit ded2a35

File tree

3 files changed

+134
-120
lines changed

3 files changed

+134
-120
lines changed

src/doValidation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ function doValidation ({
7474
for (const field of fields) {
7575
const fieldErrors = validateField({
7676
affectedKey: field.key,
77+
keysToValidate,
7778
obj,
7879
op,
7980
schema,
@@ -87,6 +88,7 @@ function doValidation ({
8788
}
8889
} else {
8990
const fieldErrors = validateField({
91+
keysToValidate,
9092
obj,
9193
schema,
9294
val: obj,

src/validation/validateField.ts

Lines changed: 120 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -141,153 +141,153 @@ export default function validateField (props: ValidateFieldProps): ValidationErr
141141
...(extendedCustomContext ?? {})
142142
}
143143

144-
if (!shouldValidateKey({
144+
if (shouldValidateKey({
145145
affectedKey,
146146
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
147147
keysToValidate
148-
})) return []
149-
148+
})) {
150149
// Perform validation for this key
151-
for (const currentDef of schema.getDefinitions(affectedKey, null, functionsContext)) {
152-
def = currentDef
153-
154-
// Whenever we try a new possible schema, clear any field errors from the previous tried schema
155-
fieldValidationErrors.length = 0
156-
157-
const validatorContext: Omit<ValidatorContext, 'definition'> = {
158-
...functionsContext,
159-
addValidationErrors (errors: ValidationError[]) {
160-
errors.forEach((error) => fieldValidationErrors.push(error))
161-
},
162-
// Value checks are not necessary for null or undefined values, except
163-
// for non-optional null array items, or for $unset or $rename values
164-
valueShouldBeChecked: shouldCheckValue({
165-
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
166-
isOptional: currentDef.optional as boolean,
167-
op,
168-
val
169-
})
170-
}
150+
for (const currentDef of schema.getDefinitions(affectedKey, null, functionsContext)) {
151+
def = currentDef
152+
153+
// Whenever we try a new possible schema, clear any field errors from the previous tried schema
154+
fieldValidationErrors.length = 0
155+
156+
const validatorContext: Omit<ValidatorContext, 'definition'> = {
157+
...functionsContext,
158+
addValidationErrors (errors: ValidationError[]) {
159+
errors.forEach((error) => fieldValidationErrors.push(error))
160+
},
161+
// Value checks are not necessary for null or undefined values, except
162+
// for non-optional null array items, or for $unset or $rename values
163+
valueShouldBeChecked: shouldCheckValue({
164+
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
165+
isOptional: currentDef.optional as boolean,
166+
op,
167+
val
168+
})
169+
}
171170

172-
// Loop through each of the definitions in the SimpleSchemaGroup.
173-
// If the value matches any, we are valid and can stop checking the rest.
174-
for (const [typeIndex, typeDef] of currentDef.type.entries()) {
171+
// Loop through each of the definitions in the SimpleSchemaGroup.
172+
// If the value matches any, we are valid and can stop checking the rest.
173+
for (const [typeIndex, typeDef] of currentDef.type.entries()) {
175174
// If the type is SimpleSchema.Any, then it is valid
176-
if (typeDef === SimpleSchema.Any) break
175+
if (typeDef === SimpleSchema.Any) break
177176

178-
const nonAnyTypeDefinition = typeDef as SchemaKeyDefinitionWithOneType
179-
const { type, ...definitionWithoutType } = currentDef
177+
const nonAnyTypeDefinition = typeDef as SchemaKeyDefinitionWithOneType
178+
const { type, ...definitionWithoutType } = currentDef
180179

181-
// @ts-expect-error
182-
const finalValidatorContext: ValidatorContext = {
183-
...validatorContext,
180+
// @ts-expect-error
181+
const finalValidatorContext: ValidatorContext = {
182+
...validatorContext,
184183

185-
// Take outer definition props like "optional" and "label"
186-
// and add them to inner props like "type" and "min"
187-
definition: {
188-
...definitionWithoutType,
189-
...nonAnyTypeDefinition
184+
// Take outer definition props like "optional" and "label"
185+
// and add them to inner props like "type" and "min"
186+
definition: {
187+
...definitionWithoutType,
188+
...nonAnyTypeDefinition
189+
}
190190
}
191-
}
192-
193-
// Order of these validators is important
194-
const customFieldValidator = nonAnyTypeDefinition.custom
195-
const fieldValidators = [
196-
requiredValidator,
197-
typeValidator,
198-
allowedValuesValidator,
199-
...(customFieldValidator == null ? [] : [customFieldValidator]),
200-
// @ts-expect-error It's fine to access private method from here
201-
...schema._validators,
202-
// @ts-expect-error It's fine to access private method from here
203-
...SimpleSchema._validators
204-
]
205191

206-
const fieldValidationErrorsForThisType = []
207-
for (const fieldValidator of fieldValidators) {
208-
const result = fieldValidator.call(finalValidatorContext)
209-
210-
// If the validator returns a string, assume it is the error type.
211-
if (typeof result === 'string') {
212-
fieldValidationErrorsForThisType.push({
213-
name: affectedKey,
214-
type: result,
215-
value: val
216-
})
192+
// Order of these validators is important
193+
const customFieldValidator = nonAnyTypeDefinition.custom
194+
const fieldValidators = [
195+
requiredValidator,
196+
typeValidator,
197+
allowedValuesValidator,
198+
...(customFieldValidator == null ? [] : [customFieldValidator]),
199+
// @ts-expect-error It's fine to access private method from here
200+
...schema._validators,
201+
// @ts-expect-error It's fine to access private method from here
202+
...SimpleSchema._validators
203+
]
204+
205+
const fieldValidationErrorsForThisType = []
206+
for (const fieldValidator of fieldValidators) {
207+
const result = fieldValidator.call(finalValidatorContext)
208+
209+
// If the validator returns a string, assume it is the error type.
210+
if (typeof result === 'string') {
211+
fieldValidationErrorsForThisType.push({
212+
name: affectedKey,
213+
type: result,
214+
value: val
215+
})
216+
}
217+
218+
// If the validator returns an object, assume it is an error object.
219+
if (typeof result === 'object' && result !== null) {
220+
fieldValidationErrorsForThisType.push({
221+
name: affectedKey,
222+
value: val,
223+
...result
224+
})
225+
}
217226
}
218227

219-
// If the validator returns an object, assume it is an error object.
220-
if (typeof result === 'object' && result !== null) {
221-
fieldValidationErrorsForThisType.push({
222-
name: affectedKey,
223-
value: val,
224-
...result
228+
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
229+
const itemErrors = validateField({
230+
extendedCustomContext,
231+
keysToValidate,
232+
obj: val,
233+
op,
234+
schema: nonAnyTypeDefinition.type as SimpleSchema,
235+
val,
236+
validationContext
225237
})
238+
if (itemErrors.length > 0) {
239+
fieldValidationErrorsForThisType.push(...itemErrors.map((error) => ({ ...error, name: `${affectedKey}.${error.name}` })))
240+
}
226241
}
227-
}
228-
229-
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
230-
const itemErrors = validateField({
231-
extendedCustomContext,
232-
keysToValidate,
233-
obj: val,
234-
op,
235-
schema: nonAnyTypeDefinition.type as SimpleSchema,
236-
val,
237-
validationContext
238-
})
239-
if (itemErrors.length > 0) {
240-
fieldValidationErrorsForThisType.push(...itemErrors.map((error) => ({ ...error, name: `${affectedKey}.${error.name}` })))
241-
}
242-
}
243242

244-
// As soon as we find a type for which the value is valid, stop checking more
245-
if (fieldValidationErrorsForThisType.length === 0) {
243+
// As soon as we find a type for which the value is valid, stop checking more
244+
if (fieldValidationErrorsForThisType.length === 0) {
246245
// One we have chosen a valid schema, there is no need to validate the
247246
// properties of this object because we validated all the way down
248-
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
249-
return fieldValidationErrors
247+
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
248+
return fieldValidationErrors
249+
}
250+
break
250251
}
251-
break
252-
}
253252

254-
if (typeIndex === currentDef.type.length - 1) {
255-
fieldValidationErrors.push(...fieldValidationErrorsForThisType)
253+
if (typeIndex === currentDef.type.length - 1) {
254+
fieldValidationErrors.push(...fieldValidationErrorsForThisType)
255+
}
256256
}
257-
}
258257

259-
// If it's valid with this schema, we don't need to try any more
260-
if (fieldValidationErrors.length === 0) break
261-
}
258+
// If it's valid with this schema, we don't need to try any more
259+
if (fieldValidationErrors.length === 0) break
260+
}
262261

263-
// Mark invalid if not found in schema
264-
if (def == null) {
262+
// Mark invalid if not found in schema
263+
if (def == null) {
265264
// We don't need KEY_NOT_IN_SCHEMA error for $unset and we also don't need to continue
266-
if (
267-
op === '$unset' ||
265+
if (
266+
op === '$unset' ||
268267
(op === '$currentDate' && affectedKey.endsWith('.$type'))
269-
) {
270-
return []
271-
}
272-
273-
return [
274-
{
275-
name: affectedKey,
276-
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
277-
value: val
268+
) {
269+
return []
278270
}
279-
]
280-
}
281271

282-
// For $rename, make sure that the new name is allowed by the schema
283-
if (op === '$rename' && !schema.allowsKey(val)) {
284-
return [
285-
{
286-
name: val,
287-
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
288-
value: null
289-
}
290-
]
272+
return [
273+
{
274+
name: affectedKey,
275+
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
276+
value: val
277+
}
278+
]
279+
}
280+
281+
// For $rename, make sure that the new name is allowed by the schema
282+
if (op === '$rename' && !schema.allowsKey(val)) {
283+
return [
284+
{
285+
name: val,
286+
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
287+
value: null
288+
}
289+
]
290+
}
291291
}
292292

293293
// Loop through arrays

test/SimpleSchema.tests.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,4 +1198,16 @@ describe('SimpleSchema', function () {
11981198
})
11991199
})
12001200
})
1201+
1202+
it('keys not in the keys list are not validated', function () {
1203+
const schema = new SimpleSchema({
1204+
foo: String,
1205+
bar: String
1206+
})
1207+
const context = schema.newContext()
1208+
context.validate({ foo: 'bizz' }, { keys: ['foo'] })
1209+
1210+
// If keys option would be respected, there should not be any validation errors
1211+
expect(context.validationErrors().length).toBe(0)
1212+
})
12011213
})

0 commit comments

Comments
 (0)