Skip to content

Commit

Permalink
fix: fix "keys" option
Browse files Browse the repository at this point in the history
fixes #477
  • Loading branch information
aldeed committed Feb 19, 2024
1 parent d0f9290 commit ded2a35
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 120 deletions.
2 changes: 2 additions & 0 deletions src/doValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function doValidation ({
for (const field of fields) {
const fieldErrors = validateField({
affectedKey: field.key,
keysToValidate,
obj,
op,
schema,
Expand All @@ -87,6 +88,7 @@ function doValidation ({
}
} else {
const fieldErrors = validateField({
keysToValidate,
obj,
schema,
val: obj,
Expand Down
240 changes: 120 additions & 120 deletions src/validation/validateField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,153 +141,153 @@ export default function validateField (props: ValidateFieldProps): ValidationErr
...(extendedCustomContext ?? {})
}

if (!shouldValidateKey({
if (shouldValidateKey({
affectedKey,
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
keysToValidate
})) return []

})) {
// Perform validation for this key
for (const currentDef of schema.getDefinitions(affectedKey, null, functionsContext)) {
def = currentDef

// Whenever we try a new possible schema, clear any field errors from the previous tried schema
fieldValidationErrors.length = 0

const validatorContext: Omit<ValidatorContext, 'definition'> = {
...functionsContext,
addValidationErrors (errors: ValidationError[]) {
errors.forEach((error) => fieldValidationErrors.push(error))
},
// Value checks are not necessary for null or undefined values, except
// for non-optional null array items, or for $unset or $rename values
valueShouldBeChecked: shouldCheckValue({
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
isOptional: currentDef.optional as boolean,
op,
val
})
}
for (const currentDef of schema.getDefinitions(affectedKey, null, functionsContext)) {
def = currentDef

// Whenever we try a new possible schema, clear any field errors from the previous tried schema
fieldValidationErrors.length = 0

const validatorContext: Omit<ValidatorContext, 'definition'> = {
...functionsContext,
addValidationErrors (errors: ValidationError[]) {
errors.forEach((error) => fieldValidationErrors.push(error))
},
// Value checks are not necessary for null or undefined values, except
// for non-optional null array items, or for $unset or $rename values
valueShouldBeChecked: shouldCheckValue({
affectedKeyGeneric: affectedKeyGeneric ?? undefined,
isOptional: currentDef.optional as boolean,
op,
val
})
}

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

const nonAnyTypeDefinition = typeDef as SchemaKeyDefinitionWithOneType
const { type, ...definitionWithoutType } = currentDef
const nonAnyTypeDefinition = typeDef as SchemaKeyDefinitionWithOneType
const { type, ...definitionWithoutType } = currentDef

// @ts-expect-error
const finalValidatorContext: ValidatorContext = {
...validatorContext,
// @ts-expect-error
const finalValidatorContext: ValidatorContext = {
...validatorContext,

// Take outer definition props like "optional" and "label"
// and add them to inner props like "type" and "min"
definition: {
...definitionWithoutType,
...nonAnyTypeDefinition
// Take outer definition props like "optional" and "label"
// and add them to inner props like "type" and "min"
definition: {
...definitionWithoutType,
...nonAnyTypeDefinition
}
}
}

// Order of these validators is important
const customFieldValidator = nonAnyTypeDefinition.custom
const fieldValidators = [
requiredValidator,
typeValidator,
allowedValuesValidator,
...(customFieldValidator == null ? [] : [customFieldValidator]),
// @ts-expect-error It's fine to access private method from here
...schema._validators,
// @ts-expect-error It's fine to access private method from here
...SimpleSchema._validators
]

const fieldValidationErrorsForThisType = []
for (const fieldValidator of fieldValidators) {
const result = fieldValidator.call(finalValidatorContext)

// If the validator returns a string, assume it is the error type.
if (typeof result === 'string') {
fieldValidationErrorsForThisType.push({
name: affectedKey,
type: result,
value: val
})
// Order of these validators is important
const customFieldValidator = nonAnyTypeDefinition.custom
const fieldValidators = [
requiredValidator,
typeValidator,
allowedValuesValidator,
...(customFieldValidator == null ? [] : [customFieldValidator]),
// @ts-expect-error It's fine to access private method from here
...schema._validators,
// @ts-expect-error It's fine to access private method from here
...SimpleSchema._validators
]

const fieldValidationErrorsForThisType = []
for (const fieldValidator of fieldValidators) {
const result = fieldValidator.call(finalValidatorContext)

// If the validator returns a string, assume it is the error type.
if (typeof result === 'string') {
fieldValidationErrorsForThisType.push({
name: affectedKey,
type: result,
value: val
})
}

// If the validator returns an object, assume it is an error object.
if (typeof result === 'object' && result !== null) {
fieldValidationErrorsForThisType.push({
name: affectedKey,
value: val,
...result
})
}
}

// If the validator returns an object, assume it is an error object.
if (typeof result === 'object' && result !== null) {
fieldValidationErrorsForThisType.push({
name: affectedKey,
value: val,
...result
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
const itemErrors = validateField({
extendedCustomContext,
keysToValidate,
obj: val,
op,
schema: nonAnyTypeDefinition.type as SimpleSchema,
val,
validationContext
})
if (itemErrors.length > 0) {
fieldValidationErrorsForThisType.push(...itemErrors.map((error) => ({ ...error, name: `${affectedKey}.${error.name}` })))
}
}
}

if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
const itemErrors = validateField({
extendedCustomContext,
keysToValidate,
obj: val,
op,
schema: nonAnyTypeDefinition.type as SimpleSchema,
val,
validationContext
})
if (itemErrors.length > 0) {
fieldValidationErrorsForThisType.push(...itemErrors.map((error) => ({ ...error, name: `${affectedKey}.${error.name}` })))
}
}

// As soon as we find a type for which the value is valid, stop checking more
if (fieldValidationErrorsForThisType.length === 0) {
// As soon as we find a type for which the value is valid, stop checking more
if (fieldValidationErrorsForThisType.length === 0) {
// One we have chosen a valid schema, there is no need to validate the
// properties of this object because we validated all the way down
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
return fieldValidationErrors
if (SimpleSchema.isSimpleSchema(nonAnyTypeDefinition.type)) {
return fieldValidationErrors
}
break
}
break
}

if (typeIndex === currentDef.type.length - 1) {
fieldValidationErrors.push(...fieldValidationErrorsForThisType)
if (typeIndex === currentDef.type.length - 1) {
fieldValidationErrors.push(...fieldValidationErrorsForThisType)
}
}
}

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

// Mark invalid if not found in schema
if (def == null) {
// Mark invalid if not found in schema
if (def == null) {
// We don't need KEY_NOT_IN_SCHEMA error for $unset and we also don't need to continue
if (
op === '$unset' ||
if (
op === '$unset' ||
(op === '$currentDate' && affectedKey.endsWith('.$type'))
) {
return []
}

return [
{
name: affectedKey,
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
value: val
) {
return []
}
]
}

// For $rename, make sure that the new name is allowed by the schema
if (op === '$rename' && !schema.allowsKey(val)) {
return [
{
name: val,
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
value: null
}
]
return [
{
name: affectedKey,
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
value: val
}
]
}

// For $rename, make sure that the new name is allowed by the schema
if (op === '$rename' && !schema.allowsKey(val)) {
return [
{
name: val,
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA,
value: null
}
]
}
}

// Loop through arrays
Expand Down
12 changes: 12 additions & 0 deletions test/SimpleSchema.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1198,4 +1198,16 @@ describe('SimpleSchema', function () {
})
})
})

it('keys not in the keys list are not validated', function () {
const schema = new SimpleSchema({
foo: String,
bar: String
})
const context = schema.newContext()
context.validate({ foo: 'bizz' }, { keys: ['foo'] })

// If keys option would be respected, there should not be any validation errors
expect(context.validationErrors().length).toBe(0)
})
})

0 comments on commit ded2a35

Please sign in to comment.